72 lines
2.2 KiB
Python
72 lines
2.2 KiB
Python
"""LLM 客户端:OpenAI 兼容适配器,支持多 provider 按模型名路由 + 流式。"""
|
|
from typing import AsyncIterator, Optional
|
|
|
|
from openai import AsyncOpenAI
|
|
|
|
from ..config import settings
|
|
|
|
|
|
def _client_for(model: str) -> AsyncOpenAI:
|
|
_, cfg = settings.find_provider(model)
|
|
return AsyncOpenAI(base_url=cfg["base_url"], api_key=cfg["api_key"] or "EMPTY")
|
|
|
|
|
|
def resolve_model(model: Optional[str]) -> str:
|
|
if not model:
|
|
return settings.DEFAULT_MODEL
|
|
return model
|
|
|
|
|
|
async def chat_completion(
|
|
messages: list[dict],
|
|
model: Optional[str] = None,
|
|
temperature: float = 0.7,
|
|
max_tokens: int = 4096,
|
|
stream: bool = False,
|
|
) -> str:
|
|
"""非流式对话补全。"""
|
|
client = _client_for(resolve_model(model))
|
|
resp = await client.chat.completions.create(
|
|
model=resolve_model(model),
|
|
messages=messages,
|
|
temperature=temperature,
|
|
max_tokens=max_tokens,
|
|
stream=False,
|
|
)
|
|
return resp.choices[0].message.content or ""
|
|
|
|
|
|
async def chat_completion_stream(
|
|
messages: list[dict],
|
|
model: Optional[str] = None,
|
|
temperature: float = 0.7,
|
|
max_tokens: int = 4096,
|
|
) -> AsyncIterator[str]:
|
|
"""流式对话补全:逐段产出增量文本。"""
|
|
client = _client_for(resolve_model(model))
|
|
stream = await client.chat.completions.create(
|
|
model=resolve_model(model),
|
|
messages=messages,
|
|
temperature=temperature,
|
|
max_tokens=max_tokens,
|
|
stream=True,
|
|
)
|
|
async for chunk in stream:
|
|
if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content:
|
|
yield chunk.choices[0].delta.content
|
|
|
|
|
|
async def vision_analysis(prompt: str, image_urls: list[str], model: Optional[str] = None) -> str:
|
|
"""多模态视觉分析:图片 URL 列表 + 提示词 → 文本结论。"""
|
|
model = model or settings.VISION_MODEL
|
|
client = _client_for(model)
|
|
content: list[dict] = [{"type": "text", "text": prompt}]
|
|
for url in image_urls:
|
|
content.append({"type": "image_url", "image_url": {"url": url}})
|
|
resp = await client.chat.completions.create(
|
|
model=model,
|
|
messages=[{"role": "user", "content": content}],
|
|
max_tokens=4096,
|
|
)
|
|
return resp.choices[0].message.content or ""
|