clearToolInfo(); } private function clearToolInfo(): void { $this->tool_info = [ 'function' => [ 'name' => '', 'arguments' => '', ], 'index' => 0, 'id' => '', 'type' => 'function', ]; } public function setHistory(History $history): void { $this->history = $history; } public function setUser(User $user): void { $this->user = $user; } public function setTools(Collection $tools): void { $this->tools = $tools; foreach ($tools as $tool) { $this->tool_functions = array_merge($this->tool_functions, $tool->data['tool_functions']); } } /** * @throws GuzzleException */ public function streamResponse() { $client = new Client([ 'headers' => [ 'Content-Type' => 'application/json', 'Accept' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Authorization' => 'Bearer '.config('services.dashscope.api_key'), ], ]); $url = config('services.dashscope.api_base').'/compatible-mode/v1/chat/completions'; $this->retry = true; while ($this->retry) { $ai_chunk_message = new AIChunkMessage(''); $request_body = [ 'model' => $this->model, 'messages' => $this->history->getForApi(), 'stream' => true, 'tools' => $this->tool_functions, ]; try { $response = $client->request('POST', $url, [ 'body' => json_encode($request_body), 'stream' => true, ]); } catch (ClientException $e) { Log::error($e->getMessage()); Log::debug('http body', [$e->getResponse()->getBody()->getContents()]); // 保存 history Log::debug('chat history', $this->history->getMessages()); return; } $body = $response->getBody(); $buffer = ''; while (! $body->eof()) { $data = $body->read(1); $buffer .= $data; // 读取到 \n\n 则一条消息结束 if (preg_match(self::END_OF_MESSAGE, $buffer)) { // 去掉 data: $d = substr($buffer, 5); // 去掉第一个空格(如果是) $d = ltrim($d, ' '); // 去除末尾的 END_OF_MESSAGE $d = rtrim($d, self::END_OF_MESSAGE); // 如果是 [DONE] if ($d == '[DONE]') { break; } if (empty($d)) { return; } // log $event = json_decode($d, true); // Log::debug('event', $event); if (isset($event['choices'][0])) { $finish_reason = $event['choices'][0]['finish_reason']; // 检测是不是 stop if ($finish_reason == 'stop') { $ai_chunk_message->processing = false; $ai_message = $ai_chunk_message->toAIMessage(); $this->history->addMessage($ai_message); yield $ai_message; // Log::debug('stop!'); break; } elseif ($finish_reason == 'tool_calls') { $this->retry = true; // Log::debug('finished_reason is tool call, set retry'); } $delta = $event['choices'][0]['delta']; if ($event['choices'][0]['finish_reason'] == 'tool_calls') { $info = json_decode($this->tool_info['function']['arguments'], true); if (isset($info['properties'])) { $info = $info['properties']; } // 开始调用 $tool_req = new ToolRequestMessage( content: $this->tool_info['function']['name'], ); yield $tool_req; // Log::debug('tool req', [$tool_req]); $tool_call_message = new AIToolCallMessage(content: ''); $tool_call_message->tool_calls = [ $this->tool_info, ]; // 调用 $r = $this->callTool($this->tool_info['function']['name'], $info); $tool_response_message = new ToolResponseMessage(content: $r); $tool_response_message->name = $this->tool_info['function']['name']; $this->history->addMessage($tool_call_message); $this->history->addMessage($tool_response_message); // Log::debug('tool call message', [$tool_call_message]); // Log::debug('tool response', [$tool_response_message]); yield $tool_call_message; yield $tool_response_message; // 清空参数,以备二次调用 $this->clearToolInfo(); } elseif (isset($delta['tool_calls'])) { // 更新 tool_info $call_info = $delta['tool_calls'][0]; if (! empty($call_info['id'])) { $this->tool_info['id'] = $call_info['id']; } if (! empty($call_info['index'])) { $this->tool_info['index'] = $call_info['index']; } if (! empty($call_info['function']['name'])) { $this->tool_info['function']['name'] = $call_info['function']['name']; } if (! empty($call_info['function']['arguments'])) { $this->tool_info['function']['arguments'] .= $call_info['function']['arguments']; } } elseif (empty($delta['choices'][0]['finish_reason'])) { if (! empty($delta['content'])) { $ai_chunk_message->append($delta['content']); $ai_chunk_message->processing = true; yield $ai_chunk_message; // Log::debug('chunk', [$ai_chunk_message]); } $this->retry = false; } } $buffer = ''; } } } $this->retry = false; } /** * @throws ConnectionException */ private function callTool($tool_name, $args): string { // 遍历 tools, 找到 foreach ($this->tools as $tool) { foreach ($tool->data['tool_functions'] as $f) { if ($f['function']['name'] == $tool_name) { $c = new LLMTool(); $c->setTool($tool->data['tool_id']); if (isset($this->user)) { $c->setUser($this->user); } $r = $c->callTool($tool_name, $args); return $r->result; } } } return '[Hint] 没有找到对应的工具 '.$tool_name.',你确定是这个吗?'; } }