BxDolAiRetrievalUnaProxy.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  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. * Retrieval that asks the UNA master to embed and search its knowledge store.
  7. *
  8. * @defgroup UnaCore UNA Core
  9. * @{
  10. */
  11. class BxDolAiRetrievalUnaProxy implements NeuronAI\RAG\Retrieval\RetrievalInterface
  12. {
  13. public const URL = 'https://api.unacms.com/m/ai_proxy/v1/knowledge/search';
  14. /**
  15. * @param callable(string, array): array{code:int, body:string}|null $fPost
  16. */
  17. public function __construct(
  18. protected string $sKey,
  19. protected string $sSecret,
  20. protected $fPost = null,
  21. ) {
  22. }
  23. public function retrieve(NeuronAI\Chat\Messages\Message $query): array
  24. {
  25. $sQuery = trim((string)$query->getContent());
  26. if ($sQuery === '' || $this->sKey === '' || $this->sSecret === '')
  27. return [];
  28. $sBody = BxDolAIUnaProxyHttpClient::encodeBody(['query' => $sQuery]);
  29. $aSigned = (new BxDolAIUnaProxyHttpClient($this->sKey, $this->sSecret))->headersForBody($sBody);
  30. $aHeaders = [
  31. 'Content-Type: application/json',
  32. 'X-Una-Key: ' . $aSigned['X-Una-Key'],
  33. 'X-Una-Sign: ' . $aSigned['X-Una-Sign'],
  34. ];
  35. try {
  36. $aResponse = $this->post($sBody, $aHeaders);
  37. }
  38. catch (Throwable $oException) {
  39. bx_log('sys_agents', 'Remote knowledge search failed: ' . $oException->getMessage(), BX_LOG_ERR);
  40. return [];
  41. }
  42. if (!is_array($aResponse) || (int)($aResponse['code'] ?? -1) !== 200) {
  43. bx_log('sys_agents', 'Remote knowledge search failed, exit code: ' . ($aResponse['code'] ?? -1), BX_LOG_ERR);
  44. return [];
  45. }
  46. return $this->documentsFrom((string)($aResponse['body'] ?? ''));
  47. }
  48. /**
  49. * @param array<int, string> $aHeaders
  50. * @return array{code:int, body:string}
  51. */
  52. protected function post(string $sBody, array $aHeaders): array
  53. {
  54. if ($this->fPost !== null)
  55. return ($this->fPost)($sBody, $aHeaders);
  56. $sHttpCode = 0;
  57. $sResult = bx_file_get_contents(self::URL, $sBody, 'post-raw', $aHeaders, $sHttpCode, [], 15);
  58. if ($sResult === false)
  59. $sResult = '';
  60. return [
  61. 'code' => (int)$sHttpCode,
  62. 'body' => (string)$sResult,
  63. ];
  64. }
  65. /**
  66. * @return NeuronAI\RAG\Document[]
  67. */
  68. protected function documentsFrom(string $sBody): array
  69. {
  70. $aBody = json_decode($sBody, true);
  71. if (!is_array($aBody) || !isset($aBody['documents']) || !is_array($aBody['documents']))
  72. return [];
  73. $aDocuments = [];
  74. foreach ($aBody['documents'] as $aDocument) {
  75. if (!is_array($aDocument))
  76. continue;
  77. $sContent = trim((string)($aDocument['content'] ?? ''));
  78. if ($sContent === '')
  79. continue;
  80. $oDocument = new NeuronAI\RAG\Document($sContent);
  81. $sSourceType = trim((string)($aDocument['sourceType'] ?? ''));
  82. $sSourceName = trim((string)($aDocument['sourceName'] ?? ''));
  83. if ($sSourceType !== '')
  84. $oDocument->sourceType = $sSourceType;
  85. if ($sSourceName !== '')
  86. $oDocument->sourceName = $sSourceName;
  87. if (isset($aDocument['score']))
  88. $oDocument->setScore((float)$aDocument['score']);
  89. $aDocuments[] = $oDocument;
  90. }
  91. return $aDocuments;
  92. }
  93. }
  94. /** @} */