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 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->request_again = true; while ($this->request_again) { $ai_chunk_message = new AIChunkMessage(""); $request_body = [ 'model' => $this->model, 'messages' => $this->history->getForApi(), 'stream' => true, 'tools' => $this->tool_functions, ]; $response = $client->request('POST', $url, [ 'body' => json_encode($request_body), 'stream' => true, ]); $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; } $event = json_decode($d, true); if (isset($event['choices'][0])) { // 检测是不是 stop if ($event['choices'][0]['finish_reason'] == 'stop') { $ai_chunk_message->processing = false; $this->history->addMessage($ai_chunk_message->toAIMessage()); break; // yield $ai_chunk_message; } $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']; } yield new ToolRequestMessage( content: $this->tool_info['function']['name'], ); $r = $this->callTool($this->tool_info['function']['name'], $info); // $tool_response = [ // 'name' => $this->tool_info['function']['name'], // 'role' => 'tool', // 'content' => $r, // ]; $tool_call_message = new AIToolCallMessage(content: ""); $tool_call_message->tool_calls = [ $this->tool_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); yield $tool_call_message; yield $tool_response_message; // // // // $this->history[] = $tool_response; // yield new ToolResponseMessage( // content: $tool_response_message->content, // ); // 清空参数,以备二次调用 $this->clearToolInfo(); // 再次请求以获取结果 $this->request_again = true; } elseif (isset($delta['tool_calls'])) { // 更新 tool_info $call_info = $delta['tool_calls'][0]; if (isset($call_info['id'])) { $this->tool_info['id'] = $call_info['id']; } if (isset($call_info['index'])) { $this->tool_info['index'] = $call_info['index']; } if (isset($call_info['function']['name'])) { $this->tool_info['function']['name'] = $call_info['function']['name']; } if (isset($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; } $this->request_again = false; } } $buffer = ''; } } } } 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->setCallbackUrl($tool->data['callback_url']); $r = $c->callTool($tool_name, $args); return $r->result; } } } return "[Hint] 没有找到对应的工具 " . $tool_name . ',你确定是这个吗?'; } }