feat: 优化 chat 接口并修复 function 消息处理
主要变更: - 使用原生 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 <noreply@anthropic.com>
This commit is contained in:
28
app/main.py
28
app/main.py
@@ -2,6 +2,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from fastapi import FastAPI, HTTPException, Depends, Request
|
from fastapi import FastAPI, HTTPException, Depends, Request
|
||||||
from starlette.responses import StreamingResponse
|
from starlette.responses import StreamingResponse
|
||||||
@@ -58,31 +59,42 @@ async def startup_event():
|
|||||||
|
|
||||||
@app.post("/v1/chat/completions")
|
@app.post("/v1/chat/completions")
|
||||||
async def chat_completions(
|
async def chat_completions(
|
||||||
request: IncomingRequest,
|
request: Request,
|
||||||
settings: Settings = Depends(get_settings)
|
settings: Settings = Depends(get_settings)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
This endpoint mimics the OpenAI Chat Completions API and supports both
|
This endpoint mimics the OpenAI Chat Completions API and supports both
|
||||||
streaming and non-streaming responses, with detailed logging.
|
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}")
|
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:
|
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.")
|
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.")
|
raise HTTPException(status_code=500, detail="LLM API Key or URL is not configured.")
|
||||||
|
|
||||||
messages_to_llm = request.messages
|
messages_to_llm = request_obj.messages
|
||||||
if request.tools:
|
if request_obj.tools:
|
||||||
messages_to_llm = inject_tools_into_prompt(request.messages, request.tools)
|
messages_to_llm = inject_tools_into_prompt(request_obj.messages, request_obj.tools)
|
||||||
|
|
||||||
# Handle streaming request
|
# Handle streaming request
|
||||||
if request.stream:
|
if request_obj.stream:
|
||||||
logger.info(f"Initiating streaming request for log ID: {log_id}")
|
logger.info(f"Initiating streaming request for log ID: {log_id}")
|
||||||
|
|
||||||
async def stream_and_log():
|
async def stream_and_log():
|
||||||
import json
|
|
||||||
|
|
||||||
stream_content_buffer = []
|
stream_content_buffer = []
|
||||||
raw_chunks = []
|
raw_chunks = []
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ from typing import List, Dict, Any, Optional
|
|||||||
class ChatMessage(BaseModel):
|
class ChatMessage(BaseModel):
|
||||||
"""Represents a single message in the chat history."""
|
"""Represents a single message in the chat history."""
|
||||||
role: str
|
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):
|
class Function(BaseModel):
|
||||||
"""Represents the function definition within a tool."""
|
"""Represents the function definition within a tool."""
|
||||||
|
|||||||
Reference in New Issue
Block a user