Initial lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import json
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
import httpx
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models import Agent, LLMModel, LLMProvider
|
||||
|
||||
|
||||
class LLMService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
async def complete(self, agent: Agent, user_message: str, history: list[dict] | None = None) -> str:
|
||||
if not agent.model_id:
|
||||
return self._fallback_answer(agent, user_message)
|
||||
model = await self.db.get(LLMModel, agent.model_id)
|
||||
if not model:
|
||||
return self._fallback_answer(agent, user_message)
|
||||
provider = await self.db.get(LLMProvider, model.provider_id)
|
||||
if not provider or provider.status != "enabled" or not provider.api_key or not provider.base_url:
|
||||
return self._fallback_answer(agent, user_message)
|
||||
|
||||
messages = [{"role": "system", "content": agent.system_prompt or f"你是 {agent.name}。"}]
|
||||
messages.extend(history or [])
|
||||
messages.append({"role": "user", "content": user_message})
|
||||
url = provider.base_url.rstrip("/") + "/chat/completions"
|
||||
payload = {
|
||||
"model": model.name,
|
||||
"messages": messages,
|
||||
"temperature": agent.temperature,
|
||||
"max_tokens": agent.max_tokens,
|
||||
"stream": False,
|
||||
}
|
||||
async with httpx.AsyncClient(timeout=60) as client:
|
||||
response = await client.post(
|
||||
url,
|
||||
headers={"Authorization": f"Bearer {provider.api_key}", "Content-Type": "application/json"},
|
||||
json=payload,
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
return data.get("choices", [{}])[0].get("message", {}).get("content") or ""
|
||||
|
||||
async def stream(self, agent: Agent, user_message: str, history: list[dict] | None = None) -> AsyncGenerator[str, None]:
|
||||
answer = await self.complete(agent, user_message, history)
|
||||
for chunk in self._chunk_text(answer):
|
||||
yield chunk
|
||||
|
||||
def _fallback_answer(self, agent: Agent, user_message: str) -> str:
|
||||
prompt = agent.system_prompt.strip() or "轻量 AI Agent"
|
||||
return (
|
||||
f"{agent.name} 已收到任务:{user_message}\n\n"
|
||||
f"当前使用本地占位响应。配置可用的 LLM Provider 和 Model 后,将自动调用真实模型。\n\n"
|
||||
f"系统提示词摘要:{prompt[:160]}"
|
||||
)
|
||||
|
||||
def _chunk_text(self, text: str) -> list[str]:
|
||||
if not text:
|
||||
return [""]
|
||||
return [text[i : i + 24] for i in range(0, len(text), 24)]
|
||||
|
||||
|
||||
def sse(data: dict | str) -> str:
|
||||
if isinstance(data, str):
|
||||
payload = data
|
||||
else:
|
||||
payload = json.dumps(data, ensure_ascii=False, default=str)
|
||||
return f"data: {payload}\n\n"
|
||||
Reference in New Issue
Block a user