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:
@@ -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"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user