Initial lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import time
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models import Agent, AgentTeam, CollaborationRun
|
||||
from app.services.llm import LLMService
|
||||
|
||||
|
||||
class CollaborationService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self.llm = LLMService(db)
|
||||
|
||||
async def run(self, team: AgentTeam, task: str) -> CollaborationRun:
|
||||
started = time.time()
|
||||
run = CollaborationRun(team_id=team.id, task=task, status="running", messages=[], final_answer="")
|
||||
self.db.add(run)
|
||||
await self.db.flush()
|
||||
|
||||
messages = []
|
||||
context = task
|
||||
for member in team.members or []:
|
||||
agent_id = member.get("agent_id")
|
||||
role = member.get("role", "member")
|
||||
agent = await self.db.get(Agent, agent_id) if agent_id else None
|
||||
if not agent:
|
||||
content = f"{role}: 未配置有效智能体,跳过。"
|
||||
else:
|
||||
content = await self.llm.complete(agent, f"你的协作角色是 {role}。请基于上下文完成任务:{context}")
|
||||
messages.append({"role": role, "agent_id": agent_id, "agent_name": agent.name if agent else "", "content": content})
|
||||
context = f"{context}\n\n[{role}] {content}"
|
||||
|
||||
run.messages = messages
|
||||
run.final_answer = messages[-1]["content"] if messages else "团队暂无成员,无法执行协作任务。"
|
||||
run.status = "completed"
|
||||
run.elapsed_time = int((time.time() - started) * 1000)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(run)
|
||||
return run
|
||||
@@ -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"
|
||||
@@ -0,0 +1,87 @@
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.security import hash_password
|
||||
from app.models import Agent, AgentTeam, Announcement, LLMModel, LLMProvider, Menu, Permission, Role, User, Workflow
|
||||
|
||||
|
||||
async def seed_database(db: AsyncSession) -> None:
|
||||
exists = await db.scalar(select(User.id).limit(1))
|
||||
if exists:
|
||||
return
|
||||
|
||||
admin = User(
|
||||
email=settings.seed_admin_email,
|
||||
username="admin",
|
||||
nickname="系统管理员",
|
||||
password_hash=hash_password(settings.seed_admin_password),
|
||||
is_superuser=True,
|
||||
)
|
||||
role = Role(name="超级管理员", code="admin", description="系统内置管理员")
|
||||
permissions = [
|
||||
Permission(name="用户管理", code="core:user", resource="user", action="manage"),
|
||||
Permission(name="角色管理", code="core:role", resource="role", action="manage"),
|
||||
Permission(name="AI 管理", code="ai:manage", resource="ai", action="manage"),
|
||||
]
|
||||
role.permissions = permissions
|
||||
admin.roles = [role]
|
||||
|
||||
menus = [
|
||||
Menu(title="工作台", path="/", icon="Monitor"),
|
||||
Menu(title="用户管理", path="/system/users", icon="User"),
|
||||
Menu(title="角色管理", path="/system/roles", icon="Lock"),
|
||||
Menu(title="菜单管理", path="/system/menus", icon="Menu"),
|
||||
Menu(title="权限管理", path="/system/permissions", icon="Key"),
|
||||
Menu(title="公告管理", path="/system/announcements", icon="Bell"),
|
||||
Menu(title="Provider", path="/ai/providers", icon="Connection"),
|
||||
Menu(title="Model", path="/ai/models", icon="Cpu"),
|
||||
Menu(title="Agent", path="/ai/agents", icon="Avatar"),
|
||||
Menu(title="Agent Chat", path="/ai/chat", icon="ChatLineRound"),
|
||||
Menu(title="Workflow", path="/ai/workflows", icon="Share"),
|
||||
Menu(title="Workflow Runs", path="/ai/workflow-runs", icon="Tickets"),
|
||||
Menu(title="Knowledge Base", path="/ai/knowledge", icon="Collection"),
|
||||
Menu(title="Agent Team", path="/ai/teams", icon="Operation"),
|
||||
]
|
||||
|
||||
provider = LLMProvider(
|
||||
name="OpenAI Compatible",
|
||||
code="openai_compatible",
|
||||
provider_type="openai_compatible",
|
||||
base_url="",
|
||||
api_key="",
|
||||
)
|
||||
model = LLMModel(provider=provider, name="gpt-compatible", display_name="默认兼容模型")
|
||||
agent = Agent(
|
||||
name="Planner",
|
||||
code="planner",
|
||||
status="published",
|
||||
system_prompt="你是任务规划智能体,负责拆解目标并给出执行计划。",
|
||||
model=model,
|
||||
)
|
||||
workflow = Workflow(
|
||||
name="最小 LLM 工作流",
|
||||
code="minimal_llm",
|
||||
status="published",
|
||||
definition={
|
||||
"nodes": [
|
||||
{"id": "start", "type": "start", "data": {}},
|
||||
{"id": "plan", "type": "agent", "data": {"agent_id": ""}},
|
||||
{"id": "end", "type": "end", "data": {}},
|
||||
],
|
||||
"edges": [],
|
||||
},
|
||||
)
|
||||
team = AgentTeam(
|
||||
name="默认协作团队",
|
||||
code="default_team",
|
||||
description="Planner -> Executor -> Reviewer 的最小协作演示",
|
||||
members=[{"agent_id": "", "role": "planner"}, {"agent_id": "", "role": "reviewer"}],
|
||||
)
|
||||
announcement = Announcement(title="AI Agent Admin 已初始化", content="轻量管理后台已可用。", status="published")
|
||||
|
||||
db.add_all([admin, role, *permissions, *menus, provider, model, agent, workflow, team, announcement])
|
||||
await db.flush()
|
||||
workflow.definition["nodes"][1]["data"]["agent_id"] = agent.id
|
||||
team.members = [{"agent_id": agent.id, "role": "planner"}, {"agent_id": agent.id, "role": "reviewer"}]
|
||||
await db.commit()
|
||||
@@ -0,0 +1,82 @@
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models import Agent, Workflow, WorkflowRun
|
||||
from app.services.llm import LLMService
|
||||
|
||||
|
||||
class WorkflowService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self.llm = LLMService(db)
|
||||
|
||||
async def run(self, workflow: Workflow, inputs: dict[str, Any]) -> WorkflowRun:
|
||||
started = time.time()
|
||||
definition = workflow.published_definition or workflow.definition or {}
|
||||
run = WorkflowRun(
|
||||
workflow_id=workflow.id,
|
||||
status="running",
|
||||
inputs=inputs,
|
||||
outputs={},
|
||||
execution_log=[],
|
||||
started_at=datetime.utcnow(),
|
||||
)
|
||||
self.db.add(run)
|
||||
await self.db.flush()
|
||||
|
||||
variables: dict[str, Any] = dict(inputs)
|
||||
log: list[dict[str, Any]] = []
|
||||
try:
|
||||
nodes = definition.get("nodes") or []
|
||||
for node in nodes:
|
||||
node_type = node.get("type", "unknown")
|
||||
node_id = node.get("id", node_type)
|
||||
data = node.get("data") or {}
|
||||
entry = {"node_id": node_id, "node_type": node_type, "status": "completed"}
|
||||
if node_type == "start":
|
||||
entry["output"] = variables
|
||||
elif node_type == "llm":
|
||||
agent_id = data.get("agent_id")
|
||||
prompt = data.get("prompt") or inputs.get("task") or inputs.get("message") or ""
|
||||
agent = await self.db.get(Agent, agent_id) if agent_id else None
|
||||
if agent:
|
||||
result = await self.llm.complete(agent, prompt)
|
||||
else:
|
||||
result = f"LLM 节点占位输出:{prompt}"
|
||||
variables[node_id] = result
|
||||
entry["output"] = result
|
||||
elif node_type == "agent":
|
||||
agent_id = data.get("agent_id")
|
||||
agent = await self.db.get(Agent, agent_id) if agent_id else None
|
||||
task = data.get("task") or inputs.get("task") or inputs.get("message") or ""
|
||||
result = await self.llm.complete(agent, task) if agent else f"Agent 节点占位输出:{task}"
|
||||
variables[node_id] = result
|
||||
entry["output"] = result
|
||||
elif node_type == "condition":
|
||||
entry["output"] = {"matched": True}
|
||||
elif node_type in {"parallel", "merge", "tool", "http"}:
|
||||
entry["output"] = f"{node_type} 节点已执行最小占位逻辑"
|
||||
elif node_type == "end":
|
||||
entry["output"] = variables
|
||||
else:
|
||||
entry["status"] = "skipped"
|
||||
entry["output"] = "未知节点类型,已跳过"
|
||||
log.append(entry)
|
||||
|
||||
run.status = "completed"
|
||||
run.outputs = {"result": variables}
|
||||
run.execution_log = log
|
||||
workflow.run_count = (workflow.run_count or 0) + 1
|
||||
except Exception as exc:
|
||||
run.status = "failed"
|
||||
run.error_message = str(exc)
|
||||
run.execution_log = log
|
||||
finally:
|
||||
run.elapsed_time = int((time.time() - started) * 1000)
|
||||
run.completed_at = datetime.utcnow()
|
||||
await self.db.commit()
|
||||
await self.db.refresh(run)
|
||||
return run
|
||||
Reference in New Issue
Block a user