- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions, schedules, executions, team_members) and unbind goals/tasks before delete - Remove hardcoded personality templates in Android, replace with dynamic system prompt generation from name + description - Set promptSectionsEnabled=false to bypass PromptComposer for personality - Add Tencent Cloud Linux deployment guide (Docker Compose) - Accumulated backend service updates, frontend UI fixes, Android app changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
238 lines
7.9 KiB
Python
238 lines
7.9 KiB
Python
"""
|
||
Agent 间知识共享服务 — 父 Agent 经验继承、跨 Agent 知识流通
|
||
|
||
子 Agent 创建时可继承父 Agent 的知识条目、学习模式和全局知识,
|
||
避免自主学习创建的 Agent 从零开始。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.database import SessionLocal
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def inherit_knowledge_from_parent(
|
||
child_agent_id: str,
|
||
parent_agent_id: str,
|
||
db: Optional[Session] = None,
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
从父 Agent 继承知识到子 Agent。
|
||
|
||
拷贝内容:
|
||
1. KnowledgeEntry 条目(知识沉淀)
|
||
2. GlobalKnowledge 条目(全局知识,标记为子 Agent 可见)
|
||
3. AgentLearningPattern 模式(工具使用经验)
|
||
"""
|
||
should_close = False
|
||
if db is None:
|
||
db = SessionLocal()
|
||
should_close = True
|
||
|
||
result = {
|
||
"child_agent_id": child_agent_id,
|
||
"parent_agent_id": parent_agent_id,
|
||
"knowledge_entries_copied": 0,
|
||
"global_knowledge_shared": 0,
|
||
"learning_patterns_copied": 0,
|
||
}
|
||
|
||
try:
|
||
# 1. 拷贝 KnowledgeEntry
|
||
from app.models.knowledge_entry import KnowledgeEntry
|
||
|
||
parent_entries = (
|
||
db.query(KnowledgeEntry)
|
||
.filter(
|
||
KnowledgeEntry.is_active == True,
|
||
)
|
||
.filter(
|
||
(KnowledgeEntry.agent_id == parent_agent_id)
|
||
| (KnowledgeEntry.source_agent_name == parent_agent_id)
|
||
)
|
||
.all()
|
||
)
|
||
|
||
import uuid
|
||
for entry in parent_entries:
|
||
new_entry = KnowledgeEntry(
|
||
id=str(uuid.uuid4()),
|
||
agent_id=child_agent_id,
|
||
title=f"[继承] {entry.title}",
|
||
category=entry.category,
|
||
tags=(entry.tags or []) + ["inherited"],
|
||
situation=entry.situation,
|
||
solution=entry.solution,
|
||
caveats=entry.caveats,
|
||
source_execution_ids=entry.source_execution_ids,
|
||
source_agent_name=entry.source_agent_name,
|
||
source_model=entry.source_model,
|
||
retrieval_count=0,
|
||
success_rate=entry.success_rate,
|
||
confidence=(entry.confidence or 0.5) * 0.8, # 继承降低置信度
|
||
is_active=True,
|
||
)
|
||
db.add(new_entry)
|
||
result["knowledge_entries_copied"] += 1
|
||
|
||
# 2. 拷贝 GlobalKnowledge 标记(来源标记关联,不复制内容)
|
||
from app.models.agent import GlobalKnowledge
|
||
from datetime import datetime, timedelta
|
||
|
||
# 查找父 Agent 贡献的全局知识
|
||
parent_knowledge = (
|
||
db.query(GlobalKnowledge)
|
||
.filter(GlobalKnowledge.source_agent_id == parent_agent_id)
|
||
.all()
|
||
)
|
||
# 将父 Agent 的全局知识也关联到子 Agent(scope)
|
||
for gk in parent_knowledge:
|
||
# 不直接修改,而是创建新的 scope 关联(scope_kind=agent, scope_id=child)
|
||
# 如果已有相同内容的记录则跳过
|
||
existing = (
|
||
db.query(GlobalKnowledge)
|
||
.filter(
|
||
GlobalKnowledge.content == gk.content,
|
||
GlobalKnowledge.scope_kind == "agent",
|
||
GlobalKnowledge.scope_id == child_agent_id,
|
||
)
|
||
.first()
|
||
)
|
||
if not existing:
|
||
shared = GlobalKnowledge(
|
||
id=str(uuid.uuid4()),
|
||
content=gk.content,
|
||
embedding=gk.embedding,
|
||
source_agent_id=parent_agent_id,
|
||
source_user_id=gk.source_user_id,
|
||
tags=(gk.tags or []) + ["inherited_from_parent"],
|
||
confidence=gk.confidence or "medium",
|
||
scope_kind="agent",
|
||
scope_id=child_agent_id,
|
||
created_at=datetime.utcnow(),
|
||
)
|
||
db.add(shared)
|
||
result["global_knowledge_shared"] += 1
|
||
|
||
# 3. 拷贝 AgentLearningPattern
|
||
from app.models.agent_learning_pattern import AgentLearningPattern
|
||
|
||
parent_patterns = (
|
||
db.query(AgentLearningPattern)
|
||
.filter(
|
||
AgentLearningPattern.scope_kind == "agent",
|
||
AgentLearningPattern.scope_id == parent_agent_id,
|
||
)
|
||
.all()
|
||
)
|
||
|
||
for pattern in parent_patterns:
|
||
new_pattern = AgentLearningPattern(
|
||
id=str(uuid.uuid4()),
|
||
scope_kind="agent",
|
||
scope_id=child_agent_id,
|
||
task_category=pattern.task_category,
|
||
task_keywords=pattern.task_keywords,
|
||
suggested_tools=pattern.suggested_tools,
|
||
effectiveness_score=pattern.effectiveness_score,
|
||
total_runs=1, # 重置计数
|
||
successful_runs=0,
|
||
avg_iterations=pattern.avg_iterations,
|
||
avg_tool_calls=pattern.avg_tool_calls,
|
||
created_at=datetime.utcnow(),
|
||
updated_at=datetime.utcnow(),
|
||
)
|
||
db.add(new_pattern)
|
||
result["learning_patterns_copied"] += 1
|
||
|
||
db.commit()
|
||
logger.info(
|
||
"Agent 知识继承完成: parent=%s → child=%s, entries=%d, global=%d, patterns=%d",
|
||
parent_agent_id,
|
||
child_agent_id,
|
||
result["knowledge_entries_copied"],
|
||
result["global_knowledge_shared"],
|
||
result["learning_patterns_copied"],
|
||
)
|
||
|
||
except Exception as e:
|
||
db.rollback()
|
||
logger.error("Agent 知识继承失败: %s", e)
|
||
raise
|
||
finally:
|
||
if should_close and db:
|
||
db.close()
|
||
|
||
return result
|
||
|
||
|
||
def get_parent_knowledge_context(
|
||
agent_id: str,
|
||
max_entries: int = 5,
|
||
db: Optional[Session] = None,
|
||
) -> str:
|
||
"""
|
||
获取 Agent 的父级知识上下文(用于注入 system prompt)。
|
||
先查直接父 Agent,再查祖父 Agent(最多上溯 3 层)。
|
||
"""
|
||
should_close = False
|
||
if db is None:
|
||
db = SessionLocal()
|
||
should_close = True
|
||
|
||
try:
|
||
from app.models.agent import Agent
|
||
from app.models.knowledge_entry import KnowledgeEntry
|
||
|
||
# 收集祖先 Agent ID 列表(向上追溯最多 3 层)
|
||
ancestor_ids: List[str] = []
|
||
current_id = agent_id
|
||
for _ in range(3):
|
||
agent = db.query(Agent).filter(Agent.id == current_id).first()
|
||
if not agent or not getattr(agent, "parent_agent_id", None):
|
||
break
|
||
parent_id = agent.parent_agent_id
|
||
if parent_id in ancestor_ids: # 防止循环引用
|
||
break
|
||
ancestor_ids.append(parent_id)
|
||
current_id = parent_id
|
||
|
||
if not ancestor_ids:
|
||
return ""
|
||
|
||
# 查询祖先 Agent 的高质量知识
|
||
entries = (
|
||
db.query(KnowledgeEntry)
|
||
.filter(
|
||
KnowledgeEntry.agent_id.in_(ancestor_ids),
|
||
KnowledgeEntry.is_active == True,
|
||
KnowledgeEntry.confidence >= 0.6,
|
||
)
|
||
.order_by(KnowledgeEntry.retrieval_count.desc())
|
||
.limit(max_entries)
|
||
.all()
|
||
)
|
||
|
||
if not entries:
|
||
return ""
|
||
|
||
lines = [f"## 父级 Agent 经验 ({len(entries)} 条)"]
|
||
for i, entry in enumerate(entries, 1):
|
||
tag_str = f"[{entry.category}]" if entry.category else ""
|
||
sol = (entry.solution or "")[:300]
|
||
lines.append(f"{i}. {tag_str} {entry.title}: {sol}")
|
||
|
||
return "\n".join(lines)
|
||
|
||
except Exception as e:
|
||
logger.warning("获取父级知识上下文失败: %s", e)
|
||
return ""
|
||
finally:
|
||
if should_close and db:
|
||
db.close()
|