1
0

BxDolAIModelFactory.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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) {
  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. // embeddings models ------------------------
  160. case 'ollama-embeddings':
  161. $o = new NeuronAI\RAG\Embeddings\OllamaEmbeddingsProvider(
  162. model: $a['model'],
  163. url: $aParameters['url'] ?? 'http://localhost:11434/api',
  164. parameters: $aParameters['parameters'] ?? [],
  165. );
  166. break;
  167. case 'voyageai-embeddings':
  168. $o = new NeuronAI\RAG\Embeddings\VoyageEmbeddingsProvider(
  169. key: $a['key'],
  170. model: $a['model'],
  171. dimensions: !empty($aParameters['dimensions']) ? (int)$aParameters['dimensions'] : null
  172. );
  173. break;
  174. case 'openai-embeddings':
  175. $o = new NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider(
  176. key: $a['key'],
  177. model: $a['model'],
  178. dimensions: !empty($aParameters['dimensions']) ? (int)$aParameters['dimensions'] : 1024
  179. );
  180. break;
  181. case 'openai-like-embeddings':
  182. $o = new NeuronAI\RAG\Embeddings\OpenAILikeEmbeddings(
  183. baseUri: $aParameters['baseUri'],
  184. key: $a['key'],
  185. model: $a['model'],
  186. dimensions: !empty($aParameters['dimensions']) ? (int)$aParameters['dimensions'] : 1024
  187. );
  188. break;
  189. case 'aws-bedrock-embeddings':
  190. $oClient = new Aws\BedrockRuntime\BedrockRuntimeClient($aParameters['client_params']);
  191. $o = new NeuronAI\RAG\Embeddings\AwsBedrockEmbeddingsProvider(
  192. client: $oClient,
  193. model: $a['model']
  194. );
  195. break;
  196. default:
  197. bx_log('sys_agents', "Model type {$a['type']} is not supported", BX_LOG_ERR);
  198. throw new Exception("Model type {$a['type']} is not supported");
  199. }
  200. $GLOBALS['bxDolClasses'][$sCacheKey] = $o;
  201. return $o;
  202. }
  203. /**
  204. * Judge models (capabilities = judge): typed questions -> structured answers, see BxDolAiJudge.
  205. */
  206. public static function getJudgeInstance(int $iId): BxDolAiJudge
  207. {
  208. $sCacheKey = __CLASS__ . '_Judge_' . $iId;
  209. if (isset($GLOBALS['bxDolClasses'][$sCacheKey]))
  210. return $GLOBALS['bxDolClasses'][$sCacheKey];
  211. list($a, $aParameters) = self::_getModel($iId);
  212. if ($a['capabilities'] != BxDolAiJudge::CAPABILITY) {
  213. bx_log('sys_agents', "Model with id {$iId} is not a judge model", BX_LOG_ERR);
  214. throw new Exception("Model with id {$iId} is not a judge model");
  215. }
  216. switch($a['type']) {
  217. case 'typesafe':
  218. $o = new BxDolAiJudgeTypeSafe($a, $aParameters);
  219. break;
  220. default:
  221. bx_log('sys_agents', "Judge model type {$a['type']} is not supported", BX_LOG_ERR);
  222. throw new Exception("Judge model type {$a['type']} is not supported");
  223. }
  224. $GLOBALS['bxDolClasses'][$sCacheKey] = $o;
  225. return $o;
  226. }
  227. /**
  228. * Load model row and its merged params; throws when the model is missing, inactive or has no key.
  229. * @return array [model row, params]
  230. */
  231. protected static function _getModel(int $iId): array
  232. {
  233. $aProvidersWithKey = ['anthropic', 'openai-embeddings', 'voyageai-embeddings', 'openai-like-embeddings', 'openai-responses', 'openai-like', 'typesafe'];
  234. $a = BxDolAiQuery::getModelObject($iId);
  235. if (!$a) {
  236. bx_log('sys_agents', "Agent AI Model with id {$iId} not found", BX_LOG_ERR);
  237. throw new Exception("Agent AI Model with id {$iId} not found");
  238. }
  239. $a['key'] = (string)$a['key'];
  240. if (in_array($a['type'], $aProvidersWithKey, true) && $a['key'] === '') {
  241. bx_log('sys_agents', "Model with id {$iId} has empty key, can't be used", BX_LOG_ERR);
  242. throw new Exception("Model with id {$iId} has empty key, can't be used");
  243. }
  244. if (!$a['active']) {
  245. bx_log('sys_agents', "Model with id {$iId} is not active, can't be used", BX_LOG_ERR);
  246. throw new Exception("Model with id {$iId} is not active, can't be used");
  247. }
  248. $aParametersSystem = !empty($a['params']) ? json_decode($a['params'], true) : [];
  249. $aParametersUser = !empty($a['params_user']) ? json_decode($a['params_user'], true) : [];
  250. $aParameters = array_merge(is_array($aParametersSystem) ? $aParametersSystem : [], is_array($aParametersUser) ? $aParametersUser : []);
  251. // replace markers {key} {model} in $aParameters recoursively
  252. $aParameters = bx_replace_markers($aParameters, [
  253. 'key' => $a['key'],
  254. 'model' => $a['model']
  255. ]);
  256. return [$a, $aParameters];
  257. }
  258. }