main: 引入 AgentProvider 流式事件与 OpenAI 兼容适配
- 增加流式事件流支持,Provider 输出 `message.delta` 等事件 - 实现 OpenAI 兼容适配器,包括 RequestBuilder、ApiClient 等模块 - 更新 Agent Run 逻辑,支持流式增量写入与模型完成状态管理 - 扩展配置项 `agent.openai.*`,支持模型、密钥等配置 - 优化文档,完善流式事件与消息类型说明 - 增加单元测试,覆盖 Provider 和 OpenAI 适配相关逻辑 - 更新环境变量与配置示例,支持新功能
This commit is contained in:
109
app/Services/Agent/OpenAi/ChatCompletionsRequestBuilder.php
Normal file
109
app/Services/Agent/OpenAi/ChatCompletionsRequestBuilder.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Agent\OpenAi;
|
||||
|
||||
use App\Models\Message;
|
||||
use App\Services\Agent\AgentContext;
|
||||
|
||||
class ChatCompletionsRequestBuilder
|
||||
{
|
||||
public function __construct(
|
||||
private ?string $model = null,
|
||||
private ?float $temperature = null,
|
||||
private ?float $topP = null,
|
||||
private ?bool $includeUsage = null,
|
||||
) {
|
||||
$this->model = $this->model ?? (string) config('agent.openai.model', 'gpt-4o-mini');
|
||||
$this->temperature = $this->temperature ?? (float) config('agent.openai.temperature', 0.7);
|
||||
$this->topP = $this->topP ?? (float) config('agent.openai.top_p', 1.0);
|
||||
$this->includeUsage = $this->includeUsage ?? (bool) config('agent.openai.include_usage', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an OpenAI-compatible Chat Completions payload from AgentContext.
|
||||
*
|
||||
* @param array<string, mixed> $options
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function build(AgentContext $context, array $options = []): array
|
||||
{
|
||||
$payload = [
|
||||
'model' => (string) ($options['model'] ?? $this->model),
|
||||
'messages' => $this->buildMessages($context),
|
||||
'stream' => true,
|
||||
];
|
||||
|
||||
if (array_key_exists('temperature', $options)) {
|
||||
$payload['temperature'] = (float) $options['temperature'];
|
||||
} else {
|
||||
$payload['temperature'] = (float) $this->temperature;
|
||||
}
|
||||
|
||||
if (array_key_exists('top_p', $options)) {
|
||||
$payload['top_p'] = (float) $options['top_p'];
|
||||
} else {
|
||||
$payload['top_p'] = (float) $this->topP;
|
||||
}
|
||||
|
||||
if (array_key_exists('max_tokens', $options)) {
|
||||
$payload['max_tokens'] = (int) $options['max_tokens'];
|
||||
}
|
||||
|
||||
if (array_key_exists('stop', $options)) {
|
||||
$payload['stop'] = $options['stop'];
|
||||
}
|
||||
|
||||
if (array_key_exists('stream_options', $options)) {
|
||||
$payload['stream_options'] = $options['stream_options'];
|
||||
} elseif ($this->includeUsage) {
|
||||
$payload['stream_options'] = ['include_usage' => true];
|
||||
}
|
||||
|
||||
if (array_key_exists('response_format', $options)) {
|
||||
$payload['response_format'] = $options['response_format'];
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{role: string, content: string}>
|
||||
*/
|
||||
private function buildMessages(AgentContext $context): array
|
||||
{
|
||||
$messages = [];
|
||||
|
||||
if ($context->systemPrompt !== '') {
|
||||
$messages[] = [
|
||||
'role' => 'system',
|
||||
'content' => $context->systemPrompt,
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($context->messages as $message) {
|
||||
$role = $this->mapRole((string) ($message['role'] ?? ''));
|
||||
$content = $message['content'] ?? null;
|
||||
|
||||
if (! $role || ! is_string($content) || $content === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$messages[] = [
|
||||
'role' => $role,
|
||||
'content' => $content,
|
||||
];
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
private function mapRole(string $role): ?string
|
||||
{
|
||||
return match ($role) {
|
||||
Message::ROLE_USER => 'user',
|
||||
Message::ROLE_AGENT => 'assistant',
|
||||
Message::ROLE_SYSTEM => 'system',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user