root c62156af53 feat(backend): 数字员工平台 B1+B2 批次实现
B1: 项目脚手架 + 数据模型 + 租户管理
- Task 1.1: FastAPI 项目脚手架、SQLite + async SQLAlchemy
- Task 1.2: 7 个数据模型 (Tenant, TenantConfig, DigitalEmployee, Conversation, Message, KnowledgeBase, Document)
- Task 1.3: 租户 CRUD API + LLM 配置(含 API Key AES 加密)

B2: 数字员工配置 + LLM Provider 抽象层
- Task 2.1: 数字员工 CRUD API(关联知识库)
- Task 2.2: BaseLLMProvider 抽象接口 + OpenAI/Qwen Provider
- Task 2.3: Provider 动态实例化 + test-provider 端点

验证: 26 个测试全部通过

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 11:29:48 +08:00

51 lines
1.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Qwen Provider (兼容 OpenAI SDK)"""
from openai import AsyncOpenAI
from app.providers.base import BaseLLMProvider, LLMMessage, LLMResponse
class QwenProvider(BaseLLMProvider):
"""通义千问 Provider使用 OpenAI SDK 兼容模式"""
def __init__(
self,
api_key: str,
model: str = "qwen-turbo",
base_url: str = "https://dashscope.aliyuncs.com/compatible-mode/v1",
):
self.api_key = api_key
self.model = model
self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)
async def chat(self, messages: list[LLMMessage]) -> LLMResponse:
response = await self.client.chat.completions.create(
model=self.model,
messages=[{"role": m.role, "content": m.content} for m in messages],
)
return LLMResponse(
content=response.choices[0].message.content or "",
model=response.model,
usage={
"total_tokens": response.usage.total_tokens,
"prompt_tokens": response.usage.prompt_tokens,
"completion_tokens": response.usage.completion_tokens,
},
)
async def chat_stream(self, messages: list[LLMMessage]) -> str:
stream = await self.client.chat.completions.create(
model=self.model,
messages=[{"role": m.role, "content": m.content} for m in messages],
stream=True,
)
async for chunk in stream:
if chunk.choices[0].delta.content:
yield chunk.choices[0].delta.content
async def embed(self, texts: list[str]) -> list[list[float]]:
# Qwen embedding 使用不同接口
response = await self.client.embeddings.create(
model="text-embedding-v3",
input=texts,
)
return [item.embedding for item in response.data]