247 lines
7.8 KiB
PHP
247 lines
7.8 KiB
PHP
<?php
|
|
|
|
namespace App\LLM;
|
|
|
|
use App\Logic\LLMTool;
|
|
use App\Repositories\LLM\AIChunkMessage;
|
|
use App\Repositories\LLM\AIToolCallMessage;
|
|
use App\Repositories\LLM\History;
|
|
use App\Repositories\LLM\ToolRequestMessage;
|
|
use App\Repositories\LLM\ToolResponseMessage;
|
|
use GuzzleHttp\Client;
|
|
use GuzzleHttp\Exception\GuzzleException;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
|
|
class Qwen implements BaseLLM
|
|
{
|
|
protected string $model = 'qwen-max';
|
|
|
|
protected History $history;
|
|
|
|
private array $tool_info = [];
|
|
|
|
private Collection $tools;
|
|
private array $tool_functions = [];
|
|
|
|
const END_OF_MESSAGE = "/\r\n\r\n|\n\n|\r\r/";
|
|
|
|
private bool $request_again = false;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->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 . ',你确定是这个吗?';
|
|
}
|
|
|
|
}
|