main: 引入 AgentProvider 流式事件与 OpenAI 兼容适配
- 增加流式事件流支持,Provider 输出 `message.delta` 等事件 - 实现 OpenAI 兼容适配器,包括 RequestBuilder、ApiClient 等模块 - 更新 Agent Run 逻辑,支持流式增量写入与模型完成状态管理 - 扩展配置项 `agent.openai.*`,支持模型、密钥等配置 - 优化文档,完善流式事件与消息类型说明 - 增加单元测试,覆盖 Provider 和 OpenAI 适配相关逻辑 - 更新环境变量与配置示例,支持新功能
This commit is contained in:
@@ -2,105 +2,28 @@
|
||||
|
||||
namespace App\Services\Agent;
|
||||
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Services\Agent\OpenAi\OpenAiChatCompletionsAdapter;
|
||||
|
||||
class HttpAgentProvider implements AgentProviderInterface
|
||||
{
|
||||
protected string $endpoint;
|
||||
protected int $timeoutSeconds;
|
||||
protected int $connectTimeoutSeconds;
|
||||
protected int $retryTimes;
|
||||
protected int $retryBackoffMs;
|
||||
private readonly bool $enabled;
|
||||
|
||||
public function __construct(?string $endpoint = null)
|
||||
public function __construct(private readonly OpenAiChatCompletionsAdapter $adapter)
|
||||
{
|
||||
$this->endpoint = $endpoint ?? config('agent.provider.endpoint', '');
|
||||
$this->timeoutSeconds = (int) config('agent.provider.timeout_seconds', 30);
|
||||
$this->connectTimeoutSeconds = (int) config('agent.provider.connect_timeout_seconds', 5);
|
||||
$this->retryTimes = (int) config('agent.provider.retry_times', 1);
|
||||
$this->retryBackoffMs = (int) config('agent.provider.retry_backoff_ms', 500);
|
||||
$baseUrl = (string) config('agent.openai.base_url', '');
|
||||
$apiKey = (string) config('agent.openai.api_key', '');
|
||||
$this->enabled = trim($baseUrl) !== '' && trim($apiKey) !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $context
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function generate(array $context, array $options = []): string
|
||||
public function stream(AgentContext $context, array $options = []): \Generator
|
||||
{
|
||||
if (empty($this->endpoint)) {
|
||||
// placeholder to avoid accidental outbound calls when未配置
|
||||
return (new DummyAgentProvider())->generate($context, $options);
|
||||
if (! $this->enabled) {
|
||||
return (new DummyAgentProvider())->stream($context, $options);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'context' => $context,
|
||||
'options' => $options,
|
||||
];
|
||||
|
||||
$attempts = $this->retryTimes + 1;
|
||||
$lastException = null;
|
||||
$lastResponseBody = null;
|
||||
$lastStatus = null;
|
||||
|
||||
for ($attempt = 1; $attempt <= $attempts; $attempt++) {
|
||||
try {
|
||||
$response = Http::connectTimeout($this->connectTimeoutSeconds)
|
||||
->timeout($this->timeoutSeconds)
|
||||
->post($this->endpoint, $payload);
|
||||
|
||||
$lastStatus = $response->status();
|
||||
$lastResponseBody = $response->body();
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
return is_string($data) ? $data : ($data['content'] ?? '');
|
||||
}
|
||||
|
||||
$retryable = $lastStatus === 429 || $lastStatus >= 500;
|
||||
if ($retryable && $attempt < $attempts) {
|
||||
usleep($this->retryBackoffMs * 1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new ProviderException(
|
||||
'HTTP_ERROR',
|
||||
'Agent provider failed',
|
||||
$retryable,
|
||||
$lastStatus,
|
||||
$lastResponseBody
|
||||
);
|
||||
} catch (ConnectionException $exception) {
|
||||
$lastException = $exception;
|
||||
if ($attempt < $attempts) {
|
||||
usleep($this->retryBackoffMs * 1000);
|
||||
continue;
|
||||
}
|
||||
} catch (\Throwable $exception) {
|
||||
$lastException = $exception;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$rawMessage = $lastException ? $lastException->getMessage() : $lastResponseBody;
|
||||
|
||||
if ($lastException instanceof ConnectionException) {
|
||||
throw new ProviderException(
|
||||
'CONNECTION_FAILED',
|
||||
'Agent provider connection failed',
|
||||
true,
|
||||
$lastStatus,
|
||||
$rawMessage
|
||||
);
|
||||
}
|
||||
|
||||
throw new ProviderException(
|
||||
'UNKNOWN_ERROR',
|
||||
'Agent provider error',
|
||||
false,
|
||||
$lastStatus,
|
||||
$rawMessage
|
||||
);
|
||||
return $this->adapter->stream($context, $options);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user