observe($logger); } else { $logger = new BxDolLoggerDb('sys_agents_' . $this->aAgent['id']); $this->observe(new LogObserver($logger)); } // what the agent did (comments, content, SQL) — shown as activity history $this->observe(new BxDolAiActivityObserver($this->aAgent, $this->aParams)); } protected function provider(): NeuronAI\Providers\AIProviderInterface { $aOverrides = []; if ((int)($this->aAgent['max_tokens'] ?? 0) > 0) $aOverrides['max_tokens'] = (int)$this->aAgent['max_tokens']; return BxDolAIModelFactory::getModelInstance((int)$this->aAgent['model_id'], $aOverrides); } protected function instructions(): string { $aPromptSystem = !empty($this->aAgent['prompt_system']) ? [$this->aAgent['prompt_system']] : []; $aPromptSteps = !empty($this->aAgent['prompt_steps']) ? [$this->aAgent['prompt_steps']] : []; $aPromptTools = !empty($this->aAgent['prompt_tools']) ? [$this->aAgent['prompt_tools']] : []; $aPromptOutput = !empty($this->aAgent['prompt_output']) ? [$this->aAgent['prompt_output']] : []; $aPromptTools[] = "Always use agent profile id = {$this->aAgent['profile_id']} as author ('profile_id' or 'author_profile_id' tool parameter), unless explicitly specified in the user instructions, no other variants strictly."; if ('alert' == $this->aAgent['trigger']) { if (!$this->aAgent['async']) { $aPromptTools[] = "Return array only, modified version of 'extra' array. Modifyable keys: " . $this->getAlertTriggerModifyableKeys() . "."; } $sDesc = trim(BxDolAiQuery::getAlertDesc($this->aAgent['alert'])); if ('.' != mb_substr($sDesc, -1)) $sDesc .= '.'; $aPromptSystem[] = $sDesc; } $oPrompt = new NeuronAI\Agent\SystemPrompt( background: $aPromptSystem, steps: $aPromptSteps, output: $aPromptOutput, toolsUsage: $aPromptTools ); return (string) $oPrompt; } /** * @return \NeuronAI\Tools\ToolInterface[] */ protected function tools(): array { $aToolInstances = []; foreach (BxDolAiAgentTools::resolveIds($this->aAgent) as $iToolId) { if (!BxDolAiQuery::getToolObject($iToolId)) continue; $aToolInstances[] = BxDolAIToolFactory::getToolInstance($iToolId); } return $aToolInstances; } protected function vectorStore(): NeuronAI\RAG\VectorStore\VectorStoreInterface { if ($this->aAgent['vector_store_id']) return BxDolAIVectorStoreFactory::getVectorStoreInstance($this->aAgent['vector_store_id']); else return new NeuronAI\RAG\VectorStore\MemoryVectorStore(); } /** * Retrieval is built for every chat. A una-proxy store is searched on the * master. Any other store needs an embedding model on this site. */ protected function retrieval(): NeuronAI\RAG\Retrieval\RetrievalInterface { if (($this->aAgent['vector_store_type'] ?? '') === 'una-proxy') { return new BxDolAiRetrievalUnaProxy( (string)getParam('sys_oauth_key'), (string)getParam('sys_oauth_secret') ); } if (!isset($this->embeddingsProvider)) { if ((int)($this->aAgent['vector_store_id'] ?? 0) > 0) throw new Exception("Vector store {$this->aAgent['vector_store_id']} has no embedding model"); return new BxDolAiRetrievalNone(); } return new NeuronAI\RAG\Retrieval\SimilarityRetrieval( $this->resolveVectorStore(), $this->resolveEmbeddingsProvider() ); } protected function embeddings(): NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface { throw new Exception('Embedding model is not configured'); } protected function chatHistory(): NeuronAI\Chat\History\ChatHistoryInterface { if ($this->aAgent['chat_history_context']) { return new BxDolAiChatHistory( thread_id: $this->getChatHistoryThreadId(), pdo: BxDolDb::getInstance()->getLink(), table: 'sys_agents_chat_history', contextWindow: $this->aAgent['chat_history_context'] ); } else { return new NeuronAI\Chat\History\InMemoryChatHistory( contextWindow: 50000 ); } } /** * Turn tool exceptions into a tool result so chat history stays a valid sequence. * * @return callable|null fn(\Throwable $e, \NeuronAI\Tools\ToolInterface $tool): string */ protected function resolveToolErrorHandler(): ?callable { return function (\Throwable $e, NeuronAI\Tools\ToolInterface $tool): string { return 'Tool "' . $tool->getName() . '" failed: ' . $e->getMessage(); }; } protected function getChatHistoryThreadId(): string { return BxDolAiChat::threadId($this->aAgent, $this->aParams); } protected function getAlertTriggerModifyableKeys(): string { if ('alert' != $this->aAgent['trigger']) return 'none'; $aAlert = BxDolAiQuery::getAlert($this->aAgent['alert']); if (!$aAlert) return 'none'; $aKeys = json_decode($aAlert['extra_refs']); if (!$aKeys) return 'none'; return implode(',', $aKeys); } }