BxDolAIModelFactory.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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. class BxDolAIModelFactory extends BxDolFactory
  10. {
  11. /**
  12. * Chat (chatllm / chatvlm) and embeddings models as NeuronAI providers.
  13. * Judge models (capabilities = judge) are not NeuronAI providers, use getJudgeInstance().
  14. */
  15. public static function getModelInstance(int $iId, array $aOverrides = []): NeuronAI\Providers\AIProviderInterface | NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface
  16. {
  17. $iMaxTokensOverride = (int)($aOverrides['max_tokens'] ?? 0);
  18. $sCacheKey = __CLASS__ . '_Model_' . $iId . ($iMaxTokensOverride > 0 ? '_' . $iMaxTokensOverride : '');
  19. if (isset($GLOBALS['bxDolClasses'][$sCacheKey]))
  20. return $GLOBALS['bxDolClasses'][$sCacheKey];
  21. list($a, $aParameters) = self::_getModel($iId);
  22. if ($a['capabilities'] == BxDolAiJudge::CAPABILITY) {
  23. bx_log('sys_agents', "Model with id {$iId} is a judge model, it can't be used as a chat/embeddings provider", BX_LOG_ERR);
  24. throw new Exception("Model with id {$iId} is a judge model, it can't be used as a chat/embeddings provider");
  25. }
  26. if ($iMaxTokensOverride > 0 && ($a['type'] ?? '') !== 'una-proxy') {
  27. $aParameters['max_tokens'] = $iMaxTokensOverride;
  28. if (!isset($aParameters['parameters']) || !is_array($aParameters['parameters']))
  29. $aParameters['parameters'] = [];
  30. switch ($a['type']) {
  31. case 'openai-responses':
  32. $aParameters['parameters']['max_output_tokens'] = $iMaxTokensOverride;
  33. break;
  34. case 'azure-openai':
  35. $aParameters['parameters']['max_completion_tokens'] = $iMaxTokensOverride;
  36. break;
  37. case 'gemini':
  38. if (!isset($aParameters['parameters']['generationConfig']) || !is_array($aParameters['parameters']['generationConfig']))
  39. $aParameters['parameters']['generationConfig'] = [];
  40. $aParameters['parameters']['generationConfig']['maxOutputTokens'] = $iMaxTokensOverride;
  41. break;
  42. case 'ollama':
  43. if (!isset($aParameters['parameters']['options']) || !is_array($aParameters['parameters']['options']))
  44. $aParameters['parameters']['options'] = [];
  45. $aParameters['parameters']['options']['num_predict'] = $iMaxTokensOverride;
  46. break;
  47. case 'aws-bedrock':
  48. if (!isset($aParameters['inferenceConfig']) || !is_array($aParameters['inferenceConfig']))
  49. $aParameters['inferenceConfig'] = [];
  50. $aParameters['inferenceConfig']['maxTokens'] = $iMaxTokensOverride;
  51. break;
  52. default:
  53. $aParameters['parameters']['max_tokens'] = $iMaxTokensOverride;
  54. break;
  55. }
  56. }
  57. switch($a['type']) {
  58. // regular AI providers ------------------------
  59. case 'anthropic':
  60. $o = new NeuronAI\Providers\Anthropic\Anthropic(
  61. key: $a['key'],
  62. model: $a['model'],
  63. version: $aParameters['version'] ?? '2023-06-01',
  64. max_tokens: $aParameters['max_tokens'] ?? 8192,
  65. parameters: $aParameters['parameters'] ?? [],
  66. );
  67. break;
  68. case 'openai-responses':
  69. $o = new NeuronAI\Providers\OpenAI\Responses\OpenAIResponses(
  70. key: $a['key'],
  71. model: $a['model'],
  72. parameters: $aParameters['parameters'] ?? [],
  73. strict_response: $aParameters['strict_response'] ?? false,
  74. );
  75. break;
  76. case 'azure-openai':
  77. $o = new NeuronAI\Providers\OpenAI\AzureOpenAI(
  78. key: $a['key'],
  79. model: $a['model'],
  80. endpoint: $aParameters['endpoint'],
  81. version: $aParameters['version'],
  82. parameters: $aParameters['parameters'] ?? [],
  83. strict_response: $aParameters['strict_response'] ?? false,
  84. );
  85. break;
  86. case 'openai-like':
  87. $o = new NeuronAI\Providers\OpenAILike(
  88. baseUri: $aParameters['baseUri'],
  89. key: $a['key'],
  90. model: $a['model'],
  91. parameters: $aParameters['parameters'] ?? [],
  92. strict_response: $aParameters['strict_response'] ?? false,
  93. );
  94. break;
  95. case 'ollama':
  96. $o = new NeuronAI\Providers\Ollama\Ollama(
  97. url: $aParameters['url'] ?? 'http://localhost:11434/api',
  98. model: $a['model'],
  99. parameters: $aParameters['parameters'] ?? [],
  100. );
  101. break;
  102. case 'gemini':
  103. $o = new NeuronAI\Providers\Gemini\Gemini(
  104. key: $a['key'],
  105. model: $a['model'],
  106. parameters: $aParameters['parameters'] ?? [],
  107. );
  108. break;
  109. case 'mistral':
  110. $o = new NeuronAI\Providers\Mistral\Mistral(
  111. key: $a['key'],
  112. model: $a['model'],
  113. parameters: $aParameters['parameters'] ?? [],
  114. strict_response: $aParameters['strict_response'] ?? false,
  115. );
  116. break;
  117. case 'huggingface':
  118. $o = new NeuronAI\Providers\HuggingFace\HuggingFace(
  119. key: $a['key'],
  120. model: $a['model'],
  121. inferenceProvider: $aParameters['inferenceProvider'] ?? 'hf-inference/models', // cohere, groq, etc - https://github.com/neuron-core/neuron-ai/blob/3.x/src/Providers/HuggingFace/InferenceProvider.php
  122. parameters: $aParameters['parameters'] ?? [],
  123. strict_response: $aParameters['strict_response'] ?? false,
  124. );
  125. break;
  126. case 'deepseek':
  127. $o = new NeuronAI\Providers\Deepseek\Deepseek(
  128. key: $a['key'],
  129. model: $a['model'],
  130. parameters: $aParameters['parameters'] ?? [],
  131. strict_response: $aParameters['strict_response'] ?? false,
  132. );
  133. break;
  134. case 'grok':
  135. $o = new NeuronAI\Providers\XAI\Grok(
  136. key: $a['key'],
  137. model: $a['model'],
  138. parameters: $aParameters['parameters'] ?? [],
  139. strict_response: $aParameters['strict_response'] ?? false,
  140. );
  141. break;
  142. case 'aws-bedrock':
  143. $oClient = new Aws\BedrockRuntime\BedrockRuntimeClient($aParameters['client_params']);
  144. $o = new NeuronAI\Providers\AWS\BedrockRuntime(
  145. client: $oClient,
  146. model: $a['model'],
  147. inferenceConfig: $aParameters['inferenceConfig'] ?? [],
  148. );
  149. break;
  150. case 'cohere':
  151. $o = new NeuronAI\Providers\Cohere\Cohere(
  152. key: $a['key'],
  153. model: $a['model'],
  154. baseUri: $aParameters['baseUri'] ?? 'https://api.cohere.ai/v2',
  155. parameters: $aParameters['parameters'] ?? [],
  156. strict_response: $aParameters['strict_response'] ?? false,
  157. );
  158. break;
  159. case 'una-proxy':
  160. $o = new BxDolAIProviderUnaProxy(
  161. sKey: (string)getParam('sys_oauth_key'),
  162. sSecret: (string)getParam('sys_oauth_secret'),
  163. sModel: (string)$a['model'],
  164. );
  165. break;
  166. // embeddings models ------------------------
  167. case 'ollama-embeddings':
  168. $o = new NeuronAI\RAG\Embeddings\OllamaEmbeddingsProvider(
  169. model: $a['model'],
  170. url: $aParameters['url'] ?? 'http://localhost:11434/api',
  171. parameters: $aParameters['parameters'] ?? [],
  172. );
  173. break;
  174. case 'voyageai-embeddings':
  175. $o = new NeuronAI\RAG\Embeddings\VoyageEmbeddingsProvider(
  176. key: $a['key'],
  177. model: $a['model'],
  178. dimensions: !empty($aParameters['dimensions']) ? (int)$aParameters['dimensions'] : null
  179. );
  180. break;
  181. case 'openai-embeddings':
  182. $o = new NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider(
  183. key: $a['key'],
  184. model: $a['model'],
  185. dimensions: !empty($aParameters['dimensions']) ? (int)$aParameters['dimensions'] : 1024
  186. );
  187. break;
  188. case 'openai-like-embeddings':
  189. $o = new NeuronAI\RAG\Embeddings\OpenAILikeEmbeddings(
  190. baseUri: $aParameters['baseUri'],
  191. key: $a['key'],
  192. model: $a['model'],
  193. dimensions: !empty($aParameters['dimensions']) ? (int)$aParameters['dimensions'] : 1024
  194. );
  195. break;
  196. case 'aws-bedrock-embeddings':
  197. $oClient = new Aws\BedrockRuntime\BedrockRuntimeClient($aParameters['client_params']);
  198. $o = new NeuronAI\RAG\Embeddings\AwsBedrockEmbeddingsProvider(
  199. client: $oClient,
  200. model: $a['model']
  201. );
  202. break;
  203. default:
  204. bx_log('sys_agents', "Model type {$a['type']} is not supported", BX_LOG_ERR);
  205. throw new Exception("Model type {$a['type']} is not supported");
  206. }
  207. $GLOBALS['bxDolClasses'][$sCacheKey] = $o;
  208. return $o;
  209. }
  210. /**
  211. * Judge models (capabilities = judge): typed questions -> structured answers, see BxDolAiJudge.
  212. */
  213. public static function getJudgeInstance(int $iId): BxDolAiJudge
  214. {
  215. $sCacheKey = __CLASS__ . '_Judge_' . $iId;
  216. if (isset($GLOBALS['bxDolClasses'][$sCacheKey]))
  217. return $GLOBALS['bxDolClasses'][$sCacheKey];
  218. list($a, $aParameters) = self::_getModel($iId);
  219. if ($a['capabilities'] != BxDolAiJudge::CAPABILITY) {
  220. bx_log('sys_agents', "Model with id {$iId} is not a judge model", BX_LOG_ERR);
  221. throw new Exception("Model with id {$iId} is not a judge model");
  222. }
  223. switch($a['type']) {
  224. case 'typesafe':
  225. $o = new BxDolAiJudgeTypeSafe($a, $aParameters);
  226. break;
  227. default:
  228. bx_log('sys_agents', "Judge model type {$a['type']} is not supported", BX_LOG_ERR);
  229. throw new Exception("Judge model type {$a['type']} is not supported");
  230. }
  231. $GLOBALS['bxDolClasses'][$sCacheKey] = $o;
  232. return $o;
  233. }
  234. /**
  235. * Load model row and its merged params; throws when the model is missing, inactive or has no key.
  236. * @return array [model row, params]
  237. */
  238. protected static function _getModel(int $iId): array
  239. {
  240. // una-proxy signs with the site UNA key. An empty provider key is expected.
  241. $aProvidersWithKey = ['anthropic', 'openai-embeddings', 'voyageai-embeddings', 'openai-like-embeddings', 'openai-responses', 'openai-like', 'typesafe'];
  242. $a = BxDolAiQuery::getModelObject($iId);
  243. if (!$a) {
  244. bx_log('sys_agents', "Agent AI Model with id {$iId} not found", BX_LOG_ERR);
  245. throw new Exception("Agent AI Model with id {$iId} not found");
  246. }
  247. $a['key'] = (string)$a['key'];
  248. if (in_array($a['type'], $aProvidersWithKey, true) && $a['key'] === '') {
  249. bx_log('sys_agents', "Model with id {$iId} has empty key, can't be used", BX_LOG_ERR);
  250. throw new Exception("Model with id {$iId} has empty key, can't be used");
  251. }
  252. if (!$a['active']) {
  253. bx_log('sys_agents', "Model with id {$iId} is not active, can't be used", BX_LOG_ERR);
  254. throw new Exception("Model with id {$iId} is not active, can't be used");
  255. }
  256. $aParametersSystem = !empty($a['params']) ? json_decode($a['params'], true) : [];
  257. $aParametersUser = !empty($a['params_user']) ? json_decode($a['params_user'], true) : [];
  258. $aParameters = array_merge(is_array($aParametersSystem) ? $aParametersSystem : [], is_array($aParametersUser) ? $aParametersUser : []);
  259. // replace markers {key} {model} in $aParameters recoursively
  260. $aParameters = bx_replace_markers($aParameters, [
  261. 'key' => $a['key'],
  262. 'model' => $a['model']
  263. ]);
  264. return [$a, $aParameters];
  265. }
  266. }