BxDolAiChatHistory.php 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  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. use NeuronAI\Chat\History\ChatHistoryInterface;
  10. use NeuronAI\Chat\History\SQLChatHistory;
  11. use NeuronAI\Chat\Messages\AssistantMessage;
  12. use NeuronAI\Chat\Messages\Message;
  13. use NeuronAI\Chat\Messages\ToolCallMessage;
  14. use NeuronAI\Chat\Messages\ToolResultMessage;
  15. use NeuronAI\Chat\Messages\UserMessage;
  16. class BxDolAiChatHistory extends SQLChatHistory
  17. {
  18. public function addMessage(Message $message): ChatHistoryInterface
  19. {
  20. $bStampIp = $this->isIncomingFirstUserMessage($message);
  21. $this->ensureValidTail($message);
  22. $o = parent::addMessage($message);
  23. if ($bStampIp)
  24. $this->stampVisitorIp();
  25. return $o;
  26. }
  27. protected function isIncomingFirstUserMessage(Message $incoming): bool
  28. {
  29. if (!($incoming instanceof UserMessage) || $incoming instanceof ToolResultMessage)
  30. return false;
  31. foreach ($this->history as $oMessage) {
  32. if ($oMessage instanceof UserMessage && !($oMessage instanceof ToolResultMessage))
  33. return false;
  34. }
  35. return true;
  36. }
  37. protected function stampVisitorIp(): void
  38. {
  39. if (!function_exists('getVisitorIP') || !function_exists('bx_get_ip_hash'))
  40. return;
  41. $iIp = bx_get_ip_hash(getVisitorIP());
  42. if ($iIp === '' || $iIp === '0' || $iIp === 0)
  43. return;
  44. $stmt = $this->pdo->prepare("UPDATE {$this->table} SET `ip` = :ip WHERE `thread_id` = :thread_id AND (`ip` = 0 OR `ip` IS NULL)");
  45. $stmt->execute([
  46. 'ip' => $iIp,
  47. 'thread_id' => $this->thread_id,
  48. ]);
  49. }
  50. protected function ensureValidTail(Message $incoming): void
  51. {
  52. if ($this->history === []) {
  53. return;
  54. }
  55. $last = $this->history[array_key_last($this->history)];
  56. // Dangling tool call: close it before anything else
  57. if ($last instanceof ToolCallMessage && !($incoming instanceof ToolResultMessage)) {
  58. foreach ($last->getTools() as $oTool) {
  59. if (method_exists($oTool, 'setResult')) {
  60. $oTool->setResult('Tool call was interrupted');
  61. }
  62. }
  63. $this->history[] = new ToolResultMessage($last->getTools());
  64. $this->setMessages($this->history);
  65. $last = $this->history[array_key_last($this->history)];
  66. }
  67. // Orphan user (failed inference) or tool result without assistant: close the pair before another user arrives.
  68. // ToolResultMessage extends UserMessage, so this also covers tool_result → user.
  69. if (
  70. $incoming instanceof UserMessage
  71. && !($incoming instanceof ToolResultMessage)
  72. && $last instanceof UserMessage
  73. ) {
  74. $this->history[] = new AssistantMessage('[Previous reply failed]');
  75. $this->setMessages($this->history);
  76. }
  77. }
  78. public function persistMessages(): void
  79. {
  80. $this->setMessages($this->history);
  81. }
  82. }