BxAiProxyModule.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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 AIProxy AI Proxy
  7. * @ingroup UnaModules
  8. *
  9. * @{
  10. */
  11. class BxAiProxyModule extends BxDolModule
  12. {
  13. function __construct($aModule)
  14. {
  15. parent::__construct($aModule);
  16. }
  17. public function serviceGetModelOptions()
  18. {
  19. $aResult = [
  20. ['key' => '', 'value' => _t('_Select_one')],
  21. ];
  22. $aModels = (new BxDolAiQuery())->getModelsBy([
  23. 'sample' => 'all_pairs',
  24. 'active' => 1,
  25. 'capabilities' => ['chatvlm', 'chatllm'],
  26. 'type_not' => 'una-proxy',
  27. ]);
  28. if (!is_array($aModels))
  29. return $aResult;
  30. foreach ($aModels as $iId => $sTitle)
  31. $aResult[] = ['key' => (int)$iId, 'value' => (string)$sTitle];
  32. return $aResult;
  33. }
  34. public function serviceGetKnowledgeOptions()
  35. {
  36. $aResult = [
  37. ['key' => '', 'value' => _t('_Select_one')],
  38. ];
  39. $aStores = $this->_oDb->getActiveKnowledgeStores();
  40. foreach ($aStores as $iId => $sTitle)
  41. $aResult[] = ['key' => (int)$iId, 'value' => (string)$sTitle];
  42. return $aResult;
  43. }
  44. public function completions(): void
  45. {
  46. bx_import('Completion', $this->_aModule);
  47. $aBody = $this->authenticatedBody();
  48. $bStarted = false;
  49. try {
  50. $iId = (int)getParam('bx_ai_proxy_model');
  51. $this->assertChatModel($iId);
  52. $fFactory = static function (int $iModelId) {
  53. return BxDolAIModelFactory::getModelInstance($iModelId);
  54. };
  55. $oCompletion = new BxAiProxyCompletion((string)getParam('bx_ai_proxy_prompt'));
  56. if (!empty($aBody['stream'])) {
  57. $oCompletion->run($aBody, $iId, $fFactory, function (string $sEvent) use (&$bStarted) {
  58. if (!$bStarted) {
  59. header('Content-Type: text/event-stream; charset=utf-8');
  60. header('Cache-Control: no-cache');
  61. header('X-Accel-Buffering: no');
  62. $bStarted = true;
  63. }
  64. echo $sEvent;
  65. if (ob_get_level())
  66. ob_flush();
  67. flush();
  68. });
  69. return;
  70. }
  71. $sJson = $oCompletion->run($aBody, $iId, $fFactory);
  72. header('Content-Type: application/json; charset=utf-8');
  73. echo (string)$sJson;
  74. }
  75. catch (BxAiProxyException $oException) {
  76. if ($bStarted || headers_sent())
  77. return;
  78. $sType = 'server_error';
  79. if ($oException->httpCode() === 400)
  80. $sType = 'invalid_request_error';
  81. elseif ($oException->httpCode() === 401)
  82. $sType = 'authentication_error';
  83. $this->jsonError($oException->httpCode(), $oException->getMessage(), $sType);
  84. }
  85. catch (Throwable $oException) {
  86. bx_log('sys_agents', 'AI proxy failed: ' . $oException->getMessage(), BX_LOG_ERR);
  87. if ($bStarted || headers_sent())
  88. return;
  89. $this->jsonError(503, 'The proxy model is not available', 'server_error');
  90. }
  91. }
  92. public function knowledge(): void
  93. {
  94. bx_import('Completion', $this->_aModule);
  95. bx_import('Knowledge', $this->_aModule);
  96. $aBody = $this->authenticatedBody();
  97. try {
  98. $iId = (int)getParam('bx_ai_proxy_knowledge');
  99. $aResult = (new BxAiProxyKnowledge())->run(
  100. $aBody,
  101. $iId,
  102. static function (int $iStoreId) {
  103. return BxDolAiQuery::getVectorStoreObject($iStoreId);
  104. },
  105. static function (int $iModelId) {
  106. return BxDolAIModelFactory::getModelInstance($iModelId);
  107. },
  108. static function (int $iStoreId) {
  109. return BxDolAIVectorStoreFactory::getVectorStoreInstance($iStoreId);
  110. }
  111. );
  112. header('Content-Type: application/json; charset=utf-8');
  113. echo json_encode($aResult, JSON_UNESCAPED_UNICODE);
  114. }
  115. catch (BxAiProxyException $oException) {
  116. $sType = 'server_error';
  117. if ($oException->httpCode() === 400)
  118. $sType = 'invalid_request_error';
  119. elseif ($oException->httpCode() === 401)
  120. $sType = 'authentication_error';
  121. $this->jsonError($oException->httpCode(), $oException->getMessage(), $sType);
  122. }
  123. catch (Throwable $oException) {
  124. bx_log('sys_agents', 'AI proxy knowledge failed: ' . $oException->getMessage(), BX_LOG_ERR);
  125. $this->jsonError(503, 'The proxy knowledge is not available', 'server_error');
  126. }
  127. }
  128. /**
  129. * @return array<string, mixed>
  130. */
  131. protected function authenticatedBody(): array
  132. {
  133. bx_import('Auth', $this->_aModule);
  134. $sBody = file_get_contents('php://input');
  135. if ($sBody === false)
  136. $sBody = '';
  137. $aAuth = (new BxAiProxyAuth())->authenticateRequest(
  138. $this->requestHeader('X-Una-Key'),
  139. $this->requestHeader('X-Una-Sign'),
  140. $sBody
  141. );
  142. if (empty($aAuth['ok']))
  143. $this->jsonError(401, 'Unauthorized', 'authentication_error');
  144. if(($sMl = 'bx_market_api') && ($sMd = 'is_operator_chat') && bx_is_srv($sMl, $sMd)) {
  145. $sKey = getParam('sys_oauth_key');
  146. if($sKey && !bx_srv($sMl, $sMd, [$sKey]))
  147. $this->jsonError(402, 'Payment Required', 'payment_required');
  148. }
  149. $aBody = json_decode($sBody, true);
  150. if (!is_array($aBody))
  151. $this->jsonError(400, 'Bad request', 'invalid_request_error');
  152. return $aBody;
  153. }
  154. public function requestHeader(string $sName): string
  155. {
  156. $sServer = 'HTTP_' . strtoupper(str_replace('-', '_', $sName));
  157. foreach ([$sServer, 'REDIRECT_' . $sServer] as $sKey) {
  158. if (isset($_SERVER[$sKey]) && (string)$_SERVER[$sKey] !== '')
  159. return trim((string)$_SERVER[$sKey]);
  160. }
  161. if (function_exists('getallheaders')) {
  162. $aHeaders = getallheaders();
  163. if (is_array($aHeaders)) {
  164. foreach ($aHeaders as $sHeader => $sValue) {
  165. if (strcasecmp((string)$sHeader, $sName) === 0)
  166. return trim((string)$sValue);
  167. }
  168. }
  169. }
  170. return '';
  171. }
  172. protected function assertChatModel(int $iId): void
  173. {
  174. bx_import('Completion', $this->_aModule);
  175. if ($iId <= 0)
  176. throw new BxAiProxyException(503, 'The proxy model is not available');
  177. $aModel = BxDolAiQuery::getModelObject($iId);
  178. if (!is_array($aModel) || empty($aModel['active']))
  179. throw new BxAiProxyException(503, 'The proxy model is not available');
  180. if (!in_array((string)($aModel['capabilities'] ?? ''), ['chatllm', 'chatvlm'], true))
  181. throw new BxAiProxyException(503, 'The proxy model is not available');
  182. if ((string)($aModel['type'] ?? '') === 'una-proxy')
  183. throw new BxAiProxyException(503, 'The proxy model is not available');
  184. }
  185. protected function jsonError(int $iCode, string $sMessage, string $sType): void
  186. {
  187. http_response_code($iCode);
  188. header('Content-Type: application/json; charset=utf-8');
  189. echo json_encode([
  190. 'error' => [
  191. 'message' => $sMessage,
  192. 'type' => $sType,
  193. ],
  194. ], JSON_UNESCAPED_UNICODE);
  195. exit;
  196. }
  197. }
  198. /** @} */