amber-laravel/app/LLM/Qwen.php
2024-07-25 01:16:41 +08:00

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 . ',你确定是这个吗?';
}
}