【open-avatar-chat】open-avatar-chat接入自定义API接口详解
改动原因原版:使用OpenAI官方SDK,自动处理API调用、认证、流式响应等修改后:使用requests库直接发送HTTP请求,json库解析响应数据为什么:我的API不是标准的OpenAI格式,需要自定义HTTP请求和响应解析# ... 前置处理 ...try:# 1. 构建请求数据data = {"question": chat_text, # 你的API需要question字段'api-k
·
从标准OpenAI兼容接口改为自定义API接口的完整改造过程。
项目:https://github.com/HumanAIGC-Engineering/OpenAvatarChat
file: src/handlers/llm/openai_compatible/llm_handler_openai_compatible.py
1. 导入模块的改动
原版(标准OpenAI):
from openai import OpenAI
修改后(自定义API):
import requests
import json
改动原因:
- 原版:使用OpenAI官方SDK,自动处理API调用、认证、流式响应等
- 修改后:使用
requests
库直接发送HTTP请求,json
库解析响应数据 - 为什么:我的API不是标准的OpenAI格式,需要自定义HTTP请求和响应解析
2. 配置类的改动
原版配置:
class LLMConfig(HandlerBaseConfigModel, BaseModel):
model_name: str = Field(default="qwen-plus")
system_prompt: str = Field(default="...")
api_key: str = Field(default=os.getenv("DASHSCOPE_API_KEY"))
api_url: str = Field(default=None)
enable_video_input: bool = Field(default=False)
修改后配置:
class LLMConfig(HandlerBaseConfigModel, BaseModel):
model_name: str = Field(default="general_practitioner")
system_prompt: str = Field(default="...")
api_key: str = Field(default="")// 请替换为真实的api_key
api_url: str = Field(default="")// 请替换为真实的 api_url
enable_video_input: bool = Field(default=False)
改动原因:
- model_name:从
qwen-plus
改为general_practitioner
,匹配你的API端点 - api_key:从环境变量改为固定值`真实的api_key
- api_url:从
None
改为具体的API地址 - 为什么:你的API有特定的模型名称和固定的认证信息
3. 客户端初始化的改动
原版(创建OpenAI客户端):
def create_context(self, session_context, handler_config=None):
# ... 其他代码 ...
context.client = OpenAI(
api_key=context.api_key,
base_url=context.api_url,
)
return context
修改后(移除客户端):
def create_context(self, session_context, handler_config=None):
# ... 其他代码 ...
# 不再创建OpenAI客户端
return context
改动原因:
- 原版:创建OpenAI客户端对象,用于后续API调用
- 修改后:不再需要客户端,直接使用
requests
发送HTTP请求 - 为什么:我的API不是OpenAI格式,不需要OpenAI客户端
4. 核心处理逻辑的完全重写
这是最重要的改动,整个handle
方法被完全重写:
原版(使用OpenAI SDK):
def handle(self, context: HandlerContext, inputs: ChatData, output_definitions: Dict[ChatDataType, HandlerDataInfo]):
# ... 前置处理 ...
# 使用OpenAI客户端发送请求
completion = context.client.chat.completions.create(
model=context.model_name,
messages=[
context.system_prompt,
] + current_content,
stream=True,
stream_options={"include_usage": True}
)
# 处理流式响应
for chunk in completion:
if (chunk and chunk.choices and chunk.choices[0] and chunk.choices[0].delta.content):
output_text = chunk.choices[0].delta.content
# ... 输出处理 ...
修改后(自定义HTTP请求):
def handle(self, context: HandlerContext, inputs: ChatData, output_definitions: Dict[ChatDataType, HandlerDataInfo]):
# ... 前置处理 ...
try:
# 1. 构建请求数据
data = {
"question": chat_text, # 你的API需要question字段
}
headers = {
'api-key': context.api_key, # 你的API使用api-key认证
'Content-Type': 'application/json',
}
# 2. 发送HTTP POST请求
response = requests.post(context.api_url, headers=headers, json=data, timeout=30)
# 3. 解析SSE格式响应
lines = raw_text.strip().split('\n') if raw_text else []
for line in lines:
if line.startswith('data: '): # 你的API返回SSE格式
data_content = line[6:] # 去掉'data: '前缀
# ... 处理每个数据块 ...
5. 请求格式的改动
原版(OpenAI格式):
# 请求体
{
"model": "qwen-plus",
"messages": [
{"role": "system", "content": "系统提示"},
{"role": "user", "content": "用户问题"}
],
"stream": True
}
# 认证方式
Authorization: Bearer sk-xxx
修改后(你的API格式):
# 请求体
{
"question": "用户问题" # 简化的请求格式
}
# 认证方式
api-key: xx# 替换成自己的
改动原因:
- 原版:使用OpenAI的复杂消息格式,包含角色、内容等
- 修改后:使用我的API的简单格式,只需要
question
字段 - 为什么:我的API设计更简单,不需要复杂的对话历史管理
6. 响应解析的改动
原版(OpenAI流式响应):
for chunk in completion:
if chunk.choices[0].delta.content:
output_text = chunk.choices[0].delta.content
# 直接输出内容
修改后(SSE格式解析):
# 解析SSE格式:data: {"result": "内容"}
lines = raw_text.strip().split('\n')
for line in lines:
if line.startswith('data: '):
data_content = line[6:] # 去掉'data: '前缀
try:
json_data = json.loads(data_content)
# 处理JSON数据
except json.JSONDecodeError:
# 处理纯文本数据
改动原因:
- 原版:OpenAI SDK自动解析流式响应
- 修改后:手动解析SSE(Server-Sent Events)格式
- 为什么:我的API返回
data:
格式的流式数据,需要手动解析
7. 错误处理的改动
原版(简单错误处理):
# 基本没有错误处理,依赖OpenAI SDK
修改后(详细错误处理):
# 1. HTTP状态码检查
if response.status_code != 200:
fallback_text = "抱歉,我这会儿有点忙,稍后再帮您详细解答。"
# 返回友好错误信息
# 2. API错误负载检查
if (status_val is not None and status_val >= 400) or (json_data.get('message') and not any(k in json_data for k in ['result','answer','content','text'])):
fallback_text = "抱歉,我这会儿有点忙,稍后再帮您详细解答。"
# 处理API返回的错误
# 3. 异常捕获
except Exception as e:
logger.error(f'API调用异常: {str(e)}')
# 返回兜底信息
改动原因:
- 原版:OpenAI SDK自动处理大部分错误
- 修改后:需要手动处理各种错误情况
- 为什么:我的API可能返回各种错误格式,需要友好地处理
8. 日志记录的改动
原版(简单日志):
logger.info(f'llm input {context.model_name} {chat_text} ')
logger.info(output_text)
修改后(详细日志):
logger.info(f'llm input {context.model_name} {chat_text} ')
logger.debug(f'发送请求到 {context.api_url}')
logger.debug(f'请求头: {headers}')
logger.debug(f'请求体: {data}')
logger.debug(f'HTTP {response.status_code}, 原始响应预览: {raw_text[:500]}...')
logger.info(f'parsed reply (chunk): {output_text}')
logger.info(f'final reply: {context.output_texts}')
改动原因:
- 原版:OpenAI SDK内部处理,日志较少
- 修改后:需要详细记录每个步骤,便于调试
- 为什么:自定义API调用需要更多调试信息
总结
这次改动的核心思想是:从使用现成的SDK改为直接控制HTTP请求。
为什么需要这样改动?
- API格式不同:API不是标准的OpenAI格式
- 认证方式不同:使用
api-key
而不是Authorization: Bearer
- 请求格式不同:只需要
question
字段,不需要复杂的消息数组 - 响应格式不同:返回SSE格式的
data:
数据流 - 错误处理不同:需要处理各种自定义错误格式
对初学者的建议:
- 理解HTTP基础:学会使用
requests
库发送HTTP请求 - 理解JSON解析:学会解析和生成JSON数据
- 理解流式响应:学会处理SSE格式的数据流
- 理解错误处理:学会优雅地处理各种错误情况
- 理解日志记录:学会使用日志来调试问题
这种改动虽然复杂,但让我完全控制了API调用的每个细节,可以适应任何自定义的API格式。
更多推荐
所有评论(0)