BxAiProxyModule.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  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 completions(): void
  35. {
  36. bx_import('Auth', $this->_aModule);
  37. bx_import('Completion', $this->_aModule);
  38. $sBody = file_get_contents('php://input');
  39. if ($sBody === false)
  40. $sBody = '';
  41. $aAuth = (new BxAiProxyAuth())->authenticateRequest(
  42. $this->requestHeader('X-Una-Key'),
  43. $this->requestHeader('X-Una-Sign'),
  44. $sBody
  45. );
  46. if (empty($aAuth['ok']))
  47. $this->jsonError(401, 'Unauthorized', 'authentication_error');
  48. $aBody = json_decode($sBody, true);
  49. if (!is_array($aBody))
  50. $this->jsonError(400, 'Bad request', 'invalid_request_error');
  51. $bStarted = false;
  52. try {
  53. $iId = (int)getParam('bx_ai_proxy_model');
  54. $this->assertChatModel($iId);
  55. $fFactory = static function (int $iModelId) {
  56. return BxDolAIModelFactory::getModelInstance($iModelId);
  57. };
  58. $oCompletion = new BxAiProxyCompletion();
  59. if (!empty($aBody['stream'])) {
  60. $oCompletion->run($aBody, $iId, $fFactory, function (string $sEvent) use (&$bStarted) {
  61. if (!$bStarted) {
  62. header('Content-Type: text/event-stream; charset=utf-8');
  63. header('Cache-Control: no-cache');
  64. header('X-Accel-Buffering: no');
  65. $bStarted = true;
  66. }
  67. echo $sEvent;
  68. if (ob_get_level())
  69. ob_flush();
  70. flush();
  71. });
  72. return;
  73. }
  74. $sJson = $oCompletion->run($aBody, $iId, $fFactory);
  75. header('Content-Type: application/json; charset=utf-8');
  76. echo (string)$sJson;
  77. }
  78. catch (BxAiProxyException $oException) {
  79. if ($bStarted || headers_sent())
  80. return;
  81. $sType = 'server_error';
  82. if ($oException->httpCode() === 400)
  83. $sType = 'invalid_request_error';
  84. elseif ($oException->httpCode() === 401)
  85. $sType = 'authentication_error';
  86. $this->jsonError($oException->httpCode(), $oException->getMessage(), $sType);
  87. }
  88. catch (Throwable $oException) {
  89. bx_log('sys_agents', 'AI proxy failed: ' . $oException->getMessage(), BX_LOG_ERR);
  90. if ($bStarted || headers_sent())
  91. return;
  92. $this->jsonError(503, 'The proxy model is not available', 'server_error');
  93. }
  94. }
  95. public function requestHeader(string $sName): string
  96. {
  97. $sServer = 'HTTP_' . strtoupper(str_replace('-', '_', $sName));
  98. foreach ([$sServer, 'REDIRECT_' . $sServer] as $sKey) {
  99. if (isset($_SERVER[$sKey]) && (string)$_SERVER[$sKey] !== '')
  100. return trim((string)$_SERVER[$sKey]);
  101. }
  102. if (function_exists('getallheaders')) {
  103. $aHeaders = getallheaders();
  104. if (is_array($aHeaders)) {
  105. foreach ($aHeaders as $sHeader => $sValue) {
  106. if (strcasecmp((string)$sHeader, $sName) === 0)
  107. return trim((string)$sValue);
  108. }
  109. }
  110. }
  111. return '';
  112. }
  113. protected function assertChatModel(int $iId): void
  114. {
  115. bx_import('Completion', $this->_aModule);
  116. if ($iId <= 0)
  117. throw new BxAiProxyException(503, 'The proxy model is not available');
  118. $aModel = BxDolAiQuery::getModelObject($iId);
  119. if (!is_array($aModel) || empty($aModel['active']))
  120. throw new BxAiProxyException(503, 'The proxy model is not available');
  121. if (!in_array((string)($aModel['capabilities'] ?? ''), ['chatllm', 'chatvlm'], true))
  122. throw new BxAiProxyException(503, 'The proxy model is not available');
  123. if ((string)($aModel['type'] ?? '') === 'una-proxy')
  124. throw new BxAiProxyException(503, 'The proxy model is not available');
  125. }
  126. protected function jsonError(int $iCode, string $sMessage, string $sType): void
  127. {
  128. http_response_code($iCode);
  129. header('Content-Type: application/json; charset=utf-8');
  130. echo json_encode([
  131. 'error' => [
  132. 'message' => $sMessage,
  133. 'type' => $sType,
  134. ],
  135. ], JSON_UNESCAPED_UNICODE);
  136. exit;
  137. }
  138. }
  139. /** @} */