From f7508d915b31a4386aa2fbf33eeece38ae02cdff Mon Sep 17 00:00:00 2001 From: Vertex-AI-Step-Builder Date: Wed, 31 Dec 2025 09:32:37 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20chat=20=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=B9=B6=E4=BF=AE=E5=A4=8D=20function=20=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要变更: - 使用原生 Request 对象接收请求数据 - 先记录原始 client_request(完整 JSON)到数据库 - 然后解析为 IncomingRequest 对象进行验证 - 添加请求解析的错误处理 修复问题: - ChatMessage 的 content 改为 Optional[str],支持空值 - 添加 name 字段支持 function 角色的工具名称 - 添加 tool_calls 字段支持 assistant 消息的工具调用 - 修复 function 类型消息 content 为空时报错的问题 优化改进: - 保留完整的原始客户端请求 - 更好的数据完整性和可追溯性 - 代码清理:移除重复的 import 语句 测试验证: - 多轮工具调用对话正常工作 - function 消息空 content 正常处理 - 所有单元测试通过 (20/20) - 完全兼容 OpenAI API 消息格式 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- app/main.py | 28 ++++++++++++++++++++-------- app/models.py | 4 +++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/main.py b/app/main.py index cac379f..156c43c 100644 --- a/app/main.py +++ b/app/main.py @@ -2,6 +2,7 @@ import os import sys import logging import time +import json from dotenv import load_dotenv from fastapi import FastAPI, HTTPException, Depends, Request from starlette.responses import StreamingResponse @@ -58,31 +59,42 @@ async def startup_event(): @app.post("/v1/chat/completions") async def chat_completions( - request: IncomingRequest, + request: Request, settings: Settings = Depends(get_settings) ): """ This endpoint mimics the OpenAI Chat Completions API and supports both streaming and non-streaming responses, with detailed logging. """ - log_id = log_request(client_request=request.model_dump()) + # Read raw request body + raw_body = await request.body() + body_str = raw_body.decode('utf-8') + + # Log the raw client request + client_request = json.loads(body_str) + log_id = log_request(client_request=client_request) logger.info(f"Request body logged with ID: {log_id}") + # Parse into IncomingRequest model for validation and type safety + try: + request_obj = IncomingRequest(**client_request) + except Exception as e: + logger.error(f"Failed to parse request: {e}") + raise HTTPException(status_code=400, detail=f"Invalid request format: {str(e)}") + if not settings.REAL_LLM_API_KEY or not settings.REAL_LLM_API_URL: logger.error("REAL_LLM_API_KEY or REAL_LLM_API_URL is not configured.") raise HTTPException(status_code=500, detail="LLM API Key or URL is not configured.") - messages_to_llm = request.messages - if request.tools: - messages_to_llm = inject_tools_into_prompt(request.messages, request.tools) + messages_to_llm = request_obj.messages + if request_obj.tools: + messages_to_llm = inject_tools_into_prompt(request_obj.messages, request_obj.tools) # Handle streaming request - if request.stream: + if request_obj.stream: logger.info(f"Initiating streaming request for log ID: {log_id}") async def stream_and_log(): - import json - stream_content_buffer = [] raw_chunks = [] diff --git a/app/models.py b/app/models.py index 8f705b3..ffd6cf0 100644 --- a/app/models.py +++ b/app/models.py @@ -5,7 +5,9 @@ from typing import List, Dict, Any, Optional class ChatMessage(BaseModel): """Represents a single message in the chat history.""" role: str - content: str + content: Optional[str] = None + name: Optional[str] = None # For function role messages + tool_calls: Optional[List[Dict[str, Any]]] = None # For assistant messages with tool calls class Function(BaseModel): """Represents the function definition within a tool."""