438 lines
16 KiB
Python
438 lines
16 KiB
Python
"""
|
||
pipeline-core: Agent 能力定义层
|
||
|
||
对照 Hermes Agent 的 config.yaml + system prompt builder + tool registry:
|
||
在此定义每个产线的 Agent 配置——模型、工具、技能、记忆、上下文压缩。
|
||
|
||
每个产线(pipeline)可以有不同的 AgentConfig,通过 sd_org_settings 或
|
||
pipelines 表的 agent_config 字段存储。pipeline-service 执行时读取此配置。
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
from dataclasses import dataclass, field
|
||
from typing import Dict, List, Optional
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════
|
||
# 数据模型
|
||
# ═══════════════════════════════════════════════════════════
|
||
|
||
@dataclass
|
||
class ToolDefinition:
|
||
"""工具定义 — 等价于 HA 的 tool schema"""
|
||
name: str
|
||
description: str
|
||
parameters: dict = field(default_factory=dict) # JSON Schema for params
|
||
enabled: bool = True
|
||
category: str = "general" # project / task / repo / shell / agent
|
||
requires_confirmation: bool = False # 是否需要用户确认
|
||
|
||
|
||
@dataclass
|
||
class CompressionConfig:
|
||
"""上下文压缩配置"""
|
||
enabled: bool = True
|
||
threshold: float = 0.50 # 达到上下文窗口的 50% 时触发压缩
|
||
target_ratio: float = 0.20 # 压缩到 20%
|
||
keep_recent: int = 8 # 保留最近 N 轮
|
||
|
||
|
||
@dataclass
|
||
class MemoryConfig:
|
||
"""记忆系统配置"""
|
||
enabled: bool = True
|
||
user_profile_enabled: bool = True # 用户偏好
|
||
cross_session_enabled: bool = True # 跨会话事实
|
||
max_entries: int = 50 # 最多保留条数
|
||
|
||
|
||
@dataclass
|
||
class SkillConfig:
|
||
"""技能配置 — 三级隔离:全局 + 组织 + 用户"""
|
||
enabled: bool = True
|
||
base_dir: str = "skills" # 技能根目录,其下为 global/orgs/users/
|
||
max_skills_per_turn: int = 5
|
||
enable_global: bool = True # 启用全局技能
|
||
enable_org: bool = True # 启用组织技能
|
||
enable_user: bool = True # 启用用户技能
|
||
|
||
|
||
@dataclass
|
||
class AgentConfig:
|
||
"""Agent 完整配置 — 一个产线一个配置"""
|
||
# ── 模型 ──
|
||
model_name: str = ""
|
||
temperature: float = 0.4
|
||
max_turns: int = 30 # 最大 tool-calling 轮次(替代硬编码 10)
|
||
|
||
# ── 系统提示词 ──
|
||
system_prompt: str = "" # 产线专属系统提示词
|
||
personality: str = "" # 人设描述
|
||
|
||
# ── 上下文 ──
|
||
compression: CompressionConfig = field(default_factory=CompressionConfig)
|
||
context_limit: int = 64000 # token 上限估计
|
||
|
||
# ── 记忆 ──
|
||
memory: MemoryConfig = field(default_factory=MemoryConfig)
|
||
|
||
# ── 技能 ──
|
||
skills: SkillConfig = field(default_factory=SkillConfig)
|
||
|
||
# ── 工具 ──
|
||
tools: List[ToolDefinition] = field(default_factory=list)
|
||
|
||
# ── 会话 ──
|
||
session_isolation: str = "project" # "project" | "user" | "none"
|
||
history_limit: int = 50 # 加载历史消息数
|
||
|
||
# ── 安全 ──
|
||
require_approval: bool = False # 危险命令需确认
|
||
allowed_workdirs: List[str] = field(default_factory=list)
|
||
|
||
def to_dict(self) -> dict:
|
||
"""序列化为 JSON"""
|
||
return {
|
||
"model_name": self.model_name,
|
||
"temperature": self.temperature,
|
||
"max_turns": self.max_turns,
|
||
"system_prompt": self.system_prompt,
|
||
"personality": self.personality,
|
||
"compression": {
|
||
"enabled": self.compression.enabled,
|
||
"threshold": self.compression.threshold,
|
||
"target_ratio": self.compression.target_ratio,
|
||
"keep_recent": self.compression.keep_recent,
|
||
},
|
||
"memory": {
|
||
"enabled": self.memory.enabled,
|
||
"user_profile_enabled": self.memory.user_profile_enabled,
|
||
"cross_session_enabled": self.memory.cross_session_enabled,
|
||
"max_entries": self.memory.max_entries,
|
||
},
|
||
"skills": {
|
||
"enabled": self.skills.enabled,
|
||
"max_skills_per_turn": self.skills.max_skills_per_turn,
|
||
"base_dir": self.skills.base_dir,
|
||
"enable_global": self.skills.enable_global,
|
||
"enable_org": self.skills.enable_org,
|
||
"enable_user": self.skills.enable_user,
|
||
},
|
||
"tools": [
|
||
{
|
||
"name": t.name,
|
||
"description": t.description,
|
||
"parameters": t.parameters,
|
||
"enabled": t.enabled,
|
||
"category": t.category,
|
||
}
|
||
for t in self.tools
|
||
],
|
||
"session_isolation": self.session_isolation,
|
||
"history_limit": self.history_limit,
|
||
"context_limit": self.context_limit,
|
||
}
|
||
|
||
@classmethod
|
||
def from_dict(cls, data: dict) -> "AgentConfig":
|
||
"""从 JSON 反序列化"""
|
||
comp = data.get("compression", {})
|
||
mem = data.get("memory", {})
|
||
sk = data.get("skills", {})
|
||
|
||
return cls(
|
||
model_name=data.get("model_name", ""),
|
||
temperature=data.get("temperature", 0.4),
|
||
max_turns=data.get("max_turns", 30),
|
||
system_prompt=data.get("system_prompt", ""),
|
||
personality=data.get("personality", ""),
|
||
compression=CompressionConfig(
|
||
enabled=comp.get("enabled", True),
|
||
threshold=comp.get("threshold", 0.50),
|
||
target_ratio=comp.get("target_ratio", 0.20),
|
||
keep_recent=comp.get("keep_recent", 8),
|
||
),
|
||
memory=MemoryConfig(
|
||
enabled=mem.get("enabled", True),
|
||
user_profile_enabled=mem.get("user_profile_enabled", True),
|
||
cross_session_enabled=mem.get("cross_session_enabled", True),
|
||
max_entries=mem.get("max_entries", 50),
|
||
),
|
||
skills=SkillConfig(
|
||
enabled=sk.get("enabled", True),
|
||
base_dir=sk.get("base_dir", "skills"),
|
||
max_skills_per_turn=sk.get("max_skills_per_turn", 5),
|
||
enable_global=sk.get("enable_global", True),
|
||
enable_org=sk.get("enable_org", True),
|
||
enable_user=sk.get("enable_user", True),
|
||
),
|
||
tools=[ToolDefinition(**t) if isinstance(t, dict) else t for t in data.get("tools", [])],
|
||
session_isolation=data.get("session_isolation", "project"),
|
||
history_limit=data.get("history_limit", 50),
|
||
context_limit=data.get("context_limit", 64000),
|
||
)
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════
|
||
# SDLC 默认配置
|
||
# ═══════════════════════════════════════════════════════════
|
||
|
||
SDLC_DEFAULT_TOOLS = [
|
||
ToolDefinition(
|
||
name="switch_project",
|
||
description="切换到指定项目。用户说「切换到XXX」时调用。",
|
||
parameters={"project_name": "项目名称"},
|
||
category="project",
|
||
),
|
||
ToolDefinition(
|
||
name="create_project",
|
||
description="创建新的软件项目",
|
||
parameters={"name": "项目名称", "description": "项目描述"},
|
||
category="project",
|
||
),
|
||
ToolDefinition(
|
||
name="create_task",
|
||
description="创建开发任务,分配给角色agent执行",
|
||
parameters={
|
||
"title": "任务标题",
|
||
"role": "目标角色(requirement/design/develop/test/deploy)",
|
||
"description": "任务详细描述",
|
||
},
|
||
category="task",
|
||
),
|
||
ToolDefinition(
|
||
name="list_tasks",
|
||
description="查看当前项目的任务列表",
|
||
parameters={"role": "按角色筛选(可选)", "state": "按状态筛选(可选)"},
|
||
category="task",
|
||
),
|
||
ToolDefinition(
|
||
name="task_detail",
|
||
description="查看任务详情",
|
||
parameters={"task_id": "任务ID"},
|
||
category="task",
|
||
),
|
||
ToolDefinition(
|
||
name="start_agents",
|
||
description="启动角色agent执行已提交的任务",
|
||
parameters={},
|
||
category="agent",
|
||
),
|
||
ToolDefinition(
|
||
name="diagnose_project",
|
||
description="诊断项目:汇总卡点/失败/问题/运行中任务",
|
||
parameters={},
|
||
category="agent",
|
||
),
|
||
ToolDefinition(
|
||
name="list_deliverables",
|
||
description="查看交付件列表",
|
||
parameters={"role": "按角色筛选(可选)"},
|
||
category="task",
|
||
),
|
||
ToolDefinition(
|
||
name="view_deliverable",
|
||
description="查看交付件详细内容",
|
||
parameters={"deliverable_id": "交付件ID"},
|
||
category="task",
|
||
),
|
||
ToolDefinition(
|
||
name="list_questions",
|
||
description="查看待回答的问题",
|
||
parameters={},
|
||
category="agent",
|
||
),
|
||
ToolDefinition(
|
||
name="answer_question",
|
||
description="回答agent提出的问题",
|
||
parameters={"question_id": "问题ID", "answer": "回答内容"},
|
||
category="agent",
|
||
),
|
||
ToolDefinition(
|
||
name="add_repo",
|
||
description="添加项目关联的Git仓库",
|
||
parameters={"repo_url": "仓库URL", "repo_name": "仓库名称", "default_branch": "默认分支(默认main)"},
|
||
category="repo",
|
||
),
|
||
ToolDefinition(
|
||
name="list_repos",
|
||
description="查看项目关联的仓库列表",
|
||
parameters={},
|
||
category="repo",
|
||
),
|
||
ToolDefinition(
|
||
name="run_command",
|
||
description="在工作空间中执行shell命令",
|
||
parameters={"command": "命令"},
|
||
category="shell",
|
||
requires_confirmation=True,
|
||
),
|
||
ToolDefinition(
|
||
name="check_progress",
|
||
description="查看项目整体进展",
|
||
parameters={},
|
||
category="agent",
|
||
),
|
||
ToolDefinition(
|
||
name="add_bug",
|
||
description="提交Bug",
|
||
parameters={"title": "Bug标题", "description": "描述", "severity": "严重程度"},
|
||
category="task",
|
||
),
|
||
# ── v2 新增 ──
|
||
ToolDefinition(
|
||
name="ask_user",
|
||
description="向用户提问澄清意图(不确定时使用)",
|
||
parameters={"question": "问题"},
|
||
category="agent",
|
||
),
|
||
ToolDefinition(
|
||
name="delegate_subtask",
|
||
description="派生子agent调查子任务(并行执行)",
|
||
parameters={"goal": "子任务目标", "context": "背景信息"},
|
||
category="agent",
|
||
),
|
||
]
|
||
|
||
|
||
SDLC_DEFAULT_CONFIG = AgentConfig(
|
||
model_name="deepseek-v4-pro",
|
||
temperature=0.4,
|
||
max_turns=30,
|
||
system_prompt="""你是 SDLC 开发产线的 AI 驾驶舱助理。
|
||
|
||
## 核心原则
|
||
- 说做就做:承诺调用工具时立刻调,不要只描述计划
|
||
- 每次只输出一个 JSON
|
||
- 工具调用后等待结果,再决定下一步
|
||
- 不确定用户意图时,使用 ask_user 工具澄清
|
||
|
||
## 项目上下文
|
||
当前项目:{current_project}
|
||
{project_context}
|
||
|
||
## 可用工具
|
||
{tools_description}
|
||
|
||
## 输出格式
|
||
每次只输出一个 JSON 对象:
|
||
- 回复用户:{"action":"reply","message":"回复内容"}
|
||
- 调用工具:{"action":"tool_call","tool":"工具名","params":{}}
|
||
- 提问澄清:{"action":"tool_call","tool":"ask_user","params":{"question":"问题"}}
|
||
""",
|
||
tools=SDLC_DEFAULT_TOOLS,
|
||
session_isolation="project",
|
||
history_limit=50,
|
||
compression=CompressionConfig(enabled=True, threshold=0.50, target_ratio=0.20, keep_recent=6),
|
||
memory=MemoryConfig(enabled=True),
|
||
skills=SkillConfig(enabled=True, base_dir="skills"),
|
||
)
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════
|
||
# 配置加载器
|
||
# ═══════════════════════════════════════════════════════════
|
||
|
||
async def load_agent_config(pipeline_id: str = None, project_id: str = None) -> AgentConfig:
|
||
"""加载产线的 Agent 配置。
|
||
|
||
优先级:
|
||
1. 项目级 sd_org_settings.agent_config
|
||
2. 产线级 pipelines.agent_config
|
||
3. SDLC_DEFAULT_CONFIG
|
||
"""
|
||
try:
|
||
from sqlor.dbpools import DBPools
|
||
|
||
db = DBPools()
|
||
async with db.sqlorContext("pipeline") as sor:
|
||
# 项目级配置
|
||
if project_id:
|
||
recs = await sor.sqlExe(
|
||
"SELECT agent_config FROM sd_org_settings WHERE project_id=${pid}$",
|
||
{"pid": project_id},
|
||
)
|
||
if recs:
|
||
raw = getattr(recs[0], "agent_config", "")
|
||
if raw:
|
||
try:
|
||
data = json.loads(raw) if isinstance(raw, str) else raw
|
||
data["tools"] = _merge_tools(data.get("tools", []), SDLC_DEFAULT_TOOLS)
|
||
return AgentConfig.from_dict(data)
|
||
except (json.JSONDecodeError, TypeError):
|
||
pass
|
||
|
||
# 产线级配置
|
||
if pipeline_id:
|
||
recs = await sor.sqlExe(
|
||
"SELECT agent_config FROM pipelines WHERE id=${pid}$",
|
||
{"pid": pipeline_id},
|
||
)
|
||
if recs:
|
||
raw = getattr(recs[0], "agent_config", "")
|
||
if raw:
|
||
try:
|
||
data = json.loads(raw) if isinstance(raw, str) else raw
|
||
data["tools"] = _merge_tools(data.get("tools", []), SDLC_DEFAULT_TOOLS)
|
||
return AgentConfig.from_dict(data)
|
||
except (json.JSONDecodeError, TypeError):
|
||
pass
|
||
except Exception:
|
||
pass
|
||
|
||
return SDLC_DEFAULT_CONFIG
|
||
|
||
|
||
def _merge_tools(custom_tools: list, default_tools: list) -> list:
|
||
"""合并自定义工具和默认工具。自定义覆盖同名工具。"""
|
||
merged = {t["name"] if isinstance(t, dict) else t.name: t for t in default_tools}
|
||
for t in custom_tools:
|
||
name = t["name"] if isinstance(t, dict) else t.name
|
||
merged[name] = t
|
||
return list(merged.values())
|
||
|
||
|
||
async def save_agent_config(project_id: str, config: AgentConfig):
|
||
"""保存项目级 Agent 配置到 sd_org_settings"""
|
||
from sqlor.dbpools import DBPools
|
||
|
||
db = DBPools()
|
||
async with db.sqlorContext("pipeline") as sor:
|
||
config_json = json.dumps(config.to_dict(), ensure_ascii=False)
|
||
# UPDATE-first 防止竞态
|
||
await sor.sqlExe(
|
||
"UPDATE sd_org_settings SET agent_config=${cfg}$ WHERE project_id=${pid}$",
|
||
{"cfg": config_json, "pid": project_id},
|
||
)
|
||
existing = await sor.sqlExe(
|
||
"SELECT id FROM sd_org_settings WHERE project_id=${pid}$",
|
||
{"pid": project_id},
|
||
)
|
||
if not existing:
|
||
from appPublic.uniqueID import getID
|
||
|
||
await sor.C("sd_org_settings", {
|
||
"id": getID(),
|
||
"project_id": project_id,
|
||
"agent_config": config_json,
|
||
})
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════
|
||
# 模块加载
|
||
# ═══════════════════════════════════════════════════════════
|
||
|
||
def load_pipeline_core():
|
||
"""注册 Agent 配置管理函数到 ServerEnv"""
|
||
from ahserver.serverenv import ServerEnv
|
||
|
||
env = ServerEnv()
|
||
env.load_agent_config = load_agent_config
|
||
env.save_agent_config = save_agent_config
|
||
env.AgentConfig = AgentConfig
|
||
env.SDLC_DEFAULT_CONFIG = SDLC_DEFAULT_CONFIG
|
||
|
||
|
||
MODULE_NAME = "pipeline_core"
|