feat: add AI学习助手 agent (KG+RAG ideal) and renshenguo feishu bot

- Add AI学习助手 agent creation script with all 39 tools, 3-layer KG+RAG memory
- Add renshenguo (人参果) feishu bot integration (app_service + ws_handler)
- Register renshenguo WS client in main.py startup
- Add RENSHENGUO_APP_ID / RENSHENGUO_APP_SECRET / RENSHENGUO_AGENT_ID config
- Reorganize docs from root into docs/ subdirectories
- Move startup scripts to scripts/startup/
- Various backend optimizations and tool improvements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
renjianbo
2026-05-06 01:37:13 +08:00
parent f33bc461ff
commit eabf90c496
171 changed files with 4906 additions and 445 deletions

View File

@@ -1756,9 +1756,9 @@ async def schedule_create_tool(
if not agent:
return json.dumps({"error": f"Agent 不存在: {agent_id}"}, ensure_ascii=False)
# 尝试计算下次执行时间
# 尝试计算下次执行时间(使用北京时间,与 schedule 表 timezone 默认值一致)
try:
next_run = compute_next_run(cron_expression)
next_run = compute_next_run(cron_expression, tz="Asia/Shanghai")
except (ValueError, KeyError) as e:
return json.dumps({"error": f"cron 表达式无效: {e}(标准 5 位格式,如 0 9 * * *"}, ensure_ascii=False)
@@ -4388,3 +4388,314 @@ SELF_REVIEW_SCHEMA = {
},
},
}
# ── knowledge_graph_search ────────────────────────────────────
async def knowledge_graph_search_tool(
query: str,
top_k: int = 5,
include_graph: bool = True,
scope_id: str = "",
) -> str:
"""知识图谱混合检索:向量语义搜索 + 图谱邻居展开。
当用户询问学习相关问题时,用此工具搜索已构建的知识图谱,
同时获取语义相似的知识实体和图谱中关联的邻居知识点。
Args:
query: 搜索查询(用户的问题或关键词)
top_k: 返回的向量匹配实体数(默认 5
include_graph: 是否展开图谱邻居(默认 true
scope_id: 作用域 ID默认使用当前 Agent ID
"""
import asyncio as _asyncio
from app.services.knowledge_graph_service import hybrid_search
try:
result = await hybrid_search(
query=query,
scope_kind="agent",
scope_id=scope_id or "learning_assistant",
top_k=top_k,
include_neighbors=include_graph,
)
formatted = result.get("formatted_context", "")
vector_count = len(result.get("vector_matches", []))
graph_count = len(result.get("graph_expansion", {}).get("entities", []))
return json.dumps({
"query": query,
"vector_matches_count": vector_count,
"graph_entities_count": graph_count,
"context": formatted,
"raw": {
"vector_matches": result.get("vector_matches", []),
"graph_expansion": result.get("graph_expansion", {}),
},
}, ensure_ascii=False)
except Exception as e:
logger.error(f"knowledge_graph_search 失败: {e}")
return json.dumps({"error": f"知识图谱搜索失败: {e}"}, ensure_ascii=False)
KNOWLEDGE_GRAPH_SEARCH_SCHEMA = {
"type": "function",
"function": {
"name": "knowledge_graph_search",
"description": (
"知识图谱混合检索:用向量语义搜索找到相关知识实体,"
"再展开图谱邻居获取关联知识点。适合学习场景中的知识检索、"
"概念关联、前置知识查找。返回结构化的知识上下文。"
),
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索查询(用户问题或关键词)"},
"top_k": {"type": "integer", "description": "返回的向量匹配实体数", "default": 5},
"include_graph": {"type": "boolean", "description": "是否展开图谱邻居", "default": True},
"scope_id": {"type": "string", "description": "作用域 ID默认 learning_assistant"},
},
"required": ["query"],
},
},
}
# ── knowledge_graph_add ────────────────────────────────────────
async def knowledge_graph_add_tool(
text: str,
scope_id: str = "",
) -> str:
"""从文本中提取知识点实体和关系,写入知识图谱。
当用户分享学习内容、知识点总结、对话中有价值的信息时,
调用此工具自动提取实体和关系并持久化到知识图谱。
Args:
text: 要提取知识的文本内容
scope_id: 作用域 ID默认使用当前 Agent ID
"""
import asyncio as _asyncio
from app.services.knowledge_graph_service import extract_from_text
if not text or len(text.strip()) < 20:
return json.dumps({
"error": "文本太短(至少需要 20 个字符)",
"entity_count": 0,
"relation_count": 0,
}, ensure_ascii=False)
try:
result = await extract_from_text(
text=text,
scope_kind="agent",
scope_id=scope_id or "learning_assistant",
)
return json.dumps({
"entity_count": result.get("entity_count", 0),
"relation_count": result.get("relation_count", 0),
"entities": [
{"name": e["name"], "type": e["entity_type"], "description": e.get("description", "")[:200]}
for e in result.get("entities", [])
],
"relations": result.get("relations", []),
"hint": f"已从文本中提取 {result.get('entity_count', 0)} 个实体和 {result.get('relation_count', 0)} 个关系,可使用 knowledge_graph_search 检索",
}, ensure_ascii=False)
except Exception as e:
logger.error(f"knowledge_graph_add 失败: {e}")
return json.dumps({"error": f"知识图谱添加失败: {e}"}, ensure_ascii=False)
KNOWLEDGE_GRAPH_ADD_SCHEMA = {
"type": "function",
"function": {
"name": "knowledge_graph_add",
"description": (
"从文本中提取知识点实体和关系,写入知识图谱。"
"自动识别概念、公式、术语、事实等实体类型,"
"并建立前置/扩展/包含/示例/应用等语义关系。"
"适合将学习材料、对话内容转化为结构化知识网络。"
),
"parameters": {
"type": "object",
"properties": {
"text": {"type": "string", "description": "从中提取知识的文本内容(学习材料、知识点总结等)"},
"scope_id": {"type": "string", "description": "作用域 ID默认 learning_assistant"},
},
"required": ["text"],
},
},
}
# ── entity_search ──────────────────────────────────────────────
async def entity_search_tool(
keyword: str = "",
entity_type: str = "",
scope_id: str = "",
limit: int = 10,
) -> str:
"""关键词搜索知识图谱中的实体。
Args:
keyword: 搜索关键词(在名称和描述中查找)
entity_type: 实体类型筛选concept/formula/fact/term/task/skill留空=全部
scope_id: 作用域 ID
limit: 返回数量
"""
from app.services.knowledge_graph_service import search_entities
from app.core.database import SessionLocal
db = None
try:
db = SessionLocal()
entities = search_entities(
db,
keyword=keyword,
entity_type=entity_type or None,
scope_kind="agent",
scope_id=scope_id or "learning_assistant",
limit=limit,
)
return json.dumps({
"keyword": keyword,
"count": len(entities),
"entities": [
{
"id": e.id,
"name": e.name,
"type": e.entity_type,
"description": e.description,
"confidence": e.confidence,
}
for e in entities
],
}, ensure_ascii=False)
except Exception as e:
logger.error(f"entity_search 失败: {e}")
return json.dumps({"error": f"实体搜索失败: {e}"}, ensure_ascii=False)
finally:
if db:
db.close()
ENTITY_SEARCH_SCHEMA = {
"type": "function",
"function": {
"name": "entity_search",
"description": (
"在知识图谱中按关键词搜索实体。"
"可按实体类型(概念/公式/术语/事实/任务/技能)筛选。"
"适合查找特定知识点或浏览知识库。"
),
"parameters": {
"type": "object",
"properties": {
"keyword": {"type": "string", "description": "搜索关键词(在名称和描述中查找)", "default": ""},
"entity_type": {
"type": "string",
"enum": ["concept", "formula", "fact", "term", "task", "skill"],
"description": "实体类型筛选,留空=全部",
},
"scope_id": {"type": "string", "description": "作用域 ID"},
"limit": {"type": "integer", "description": "返回数量", "default": 10},
},
"required": [],
},
},
}
# ── learning_path ─────────────────────────────────────────────
async def learning_path_tool(
entity_names: str,
scope_id: str = "",
) -> str:
"""基于知识图谱推荐学习路径。
给定一组目标知识点名称(逗号分隔),分析其前置依赖关系,
返回建议的学习顺序。
Args:
entity_names: 目标知识点名称,逗号分隔(如 "微积分,导数,极限"
scope_id: 作用域 ID
"""
from app.services.knowledge_graph_service import search_entities, recommend_learning_path
from app.core.database import SessionLocal
if not entity_names or not entity_names.strip():
return json.dumps({"error": "请提供至少一个知识点名称"}, ensure_ascii=False)
names = [n.strip() for n in entity_names.split(",") if n.strip()]
db = None
try:
db = SessionLocal()
entity_ids = []
found_names = []
for name in names:
entities = search_entities(
db, keyword=name,
scope_kind="agent", scope_id=scope_id or "learning_assistant",
limit=3,
)
for e in entities:
if e.name == name or name in e.name:
entity_ids.append(e.id)
found_names.append(e.name)
break
if not entity_ids:
return json.dumps({
"message": f"未在知识图谱中找到这些知识点: {', '.join(names)}",
"hint": "请先用 knowledge_graph_add 添加相关知识,或使用更通用的名称搜索",
}, ensure_ascii=False)
path = recommend_learning_path(
db, entity_ids,
scope_kind="agent", scope_id=scope_id or "learning_assistant",
)
return json.dumps({
"target_entities": names,
"found_entities": found_names,
"suggested_order": path.get("suggested_order", []),
"summary": path.get("summary", ""),
"prerequisites": [
{
"prerequisite": p["prerequisite"]["name"],
"target": p["target"]["name"],
"relation": p["relation"].get("description", ""),
}
for p in path.get("prerequisites", [])
],
}, ensure_ascii=False)
except Exception as e:
logger.error(f"learning_path 失败: {e}")
return json.dumps({"error": f"学习路径推荐失败: {e}"}, ensure_ascii=False)
finally:
if db:
db.close()
LEARNING_PATH_SCHEMA = {
"type": "function",
"function": {
"name": "learning_path",
"description": (
"基于知识图谱推荐学习路径。给定一组目标知识点,"
"分析前置依赖关系,给出建议的学习顺序。"
"适合制定学习计划、了解知识点间的先后关系。"
),
"parameters": {
"type": "object",
"properties": {
"entity_names": {"type": "string", "description": "目标知识点名称,逗号分隔(如 \"微积分,导数,极限\""},
"scope_id": {"type": "string", "description": "作用域 ID"},
},
"required": ["entity_names"],
},
},
}