Initial lightweight AI agent admin

This commit is contained in:
Hermes Agent
2026-06-08 15:05:57 +08:00
commit 0e485492cf
49 changed files with 4520 additions and 0 deletions
+39
View File
@@ -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
+69
View File
@@ -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"
+87
View File
@@ -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()
+82
View File
@@ -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