Files
aistream-test/python/llm_client.py
T
2025-12-01 21:00:54 +08:00

175 lines
7.2 KiB
Python

import asyncio
import json
from typing import Optional, Dict, Callable, Awaitable
from dataclasses import dataclass, field
import aiohttp # 新增:异步HTTP库
# 大模型配置(集中管理)
LLM_CONFIG = {
"base_url": "http://10.10.10.202:8088/v1",
"api_key": "app-m7HZNV1aGiheh3wr6wNVHFxX",
"timeout": 30, # 请求超时时间(秒)
"default_scene": "通用聊天场景", # 默认场景描述
"stream_chunk_size": 1024 # 流式接收块大小
}
# 定义流式回调函数类型(异步)
LLMStreamCallback = Callable[[str, Optional[str], bool], Awaitable[None]]
"""
回调函数参数说明:
- chunk: 单次流式返回的文本片段
- conversation_id: 会话ID(首次返回,后续复用)
- is_finished: 是否结束(True=流式结束/同步返回完成)
"""
@dataclass
class LLMConversation:
"""会话对象(管理会话ID和上下文)"""
conversation_id: Optional[str] = None
user_id: str = ""
scene_description: str = LLM_CONFIG["default_scene"]
# 可选:存储会话历史(如需上下文管理)
history: list = field(default_factory=list)
class LLMClient:
"""大模型客户端封装(异步修复版)"""
def __init__(self):
self.base_url = LLM_CONFIG["base_url"]
self.headers = {
"Authorization": f"Bearer {LLM_CONFIG['api_key']}",
"Content-Type": "application/json"
}
self.timeout = aiohttp.ClientTimeout(total=LLM_CONFIG["timeout"]) # 异步超时
self._session: Optional[aiohttp.ClientSession] = None # 异步会话(复用连接)
async def _get_session(self) -> aiohttp.ClientSession:
"""获取/复用异步HTTP会话"""
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession(timeout=self.timeout)
return self._session
async def send_message(
self,
query: str,
conversation: LLMConversation,
stream_callback: Optional[LLMStreamCallback] = None,
response_mode: str = "streaming"
) -> tuple[Optional[str], str]:
"""
发送消息到大模型(纯异步版,无线程池阻塞)
"""
payload = {
"query": query,
"inputs": {"scene_description": conversation.scene_description},
"response_mode": response_mode,
"user": conversation.user_id
}
if conversation.conversation_id:
payload["conversation_id"] = conversation.conversation_id
url = f"{self.base_url}/chat-messages"
full_response = ""
res_conversation_id = conversation.conversation_id
try:
session = await self._get_session()
if response_mode == "streaming":
# 异步流式请求(无线程池,纯异步IO)
async with session.post(url, headers=self.headers, json=payload) as response:
response.raise_for_status()
# 实时迭代流式响应
async for line in response.content.iter_chunked(LLM_CONFIG["stream_chunk_size"]):
if not line:
continue
line_data = line.decode("utf-8")
if line_data.startswith("data: "):
json_str = line_data[6:].strip()
if json_str == "[DONE]":
if stream_callback:
await stream_callback("", res_conversation_id, True)
break
try:
data = json.loads(json_str)
# 更新会话ID
if not res_conversation_id and "conversation_id" in data:
res_conversation_id = data["conversation_id"]
# 提取内容
chunk = data.get("content", data.get("answer", data.get("message", "")))
# print('大模型返回的', chunk)
if chunk:
full_response += chunk
if stream_callback:
await stream_callback(chunk, res_conversation_id, False)
await asyncio.sleep(0) # 让出调度权
except json.JSONDecodeError as e:
# print(f"大模型解析流式数据失败: {e}")
continue
else:
# 异步非流式请求
async with session.post(url, headers=self.headers, json=payload) as response:
response.raise_for_status()
data = await response.json()
res_conversation_id = data.get("conversation_id", conversation.conversation_id)
full_response = data.get("content", data.get("answer", data.get("message", "")))
if stream_callback:
await stream_callback(full_response, res_conversation_id, True)
conversation.conversation_id = res_conversation_id
return res_conversation_id, full_response
except aiohttp.ClientError as e:
error_msg = f"大模型请求失败: {str(e)}"
print(error_msg)
if stream_callback:
await stream_callback(f"[错误] {error_msg}", res_conversation_id, True)
return res_conversation_id, ""
except Exception as e:
error_msg = f"大模型处理异常: {str(e)}"
print(error_msg)
if stream_callback:
await stream_callback(f"[错误] {error_msg}", res_conversation_id, True)
return res_conversation_id, ""
async def close(self):
"""关闭异步会话(程序退出时调用)"""
if self._session and not self._session.closed:
await self._session.close()
# 全局单例客户端(异步版)
llm_client = LLMClient()
# 快捷调用函数(保持原有接口不变)
async def call_llm(
query: str,
user_id: str,
scene_description: str = LLM_CONFIG["default_scene"],
conversation_id: Optional[str] = None,
stream_callback: Optional[LLMStreamCallback] = None,
response_mode: str = "streaming"
) -> tuple[Optional[str], str]:
"""
快捷调用大模型(无需手动创建会话对象)
:param query: 用户提问
:param user_id: 用户ID
:param scene_description: 场景描述
:param conversation_id: 会话ID(续聊用)
:param stream_callback: 流式回调
:param response_mode: 响应模式
:return: (conversation_id, 完整回复)
"""
conversation = LLMConversation(
conversation_id=conversation_id,
user_id=user_id,
scene_description=scene_description
)
return await llm_client.send_message(
query=query,
conversation=conversation,
stream_callback=stream_callback,
response_mode=response_mode
)
# 可选:程序退出时关闭会话(如FastAPI的shutdown事件)
async def shutdown_llm_client():
await llm_client.close()