实现 函数调用
This commit is contained in:
parent
97753a264a
commit
ab03c27304
76
app/Console/Commands/TestLLM.php
Normal file
76
app/Console/Commands/TestLLM.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\LLM\Qwen;
|
||||
use App\Models\Assistant;
|
||||
use App\Models\Tool;
|
||||
use App\Repositories\LLM\AIMessage;
|
||||
use App\Repositories\LLM\ChatEnum;
|
||||
use App\Repositories\LLM\History;
|
||||
use App\Repositories\LLM\HumanMessage;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class TestLLM extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:testllm';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$llm = new Qwen();
|
||||
|
||||
$history = new History();
|
||||
|
||||
$tool = Tool::get();
|
||||
|
||||
$llm->setTools($tool);
|
||||
|
||||
$llm->setHistory($history);
|
||||
|
||||
while (true) {
|
||||
// var_dump($history->getMessages());
|
||||
|
||||
$q = $this->ask('请输入问题');
|
||||
|
||||
if (empty($q)) {
|
||||
$q = "北京天气";
|
||||
}
|
||||
|
||||
$history->addMessage(new HumanMessage($q));
|
||||
|
||||
$s = $llm->streamResponse();
|
||||
// 循环输出
|
||||
foreach ($s as $item) {
|
||||
if ($item->role == ChatEnum::Tool) {
|
||||
if ($item->processing) {
|
||||
$this->info("正在执行: " . $item->content);
|
||||
echo "\n";
|
||||
} else {
|
||||
$this->info("执行结果: " . $item->content);
|
||||
}
|
||||
} else if ($item->role == ChatEnum::AssistantChunk) {
|
||||
echo $item->getLastAppend();
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Assistant;
|
||||
use App\Models\Chat;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ChatController extends Controller
|
||||
@ -10,9 +12,11 @@ class ChatController extends Controller
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
//
|
||||
return $this->success(
|
||||
Chat::whereUserId($request->user('api')->id)->get()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -20,7 +24,26 @@ public function index()
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
$request->validate([
|
||||
'name' => 'string|required',
|
||||
'assistant_id' => 'exists:assistants,id|required',
|
||||
]);
|
||||
|
||||
$assistant = Assistant::find($request->input('assistant_id'));
|
||||
|
||||
if ($assistant->user_id !== $request->user('api')->id) {
|
||||
return $this->forbidden();
|
||||
}
|
||||
|
||||
$chatModel = new Chat();
|
||||
|
||||
$chat = $chatModel->create([
|
||||
'name' => $request->input('name'),
|
||||
'assistant_id' => $assistant->id,
|
||||
'user_id' => $request->user('api')->id,
|
||||
]);
|
||||
|
||||
return $this->created($chat);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,4 +69,11 @@ public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function histories(Request $request, Chat $chat)
|
||||
{
|
||||
return $this->success(
|
||||
$chat->histories()->get()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
73
app/Http/Controllers/Api/ChatHistoryController.php
Normal file
73
app/Http/Controllers/Api/ChatHistoryController.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Chat;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ChatHistoryController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Chat $chat)
|
||||
{
|
||||
return $this->success(
|
||||
$chat->histories()->get()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request, Chat $chat)
|
||||
{
|
||||
// 获取上一条记录
|
||||
$last_history = $chat->histories()->orderBy('id', 'desc')->first();
|
||||
|
||||
// 如果存在
|
||||
if ($last_history) {
|
||||
// 如果上一条是 user
|
||||
if ($last_history->role == 'user') {
|
||||
// 不允许发送消息
|
||||
return $this->badRequest('你已经回复过了,请等待 AI 响应。');
|
||||
}
|
||||
|
||||
// 检查缓存是否存在
|
||||
$last_stream_key = 'chat_history_id:'.$last_history->id;
|
||||
|
||||
// 如果存在
|
||||
if (Cache::has($last_stream_key)) {
|
||||
return $this->conflict('上一个流信息还没有获取,请等待一分钟后重试。');
|
||||
}
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'message' => 'required',
|
||||
]);
|
||||
|
||||
$chat->histories()->create([
|
||||
'content' => $request->input('message'),
|
||||
'role' => 'user',
|
||||
]);
|
||||
|
||||
$random_id = Str::random(20);
|
||||
|
||||
$last_stream_key = 'chat_history_id:'.$last_history->id;
|
||||
// 设置缓存
|
||||
Cache::put($last_stream_key, $random_id, 60);
|
||||
Cache::put("chat_history_stream_id:$random_id", $last_history->id, 60);
|
||||
|
||||
return $this->success([
|
||||
'stream_url' => route('chat-stream', $random_id),
|
||||
]);
|
||||
}
|
||||
|
||||
public function stream(string $stream_id)
|
||||
{
|
||||
return $stream_id;
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ class JSONRequest
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$request->headers->set('Accept', 'application/json');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\LLM;
|
||||
|
||||
class Base {}
|
14
app/LLM/BaseLLM.php
Normal file
14
app/LLM/BaseLLM.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\LLM;
|
||||
use App\Repositories\LLM\History;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
interface BaseLLM
|
||||
{
|
||||
public function setHistory(History $history);
|
||||
|
||||
public function setTools(Collection $tools);
|
||||
|
||||
|
||||
public function streamResponse();
|
||||
}
|
245
app/LLM/Qwen.php
245
app/LLM/Qwen.php
@ -2,60 +2,245 @@
|
||||
|
||||
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 extends Base
|
||||
class Qwen implements BaseLLM
|
||||
{
|
||||
protected string $model = 'qwen-max';
|
||||
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
public function getResponse(): void
|
||||
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'),
|
||||
'Authorization' => 'Bearer ' . config('services.dashscope.api_key'),
|
||||
],
|
||||
]);
|
||||
|
||||
$url = config('services.dashscope.api_base').'/compatible-mode/v1/chat/completions';
|
||||
$url = config('services.dashscope.api_base') . '/compatible-mode/v1/chat/completions';
|
||||
|
||||
$messages = [
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => '你好',
|
||||
],
|
||||
];
|
||||
|
||||
$jsonBody = json_encode([
|
||||
'model' => $this->model,
|
||||
'messages' => $messages,
|
||||
'stream' => true,
|
||||
]);
|
||||
$this->request_again = true;
|
||||
while ($this->request_again) {
|
||||
|
||||
$response = $client->request('POST', $url, [
|
||||
'body' => $jsonBody,
|
||||
'stream' => true,
|
||||
]);
|
||||
$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 = '';
|
||||
}
|
||||
}
|
||||
|
||||
$body = $response->getBody();
|
||||
// while (!$body->eof()) {
|
||||
// echo $body->read(1024);
|
||||
// }
|
||||
|
||||
while (! $body->eof()) {
|
||||
// $body->read(1);
|
||||
echo $body->read(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 . ',你确定是这个吗?';
|
||||
}
|
||||
|
||||
}
|
||||
|
5
app/Logic/LLM.php
Normal file
5
app/Logic/LLM.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Logic;
|
||||
|
||||
class LLM {}
|
53
app/Logic/LLMTool.php
Normal file
53
app/Logic/LLMTool.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Logic;
|
||||
|
||||
use App\Repositories\LLM\FunctionCall;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class LLMTool
|
||||
{
|
||||
protected string $callback_url;
|
||||
|
||||
public function setCallbackUrl(string $callback_url): void
|
||||
{
|
||||
$this->callback_url = $callback_url;
|
||||
}
|
||||
|
||||
public function callTool(string $function_name, $parameters = []): FunctionCall
|
||||
{
|
||||
// 使用 _ 分割
|
||||
$names = explode('_', $function_name)[0];
|
||||
$prefix_length = strlen($names) + 1;
|
||||
|
||||
$function_name = substr($function_name, $prefix_length);
|
||||
|
||||
$http = Http::post($this->callback_url, [
|
||||
'function_name' => $function_name,
|
||||
'parameters' => $parameters,
|
||||
]);
|
||||
|
||||
$r = new FunctionCall();
|
||||
$r->name = $function_name;
|
||||
$r->parameters = $parameters;
|
||||
|
||||
if (! $http->ok()) {
|
||||
$r->success = false;
|
||||
$r->result = "[Error] 我们的服务器与工具 $function_name 通讯失败";
|
||||
}
|
||||
|
||||
$d = $http->json();
|
||||
|
||||
// 必须有 success 和 message 两个
|
||||
if (! isset($d['success']) || ! isset($d['message'])) {
|
||||
$r->success = false;
|
||||
$r->result = "[Error] 和 工具 $function_name 通讯失败,返回数据格式错误。";
|
||||
return $r;
|
||||
}
|
||||
|
||||
$r->success= $d['success'];
|
||||
$r->result = $d['message'];
|
||||
|
||||
return $r;
|
||||
}
|
||||
}
|
@ -8,6 +8,14 @@
|
||||
|
||||
class Chat extends Model
|
||||
{
|
||||
public const ROLE_USER = 'user';
|
||||
|
||||
public const ROLE_ASSISTANT = 'assistant';
|
||||
|
||||
public const ROLE_SYSTEM = 'system';
|
||||
|
||||
public const ROLE_META = 'meta';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'assistant_id',
|
||||
|
@ -30,5 +30,4 @@ public function functions(): HasMany
|
||||
{
|
||||
return $this->hasMany(ToolFunction::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ private function setJWTGuard(): void
|
||||
|
||||
if (empty($jwt)) {
|
||||
return null;
|
||||
// return response()->json(['error' => 'No token provided'], 401);
|
||||
// return response()->json(['error' => 'No token provided'], 401);
|
||||
}
|
||||
|
||||
$headers = new stdClass();
|
||||
@ -50,18 +50,17 @@ private function setJWTGuard(): void
|
||||
$decoded = JWT::decode($jwt, $keys, $headers);
|
||||
// $request->attributes->add(['token_type' => $headers->typ]);
|
||||
} catch (Exception $e) {
|
||||
// dd($e);
|
||||
// dd($e);
|
||||
return null;
|
||||
|
||||
|
||||
// return response()->json(['error' => 'Invalid token, '.$e->getMessage()], 401);
|
||||
// return response()->json(['error' => 'Invalid token, '.$e->getMessage()], 401);
|
||||
}
|
||||
|
||||
// must id_token
|
||||
if ($headers->typ !== 'id_token') {
|
||||
return null;
|
||||
|
||||
// return response()->json(['error' => 'The token not id_token'], 401);
|
||||
// return response()->json(['error' => 'The token not id_token'], 401);
|
||||
}
|
||||
|
||||
// 检查是否有 字段
|
||||
@ -70,22 +69,22 @@ private function setJWTGuard(): void
|
||||
];
|
||||
|
||||
foreach ($required_fields as $field) {
|
||||
if (!isset($decoded->$field)) {
|
||||
if (! isset($decoded->$field)) {
|
||||
return null;
|
||||
|
||||
// return response()->json(['error' => 'The token not contain the field '.$field], 401);
|
||||
// return response()->json(['error' => 'The token not contain the field '.$field], 401);
|
||||
}
|
||||
}
|
||||
|
||||
if (config('oauth.force_aud')) {
|
||||
if (!in_array($decoded->aud, config('oauth.trusted_aud'))) {
|
||||
throw new Exception('The application rejected the token, token aud is ' . $decoded->aud . ', app aud is ' . config('oauth.client_id'));
|
||||
// return response()->json(['error' => 'The application rejected the token, token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id')], 401);
|
||||
if (! in_array($decoded->aud, config('oauth.trusted_aud'))) {
|
||||
throw new Exception('The application rejected the token, token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id'));
|
||||
// return response()->json(['error' => 'The application rejected the token, token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id')], 401);
|
||||
}
|
||||
|
||||
// throw
|
||||
throw new Exception('The token not match the application, ' . ' token aud is ' . $decoded->aud . ', app aud is ' . config('oauth.client_id'));
|
||||
// return response()->json(['error' => 'The token not match the application, '.' token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id')], 401);
|
||||
throw new Exception('The token not match the application, '.' token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id'));
|
||||
// return response()->json(['error' => 'The token not match the application, '.' token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id')], 401);
|
||||
}
|
||||
|
||||
return User::where('external_id', $decoded->sub)->firstOrCreate([
|
||||
|
30
app/Repositories/LLM/AIChunkMessage.php
Normal file
30
app/Repositories/LLM/AIChunkMessage.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
class AIChunkMessage extends BaseMessage
|
||||
{
|
||||
public ChatEnum $role = ChatEnum::AssistantChunk;
|
||||
protected string $last_append = "";
|
||||
|
||||
public function toAIMessage(): AIMessage
|
||||
{
|
||||
$a = new AIMessage(
|
||||
content: $this->content,
|
||||
);
|
||||
$a->processing = false;
|
||||
|
||||
return $a;
|
||||
}
|
||||
|
||||
public function append(string $content): void
|
||||
{
|
||||
$this->content .= $content;
|
||||
$this->last_append = $content;
|
||||
}
|
||||
|
||||
public function getLastAppend(): string
|
||||
{
|
||||
return $this->last_append;
|
||||
}
|
||||
}
|
8
app/Repositories/LLM/AIMessage.php
Normal file
8
app/Repositories/LLM/AIMessage.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
class AIMessage extends BaseMessage
|
||||
{
|
||||
public ChatEnum $role = ChatEnum::Assistant;
|
||||
}
|
12
app/Repositories/LLM/AIToolCallMessage.php
Normal file
12
app/Repositories/LLM/AIToolCallMessage.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
class AIToolCallMessage extends BaseMessage
|
||||
{
|
||||
|
||||
public ChatEnum $role = ChatEnum::Assistant;
|
||||
|
||||
public array $tool_calls;
|
||||
|
||||
}
|
31
app/Repositories/LLM/BaseMessage.php
Normal file
31
app/Repositories/LLM/BaseMessage.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
class BaseMessage
|
||||
{
|
||||
public string $content;
|
||||
public bool $processing = false;
|
||||
|
||||
public function __construct(string $content)
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
|
||||
public function append(string $content): void
|
||||
{
|
||||
$this->content .= $content;
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$this->content = '';
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
}
|
11
app/Repositories/LLM/ChatEnum.php
Normal file
11
app/Repositories/LLM/ChatEnum.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
enum ChatEnum: string
|
||||
{
|
||||
case Assistant = 'assistant';
|
||||
case AssistantChunk = 'assistant_chunk';
|
||||
case Tool = 'tool';
|
||||
case Human = 'user';
|
||||
}
|
12
app/Repositories/LLM/ChatResponse.php
Normal file
12
app/Repositories/LLM/ChatResponse.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
class ChatResponse
|
||||
{
|
||||
public ChatEnum $type;
|
||||
|
||||
public FunctionCall $functionCall;
|
||||
|
||||
public string $content;
|
||||
}
|
14
app/Repositories/LLM/FunctionCall.php
Normal file
14
app/Repositories/LLM/FunctionCall.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
class FunctionCall
|
||||
{
|
||||
public string $name;
|
||||
|
||||
public array $parameters;
|
||||
|
||||
public bool $success;
|
||||
|
||||
public string $result;
|
||||
}
|
52
app/Repositories/LLM/History.php
Normal file
52
app/Repositories/LLM/History.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
class History
|
||||
{
|
||||
protected array $history = [];
|
||||
|
||||
public function addMessage(BaseMessage $message): void
|
||||
{
|
||||
$this->history[] = $message;
|
||||
}
|
||||
|
||||
public function getMessages(): array
|
||||
{
|
||||
return $this->history;
|
||||
}
|
||||
|
||||
public function setHistory(array $history): void
|
||||
{
|
||||
$this->history = $history;
|
||||
}
|
||||
|
||||
public function clearHistory(): void
|
||||
{
|
||||
$this->history = [];
|
||||
}
|
||||
|
||||
public function getForApi(): array
|
||||
{
|
||||
$history = [];
|
||||
|
||||
|
||||
foreach ($this->history as $h) {
|
||||
|
||||
$a = [
|
||||
'role' => $h->role->value,
|
||||
'content' => $h->content,
|
||||
];
|
||||
|
||||
if (isset($h->tool_calls)) {
|
||||
$a['tool_calls'] = $h->tool_calls;
|
||||
}
|
||||
|
||||
$history[] = $a;
|
||||
}
|
||||
|
||||
return $history;
|
||||
}
|
||||
|
||||
|
||||
}
|
11
app/Repositories/LLM/HumanMessage.php
Normal file
11
app/Repositories/LLM/HumanMessage.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
class HumanMessage extends BaseMessage
|
||||
{
|
||||
public ChatEnum $role = ChatEnum::Human;
|
||||
|
||||
|
||||
|
||||
}
|
9
app/Repositories/LLM/ToolMessage.php
Normal file
9
app/Repositories/LLM/ToolMessage.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
class ToolMessage extends BaseMessage
|
||||
{
|
||||
public ChatEnum $role = ChatEnum::Tool;
|
||||
|
||||
}
|
9
app/Repositories/LLM/ToolRequestMessage.php
Normal file
9
app/Repositories/LLM/ToolRequestMessage.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
class ToolRequestMessage extends BaseMessage
|
||||
{
|
||||
public ChatEnum $role = ChatEnum::Tool;
|
||||
public bool $processing = true;
|
||||
}
|
12
app/Repositories/LLM/ToolResponseMessage.php
Normal file
12
app/Repositories/LLM/ToolResponseMessage.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\LLM;
|
||||
|
||||
class ToolResponseMessage extends BaseMessage
|
||||
{
|
||||
public ChatEnum $role = ChatEnum::Tool;
|
||||
|
||||
public bool $requesting = false;
|
||||
public string $name;
|
||||
|
||||
}
|
@ -14,7 +14,7 @@ class Tool
|
||||
|
||||
public string $description;
|
||||
|
||||
public array $toolFunctions;
|
||||
public array $tool_functions;
|
||||
|
||||
private array $data;
|
||||
|
||||
@ -63,7 +63,12 @@ private function validate(): bool
|
||||
private function fetchFunctions(): void
|
||||
{
|
||||
foreach ($this->data['functions'] as $f) {
|
||||
$this->toolFunctions[] = new ToolFunction($f);
|
||||
$a = [
|
||||
'type' => 'function',
|
||||
'function' => new ToolFunction($f),
|
||||
];
|
||||
|
||||
$this->tool_functions[] = $a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Repositories\Tool;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ToolFunction
|
||||
{
|
||||
@ -15,7 +16,6 @@ class ToolFunction
|
||||
protected array $data;
|
||||
|
||||
public array $required;
|
||||
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*
|
||||
@ -32,7 +32,8 @@ public function __construct(array $data)
|
||||
*/
|
||||
private function parse(): void
|
||||
{
|
||||
$this->name = $this->data['name'];
|
||||
$random_str = Str::random(config('settings.function_call.random_prefix_length'));
|
||||
$this->name = $random_str . '_' . $this->data['name'];
|
||||
$this->description = $this->data['description'];
|
||||
|
||||
// 如果 parameters 不为空,则验证
|
||||
|
@ -19,7 +19,7 @@
|
||||
// ]);
|
||||
// add for api
|
||||
$middleware->api([
|
||||
JSONRequest::class
|
||||
JSONRequest::class,
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
|
@ -42,7 +42,7 @@
|
||||
],
|
||||
'api' => [
|
||||
'driver' => 'jwt',
|
||||
// 'provider' => 'users',
|
||||
// 'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
||||
|
@ -40,4 +40,5 @@
|
||||
'api_base' => env('DASHSCOPE_API_BASE', 'https://dashscope.aliyuncs.com'),
|
||||
],
|
||||
|
||||
|
||||
];
|
||||
|
8
config/settings.php
Normal file
8
config/settings.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'function_call' => [
|
||||
// 防止命名冲突的随机长度,如果你更改了它,则需要更新全部函数的 name
|
||||
'random_prefix_length' => 8
|
||||
]
|
||||
];
|
@ -21,12 +21,12 @@ public function up(): void
|
||||
// $table->string('model');
|
||||
|
||||
// input tokens
|
||||
$table->integer('input_tokens')->default(null);
|
||||
$table->integer('input_tokens')->nullable();
|
||||
|
||||
// output tokens
|
||||
$table->integer('output_tokens')->default(null);
|
||||
$table->integer('output_tokens')->nullable();
|
||||
|
||||
$table->integer('total_tokens')->default(null);
|
||||
$table->integer('total_tokens')->nullable();
|
||||
|
||||
// index
|
||||
$table->index(['chat_id', 'role']);
|
||||
|
@ -3,8 +3,8 @@
|
||||
use App\Http\Controllers\Api\AssistantController;
|
||||
use App\Http\Controllers\Api\AssistantToolController;
|
||||
use App\Http\Controllers\Api\ChatController;
|
||||
use App\Http\Controllers\Api\ChatHistoryController;
|
||||
use App\Http\Controllers\Api\ToolController;
|
||||
use App\Http\Middleware\JSONRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@ -16,8 +16,11 @@
|
||||
Route::apiResource('tools', ToolController::class);
|
||||
Route::apiResource('assistants', AssistantController::class);
|
||||
Route::apiResource('chats', ChatController::class);
|
||||
Route::apiResource('chats.histories', ChatHistoryController::class)->only(['index', 'store']);
|
||||
|
||||
Route::get('assistants/{assistant}/tools', [AssistantToolController::class, 'index']);
|
||||
Route::post('assistants/{assistant}/tools', [AssistantToolController::class, 'store']);
|
||||
Route::delete('assistants/{assistant}/tools/{tool}', [AssistantToolController::class, 'destroy']);
|
||||
});
|
||||
|
||||
Route::get('chat_stream/{stream_id}', [ChatHistoryController::class, 'stream'])->name('chat-stream');
|
||||
|
Loading…
Reference in New Issue
Block a user