Files
companion-assistant/backend/app/core/llm.py
T

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 ""