BxDolAiAgent.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup UnaCore UNA Core
  7. * @{
  8. */
  9. use NeuronAI\RAG\RAG;
  10. use NeuronAI\Observability\InspectorObserver;
  11. use NeuronAI\Observability\LogObserver;
  12. class BxDolAiAgent extends RAG
  13. {
  14. public function __construct(protected array $aAgent, protected array $aParams = [])
  15. {
  16. parent::__construct();
  17. $sKey = getParam('sys_agents_inspector_key');
  18. if ($sKey) {
  19. $logger = InspectorObserver::instance(
  20. key: $sKey,
  21. autoFlush: true,
  22. );
  23. $this->observe($logger);
  24. }
  25. else {
  26. $logger = new BxDolLoggerDb('sys_agents_' . $this->aAgent['id']);
  27. $this->observe(new LogObserver($logger));
  28. }
  29. // what the agent did (comments, content, SQL) — shown as activity history
  30. $this->observe(new BxDolAiActivityObserver($this->aAgent, $this->aParams));
  31. }
  32. protected function provider(): NeuronAI\Providers\AIProviderInterface
  33. {
  34. $aOverrides = [];
  35. if ((int)($this->aAgent['max_tokens'] ?? 0) > 0)
  36. $aOverrides['max_tokens'] = (int)$this->aAgent['max_tokens'];
  37. return BxDolAIModelFactory::getModelInstance((int)$this->aAgent['model_id'], $aOverrides);
  38. }
  39. protected function instructions(): string
  40. {
  41. $aPromptSystem = !empty($this->aAgent['prompt_system']) ? [$this->aAgent['prompt_system']] : [];
  42. $aPromptSteps = !empty($this->aAgent['prompt_steps']) ? [$this->aAgent['prompt_steps']] : [];
  43. $aPromptTools = !empty($this->aAgent['prompt_tools']) ? [$this->aAgent['prompt_tools']] : [];
  44. $aPromptOutput = !empty($this->aAgent['prompt_output']) ? [$this->aAgent['prompt_output']] : [];
  45. $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.";
  46. if ('alert' == $this->aAgent['trigger']) {
  47. if (!$this->aAgent['async']) {
  48. $aPromptTools[] = "Return array only, modified version of 'extra' array. Modifyable keys: " . $this->getAlertTriggerModifyableKeys() . ".";
  49. }
  50. $sDesc = trim(BxDolAiQuery::getAlertDesc($this->aAgent['alert']));
  51. if ('.' != mb_substr($sDesc, -1))
  52. $sDesc .= '.';
  53. $aPromptSystem[] = $sDesc;
  54. }
  55. $oPrompt = new NeuronAI\Agent\SystemPrompt(
  56. background: $aPromptSystem,
  57. steps: $aPromptSteps,
  58. output: $aPromptOutput,
  59. toolsUsage: $aPromptTools
  60. );
  61. return (string) $oPrompt;
  62. }
  63. /**
  64. * @return \NeuronAI\Tools\ToolInterface[]
  65. */
  66. protected function tools(): array
  67. {
  68. if ($this->aAgent['tools']) {
  69. $aTools = explode(',', $this->aAgent['tools']);
  70. $aToolInstances = [];
  71. foreach ($aTools as $sToolId) {
  72. $iToolId = (int)trim($sToolId);
  73. if ($iToolId <= 0)
  74. continue;
  75. $aToolInstances[] = BxDolAIToolFactory::getToolInstance($iToolId);
  76. }
  77. return $aToolInstances;
  78. }
  79. else {
  80. return [];
  81. }
  82. }
  83. protected function vectorStore(): NeuronAI\RAG\VectorStore\VectorStoreInterface
  84. {
  85. if ($this->aAgent['vector_store_id'])
  86. return BxDolAIVectorStoreFactory::getVectorStoreInstance($this->aAgent['vector_store_id']);
  87. else
  88. return new NeuronAI\RAG\VectorStore\MemoryVectorStore();
  89. }
  90. /**
  91. * Retrieval is built for every chat. A una-proxy store is searched on the
  92. * master. Any other store needs an embedding model on this site.
  93. */
  94. protected function retrieval(): NeuronAI\RAG\Retrieval\RetrievalInterface
  95. {
  96. if (($this->aAgent['vector_store_type'] ?? '') === 'una-proxy') {
  97. return new BxDolAiRetrievalUnaProxy(
  98. (string)getParam('sys_oauth_key'),
  99. (string)getParam('sys_oauth_secret')
  100. );
  101. }
  102. if (!isset($this->embeddingsProvider)) {
  103. if ((int)($this->aAgent['vector_store_id'] ?? 0) > 0)
  104. throw new Exception("Vector store {$this->aAgent['vector_store_id']} has no embedding model");
  105. return new BxDolAiRetrievalNone();
  106. }
  107. return new NeuronAI\RAG\Retrieval\SimilarityRetrieval(
  108. $this->resolveVectorStore(),
  109. $this->resolveEmbeddingsProvider()
  110. );
  111. }
  112. protected function embeddings(): NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface
  113. {
  114. throw new Exception('Embedding model is not configured');
  115. }
  116. protected function chatHistory(): NeuronAI\Chat\History\ChatHistoryInterface
  117. {
  118. if ($this->aAgent['chat_history_context']) {
  119. return new BxDolAiChatHistory(
  120. thread_id: $this->getChatHistoryThreadId(),
  121. pdo: BxDolDb::getInstance()->getLink(),
  122. table: 'sys_agents_chat_history',
  123. contextWindow: $this->aAgent['chat_history_context']
  124. );
  125. }
  126. else {
  127. return new NeuronAI\Chat\History\InMemoryChatHistory(
  128. contextWindow: 50000
  129. );
  130. }
  131. }
  132. /**
  133. * Turn tool exceptions into a tool result so chat history stays a valid sequence.
  134. *
  135. * @return callable|null fn(\Throwable $e, \NeuronAI\Tools\ToolInterface $tool): string
  136. */
  137. protected function resolveToolErrorHandler(): ?callable
  138. {
  139. return function (\Throwable $e, NeuronAI\Tools\ToolInterface $tool): string {
  140. return 'Tool "' . $tool->getName() . '" failed: ' . $e->getMessage();
  141. };
  142. }
  143. protected function getChatHistoryThreadId(): string
  144. {
  145. return BxDolAiChat::threadId($this->aAgent, $this->aParams);
  146. }
  147. protected function getAlertTriggerModifyableKeys(): string
  148. {
  149. if ('alert' != $this->aAgent['trigger'])
  150. return 'none';
  151. $aAlert = BxDolAiQuery::getAlert($this->aAgent['alert']);
  152. if (!$aAlert)
  153. return 'none';
  154. $aKeys = json_decode($aAlert['extra_refs']);
  155. if (!$aKeys)
  156. return 'none';
  157. return implode(',', $aKeys);
  158. }
  159. }