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); $this->toolChoice = $this->toolChoice ?? (string) config('agent.tools.tool_choice', 'auto'); } /** * Builds an OpenAI-compatible Chat Completions payload from AgentContext. * * @param array $options * @return array */ 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']; } $toolsSpec = $this->toolRegistry->openAiToolsSpec(); $hasToolMessages = $this->hasToolMessages($context); // 支持 disable_tools 选项,用于在达到工具调用上限后禁用工具 $disableTools = $options['disable_tools'] ?? false; if (! empty($toolsSpec)) { if ($disableTools) { $payload['tool_choice'] = 'none'; if ($hasToolMessages) { // 历史包含工具消息时仍需携带 tools 定义以满足接口校验 $payload['tools'] = $toolsSpec; } } else { $payload['tools'] = $toolsSpec; $payload['tool_choice'] = $options['tool_choice'] ?? $this->toolChoice ?? 'auto'; } } return $payload; } /** * @return array> */ 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; $type = (string) ($message['type'] ?? ''); $payload = $message['payload'] ?? null; if (! $role || ! is_string($content) || $content === '') { $content = null; } if ($type === 'tool.call' && is_array($payload)) { $toolCall = $this->normalizeToolCallPayload($payload, $content); if ($toolCall) { $messages[] = $toolCall; logger('openai adapter: added tool.call', [ 'tool_call_id' => $payload['tool_call_id'] ?? null, 'name' => $payload['name'] ?? null, ]); } continue; } if ($type === 'tool.result' && is_array($payload)) { $toolResult = $this->normalizeToolResultPayload($payload, $content); if ($toolResult) { $messages[] = $toolResult; logger('openai adapter: added tool.result', [ 'tool_call_id' => $payload['tool_call_id'] ?? null, 'name' => $payload['name'] ?? null, 'content_length' => strlen($toolResult['content'] ?? ''), ]); } else { logger('openai adapter: tool.result normalized to null', [ 'payload' => $payload, 'content' => $content, ]); } continue; } if ($content !== null) { $messages[] = [ 'role' => $role, 'content' => $content, ]; } } logger('openai adapter: built messages', [ 'total_messages' => count($messages), 'message_roles' => array_column($messages, 'role'), ]); return $messages; } private function mapRole(string $role): ?string { return match ($role) { Message::ROLE_USER => 'user', Message::ROLE_AGENT => 'assistant', Message::ROLE_SYSTEM => 'system', Message::ROLE_TOOL => 'tool', default => null, }; } /** * @param array $payload * @return array|null */ private function normalizeToolCallPayload(array $payload, ?string $content): ?array { $toolCallId = $payload['tool_call_id'] ?? null; $name = $payload['name'] ?? null; $arguments = $payload['arguments'] ?? null; if (! is_string($toolCallId) || ! is_string($name) || $toolCallId === '' || $name === '') { return null; } $argumentsString = is_string($arguments) ? $arguments : json_encode($arguments, JSON_UNESCAPED_UNICODE); return [ 'role' => 'assistant', 'content' => $content, 'tool_calls' => [ [ 'id' => $toolCallId, 'type' => 'function', 'function' => [ 'name' => $name, 'arguments' => $argumentsString ?? '', ], ], ], ]; } /** * @param array $payload * @return array|null */ private function normalizeToolResultPayload(array $payload, ?string $content): ?array { $toolCallId = $payload['tool_call_id'] ?? null; $name = $payload['name'] ?? null; if (! is_string($toolCallId) || $toolCallId === '') { return null; } $resultContent = $content ?? ($payload['output'] ?? null); if (! is_string($resultContent)) { $resultContent = json_encode($payload['output'] ?? $payload, JSON_UNESCAPED_UNICODE); } return [ 'role' => 'tool', 'tool_call_id' => $toolCallId, 'name' => is_string($name) ? $name : null, 'content' => $resultContent, ]; } private function hasToolMessages(AgentContext $context): bool { foreach ($context->messages as $message) { $type = (string) ($message['type'] ?? ''); if ($type === 'tool.call' || $type === 'tool.result') { return true; } } return false; } }