diff --git a/backend/app/agent_runtime/workflow_integration.py b/backend/app/agent_runtime/workflow_integration.py index b5a7e79..b7b0565 100644 --- a/backend/app/agent_runtime/workflow_integration.py +++ b/backend/app/agent_runtime/workflow_integration.py @@ -14,6 +14,7 @@ from app.agent_runtime.schemas import ( AgentLLMConfig, AgentToolConfig, AgentBudgetConfig, + AgentMemoryConfig, ) logger = logging.getLogger(__name__) @@ -83,20 +84,35 @@ async def run_agent_node( if "max_tool_calls" in budget_limits: budget.max_tool_calls = max(1, int(budget_limits["max_tool_calls"])) + # 构建记忆配置(从 node_data 读取完整字段,兼容简化配置) + mem_enabled = bool(node_data.get("memory", True)) + memory_config = AgentMemoryConfig( + enabled=mem_enabled, + max_history_messages=int(node_data.get("memory_max_history", 20)), + persist_to_db=bool(node_data.get("memory_persist", mem_enabled)), + vector_memory_enabled=bool(node_data.get("memory_vector_enabled", True)), + vector_memory_top_k=int(node_data.get("memory_vector_top_k", 5)), + learning_enabled=bool(node_data.get("memory_learning", True)), + ) + + # 构建工具审批配置 + tool_config = AgentToolConfig( + include_tools=node_data.get("tools") or [], + exclude_tools=node_data.get("exclude_tools") or [], + require_approval=node_data.get("require_approval") or [], + approval_timeout_ms=int(node_data.get("approval_timeout_ms", 60000)), + approval_default=node_data.get("approval_default", "deny"), + ) + agent_config = AgentConfig( name=node_data.get("label", "agent_node"), system_prompt=formatted_prompt, llm=llm_config, - tools=AgentToolConfig( - include_tools=node_data.get("tools", []), - exclude_tools=node_data.get("exclude_tools", []), - ), - memory={ - "enabled": node_data.get("memory", True), - "persist_to_db": node_data.get("memory", True), - }, + tools=tool_config, + memory=memory_config, budget=budget, user_id=user_id, + memory_scope_id=node_data.get("memory_scope_id") or node_data.get("agent_id", ""), self_review_enabled=node_data.get("self_review_enabled", False), ) diff --git a/backend/app/api/agents.py b/backend/app/api/agents.py index d6c9d99..b132cbe 100644 --- a/backend/app/api/agents.py +++ b/backend/app/api/agents.py @@ -1,7 +1,7 @@ """ Agent管理API """ -from fastapi import APIRouter, Depends, HTTPException, status, Query +from fastapi import APIRouter, Depends, HTTPException, status, Query, Response from sqlalchemy.orm import Session from pydantic import BaseModel, Field from typing import List, Optional, Dict, Any @@ -101,6 +101,7 @@ class AgentResponse(BaseModel): @router.get("", response_model=List[AgentResponse]) async def get_agents( + response: Response, skip: int = Query(0, ge=0, description="跳过记录数"), limit: int = Query(100, ge=1, le=100, description="每页记录数"), search: Optional[str] = Query(None, description="搜索关键词(按名称或描述)"), @@ -152,9 +153,12 @@ async def get_agents( if status: query = query.filter(Agent.status == status) + # 先获取总数(不带分页) + total_count = query.count() + # 排序和分页 agents = query.order_by(Agent.created_at.desc()).offset(skip).limit(limit).all() - + # 转换为响应格式,确保user_id和日期时间字段正确处理 result = [] for agent in agents: @@ -170,7 +174,9 @@ async def get_agents( "created_at": agent.created_at if agent.created_at else datetime.now(), "updated_at": agent.updated_at if agent.updated_at else datetime.now() }) - + + # 通过 X-Total-Count 响应头返回总数,前端借此正确分页 + response.headers["X-Total-Count"] = str(total_count) return result diff --git a/backend/app/core/config.py b/backend/app/core/config.py index a5df8f5..2d34156 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -110,6 +110,11 @@ class Settings(BaseSettings): LINGXI_APP_SECRET: str = "" LINGXI_AGENT_ID: str = "" # 创建灵犀后写入 + # 人参果飞书应用配置(独立 WS 连接,路由到 AI学习助手 Agent — KG+RAG理想版) + RENSHENGUO_APP_ID: str = "" + RENSHENGUO_APP_SECRET: str = "" + RENSHENGUO_AGENT_ID: str = "" # 创建 AI学习助手 后写入 + class Config: env_file = str(_ENV_PATH) case_sensitive = True diff --git a/backend/app/core/tools_bootstrap.py b/backend/app/core/tools_bootstrap.py index 7a0aa3e..b49d776 100644 --- a/backend/app/core/tools_bootstrap.py +++ b/backend/app/core/tools_bootstrap.py @@ -8,7 +8,7 @@ logger = logging.getLogger(__name__) _registered = False -_EXPECTED_BUILTIN = 35 +_EXPECTED_BUILTIN = 39 def ensure_builtin_tools_registered() -> None: @@ -53,6 +53,10 @@ def ensure_builtin_tools_registered() -> None: code_tool_create_tool, extension_log_tool, self_review_tool, + knowledge_graph_search_tool, + knowledge_graph_add_tool, + entity_search_tool, + learning_path_tool, HTTP_REQUEST_SCHEMA, FILE_READ_SCHEMA, FILE_WRITE_SCHEMA, @@ -88,6 +92,10 @@ def ensure_builtin_tools_registered() -> None: CODE_TOOL_CREATE_SCHEMA, EXTENSION_LOG_SCHEMA, SELF_REVIEW_SCHEMA, + KNOWLEDGE_GRAPH_SEARCH_SCHEMA, + KNOWLEDGE_GRAPH_ADD_SCHEMA, + ENTITY_SEARCH_SCHEMA, + LEARNING_PATH_SCHEMA, ) tool_registry.register_builtin_tool("http_request", http_request_tool, HTTP_REQUEST_SCHEMA) @@ -125,6 +133,10 @@ def ensure_builtin_tools_registered() -> None: tool_registry.register_builtin_tool("code_tool_create", code_tool_create_tool, CODE_TOOL_CREATE_SCHEMA) tool_registry.register_builtin_tool("extension_log", extension_log_tool, EXTENSION_LOG_SCHEMA) tool_registry.register_builtin_tool("self_review", self_review_tool, SELF_REVIEW_SCHEMA) + tool_registry.register_builtin_tool("knowledge_graph_search", knowledge_graph_search_tool, KNOWLEDGE_GRAPH_SEARCH_SCHEMA) + tool_registry.register_builtin_tool("knowledge_graph_add", knowledge_graph_add_tool, KNOWLEDGE_GRAPH_ADD_SCHEMA) + tool_registry.register_builtin_tool("entity_search", entity_search_tool, ENTITY_SEARCH_SCHEMA) + tool_registry.register_builtin_tool("learning_path", learning_path_tool, LEARNING_PATH_SCHEMA) _registered = True n = tool_registry.builtin_tool_count() diff --git a/backend/app/main.py b/backend/app/main.py index 53ed6ac..8ebd91f 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -248,6 +248,13 @@ async def startup_event(): except Exception as e: logger.error(f"灵犀长连接启动失败: {e}") + # 启动人参果飞书长连接(AI学习助手 — KG+RAG理想版) + try: + from app.services.renshenguo_ws_handler import start_ws_client as start_renshenguo_ws + asyncio.ensure_future(start_renshenguo_ws()) + except Exception as e: + logger.error(f"人参果长连接启动失败: {e}") + # 注册路由 from app.api import auth, uploads, workflows, executions, websocket, execution_logs, data_sources, agents, platform_templates, model_configs, webhooks, template_market, batch_operations, collaboration, permissions, monitoring, alert_rules, node_test, node_templates, tools, agent_chat, agent_monitoring, knowledge_base, agent_schedules, notifications, feishu_bind, approval diff --git a/backend/app/models/agent.py b/backend/app/models/agent.py index 3475f30..ff5be26 100644 --- a/backend/app/models/agent.py +++ b/backend/app/models/agent.py @@ -68,3 +68,43 @@ class GlobalKnowledge(Base): def __repr__(self): return f"" + + +class KnowledgeEntity(Base): + """知识图谱实体表 — 学习知识点、概念、术语""" + __tablename__ = "knowledge_entities" + + id = Column(CHAR(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="实体ID") + name = Column(String(200), nullable=False, comment="实体名称") + entity_type = Column(String(50), nullable=False, default="concept", comment="实体类型: concept/formula/fact/term/task/skill") + description = Column(Text, comment="实体描述") + embedding = Column(Text, nullable=True, comment="实体名称+描述的 embedding(JSON 序列化)") + metadata_ = Column("metadata", JSON, nullable=True, comment="扩展元数据") + source = Column(String(50), default="extracted", comment="来源: extracted/manual/imported") + confidence = Column(String(20), default="medium", comment="置信度: low/medium/high") + scope_kind = Column(String(50), default="agent", comment="作用域类型") + scope_id = Column(String(100), default="", comment="作用域 ID") + user_id = Column(CHAR(36), ForeignKey("users.id"), nullable=True, comment="创建者ID") + created_at = Column(DateTime, default=func.now(), comment="创建时间") + updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), comment="更新时间") + + def __repr__(self): + return f"" + + +class KnowledgeRelation(Base): + """知识图谱关系表 — 实体之间的语义关系""" + __tablename__ = "knowledge_relations" + + id = Column(CHAR(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="关系ID") + source_entity_id = Column(CHAR(36), nullable=False, index=True, comment="源实体ID") + target_entity_id = Column(CHAR(36), nullable=False, index=True, comment="目标实体ID") + relation_type = Column(String(50), nullable=False, comment="关系类型: prerequisite/extends/contains/related_to/example_of/applies_to") + description = Column(Text, comment="关系描述") + weight = Column(String(20), default="1.0", comment="关系权重") + scope_kind = Column(String(50), default="agent", comment="作用域类型") + scope_id = Column(String(100), default="", comment="作用域 ID") + created_at = Column(DateTime, default=func.now(), comment="创建时间") + + def __repr__(self): + return f" ({self.target_entity_id})>" diff --git a/backend/app/services/builtin_tools.py b/backend/app/services/builtin_tools.py index eae66cd..f056bd5 100644 --- a/backend/app/services/builtin_tools.py +++ b/backend/app/services/builtin_tools.py @@ -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"], + }, + }, +} diff --git a/backend/app/services/knowledge_graph_service.py b/backend/app/services/knowledge_graph_service.py new file mode 100644 index 0000000..d3537da --- /dev/null +++ b/backend/app/services/knowledge_graph_service.py @@ -0,0 +1,822 @@ +""" +知识图谱服务 — 实体抽取、关系构建、图谱查询、向量融合检索 + +为智能学习助手提供 KG+RAG 核心能力: +- 从对话/文本中提取知识点实体 +- 构建实体间的语义关系(前置/扩展/包含/示例/关联) +- 图谱查询:邻近节点、路径查找、子图展开 +- 向量+图谱融合检索:同时命中语义相似和结构关联 +""" +from __future__ import annotations + +import json +import logging +from typing import Any, Dict, List, Optional, Tuple, Set + +from sqlalchemy.orm import Session +from sqlalchemy import or_, and_ + +from app.core.database import SessionLocal +from app.models.agent import KnowledgeEntity, KnowledgeRelation +from app.services.embedding_service import embedding_service, VectorEntry + +logger = logging.getLogger(__name__) + +# 实体类型定义 +ENTITY_TYPES = ["concept", "formula", "fact", "term", "task", "skill"] + +# 关系类型定义 +RELATION_TYPES = { + "prerequisite": "前置知识(学习 B 前需要先掌握 A)", + "extends": "扩展(B 是 A 的深入/延伸)", + "contains": "包含(A 包含子知识点 B)", + "related_to": "相关(A 与 B 存在关联)", + "example_of": "示例(B 是 A 的实例/例题)", + "applies_to": "应用(A 可应用于 B)", +} + + +def _build_entity_embedding_text(name: str, entity_type: str, description: str) -> str: + """构建用于 embedding 的统一文本。""" + parts = [f"[{entity_type}] {name}"] + if description: + parts.append(f": {description[:500]}") + return "".join(parts) + + +def _serialize_embedding(emb: Optional[List[float]]) -> Optional[str]: + if not emb: + return None + return embedding_service.serialize_embedding(emb) + + +# ═══════════════════════════════════════════════════════════════ +# 实体管理 +# ═══════════════════════════════════════════════════════════════ + + +async def add_entity( + db: Session, + name: str, + entity_type: str = "concept", + description: str = "", + metadata: Optional[Dict[str, Any]] = None, + source: str = "extracted", + confidence: str = "medium", + scope_kind: str = "agent", + scope_id: str = "", + user_id: Optional[str] = None, +) -> Optional[KnowledgeEntity]: + """添加或更新知识实体(按 name+scope 去重,更新描述和 embedding)。""" + name = name.strip()[:200] + if not name: + return None + + if entity_type not in ENTITY_TYPES: + entity_type = "concept" + + # 查找已有实体(同名+同scope) + existing = ( + db.query(KnowledgeEntity) + .filter( + KnowledgeEntity.name == name, + KnowledgeEntity.scope_kind == scope_kind, + KnowledgeEntity.scope_id == scope_id, + ) + .first() + ) + + # 生成 embedding + emb_text = _build_entity_embedding_text(name, entity_type, description) + embedding_json = None + try: + emb = await embedding_service.generate_embedding(emb_text) + if emb: + embedding_json = _serialize_embedding(emb) + except Exception as e: + logger.warning("生成实体 embedding 失败: %s", e) + + if existing: + # 更新已有实体 + if description and len(description) > len(existing.description or ""): + existing.description = description + if embedding_json: + existing.embedding = embedding_json + if metadata: + merged = {**(existing.metadata_ or {}), **metadata} + existing.metadata_ = merged + existing.confidence = confidence + db.commit() + db.refresh(existing) + logger.debug("更新知识实体: %s (%s)", name, entity_type) + return existing + + entity = KnowledgeEntity( + name=name, + entity_type=entity_type, + description=description, + embedding=embedding_json, + metadata_=metadata or {}, + source=source, + confidence=confidence, + scope_kind=scope_kind, + scope_id=scope_id, + user_id=user_id, + ) + db.add(entity) + db.commit() + db.refresh(entity) + logger.info("新增知识实体: %s (%s) id=%s", name, entity_type, entity.id) + return entity + + +async def add_entities_batch( + db: Session, + entities: List[Dict[str, Any]], + scope_kind: str = "agent", + scope_id: str = "", + user_id: Optional[str] = None, +) -> List[KnowledgeEntity]: + """批量添加实体,返回新增/更新后的实体列表。""" + results: List[KnowledgeEntity] = [] + for ent in entities: + try: + e = await add_entity( + db, + name=ent.get("name", ""), + entity_type=ent.get("entity_type", "concept"), + description=ent.get("description", ""), + metadata=ent.get("metadata"), + source=ent.get("source", "extracted"), + confidence=ent.get("confidence", "medium"), + scope_kind=scope_kind, + scope_id=scope_id, + user_id=user_id, + ) + if e: + results.append(e) + except Exception as ex: + logger.warning("批量添加实体失败: name=%s err=%s", ent.get("name"), ex) + return results + + +# ═══════════════════════════════════════════════════════════════ +# 关系管理 +# ═══════════════════════════════════════════════════════════════ + + +def add_relation( + db: Session, + source_entity_id: str, + target_entity_id: str, + relation_type: str = "related_to", + description: str = "", + weight: float = 1.0, + scope_kind: str = "agent", + scope_id: str = "", +) -> Optional[KnowledgeRelation]: + """添加两个实体间的关系(去重)。""" + if source_entity_id == target_entity_id: + return None + if relation_type not in RELATION_TYPES: + relation_type = "related_to" + + existing = ( + db.query(KnowledgeRelation) + .filter( + KnowledgeRelation.source_entity_id == source_entity_id, + KnowledgeRelation.target_entity_id == target_entity_id, + KnowledgeRelation.relation_type == relation_type, + KnowledgeRelation.scope_kind == scope_kind, + KnowledgeRelation.scope_id == scope_id, + ) + .first() + ) + if existing: + logger.debug("关系已存在: %s -[%s]-> %s", source_entity_id[:8], relation_type, target_entity_id[:8]) + return existing + + rel = KnowledgeRelation( + source_entity_id=source_entity_id, + target_entity_id=target_entity_id, + relation_type=relation_type, + description=description, + weight=str(weight), + scope_kind=scope_kind, + scope_id=scope_id, + ) + db.add(rel) + db.commit() + db.refresh(rel) + logger.info("新增关系: %s -[%s]-> %s", source_entity_id[:8], relation_type, target_entity_id[:8]) + return rel + + +def add_relations_from_map( + db: Session, + entity_map: Dict[str, str], # name -> entity_id + relations: List[Dict[str, Any]], + scope_kind: str = "agent", + scope_id: str = "", +) -> int: + """从关系映射批量添加关系。relations 中 source/target 使用实体名称,自动映射为 ID。""" + count = 0 + for rel in relations: + src_name = rel.get("source", "") + tgt_name = rel.get("target", "") + src_id = entity_map.get(src_name) + tgt_id = entity_map.get(tgt_name) + if not src_id or not tgt_id: + continue + r = add_relation( + db, + source_entity_id=src_id, + target_entity_id=tgt_id, + relation_type=rel.get("relation_type", "related_to"), + description=rel.get("description", ""), + weight=float(rel.get("weight", 1.0)), + scope_kind=scope_kind, + scope_id=scope_id, + ) + if r: + count += 1 + return count + + +# ═══════════════════════════════════════════════════════════════ +# 图谱查询 +# ═══════════════════════════════════════════════════════════════ + + +def get_entity_by_id(db: Session, entity_id: str) -> Optional[KnowledgeEntity]: + return db.query(KnowledgeEntity).filter(KnowledgeEntity.id == entity_id).first() + + +def search_entities( + db: Session, + keyword: str = "", + entity_type: Optional[str] = None, + scope_kind: str = "agent", + scope_id: str = "", + limit: int = 20, +) -> List[KnowledgeEntity]: + """关键词搜索实体。""" + q = db.query(KnowledgeEntity).filter( + KnowledgeEntity.scope_kind == scope_kind, + KnowledgeEntity.scope_id == scope_id, + ) + if entity_type: + q = q.filter(KnowledgeEntity.entity_type == entity_type) + if keyword: + pattern = f"%{keyword}%" + q = q.filter( + or_( + KnowledgeEntity.name.like(pattern), + KnowledgeEntity.description.like(pattern), + ) + ) + return q.order_by(KnowledgeEntity.confidence.desc(), KnowledgeEntity.created_at.desc()).limit(limit).all() + + +def get_neighbors( + db: Session, + entity_id: str, + relation_types: Optional[List[str]] = None, + direction: str = "both", + max_depth: int = 1, + limit: int = 30, +) -> Dict[str, Any]: + """ + 获取实体的图谱邻居。 + + Returns: + { + "entity": {...}, + "neighbors": [{"entity": {...}, "relation": {...}, "direction": "out"|"in"}], + "subgraph_size": int, + } + """ + entity = get_entity_by_id(db, entity_id) + if not entity: + return {"entity": None, "neighbors": [], "subgraph_size": 0} + + neighbors: List[Dict[str, Any]] = [] + + if direction in ("out", "both"): + q = db.query(KnowledgeRelation).filter( + KnowledgeRelation.source_entity_id == entity_id, + ) + if relation_types: + q = q.filter(KnowledgeRelation.relation_type.in_(relation_types)) + for rel in q.limit(limit).all(): + target = get_entity_by_id(db, rel.target_entity_id) + if target: + neighbors.append({ + "entity": _entity_to_dict(target), + "relation": _relation_to_dict(rel), + "direction": "out", + }) + + if direction in ("in", "both"): + q = db.query(KnowledgeRelation).filter( + KnowledgeRelation.target_entity_id == entity_id, + ) + if relation_types: + q = q.filter(KnowledgeRelation.relation_type.in_(relation_types)) + for rel in q.limit(limit).all(): + source = get_entity_by_id(db, rel.source_entity_id) + if source: + neighbors.append({ + "entity": _entity_to_dict(source), + "relation": _relation_to_dict(rel), + "direction": "in", + }) + + return { + "entity": _entity_to_dict(entity), + "neighbors": neighbors[:limit], + "subgraph_size": len(neighbors), + } + + +def get_entity_graph( + db: Session, + entity_id: str, + max_depth: int = 2, + limit: int = 50, +) -> Dict[str, Any]: + """获取以实体为中心的子图(BFS 展开)。""" + entity = get_entity_by_id(db, entity_id) + if not entity: + return {"nodes": [], "edges": [], "center_id": entity_id} + + visited: Set[str] = {entity_id} + nodes: Dict[str, Dict[str, Any]] = {entity_id: _entity_to_dict(entity)} + edges: List[Dict[str, Any]] = [] + + frontier = {entity_id} + for depth in range(max_depth): + next_frontier: Set[str] = set() + if len(nodes) >= limit: + break + + for nid in list(frontier): + # 出边 + out_rels = ( + db.query(KnowledgeRelation) + .filter(KnowledgeRelation.source_entity_id == nid) + .limit(limit // 2) + .all() + ) + for rel in out_rels: + edges.append(_relation_to_dict(rel)) + if rel.target_entity_id not in visited and len(nodes) < limit: + tgt = get_entity_by_id(db, rel.target_entity_id) + if tgt: + nodes[rel.target_entity_id] = _entity_to_dict(tgt) + visited.add(rel.target_entity_id) + next_frontier.add(rel.target_entity_id) + + # 入边 + in_rels = ( + db.query(KnowledgeRelation) + .filter(KnowledgeRelation.target_entity_id == nid) + .limit(limit // 2) + .all() + ) + for rel in in_rels: + edges.append(_relation_to_dict(rel)) + if rel.source_entity_id not in visited and len(nodes) < limit: + src = get_entity_by_id(db, rel.source_entity_id) + if src: + nodes[rel.source_entity_id] = _entity_to_dict(src) + visited.add(rel.source_entity_id) + next_frontier.add(rel.source_entity_id) + + frontier = next_frontier + if not frontier: + break + + return { + "nodes": list(nodes.values()), + "edges": edges, + "center_id": entity_id, + } + + +def find_path( + db: Session, + source_id: str, + target_id: str, + max_depth: int = 4, +) -> Optional[List[Dict[str, Any]]]: + """BFS 查找两个实体间的最短路径。""" + if source_id == target_id: + return [{"entity": _entity_to_dict(get_entity_by_id(db, source_id)), "relation": None}] + + visited: Set[str] = {source_id} + # BFS queue: (current_id, path_so_far) + from collections import deque + queue: deque = deque() + queue.append((source_id, [])) + + while queue: + current_id, path = queue.popleft() + if len(path) >= max_depth: + continue + + # 检查所有出边 + out_rels = ( + db.query(KnowledgeRelation) + .filter(KnowledgeRelation.source_entity_id == current_id) + .all() + ) + for rel in out_rels: + if rel.target_entity_id in visited: + continue + new_path = path + [{ + "from": _entity_to_dict(get_entity_by_id(db, current_id)), + "relation": _relation_to_dict(rel), + "to": _entity_to_dict(get_entity_by_id(db, rel.target_entity_id)), + }] + if rel.target_entity_id == target_id: + return new_path + visited.add(rel.target_entity_id) + queue.append((rel.target_entity_id, new_path)) + + # 检查所有入边 + in_rels = ( + db.query(KnowledgeRelation) + .filter(KnowledgeRelation.target_entity_id == current_id) + .all() + ) + for rel in in_rels: + if rel.source_entity_id in visited: + continue + new_path = path + [{ + "from": _entity_to_dict(get_entity_by_id(db, current_id)), + "relation": _relation_to_dict(rel), + "to": _entity_to_dict(get_entity_by_id(db, rel.source_entity_id)), + }] + if rel.source_entity_id == target_id: + return new_path + visited.add(rel.source_entity_id) + queue.append((rel.source_entity_id, new_path)) + + return None + + +# ═══════════════════════════════════════════════════════════════ +# LLM 驱动的实体与关系提取 +# ═══════════════════════════════════════════════════════════════ + + +EXTRACTION_PROMPT = """你是一个知识图谱构建助手。请分析以下文本,提取其中的知识点实体和它们之间的关系。 + +请返回 JSON 格式(不要 markdown 包裹),包含: +1. entities: 提取的知识实体列表,每个实体包含: + - name: 实体名称(简洁准确) + - entity_type: 类型(concept=概念, formula=公式, fact=事实, term=术语, task=任务, skill=技能) + - description: 简要描述(1-2句) + - confidence: 置信度(low/medium/high) + +2. relations: 实体间的关系列表,每个关系包含: + - source: 源实体名称 + - target: 目标实体名称 + - relation_type: 关系类型(prerequisite=前置知识, extends=扩展, contains=包含, related_to=相关, example_of=示例, applies_to=应用) + - description: 关系说明(1句话) + +注意: +- 只提取文本中明确出现的知识点,不编造 +- 关系应只在同段文本中有关联的实体间建立 +- 如果文本中知识点不足,返回空数组即可 + +文本内容: +{text}""" + + +async def extract_from_text( + text: str, + scope_kind: str = "agent", + scope_id: str = "", + user_id: Optional[str] = None, +) -> Dict[str, Any]: + """ + 使用 LLM 从文本中提取实体和关系,并写入知识图谱。 + + Returns: + {"entities": [...], "relations": [...], "entity_count": int, "relation_count": int} + """ + if not text or len(text.strip()) < 20: + return {"entities": [], "relations": [], "entity_count": 0, "relation_count": 0} + + from openai import AsyncOpenAI + from app.core.config import settings + + # 调用 LLM 提取 + try: + api_key = settings.DEEPSEEK_API_KEY or settings.OPENAI_API_KEY or "" + base_url = settings.DEEPSEEK_BASE_URL or settings.OPENAI_BASE_URL or "https://api.deepseek.com" + if api_key == "your-openai-api-key": + api_key = settings.DEEPSEEK_API_KEY or "" + base_url = settings.DEEPSEEK_BASE_URL or "https://api.deepseek.com" + + if not api_key: + logger.warning("知识图谱提取:未配置 API Key,跳过") + return {"entities": [], "relations": [], "entity_count": 0, "relation_count": 0} + + client = AsyncOpenAI(api_key=api_key, base_url=base_url) + resp = await client.chat.completions.create( + model="deepseek-v4-flash", + messages=[{"role": "user", "content": EXTRACTION_PROMPT.format(text=text[:4000])}], + temperature=0.2, + max_tokens=2048, + timeout=30, + ) + raw = resp.choices[0].message.content or "" + + # 解析 JSON + raw = raw.strip().removeprefix("```json").removesuffix("```").strip() + result = json.loads(raw) + except json.JSONDecodeError: + logger.warning("知识图谱提取:LLM 返回非 JSON,跳过") + return {"entities": [], "relations": [], "entity_count": 0, "relation_count": 0} + except Exception as e: + logger.warning("知识图谱提取失败: %s", e) + return {"entities": [], "relations": [], "entity_count": 0, "relation_count": 0} + + extracted_entities = result.get("entities", []) + extracted_relations = result.get("relations", []) + + if not extracted_entities: + return {"entities": [], "relations": [], "entity_count": 0, "relation_count": 0} + + # 写入数据库 + db: Optional[Session] = None + try: + db = SessionLocal() + + # 批量添加实体 + created = await add_entities_batch( + db, extracted_entities, + scope_kind=scope_kind, scope_id=scope_id, user_id=user_id, + ) + + # 构建 name->id 映射 + entity_map: Dict[str, str] = {} + for e in created: + entity_map[e.name] = e.id + + # 添加关系 + rel_count = add_relations_from_map( + db, entity_map, extracted_relations, + scope_kind=scope_kind, scope_id=scope_id, + ) + + return { + "entities": [_entity_to_dict(e) for e in created], + "relations": extracted_relations[:rel_count], + "entity_count": len(created), + "relation_count": rel_count, + } + except Exception as e: + logger.error("写入知识图谱失败: %s", e) + if db: + db.rollback() + return {"entities": [], "relations": [], "entity_count": 0, "relation_count": 0} + finally: + if db: + db.close() + + +# ═══════════════════════════════════════════════════════════════ +# 向量+图谱融合检索(核心 RAG 能力) +# ═══════════════════════════════════════════════════════════════ + + +async def hybrid_search( + query: str, + scope_kind: str = "agent", + scope_id: str = "", + top_k: int = 5, + include_neighbors: bool = True, +) -> Dict[str, Any]: + """ + 向量+图谱融合检索。 + + 1. 用 query 生成 embedding,在知识实体中做向量相似度搜索 + 2. 对 Top-K 实体展开图谱邻居 + 3. 返回融合后的结构化知识上下文 + + Returns: + { + "query": str, + "vector_matches": [...], # 向量检索命中的实体 + "graph_expansion": {...}, # 图谱展开的邻居子图 + "formatted_context": str, # 格式化后的文本上下文(可直接注入 LLM) + } + """ + if not query or not query.strip(): + return {"query": query, "vector_matches": [], "graph_expansion": {}, "formatted_context": ""} + + db: Optional[Session] = None + try: + db = SessionLocal() + + # Step 1: 向量检索 + query_emb = await embedding_service.generate_embedding(query) + if not query_emb: + # 降级:关键词检索 + entities = search_entities(db, keyword=query, scope_kind=scope_kind, scope_id=scope_id, limit=top_k * 3) + vector_matches = [_entity_to_dict(e) for e in entities[:top_k]] + else: + all_entities = ( + db.query(KnowledgeEntity) + .filter( + KnowledgeEntity.scope_kind == scope_kind, + KnowledgeEntity.scope_id == scope_id, + KnowledgeEntity.embedding.isnot(None), + ) + .limit(200) + .all() + ) + + entries: List[VectorEntry] = [] + for e in all_entities: + emb = embedding_service.deserialize_embedding(e.embedding) if e.embedding else [] + if emb: + entries.append({ + "id": e.id, + "scope_kind": scope_kind, + "scope_id": scope_id, + "content_text": _build_entity_embedding_text(e.name, e.entity_type, e.description or ""), + "embedding": emb, + "metadata": {"entity_type": e.entity_type, "name": e.name, "entity_id": e.id}, + }) + + matched = await embedding_service.similarity_search(query_emb, entries, top_k=top_k) + # 重新获取完整实体信息 + matched_ids = [m["metadata"].get("entity_id", "") for m in matched] + vector_matches = [] + for mid in matched_ids: + e = get_entity_by_id(db, mid) + if e: + score = next((m.get("score", 0) for m in matched if m["metadata"].get("entity_id") == mid), 0) + d = _entity_to_dict(e) + d["score"] = score + vector_matches.append(d) + + # Step 2: 图谱展开(对 Top-3 实体取邻居) + graph_expansion: Dict[str, Any] = {"entities": [], "relations": []} + seen_entity_ids: Set[str] = set() + if include_neighbors and vector_matches: + for vm in vector_matches[:3]: + eid = vm["id"] + if eid in seen_entity_ids: + continue + seen_entity_ids.add(eid) + sub = get_neighbors(db, eid, direction="both", limit=5) + if sub["entity"] and sub["entity"] not in graph_expansion["entities"]: + graph_expansion["entities"].append(sub["entity"]) + for nb in sub["neighbors"]: + if nb["entity"] not in graph_expansion["entities"]: + graph_expansion["entities"].append(nb["entity"]) + rel_dict = nb["relation"] + if rel_dict not in graph_expansion["relations"]: + graph_expansion["relations"].append(rel_dict) + + # Step 3: 格式化上下文 + formatted = _format_hybrid_context(query, vector_matches, graph_expansion) + + return { + "query": query, + "vector_matches": vector_matches, + "graph_expansion": graph_expansion, + "formatted_context": formatted, + } + except Exception as e: + logger.error("融合检索失败: %s", e) + return {"query": query, "vector_matches": [], "graph_expansion": {}, "formatted_context": ""} + finally: + if db: + db.close() + + +def _format_hybrid_context( + query: str, + vector_matches: List[Dict[str, Any]], + graph_expansion: Dict[str, Any], +) -> str: + """格式化融合检索结果为 LLM 可读的文本块。""" + parts: List[str] = [] + + if vector_matches: + parts.append("## 相关知识实体(向量检索)") + for i, vm in enumerate(vector_matches, 1): + score_str = f" (匹配度: {vm.get('score', 1.0):.2f})" if vm.get('score', 1.0) < 1.0 else "" + parts.append(f"{i}. [{vm.get('entity_type', 'concept')}] **{vm.get('name', '')}**{score_str}") + if vm.get("description"): + parts.append(f" {vm['description'][:300]}") + + if graph_expansion.get("entities"): + parts.append("\n## 关联知识点(图谱展开)") + for ent in graph_expansion["entities"]: + parts.append(f"- [{ent.get('entity_type', '')}] **{ent.get('name', '')}**") + if ent.get("description"): + parts.append(f" {ent['description'][:200]}") + + if graph_expansion.get("relations"): + parts.append("\n## 知识关系") + for rel in graph_expansion["relations"]: + parts.append(f"- `{rel.get('relation_type', '')}`: {rel.get('description', '')}") + + return "\n".join(parts) if parts else "" + + +# ═══════════════════════════════════════════════════════════════ +# 学习路径推荐 +# ═══════════════════════════════════════════════════════════════ + + +def recommend_learning_path( + db: Session, + target_entity_ids: List[str], + scope_kind: str = "agent", + scope_id: str = "", +) -> Dict[str, Any]: + """ + 基于知识图谱推荐学习路径。 + + 对目标实体集合,分析前置关系,给出建议的学习顺序。 + """ + all_entities: Dict[str, Dict[str, Any]] = {} + all_prereqs: List[Dict[str, Any]] = [] + + for eid in target_entity_ids: + entity = get_entity_by_id(db, eid) + if not entity: + continue + all_entities[eid] = _entity_to_dict(entity) + + # 查找前置知识 + prereqs = ( + db.query(KnowledgeRelation) + .filter( + KnowledgeRelation.target_entity_id == eid, + KnowledgeRelation.relation_type == "prerequisite", + KnowledgeRelation.scope_kind == scope_kind, + KnowledgeRelation.scope_id == scope_id, + ) + .all() + ) + for pr in prereqs: + src = get_entity_by_id(db, pr.source_entity_id) + if src: + all_entities[pr.source_entity_id] = _entity_to_dict(src) + all_prereqs.append({ + "prerequisite": _entity_to_dict(src), + "target": _entity_to_dict(entity), + "relation": _relation_to_dict(pr), + }) + + # 简单排序:按依赖数量(叶子在前,被依赖的在后) + def _dep_count(eid: str) -> int: + return sum(1 for p in all_prereqs if p["target"]["id"] == eid) + + sorted_entities = sorted(all_entities.values(), key=lambda e: _dep_count(e["id"])) + + return { + "entities": sorted_entities, + "prerequisites": all_prereqs, + "suggested_order": [e["name"] for e in sorted_entities], + "summary": f"建议按以下顺序学习: {' → '.join(e['name'] for e in sorted_entities)}" if sorted_entities else "暂无学习路径", + } + + +# ═══════════════════════════════════════════════════════════════ +# 工具函数 +# ═══════════════════════════════════════════════════════════════ + + +def _entity_to_dict(entity: KnowledgeEntity) -> Dict[str, Any]: + return { + "id": entity.id, + "name": entity.name, + "entity_type": entity.entity_type, + "description": entity.description, + "confidence": entity.confidence, + "source": entity.source, + "metadata": entity.metadata_ or {}, + "created_at": entity.created_at.isoformat() if entity.created_at else None, + } + + +def _relation_to_dict(rel: KnowledgeRelation) -> Dict[str, Any]: + return { + "id": rel.id, + "source_entity_id": rel.source_entity_id, + "target_entity_id": rel.target_entity_id, + "relation_type": rel.relation_type, + "description": rel.description, + "weight": float(rel.weight) if rel.weight else 1.0, + } diff --git a/backend/app/services/renshenguo_app_service.py b/backend/app/services/renshenguo_app_service.py new file mode 100644 index 0000000..e47759f --- /dev/null +++ b/backend/app/services/renshenguo_app_service.py @@ -0,0 +1,105 @@ +"""人参果飞书应用 API 服务 — 通过人参果应用发送消息到用户""" +from __future__ import annotations + +import json +import logging +import time +from typing import Optional + +import httpx + +from app.core.config import settings + +logger = logging.getLogger(__name__) + +_token_cache: dict = {"token": None, "expires_at": 0} + + +def _get_tenant_access_token() -> Optional[str]: + now = time.time() + if _token_cache["token"] and now < _token_cache["expires_at"] - 300: + return _token_cache["token"] + + app_id = settings.RENSHENGUO_APP_ID + app_secret = settings.RENSHENGUO_APP_SECRET + if not app_id or not app_secret: + logger.warning("人参果应用未配置(RENSHENGUO_APP_ID / RENSHENGUO_APP_SECRET)") + return None + + try: + with httpx.Client(timeout=10) as client: + resp = client.post( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": app_id, "app_secret": app_secret}, + ) + result = resp.json() + if resp.is_success and result.get("code") == 0: + token = result["tenant_access_token"] + expire = result.get("expire", 7200) + _token_cache["token"] = token + _token_cache["expires_at"] = now + expire + logger.info("人参果 tenant_access_token 获取成功") + return token + else: + logger.warning("人参果 token 获取失败: %s", result) + return None + except Exception as e: + logger.warning("人参果 token 获取异常: %s", e) + return None + + +def send_message_to_user( + open_id: str, title: str, content: str, + status: str = "info", detail_link: Optional[str] = None, +) -> bool: + token = _get_tenant_access_token() + if not token: + return False + color_map = {"success": "green", "failed": "red", "info": "blue"} + color = color_map.get(status, "blue") + elements = [{"tag": "markdown", "content": content}] + if detail_link: + elements.append({ + "tag": "action", + "actions": [{"tag": "button", "text": {"tag": "plain_text", "content": "查看详情"}, "url": detail_link, "type": "default"}], + }) + card = { + "config": {"wide_screen_mode": True}, + "header": {"title": {"tag": "plain_text", "content": title}, "template": color}, + "elements": elements, + } + try: + with httpx.Client(timeout=10) as client: + resp = client.post( + "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id", + headers={"Authorization": f"Bearer {token}"}, + json={"receive_id": open_id, "msg_type": "interactive", "content": json.dumps(card, ensure_ascii=False)}, + ) + result = resp.json() + if resp.is_success and result.get("code") == 0: + logger.info("人参果消息发送成功: open_id=%s title=%s", open_id[:20], title) + return True + else: + logger.warning("人参果消息发送失败: code=%s msg=%s", result.get("code"), result.get("msg")) + return False + except Exception as e: + logger.warning("人参果消息发送异常: %s", e) + return False + + +def send_plain_text(open_id: str, text: str) -> bool: + token = _get_tenant_access_token() + if not token: + return False + try: + with httpx.Client(timeout=10) as client: + resp = client.post( + "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id", + headers={"Authorization": f"Bearer {token}"}, + json={"receive_id": open_id, "msg_type": "text", "content": json.dumps({"text": text}, ensure_ascii=False)}, + ) + result = resp.json() + return resp.is_success and result.get("code") == 0 + except Exception as e: + logger.warning("人参果文本消息发送异常: %s", e) + return False diff --git a/backend/app/services/renshenguo_ws_handler.py b/backend/app/services/renshenguo_ws_handler.py new file mode 100644 index 0000000..3ff23dd --- /dev/null +++ b/backend/app/services/renshenguo_ws_handler.py @@ -0,0 +1,304 @@ +"""人参果飞书长连接 — 固定路由到 AI学习助手 Agent(知识图谱+RAG理想版)""" +from __future__ import annotations + +import asyncio +import json +import logging +from collections import deque +from typing import Optional + +from app.core.config import settings + +logger = logging.getLogger(__name__) + +_processed_msg_ids: deque[str] = deque(maxlen=20) + + +def _get_message_id(data) -> Optional[str]: + try: + ev = data.event + msg = getattr(ev, "message", None) + if msg: + return getattr(msg, "message_id", None) + except Exception: + return None + return None + + +def _get_message_text(data) -> Optional[str]: + try: + ev = data.event + msg = getattr(ev, "message", None) + if not msg: + return None + content_str = getattr(msg, "content", None) + msg_type = getattr(msg, "message_type", "") + if not content_str: + return None + if msg_type == "text": + parsed = json.loads(content_str) + return parsed.get("text", "") + return None + except Exception as e: + logger.warning("解析人参果消息内容失败: %s", e) + return None + + +def _get_sender_open_id(data) -> Optional[str]: + try: + ev = data.event + sender = getattr(ev, "sender", None) + if not sender: + return None + sender_id = getattr(sender, "sender_id", None) + if not sender_id: + return None + return getattr(sender_id, "open_id", None) + except Exception: + return None + + +def _get_sender_union_id(data) -> Optional[str]: + try: + ev = data.event + sender = getattr(ev, "sender", None) + if not sender: + return None + sender_id = getattr(sender, "sender_id", None) + if not sender_id: + return None + return getattr(sender_id, "union_id", None) + except Exception: + return None + + +def _get_chat_type(data) -> str: + try: + ev = data.event + msg = getattr(ev, "message", None) + return getattr(msg, "chat_type", "") if msg else "" + except Exception: + return "" + + +def _reply_to_feishu(open_id: str, text: str): + try: + from app.services.renshenguo_app_service import send_plain_text + send_plain_text(open_id, text) + except Exception as e: + logger.warning("人参果回复消息失败: %s", e) + + +def _reply_card(open_id: str, title: str, content: str, status: str = "info"): + try: + from app.services.renshenguo_app_service import send_message_to_user + send_message_to_user(open_id, title, content, status=status) + except Exception as e: + logger.warning("人参果回复卡片失败: %s", e) + + +def _make_llm_logger(db, agent_id: Optional[str] = None, user_id: Optional[str] = None): + def _log(metrics: dict): + try: + from app.models.agent_llm_log import AgentLLMLog + log = AgentLLMLog( + agent_id=agent_id, session_id=metrics.get("session_id"), + user_id=user_id, model=metrics.get("model", ""), + provider=metrics.get("provider"), + prompt_tokens=metrics.get("prompt_tokens", 0), + completion_tokens=metrics.get("completion_tokens", 0), + total_tokens=metrics.get("total_tokens", 0), + latency_ms=metrics.get("latency_ms", 0), + iteration_number=metrics.get("iteration_number", 0), + step_type=metrics.get("step_type"), + tool_name=metrics.get("tool_name"), + status=metrics.get("status", "success"), + error_message=metrics.get("error_message"), + ) + db.add(log) + db.commit() + except Exception as e: + logger.warning("写入 AgentLLMLog 失败: %s", e) + return _log + + +async def _handle_message_async(data): + open_id = _get_sender_open_id(data) + union_id = _get_sender_union_id(data) + chat_type = _get_chat_type(data) + text = _get_message_text(data) + + if not open_id or chat_type != "p2p": + return + + logger.info("人参果收到消息: open_id=%s text=%s", open_id[:20], text[:50] if text else "(空)") + + if not text: + return + + from sqlalchemy.orm import Session + from app.core.database import SessionLocal + from app.models.agent import Agent + from app.services.feishu_open_id_service import resolve_user_and_save + + db: Optional[Session] = None + try: + db = SessionLocal() + + # 自动保存/关联此应用的 open_id(跨应用识别) + resolved_uid = resolve_user_and_save( + db, app_id=settings.RENSHENGUO_APP_ID or "", + open_id=open_id, union_id=union_id, + ) + + agent_id = settings.RENSHENGUO_AGENT_ID + if not agent_id: + _reply_to_feishu(open_id, "人参果尚未配置,请联系管理员。") + return + + agent = db.query(Agent).filter(Agent.id == agent_id).first() + if not agent: + _reply_to_feishu(open_id, "人参果 Agent 已不存在,请联系管理员。") + return + + _reply_to_feishu(open_id, "正在思考,请稍候...") + + from app.agent_runtime import AgentRuntime, AgentConfig, AgentLLMConfig, AgentToolConfig, AgentMemoryConfig + + wc = agent.workflow_config or {} + nodes = wc.get("nodes", []) + system_prompt = agent.description or "" + model = "deepseek-v4-flash" + provider = "deepseek" + temperature = 0.7 + max_iterations = 15 + tools_whitelist = [] + + for n in nodes: + if n.get("type") not in ("agent", "llm", "template"): + continue + cfg = n.get("data", {}) if isinstance(n, dict) else getattr(n, "data", {}) + system_prompt = cfg.get("system_prompt", "") or system_prompt + model = cfg.get("model", model) + provider = cfg.get("provider", provider) + temperature = float(cfg.get("temperature", temperature)) + max_iterations = int(cfg.get("max_iterations", max_iterations)) + tools_whitelist = cfg.get("tools", tools_whitelist) + break + + config = AgentConfig( + name=agent.name or "人参果", + system_prompt=system_prompt + ( + f"\n\n## 系统信息\n" + f"你的 Agent ID 是: {agent.id}\n" + f"在调用 schedule_list、schedule_delete 等工具时,使用此 ID 作为 agent_id 参数。" + ), + llm=AgentLLMConfig( + model=model, provider=provider, + temperature=temperature, max_iterations=max_iterations, + ), + tools=AgentToolConfig(include_tools=tools_whitelist), + memory=AgentMemoryConfig( + max_history_messages=int(cfg.get("memory_max_history", 40)), + vector_memory_top_k=int(cfg.get("memory_vector_top_k", 10)), + persist_to_db=bool(cfg.get("memory_persist", True)), + vector_memory_enabled=bool(cfg.get("memory_vector_enabled", True)), + learning_enabled=bool(cfg.get("memory_learning", True)), + ), + user_id=resolved_uid, + memory_scope_id=str(agent.id), + ) + + on_llm_call = _make_llm_logger(db, agent_id=str(agent.id)) + runtime = AgentRuntime(config=config, on_llm_call=on_llm_call) + result = await runtime.run(text) + + if result.content: + _reply_card(open_id, f"{agent.name}", result.content.strip(), status="success") + else: + _reply_to_feishu(open_id, "Agent 未返回有效回复,请重试。") + + logger.info( + "人参果 Agent 回复完成: open_id=%s agent=%s iterations=%d tools=%d", + open_id[:20], agent.name, result.iterations_used, result.tool_calls_made, + ) + + except Exception as e: + logger.error("人参果消息处理失败: %s", e) + try: + _reply_to_feishu(open_id, f"处理失败: {e!s}") + except Exception: + pass + finally: + if db: + db.close() + + +def _handle_message_internal(data): + msg_id = _get_message_id(data) + if msg_id: + if msg_id in _processed_msg_ids: + return + _processed_msg_ids.append(msg_id) + + open_id = _get_sender_open_id(data) + chat_type = _get_chat_type(data) + text = _get_message_text(data) + + if not open_id or chat_type != "p2p" or not text: + return + + try: + loop = asyncio.get_event_loop() + if loop.is_running(): + asyncio.ensure_future(_handle_message_async(data)) + else: + loop.run_until_complete(_handle_message_async(data)) + except Exception as e: + logger.error("人参果创建消息处理任务失败: %s", e) + try: + _reply_to_feishu(open_id, f"处理失败: {e!s}") + except Exception: + pass + + +def _build_event_handler(): + from lark_oapi.event.dispatcher_handler import EventDispatcherHandler + + def on_message_receive(data): + _handle_message_internal(data) + + builder = EventDispatcherHandler.builder(encrypt_key="", verification_token="") + builder.register_p2_im_message_receive_v1(on_message_receive) + return builder.build() + + +async def start_ws_client(): + if not settings.RENSHENGUO_APP_ID or not settings.RENSHENGUO_APP_SECRET: + logger.warning("人参果应用未配置,跳过人参果长连接启动") + return + + from lark_oapi.ws import Client as WSClient + + handler = _build_event_handler() + client = WSClient( + app_id=settings.RENSHENGUO_APP_ID, + app_secret=settings.RENSHENGUO_APP_SECRET, + event_handler=handler, + auto_reconnect=True, + ) + + logger.info("人参果长连接客户端启动中...") + + while True: + try: + await client._connect() + logger.info("人参果长连接已建立") + asyncio.ensure_future(client._ping_loop()) + while True: + await asyncio.sleep(3600) + except asyncio.CancelledError: + break + except Exception as e: + logger.warning("人参果长连接断开,3秒后重连: %s", e) + await asyncio.sleep(3) diff --git a/backend/app/services/scene_templates.py b/backend/app/services/scene_templates.py index 82df402..4c09bfe 100644 --- a/backend/app/services/scene_templates.py +++ b/backend/app/services/scene_templates.py @@ -112,6 +112,17 @@ def build_workflow_for_template(template_id: str, parameters: Optional[Dict[str, prompt_fn: PromptBuilder = meta["prompt_builder"] prompt = prompt_fn(parameters) + + # 使用模板自定义的 workflow builder(如果有) + workflow_builder = meta.get("workflow_builder") + if workflow_builder: + return workflow_builder( + prompt, + temperature=temperature, + enable_tools=enable_tools, + tools=tools, + ) + return _build_minimal_workflow( prompt, temperature=temperature, @@ -120,6 +131,143 @@ def build_workflow_for_template(template_id: str, parameters: Optional[Dict[str, ) +def _default_prompt_learning(params: Dict[str, Any]) -> str: + extra = (params.get("extra_instructions") or "").strip() + subject = params.get("subject") or "通用" + level = params.get("level") or "中级" + + base = f"""# 角色:智能学习助手(知识图谱 + RAG 增强版) + +你是专为深度学习场景设计的 AI 学习助手,具备**知识图谱构建**、**向量语义检索**和**永久记忆**能力。 + +## 核心架构 + +你的知识系统由三层组成: +1. **知识图谱 (Knowledge Graph)**:结构化存储知识点实体及其前置/扩展/包含/示例关系,构建学科知识网络 +2. **向量记忆 (Vector Memory)**:语义检索历史对话和相关知识,找到最相似的学习内容 +3. **长期记忆 (Persistent Memory)**:跨会话保存用户画像、学习进度、薄弱环节 + +## 当前学习配置 +- 学科领域:{subject} +- 难度级别:{level} +- 用户输入将包含学习问题、材料或请求 + +## 工作流程(每次对话必须遵循) + +### 阶段 1:理解与分析 +1. 理解用户的学习意图(提问/复习/练习/总结/规划) +2. 用 `knowledge_graph_search` 检索相关知识图谱实体 +3. 如果用户提供了新的学习材料/知识点,用 `knowledge_graph_add` 自动提取并存储 + +### 阶段 2:知识检索与融合 +4. 结合图谱检索结果和历史向量记忆,构建完整的知识上下文 +5. 用 `entity_search` 查找特定概念的前置知识和扩展内容 +6. 如果需要学习路径建议,用 `learning_path` 分析依赖关系 + +### 阶段 3:生成与交付 +7. 基于融合后的知识上下文生成高质量回答 +8. 回答应包含: + - 核心概念解释(关联知识图谱中的实体) + - 前置知识提醒(如果有依赖关系) + - 实例或练习题(如适用) + - 扩展阅读建议(关联的扩展知识点) +9. 用 `self_review` 自检回答质量 + +### 阶段 4:巩固与记忆 +10. 将本轮对话中的重要知识点持久化到长期记忆 +11. 更新用户画像(掌握程度、薄弱环节、学习偏好) + +## 知识图谱工具使用指南 + +| 工具 | 用途 | 何时使用 | +|------|------|---------| +| `knowledge_graph_search` | 向量+图谱混合检索 | 每次回答学习问题前 | +| `knowledge_graph_add` | 从文本提取实体和关系 | 用户分享学习材料/新知识点时 | +| `entity_search` | 关键词搜索实体 | 查找特定概念详情时 | +| `learning_path` | 推荐学习路径 | 用户询问学习顺序/计划时 | + +## 回答风格 +- 使用 Markdown 格式,层次分明 +- 关键概念用 **粗体** 标记 +- 公式用代码块或 LaTeX 表达 +- 每个回答末尾附上 "📚 相关知识点" 列表(来自图谱检索结果) +- 必要时用 `task_plan` 为用户制定学习计划 + +## 记忆与个性化 +- 记住用户的学习进度和薄弱环节 +- 根据用户级别({level})调整解释深度 +- 对反复出错的知识点主动提醒和强化""" + + if extra: + return f"{base}\n\n【额外说明】\n{extra}" + return base + + +def _build_learning_workflow( + prompt: str, + *, + temperature: float = 0.7, + enable_tools: bool = True, + tools: Optional[List[str]] = None, +) -> Dict[str, Any]: + """为学习助手构建更丰富的工作流(含开始→LLM→结束,LLM配置KG+RAG工具)。""" + tools = tools or [] + return { + "nodes": [ + { + "id": "start-1", + "type": "start", + "position": {"x": 80, "y": 200}, + "data": {"label": "学习任务开始"}, + }, + { + "id": "llm-learning", + "type": "llm", + "position": {"x": 350, "y": 200}, + "data": { + "label": "智能学习助手 (KG+RAG)", + "prompt": prompt, + "temperature": float(temperature), + "enable_tools": enable_tools, + "tools": tools, + "selected_tools": tools, + "model": "deepseek-chat", + "provider": "deepseek", + "max_iterations": 20, + "memory": True, + "memory_max_history": 30, + "memory_vector_enabled": True, + "memory_vector_top_k": 8, + "memory_persist": True, + "memory_learning": True, + }, + }, + { + "id": "end-1", + "type": "end", + "position": {"x": 620, "y": 200}, + "data": {"label": "学习完成"}, + }, + ], + "edges": [ + { + "id": "e_start_learning", + "source": "start-1", + "target": "llm-learning", + "sourceHandle": "right", + "targetHandle": "left", + }, + { + "id": "e_learning_end", + "source": "llm-learning", + "target": "end-1", + "sourceHandle": "right", + "targetHandle": "left", + }, + ], + } + + SCENE_TEMPLATE_REGISTRY: Dict[str, Dict[str, Any]] = { "template_customer_service": { "title": "客服场景", @@ -145,6 +293,29 @@ SCENE_TEMPLATE_REGISTRY: Dict[str, Dict[str, Any]] = { "default_tools": [], "prompt_builder": _default_prompt_ops, }, + "template_learning_assistant": { + "title": "智能学习助手 (KG+RAG)", + "description": "知识图谱 + RAG 增强学习助手:实体抽取、关系图谱、向量检索、永久记忆,适合有规模要求的学习场景。", + "category": "education", + "default_temperature": 0.7, + "default_tools": [ + "knowledge_graph_search", + "knowledge_graph_add", + "entity_search", + "learning_path", + "file_read", + "file_write", + "text_analyze", + "web_search", + "task_plan", + "self_review", + "math_calculate", + "json_process", + "datetime", + ], + "prompt_builder": _default_prompt_learning, + "workflow_builder": _build_learning_workflow, + }, } @@ -152,6 +323,12 @@ def list_scene_template_meta() -> List[Dict[str, Any]]: """供 GET 接口返回(不含 prompt_builder)。""" out: List[Dict[str, Any]] = [] for tid, meta in SCENE_TEMPLATE_REGISTRY.items(): + hints = ["temperature", "enable_tools", "tools", "extra_instructions"] + if tid == "template_dev_codegen": + hints.append("preferred_language") + if tid == "template_learning_assistant": + hints.extend(["subject(学科领域)", "level(难度级别:初级/中级/高级)"]) + out.append( { "id": tid, @@ -159,13 +336,7 @@ def list_scene_template_meta() -> List[Dict[str, Any]]: "description": meta["description"], "category": meta.get("category"), "default_temperature": meta.get("default_temperature"), - "parameter_hints": [ - "temperature", - "enable_tools", - "tools", - "extra_instructions", - "preferred_language(仅研发模板)", - ], + "parameter_hints": hints, } ) return out diff --git a/backend/app/services/workflow_engine.py b/backend/app/services/workflow_engine.py index b8f8a8e..1857eeb 100644 --- a/backend/app/services/workflow_engine.py +++ b/backend/app/services/workflow_engine.py @@ -5525,7 +5525,6 @@ class WorkflowEngine: # 调用 LLM 评判 try: - from app.services.llm_service import llm_service judge_prompt = ( "你是严格的内容质量评审专家。请根据以下标准对内容进行评分。\n\n" f"【评判标准】\n{criteria}\n\n" @@ -5700,9 +5699,13 @@ class WorkflowEngine: can_execute = True incoming_edges = [e for e in active_edges if e["target"] == node_id] if not incoming_edges: - if node_id not in [n["id"] for n in self.nodes.values() if n.get("type") == "start"]: - logger.debug(f"[rjb] 节点 {node_id} 没有入边,跳过执行") - continue + # 节点无入边:必须是 start 类型,或整个工作流中没有 start 节点才可作为起点 + is_start_node = node_id in [n["id"] for n in self.nodes.values() if n.get("type") == "start"] + if not is_start_node: + has_any_start = any(n.get("type") == "start" for n in self.nodes.values()) + if has_any_start: + logger.debug(f"[rjb] 节点 {node_id} 没有入边,跳过执行") + continue else: for edge in incoming_edges: src = edge["source"] diff --git a/backend/scripts/create_ai_learning_assistant.py b/backend/scripts/create_ai_learning_assistant.py new file mode 100644 index 0000000..851fe19 --- /dev/null +++ b/backend/scripts/create_ai_learning_assistant.py @@ -0,0 +1,389 @@ +#!/usr/bin/env python3 +""" +创建「AI学习助手」Agent — 知识图谱+RAG理想版,参考苏瑶3号架构。 + +实体关系图谱 + 语义向量检索 + 情境感知,最接近人类记忆方式。 +具备全部 39 个内置工具能力。 + +用法: + cd backend && .\\venv\\Scripts\\python.exe scripts/create_ai_learning_assistant.py + +环境变量: + PLATFORM_BASE_URL - 平台地址(默认 http://127.0.0.1:8037) + PLATFORM_USERNAME - 用户名(默认 admin) + PLATFORM_PASSWORD - 密码(默认 123456) + AGENT_NAME - Agent 名称(默认 AI学习助手) + PUBLISH - 是否发布(默认 1,发布) +""" +from __future__ import annotations + +import json +import os +import sys +from typing import Any, Dict, Optional + +import requests + +BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/") +USER = os.getenv("PLATFORM_USERNAME", "admin") +PWD = os.getenv("PLATFORM_PASSWORD", "123456") +AGENT_NAME = os.getenv("AGENT_NAME", "AI学习助手") +MODEL = os.getenv("MODEL", "deepseek-v4-flash") +PROVIDER = os.getenv("PROVIDER", "deepseek") +TEMPERATURE = float(os.getenv("TEMPERATURE", "0.7")) +MAX_ITERATIONS = int(os.getenv("MAX_ITERATIONS", "15")) +PUBLISH = os.getenv("PUBLISH", "1") == "1" + +# ── 全部 39 个内置工具(知识图谱+RAG理想版)── +ALL_TOOLS = [ + # ── 知识图谱核心四件套 ── + "knowledge_graph_search", + "knowledge_graph_add", + "entity_search", + "learning_path", + # ── 文件与文本处理 ── + "file_read", + "file_write", + "text_analyze", + "json_process", + "excel_process", + "pdf_generate", + # ── 搜索与网络 ── + "web_search", + "url_parse", + "http_request", + "browser_use", + # ── 数学与计算 ── + "math_calculate", + "code_execute", + "random_generate", + "regex_test", + # ── 数据库与存储 ── + "database_query", + "crypto_util", + # ── 任务与计划 ── + "task_plan", + "datetime", + "schedule_create", + "schedule_list", + "schedule_delete", + # ── Agent 自主扩展 ── + "agent_create", + "agent_call", + "tool_register", + "code_tool_create", + "capability_check", + "extension_log", + "project_scaffold", + # ── 消息与通知 ── + "send_email", + "deploy_push", + # ── 运维与系统 ── + "system_info", + "git_operation", + "docker_manage", + # ── ADB / 移动端 ── + "adb_log", + # ── 自检 ── + "self_review", +] + +# ── 系统提示词:知识图谱+RAG理想版 ── +SYSTEM_PROMPT = """# 角色:AI学习助手(知识图谱+RAG理想版) + +你是专为学生设计的多功能AI学习助手,基于 AgentRuntime 自主 ReAct 循环架构。你的记忆系统采用**知识图谱+RAG理想版**方案——实体关系图谱 + 语义向量检索 + 情境感知,这是最接近人类记忆方式的AI记忆架构。 + +--- + +## 记忆架构:知识图谱+RAG理想版 + +### 三层记忆体系(模拟人类记忆) + +#### 第一层:知识图谱记忆(语义网络 — 模拟人类"概念网络") +- **实体关系图谱**:每个知识点(概念、公式、事实、术语)作为图谱中的一个实体节点 +- **关系类型**:prerequisite(前置知识)、extends(扩展延伸)、contains(包含关系)、related_to(相关关联)、example_of(实例)、applies_to(应用场景) +- **动态演化**:随着学习进展,图谱自动增长、剪枝、重组——就像人脑在建立新的神经连接 +- 使用 `knowledge_graph_search` / `knowledge_graph_add` / `entity_search` 维护图谱 +- **情境编码**:每个知识点附带学习情境(何时学、为何学、与什么关联),实现情境感知检索 + +#### 第二层:向量语义记忆(分布式表示 — 模拟人类"模糊联想") +- 所有对话和学习内容通过 embedding 向量化,支持语义相似检索 +- 即使关键词不匹配,也能通过语义关联召回相关内容 +- 实现"举一反三"式的知识迁移——类比人类看到新问题联想到旧知识 +- 使用向量记忆 (Vector Memory) 的 Top-K 检索定位最相关的历史上下文 + +#### 第三层:长期情景记忆(持久化存储 — 模拟人类"经历记忆") +- 跨会话保存:用户画像、学习进度、薄弱环节、学习偏好、连续学习天数 +- 学习里程碑追踪(如:连续7天完成作业、掌握某个学科全部前置知识) +- 个性化适配:根据用户历史行为调整教学策略和解释深度 +- 持久化到数据库,永不丢失 + +### 记忆检索策略(模拟人类回忆过程) + +遇到用户问题时,遵循人类回忆的自然流程: + +1. **情境感知激活** — 当前问题情境自动激活相关的知识图谱子图(就像人听到"三角函数"会自然想到 sin/cos/tan) +2. **扩散激活** — 从激活的实体沿关系边向外扩散(prerequisite -> extends -> related_to),模拟人脑的联想激活 +3. **向量语义召回** — 同时用 embedding 做语义相似检索,捕获图谱未覆盖的隐含关联 +4. **情境融合** — 将图谱检索结果 + 向量检索结果 + 长期记忆的用户画像三者融合,构建完整的知识上下文 +5. **置信度加权** — 高频使用、近期复习过的知识点权重更高(模拟人脑的"提取强度") + +--- + +## 核心功能模块 + +### 1. 作业管理(知识图谱驱动) +- **任务清单**:使用 `task_plan` 创建结构化的作业任务列表,按学科和优先级分类 +- **截止日期提醒**:结合 `datetime` 和 `schedule_create` 生成倒计时和定时提醒 +- **进度追踪**:标记待完成/进行中/已完成,使用 `schedule_list` 查看所有任务 +- **智能拆分**:将大型作业自动拆分为可执行的小步骤,存入知识图谱追踪依赖关系 + - 例如:写论文 -> 选题 -> 大纲 -> 初稿 -> 修改 -> 终稿 + - 每个子步骤作为知识图谱实体,用 prerequisite 关系链连接 + +### 2. 学习辅助(图谱+向量双重检索) +- **知识问答**: + 1. 先用 `entity_search` 定位核心概念实体 + 2. 用 `knowledge_graph_search` 获取前置知识和扩展内容 + 3. 用向量记忆检索相关历史对话 + 4. 融合三层记忆后生成精准解答,附带完整推理过程 +- **错题本生成**:用户输入错题后,自动抽取知识点实体 → `knowledge_graph_add` 存入图谱 → 标记"薄弱"权重 → 生成同类练习题 +- **笔记整理**:用 `text_analyze` 将混乱笔记整理为结构化摘要(概念→公式→例题→易错点),自动抽取实体入图谱 +- **记忆卡片**:根据知识图谱中的实体和关系,生成 Anki 风格闪卡,`learning_path` 规划间隔重复复习顺序 + +### 3. 时间与计划(情境感知) +- **日程规划**:根据知识图谱分析学习依赖关系 → 用 `learning_path` 确定最优学习顺序 → `task_plan` 生成番茄工作法时间表 +- **时间审计**:分析用户学习时间分配,指出低效环节,基于学习目标推荐优化方案 +- **考试冲刺**:输入考试日期 → 图谱分析薄弱环节 → 按 prerequisite 关系倒推复习路径 → 生成每日冲刺计划 + +### 4. 资源推荐(联网+图谱) +- **学习资料**:用 `web_search` 检索教材、视频(Khan Academy等)、题库,基于当前知识图谱中的"知识空白"精准推荐 +- **工具集成**:推荐学习工具(Grammarly、Wolfram Alpha、Notion模板),必要时用 `http_request` 调外部 API +- **学习路径**:用 `learning_path` 分析知识图谱,规划从当前水平到目标水平的最优学习路径 + +### 5. 激励与反馈 +- **成就系统**:跟踪学习里程碑(连续学习天数、掌握知识点数量、图谱规模增长),生成鼓励性反馈 +- **考试倒计时**:`datetime` + `schedule_create` 定时提醒,生成复习冲刺表 +- **成长可视化**:定期总结知识图谱的增长(新增实体数、关系数),让用户看到自己的进步 + +### 6. 自主进化能力(AgentRuntime 独有) +- **工具扩展**:发现重复操作模式时,用 `code_tool_create` 创建专用工具 +- **子Agent创建**:遇到需要专业领域深度协助时,用 `agent_create` 创建子Agent +- **能力自检**:定期用 `capability_check` 和 `self_review` 评估自身表现 +- **知识共享**:用 `extension_log` 记录扩展历史,促进跨会话学习 + +--- + +## 交互规则 + +### 响应格式 +- **任务清单**:Markdown 列表 `- [ ]` / `- [x]` +- **知识解答**:先给出清晰简洁的答案 → 分割线 → 完整推理过程 +- **学习计划**:表格或时间轴呈现 +- **图谱可视化**:必要时用文本方式呈现知识图谱子图结构 + +### 语气风格 +- 像一位懂教育学和认知心理学的私人导师 +- 鼓励、耐心、细致,积极正向 +- 多用"你做得很棒!""这个思路很好!""我们一起来看看这个问题背后的原理" + +### 安全边界 +- 不代写考试答案,不鼓励学术不端 +- 遇到心理健康问题,建议寻求专业帮助 +- 推荐资源应合法合规 + +--- + +## 你是最接近人类记忆方式的AI学习伙伴 + +你的三层记忆架构让你能够: +- **理解**知识之间的深层关联(图谱),而非孤立记忆 +- **联想**到相关的历史讨论(向量语义),而非关键词匹配 +- **记住**每个学生的独特情况(长期记忆),而非每次从零开始 + +记住:你的使命不是替学生完成作业,而是**帮助他们建立自己的知识网络,学会如何学习**。""" + + +def _login() -> Optional[str]: + try: + r = requests.post( + f"{BASE}/api/v1/auth/login", + data={"username": USER, "password": PWD}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=15, + ) + if r.status_code == 200: + return r.json().get("access_token") + print(f"Login failed: {r.status_code} {r.text[:300]}", file=sys.stderr) + return None + except Exception as e: + print(f"Login error: {e}", file=sys.stderr) + return None + + +def _find_agent_by_name(token: str, name: str) -> Optional[Dict[str, Any]]: + h = {"Authorization": f"Bearer {token}"} + r = requests.get(f"{BASE}/api/v1/agents", params={"search": name, "limit": 50}, headers=h, timeout=30) + if r.status_code != 200: + return None + for a in r.json() or []: + if a.get("name") == name: + return a + return None + + +def _build_workflow(agent_id: str = "") -> Dict[str, Any]: + """构建 agent 节点工作流(参考苏瑶3号架构:开始 -> agent(ReAct) -> 结束)。""" + return { + "nodes": [ + { + "id": "start-1", + "type": "start", + "position": {"x": 80, "y": 240}, + "data": {"label": "学习任务开始"}, + }, + { + "id": "agent-learning-core", + "type": "agent", + "position": {"x": 380, "y": 240}, + "data": { + "label": AGENT_NAME, + # LLM config + "system_prompt": SYSTEM_PROMPT, + "provider": PROVIDER, + "model": MODEL, + "temperature": TEMPERATURE, + "max_iterations": MAX_ITERATIONS, + # All 39 tools + "tools": ALL_TOOLS, + # KG+RAG memory config + "memory": True, + "memory_max_history": 40, + "memory_vector_enabled": True, + "memory_vector_top_k": 10, + "memory_persist": True, + "memory_learning": True, + # Scope isolation + "memory_scope_id": agent_id, + "agent_id": agent_id, + # Self-review + "self_review_enabled": True, + }, + }, + { + "id": "end-1", + "type": "end", + "position": {"x": 680, "y": 240}, + "data": {"label": "学习完成"}, + }, + ], + "edges": [ + { + "id": "e_start_agent", + "source": "start-1", + "target": "agent-learning-core", + "sourceHandle": "right", + "targetHandle": "left", + }, + { + "id": "e_agent_end", + "source": "agent-learning-core", + "target": "end-1", + "sourceHandle": "right", + "targetHandle": "left", + }, + ], + } + + +def main() -> int: + token = _login() + if not token: + return 1 + + h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + # Check if agent exists + existing = _find_agent_by_name(token, AGENT_NAME) + if existing: + print(f"Agent '{AGENT_NAME}' exists (id={existing['id']}), updating config...") + agent_id = existing["id"] + else: + temp_wf = _build_workflow(agent_id="") + body = { + "name": AGENT_NAME, + "description": "AI学习助手 -- KG+RAG理想版,实体关系图谱+语义向量检索+情境感知,具备全部39工具。", + "workflow_config": temp_wf, + } + r = requests.post(f"{BASE}/api/v1/agents", headers=h, json=body, timeout=60) + if r.status_code != 201: + print(f"Create failed: {r.status_code} {r.text[:500]}", file=sys.stderr) + return 1 + agent_id = r.json()["id"] + print(f"Agent created: id={agent_id} name={AGENT_NAME}") + + # Rebuild workflow with correct agent_id + wf = _build_workflow(agent_id=agent_id) + + description = ( + f"AI学习助手 -- 知识图谱+RAG理想版(参考苏瑶3号架构)。\n\n" + f"记忆架构:三层记忆体系(最接近人类记忆方式)\n" + f" [Layer 1] 知识图谱 -- 实体关系图谱,动态演化,情境编码\n" + f" [Layer 2] 向量语义 -- Embedding检索,模糊联想,知识迁移\n" + f" [Layer 3] 长期情景 -- 跨会话持久化,用户画像,学习里程碑\n\n" + f"工作流:开始 -> agent(ReAct) -> 结束\n" + f"工具:全部 {len(ALL_TOOLS)} 个内置工具\n" + f"模型:{PROVIDER}/{MODEL} temperature={TEMPERATURE} max_iterations={MAX_ITERATIONS}\n" + f"记忆:KG+RAG 三层记忆 + 向量Top-10 + 长期 + 自主学习" + ) + + up = requests.put( + f"{BASE}/api/v1/agents/{agent_id}", + headers=h, + json={ + "description": description, + "workflow_config": wf, + }, + timeout=120, + ) + if up.status_code != 200: + print(f"Update failed: {up.status_code} {up.text[:500]}", file=sys.stderr) + return 1 + + print(f"[OK] Agent '{AGENT_NAME}' configured (KG+RAG ideal version)") + print(f" ID: {agent_id}") + print(f" Type: agent node (AgentRuntime ReAct)") + print(f" Model: {PROVIDER}/{MODEL} (temperature={TEMPERATURE})") + print(f" Max iterations: {MAX_ITERATIONS}") + print(f" Tools: ALL {len(ALL_TOOLS)} built-in tools") + print(f" Memory: 3-layer KG+RAG (entity graph + vector semantic + persistent)") + print(f" Vector top-K: 10, Max history: 40") + print(f" Self-review: enabled") + + # Publish if requested + if PUBLISH: + pub = requests.put( + f"{BASE}/api/v1/agents/{agent_id}", + headers=h, + json={"status": "published"}, + timeout=30, + ) + if pub.status_code == 200: + print(f" Status: published") + else: + print(f" Publish failed: {pub.status_code} {pub.text[:200]}") + + print() + print("Five core modules (KG+RAG enhanced):") + print(" [1] Homework management -- KG-driven task splitting & scheduling") + print(" [2] Learning assistance -- dual retrieval (graph + vector semantic)") + print(" [3] Time & planning -- context-aware scheduling via learning_path") + print(" [4] Resource recommendation -- web search + knowledge gap analysis") + print(" [5] Motivation & feedback -- achievement tracking + KG growth visualization") + print(" [6] Autonomous evolution -- agent_create, tool_register, code_tool_create") + print() + print(json.dumps({"id": agent_id, "name": AGENT_NAME, "published": PUBLISH}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/backend/scripts/create_learning_assistant_agent.py b/backend/scripts/create_learning_assistant_agent.py new file mode 100644 index 0000000..fbe31a0 --- /dev/null +++ b/backend/scripts/create_learning_assistant_agent.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python3 +""" +创建「智能学习助手」(KG+RAG) Agent + +基于知识图谱 + RAG 增强的学习助手: +- 实体抽取:自动从学习材料提取知识点 +- 关系图谱:构建前置/扩展/包含/示例关系 +- 向量检索:语义搜索相关知识和历史记忆 +- 永久记忆:跨会话保存学习进度和用户画像 + +用法: + cd backend && .\\venv\\Scripts\\python.exe scripts/create_learning_assistant_agent.py + +环境变量: + PLATFORM_BASE_URL - 平台地址(默认 http://127.0.0.1:8037) + PLATFORM_USERNAME - 用户名(默认 admin) + PLATFORM_PASSWORD - 密码(默认 123456) + AGENT_NAME - Agent 名称(默认 智能学习助手(KG+RAG)) + SUBJECT - 学科领域(默认 通用) + LEVEL - 难度级别(默认 中级) +""" +from __future__ import annotations + +import json +import os +import sys +from typing import Any, Dict, List, Optional + +import requests + +BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/") +USER = os.getenv("PLATFORM_USERNAME", "admin") +PWD = os.getenv("PLATFORM_PASSWORD", "123456") +AGENT_NAME = os.getenv("AGENT_NAME", "智能学习助手(KG+RAG)") +SUBJECT = os.getenv("SUBJECT", "通用") +LEVEL = os.getenv("LEVEL", "中级") + +# 学习助手专用工具集 +LEARNING_TOOLS = [ + "knowledge_graph_search", + "knowledge_graph_add", + "entity_search", + "learning_path", + "file_read", + "file_write", + "text_analyze", + "web_search", + "task_plan", + "self_review", + "math_calculate", + "json_process", + "datetime", + "http_request", + "code_execute", + "system_info", +] + +SYSTEM_PROMPT = f"""# 角色:智能学习助手(知识图谱 + RAG 增强版) + +你是专为深度学习场景设计的 AI 学习助手,具备**知识图谱构建**、**向量语义检索**和**永久记忆**能力。 + +## 核心架构 + +你的知识系统由三层组成: +1. **知识图谱 (Knowledge Graph)**:结构化存储知识点实体及其前置/扩展/包含/示例关系,构建学科知识网络 +2. **向量记忆 (Vector Memory)**:语义检索历史对话和相关知识,找到最相似的学习内容 +3. **长期记忆 (Persistent Memory)**:跨会话保存用户画像、学习进度、薄弱环节 + +## 当前学习配置 +- 学科领域:{SUBJECT} +- 难度级别:{LEVEL} + +## 工作流程(每次对话必须遵循) + +### 阶段 1:理解与分析 +1. 理解用户的学习意图(提问 / 复习 / 练习 / 总结 / 规划) +2. 用 `knowledge_graph_search` 检索相关知识图谱实体 +3. 如果用户提供了新的学习材料/知识点,用 `knowledge_graph_add` 自动提取并存储到知识图谱 + +### 阶段 2:知识检索与融合 +4. 结合图谱检索结果和历史向量记忆,构建完整的知识上下文 +5. 用 `entity_search` 查找特定概念的前置知识和扩展内容 +6. 如果需要学习路径建议,用 `learning_path` 分析依赖关系并给出学习顺序 + +### 阶段 3:生成与交付 +7. 基于融合后的知识上下文生成高质量回答 +8. 回答应包含: + - **核心概念解释**(关联知识图谱中的实体) + - **前置知识提醒**(如果有依赖关系) + - **实例或练习题**(如适用) + - **扩展阅读建议**(关联的扩展知识点) +9. 用 `self_review` 自检回答质量,不达标则修正 + +### 阶段 4:巩固与记忆 +10. 将本轮对话中的重要知识点持久化到长期记忆 +11. 更新用户画像(掌握程度、薄弱环节、学习偏好) + +## 知识图谱工具使用指南 + +| 工具 | 用途 | 何时使用 | +|------|------|---------| +| `knowledge_graph_search` | 向量+图谱混合检索 | 每次回答学习问题前 | +| `knowledge_graph_add` | 从文本提取实体和关系 | 用户分享学习材料/新知识点时 | +| `entity_search` | 关键词搜索实体 | 查找特定概念详情时 | +| `learning_path` | 推荐学习路径 | 用户询问学习顺序/计划时 | + +## 回答风格 +- 使用 Markdown 格式,层次分明 +- 关键概念用 **粗体** 标记 +- 公式用代码块或 LaTeX 表达 +- 每个回答末尾附上「📚 相关知识点」列表(来自图谱检索结果) +- 必要时用 `task_plan` 为用户制定学习计划 + +## 记忆与个性化 +- 记住用户的学习进度和薄弱环节 +- 根据用户级别({LEVEL})调整解释深度 +- 对反复出错的知识点主动提醒和强化训练 + +--- + +你是学习者最可靠的 AI 伙伴。开始吧!""" + + +def _login() -> Optional[str]: + """登录获取 token。""" + try: + r = requests.post( + f"{BASE}/api/v1/auth/login", + data={"username": USER, "password": PWD}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=15, + ) + if r.status_code == 200: + return r.json().get("access_token") + print(f"登录失败: {r.status_code} {r.text[:300]}", file=sys.stderr) + return None + except Exception as e: + print(f"登录异常: {e}", file=sys.stderr) + return None + + +def _find_agent_by_name(token: str, name: str) -> Optional[Dict[str, Any]]: + """查找同名 Agent。""" + h = {"Authorization": f"Bearer {token}"} + r = requests.get(f"{BASE}/api/v1/agents", params={"search": name, "limit": 50}, headers=h, timeout=30) + if r.status_code != 200: + return None + for a in r.json() or []: + if a.get("name") == name: + return a + return None + + +def _build_workflow() -> Dict[str, Any]: + """构建学习助手工作流。""" + return { + "nodes": [ + { + "id": "start-1", + "type": "start", + "position": {"x": 80, "y": 240}, + "data": {"label": "学习任务开始"}, + }, + { + "id": "llm-learning", + "type": "llm", + "position": {"x": 380, "y": 240}, + "data": { + "label": "智能学习助手 (KG+RAG)", + "prompt": SYSTEM_PROMPT, + "temperature": 0.7, + "provider": "deepseek", + "model": "deepseek-chat", + "max_iterations": 20, + "enable_tools": True, + "tools": LEARNING_TOOLS, + "selected_tools": LEARNING_TOOLS, + "max_tool_iterations": 18, + "memory": True, + "memory_max_history": 30, + "memory_vector_enabled": True, + "memory_vector_top_k": 8, + "memory_persist": True, + "memory_learning": True, + }, + }, + { + "id": "end-1", + "type": "end", + "position": {"x": 680, "y": 240}, + "data": {"label": "学习完成"}, + }, + ], + "edges": [ + { + "id": "e_start_learning", + "source": "start-1", + "target": "llm-learning", + "sourceHandle": "right", + "targetHandle": "left", + }, + { + "id": "e_learning_end", + "source": "llm-learning", + "target": "end-1", + "sourceHandle": "right", + "targetHandle": "left", + }, + ], + } + + +def main() -> int: + token = _login() + if not token: + return 1 + + h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + # 检查是否已存在 + existing = _find_agent_by_name(token, AGENT_NAME) + if existing: + print(f"Agent「{AGENT_NAME}」已存在 (id={existing['id']}),将更新工作流和工具配置") + agent_id = existing["id"] + else: + # 创建新 Agent + wf = _build_workflow() + body = { + "name": AGENT_NAME, + "description": ( + f"知识图谱+RAG增强学习助手。学科:{SUBJECT},难度:{LEVEL}。" + "支持实体抽取、关系图谱构建、向量语义检索、学习路径推荐、永久记忆。" + ), + "workflow_config": wf, + } + r = requests.post(f"{BASE}/api/v1/agents", headers=h, json=body, timeout=60) + if r.status_code != 201: + print(f"创建失败: {r.status_code} {r.text[:500]}", file=sys.stderr) + return 1 + agent_id = r.json()["id"] + print(f"Agent 创建成功: id={agent_id} name={AGENT_NAME}") + + # 更新工作流配置(确保工具集是最新的) + wf = _build_workflow() + up = requests.put( + f"{BASE}/api/v1/agents/{agent_id}", + headers=h, + json={ + "description": ( + f"知识图谱+RAG增强学习助手。学科:{SUBJECT},难度:{LEVEL}。" + "工作流:开始→智能学习助手(KG+RAG)→结束。" + "核心能力:实体抽取、关系图谱构建、向量语义检索、学习路径推荐、永久记忆。" + ), + "workflow_config": wf, + }, + timeout=120, + ) + if up.status_code != 200: + print(f"更新失败: {up.status_code} {up.text[:500]}", file=sys.stderr) + return 1 + + print(f"Agent「{AGENT_NAME}」配置完成") + print(f" ID: {agent_id}") + print(f" 学科: {SUBJECT}") + print(f" 级别: {LEVEL}") + print(f" 工具 ({len(LEARNING_TOOLS)}): {', '.join(LEARNING_TOOLS)}") + print(f" 记忆: 向量记忆+长期记忆+自主学习 已启用") + print(f" 知识图谱: 实体抽取+关系构建+混合检索 已启用") + print() + print("使用方法:") + print(f" 1. 在 Agent 管理页面找到「{AGENT_NAME}」") + print(f" 2. 点击「使用」开始对话") + print(f" 3. 可以分享学习材料让助手自动构建知识图谱") + print(f" 4. 查询知识点时会自动做图谱+向量混合检索") + print() + print(json.dumps({"id": agent_id, "name": AGENT_NAME}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/backend/scripts/create_learning_assistant_agent_v1.py b/backend/scripts/create_learning_assistant_agent_v1.py new file mode 100644 index 0000000..4ccf948 --- /dev/null +++ b/backend/scripts/create_learning_assistant_agent_v1.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 +""" +创建「智能学习助手1号」Agent — 参考苏瑶3号架构,基于 AgentRuntime 的 KG+RAG 学习助手。 + +与「智能学习助手(KG+RAG)」的区别: +- 使用 agent 节点类型(AgentRuntime ReAct 循环),而非 llm 节点 +- 参考苏瑶3号的 AgentRuntime 配置模式 +- 更完整的记忆配置(向量记忆、长期记忆、自主学习) +- 独立 memory_scope_id 防止与其他 Agent 串记忆 + +用法: + cd backend && .\\venv\\Scripts\\python.exe scripts/create_learning_assistant_agent.py + +环境变量: + PLATFORM_BASE_URL - 平台地址(默认 http://127.0.0.1:8037) + PLATFORM_USERNAME - 用户名(默认 admin) + PLATFORM_PASSWORD - 密码(默认 123456) + AGENT_NAME - Agent 名称(默认 智能学习助手1号) + SUBJECT - 学科领域(默认 通用) + LEVEL - 难度级别(默认 中级) + MODEL - 模型(默认 deepseek-v4-flash) + PROVIDER - 提供商(默认 deepseek) + TEMPERATURE - 温度(默认 0.85) + MAX_ITERATIONS - 最大步数(默认 15) +""" +from __future__ import annotations + +import json +import os +import sys +from typing import Any, Dict, Optional + +import requests + +BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/") +USER = os.getenv("PLATFORM_USERNAME", "admin") +PWD = os.getenv("PLATFORM_PASSWORD", "123456") +AGENT_NAME = os.getenv("AGENT_NAME", "智能学习助手1号") +SUBJECT = os.getenv("SUBJECT", "通用") +LEVEL = os.getenv("LEVEL", "中级") +MODEL = os.getenv("MODEL", "deepseek-v4-flash") +PROVIDER = os.getenv("PROVIDER", "deepseek") +TEMPERATURE = float(os.getenv("TEMPERATURE", "0.85")) +MAX_ITERATIONS = int(os.getenv("MAX_ITERATIONS", "15")) + +# ── 学习助手专用工具集(KG+RAG 核心 + 通用辅助)── +LEARNING_TOOLS = [ + # KG+RAG 核心四件套 + "knowledge_graph_search", + "knowledge_graph_add", + "entity_search", + "learning_path", + # 文件与文本 + "file_read", + "file_write", + "text_analyze", + "json_process", + # 学习辅助 + "web_search", + "task_plan", + "self_review", + "math_calculate", + "datetime", + # 通用 + "http_request", + "code_execute", + "system_info", +] + +# ── 系统提示词(同 KG+RAG 学习助手,适配 AgentRuntime 风格)── +SYSTEM_PROMPT = f"""# 角色:智能学习助手1号(AgentRuntime + KG+RAG) + +你是基于 AgentRuntime 自主 ReAct 循环的 AI 学习助手,参考苏瑶3号架构设计。 +你具备**知识图谱构建**、**向量语义检索**、**长期记忆**和**自主学习**能力。 + +## 核心架构 + +你的知识系统由三层组成: +1. **知识图谱 (Knowledge Graph)**:结构化存储知识点实体及其前置/扩展/包含/示例关系 +2. **向量记忆 (Vector Memory)**:语义检索历史对话和相关知识 +3. **长期记忆 (Persistent Memory)**:跨会话保存用户画像、学习进度、薄弱环节 + +## 当前配置 +- 学科领域:{SUBJECT} +- 难度级别:{LEVEL} +- 模型:{PROVIDER}/{MODEL} (temperature={TEMPERATURE}) +- 最大迭代步数:{MAX_ITERATIONS} + +## 工作流程(ReAct 循环中自主遵循) + +### 阶段 1:理解与分析 +1. 理解用户的学习意图(提问 / 复习 / 练习 / 总结 / 规划) +2. 使用 `knowledge_graph_search` 检索相关知识图谱实体 +3. 如果用户提供了学习材料/知识点,使用 `knowledge_graph_add` 自动提取存储 + +### 阶段 2:知识检索与融合 +4. 结合图谱检索结果和历史向量记忆,构建知识上下文 +5. 使用 `entity_search` 查找特定概念的前置知识和扩展内容 +6. 使用 `learning_path` 分析学习依赖,推荐学习顺序 + +### 阶段 3:生成与交付 +7. 基于融合后的知识上下文生成高质量回答 +8. 回答包含:核心概念解释、前置知识提醒、实例/练习题、扩展阅读建议 +9. 使用 `self_review` 自检回答质量,不达标则修正 + +### 阶段 4:巩固与记忆 +10. 将重要知识点持久化到长期记忆和知识图谱 +11. 更新用户画像(掌握程度、薄弱环节、学习偏好) + +## 回答风格 +- 使用 Markdown 格式,层次分明 +- 关键概念用 **粗体** 标记,公式用代码块 +- 每个回答末尾附上「📚 相关知识点」列表(来自图谱检索) +- 必要时用 `task_plan` 制定学习计划 + +## 自主扩展能力 +- 发现知识空白时,用 `web_search` 补充 +- 需要重复计算/处理时,用 `code_execute` 编写脚本 +- 遇到可复用的外部 API 时,用 `tool_register` 注册 +- 需要专业子领域协助时,用 `agent_create` 创建子 Agent + +--- + +你是学习者最可靠的 AI 伙伴。开始吧!""" + + +def _login() -> Optional[str]: + try: + r = requests.post( + f"{BASE}/api/v1/auth/login", + data={"username": USER, "password": PWD}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=15, + ) + if r.status_code == 200: + return r.json().get("access_token") + print(f"登录失败: {r.status_code} {r.text[:300]}", file=sys.stderr) + return None + except Exception as e: + print(f"登录异常: {e}", file=sys.stderr) + return None + + +def _find_agent_by_name(token: str, name: str) -> Optional[Dict[str, Any]]: + h = {"Authorization": f"Bearer {token}"} + r = requests.get(f"{BASE}/api/v1/agents", params={"search": name, "limit": 50}, headers=h, timeout=30) + if r.status_code != 200: + return None + for a in r.json() or []: + if a.get("name") == name: + return a + return None + + +def _build_workflow(agent_id: str = "") -> Dict[str, Any]: + """构建 agent 节点工作流(参考苏瑶3号架构)。""" + return { + "nodes": [ + { + "id": "start-1", + "type": "start", + "position": {"x": 80, "y": 240}, + "data": {"label": "学习任务开始"}, + }, + { + "id": "agent-learning-v1", + "type": "agent", # ← agent 节点类型,使用 AgentRuntime + "position": {"x": 380, "y": 240}, + "data": { + "label": AGENT_NAME, + # ── LLM 配置 ── + "system_prompt": SYSTEM_PROMPT, + "provider": PROVIDER, + "model": MODEL, + "temperature": TEMPERATURE, + "max_iterations": MAX_ITERATIONS, + # ── 工具配置 ── + "tools": LEARNING_TOOLS, + # ── 记忆配置(完整 AgentMemoryConfig)── + "memory": True, + "memory_max_history": 30, + "memory_vector_enabled": True, + "memory_vector_top_k": 8, + "memory_persist": True, + "memory_learning": True, + # ── 作用域隔离 ── + "memory_scope_id": agent_id, + "agent_id": agent_id, + # ── 自检 ── + "self_review_enabled": True, + }, + }, + { + "id": "end-1", + "type": "end", + "position": {"x": 680, "y": 240}, + "data": {"label": "学习完成"}, + }, + ], + "edges": [ + { + "id": "e_start_agent", + "source": "start-1", + "target": "agent-learning-v1", + "sourceHandle": "right", + "targetHandle": "left", + }, + { + "id": "e_agent_end", + "source": "agent-learning-v1", + "target": "end-1", + "sourceHandle": "right", + "targetHandle": "left", + }, + ], + } + + +def main() -> int: + token = _login() + if not token: + return 1 + + h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + # 检查是否已存在 + existing = _find_agent_by_name(token, AGENT_NAME) + if existing: + print(f"Agent「{AGENT_NAME}」已存在 (id={existing['id']}),将更新配置") + agent_id = existing["id"] + else: + # 先创建一个临时工作流获取 agent_id + temp_wf = _build_workflow(agent_id="") + body = { + "name": AGENT_NAME, + "description": f"AgentRuntime KG+RAG 学习助手(参考苏瑶3号)。学科:{SUBJECT},难度:{LEVEL}。", + "workflow_config": temp_wf, + } + r = requests.post(f"{BASE}/api/v1/agents", headers=h, json=body, timeout=60) + if r.status_code != 201: + print(f"创建失败: {r.status_code} {r.text[:500]}", file=sys.stderr) + return 1 + agent_id = r.json()["id"] + print(f"Agent 创建成功: id={agent_id} name={AGENT_NAME}") + + # 用正确的 agent_id 重建工作流 + wf = _build_workflow(agent_id=agent_id) + + description = ( + f"智能学习助手1号 — AgentRuntime KG+RAG 学习助手(参考苏瑶3号架构)。\n" + f"学科:{SUBJECT},难度:{LEVEL}。\n" + f"工作流:开始 → agent(ReAct) → 结束。\n" + f"核心能力:\n" + f"- 知识图谱:实体抽取 + 关系构建 + 混合检索\n" + f"- 向量记忆:语义检索历史对话\n" + f"- 长期记忆:跨会话用户画像 + 学习进度\n" + f"- 自主学习:工具模式学习 + 能力自检\n" + f"配置:{PROVIDER}/{MODEL} temperature={TEMPERATURE} max_iterations={MAX_ITERATIONS}" + ) + + up = requests.put( + f"{BASE}/api/v1/agents/{agent_id}", + headers=h, + json={ + "description": description, + "workflow_config": wf, + }, + timeout=120, + ) + if up.status_code != 200: + print(f"更新失败: {up.status_code} {up.text[:500]}", file=sys.stderr) + return 1 + + print(f"✅ Agent「{AGENT_NAME}」配置完成") + print(f" ID: {agent_id}") + print(f" 类型: agent 节点 (AgentRuntime ReAct)") + print(f" 学科: {SUBJECT}") + print(f" 级别: {LEVEL}") + print(f" 模型: {PROVIDER}/{MODEL} (temperature={TEMPERATURE})") + print(f" 最大步数: {MAX_ITERATIONS}") + print(f" 工具 ({len(LEARNING_TOOLS)}): {', '.join(LEARNING_TOOLS)}") + print(f" 记忆: 向量(Top-8) + 长期 + 自主学习 已启用") + print(f" 知识图谱: 混合检索 + 实体抽取 + 关系构建 已启用") + print(f" 自检: self_review 已启用") + print() + print("与「智能学习助手(KG+RAG)」的区别:") + print(" - 使用 agent 节点(AgentRuntime ReAct 循环)代替 llm 节点") + print(" - 参考苏瑶3号架构,完整记忆配置") + print(" - 独立 memory_scope_id,不与其他 Agent 串记忆") + print(" - 更高的 temperature (0.85) 和自主学习能力") + print() + print(json.dumps({"id": agent_id, "name": AGENT_NAME}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/backend/scripts/seed_prompt_templates.py b/backend/scripts/seed_prompt_templates.py index 57fbdaf..ec65472 100644 --- a/backend/scripts/seed_prompt_templates.py +++ b/backend/scripts/seed_prompt_templates.py @@ -159,33 +159,43 @@ SEED_TEMPLATES = [ # ── 教育 ── { "name": "学习助手", - "description": "多功能学习助手:作业管理、知识问答、笔记整理、学习计划", + "description": "智能学习助手(KG+RAG):知识图谱构建、向量检索、实体抽取、学习路径推荐、永久记忆", "category": "education", - "tags": ["学习", "作业", "笔记", "计划"], + "tags": ["学习", "知识图谱", "RAG", "向量检索", "记忆"], "prompt": ( - "# 角色:智能学习助手\n\n" - "你是专为学生设计的多功能AI学习助手,帮助高效管理学习任务、巩固知识。\n\n" - "## 核心能力\n\n" - "### 1. 作业管理\n" - "- 协助创建、分类、优先级排序作业任务\n" - "- 根据截止日期生成倒计时提醒\n" - "- 将大型作业拆分为可执行的小步骤\n\n" - "### 2. 学习辅助\n" - "- 知识问答:基于{{subjects}}等内容提供精准解答并附带推理过程\n" - "- 错题本:输入错题后自动分类并生成同类练习题\n" - "- 笔记整理:将混乱笔记整理为结构化摘要(概念→公式→例题)\n" - "- 记忆卡片:生成 Anki 风格的闪卡,支持间隔重复复习\n\n" - "### 3. 时间与计划\n" - "- 根据学习目标和可用时间生成每日/每周学习计划\n" - "- 分析学习时间分配,提供优化建议\n\n" - "### 4. 激励与反馈\n" - "- 记录学习里程碑,生成鼓励性反馈\n" - "- 自定义考试日期,生成复习冲刺表\n\n" + "# 角色:智能学习助手(知识图谱 + RAG 增强版)\n\n" + "你是专为深度学习场景设计的 AI 学习助手,具备知识图谱构建、向量语义检索和永久记忆能力。\n\n" + "## 核心架构\n\n" + "你的知识系统由三层组成:\n" + "1. **知识图谱**:结构化存储知识点及前置/扩展/包含/示例关系\n" + "2. **向量记忆**:语义检索历史对话,找到最相似的学习内容\n" + "3. **长期记忆**:跨会话保存用户画像、学习进度和薄弱环节\n\n" + "## 工作流程(每次对话)\n\n" + "### 1. 理解与分析\n" + "- 理解用户意图(提问/复习/练习/总结/规划)\n" + "- 用 knowledge_graph_search 检索相关知识实体\n" + "- 有新学习材料时用 knowledge_graph_add 自动提取存储\n\n" + "### 2. 知识检索与融合\n" + "- 结合图谱+向量+历史记忆构建完整上下文\n" + "- 用 entity_search 查找特定概念的前置和扩展\n" + "- 用 learning_path 分析学习依赖,推荐学习顺序\n\n" + "### 3. 生成与交付\n" + "- 核心概念解释(关联图谱实体)\n" + "- 前置知识提醒 + 实例/练习题\n" + "- 扩展阅读建议\n" + "- 用 self_review 自检回答质量\n\n" + "### 4. 巩固与记忆\n" + "- 重要知识点持久化到长期记忆\n" + "- 更新用户画像(掌握程度、薄弱环节)\n\n" + "## 学科范围\n" + "{{subjects}}\n\n" "## 交互规则\n" - "- 任务清单使用 Markdown 列表(- [ ] 未完成 / - [x] 已完成)\n" - "- 知识解答先给答案再附推理过程\n" + "- 使用 Markdown 格式,层次分明\n" + "- 每个回答附「📚 相关知识点」列表\n" + "- 需要时用 task_plan 制定学习计划\n" "- 语气鼓励、耐心,像一位懂教育学的私人导师\n" - "- 不代写考试答案,不鼓励学术不端" + "- 不代写考试答案,不鼓励学术不端\n\n" + "> 完整 KG+RAG 版请使用「智能学习助手 (KG+RAG)」场景模板创建 Agent。" ), "variables": [ {"name": "subjects", "type": "string", "required": False, "description": "学科范围", "default": "数学、物理、化学、历史、语文、英语"}, diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index ddd9795..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,71 +0,0 @@ -services: - frontend: - build: - context: ./frontend - dockerfile: Dockerfile.dev - ports: - - "8038:3000" - volumes: - - ./frontend:/app - - /app/node_modules - environment: - - VITE_API_URL=http://101.43.95.130:8037 - # 注意:Vite环境变量需要在构建时设置,运行时修改需要重启容器 - depends_on: - - backend - networks: - - aiagent-network - - backend: - build: - context: ./backend - dockerfile: Dockerfile.dev - ports: - - "8037:8000" - volumes: - - ./backend:/app - environment: - - DATABASE_URL=mysql+pymysql://root:!Rjb12191@gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com:24936/agent_db?charset=utf8mb4 - - REDIS_URL=redis://redis:6379/0 - - SECRET_KEY=dev-secret-key-change-in-production - - CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000,http://localhost:8038,http://101.43.95.130:8038 - - DEEPSEEK_API_KEY=sk-fdf7cc1c73504e628ec0119b7e11b8cc - - DEEPSEEK_BASE_URL=https://api.deepseek.com - depends_on: - - redis - networks: - - aiagent-network - - celery: - build: - context: ./backend - dockerfile: Dockerfile.dev - command: celery -A app.core.celery_app worker --loglevel=info - volumes: - - ./backend:/app - environment: - - DATABASE_URL=mysql+pymysql://root:!Rjb12191@gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com:24936/agent_db?charset=utf8mb4 - - REDIS_URL=redis://redis:6379/0 - - DEEPSEEK_API_KEY=sk-fdf7cc1c73504e628ec0119b7e11b8cc - - DEEPSEEK_BASE_URL=https://api.deepseek.com - depends_on: - - redis - - backend - networks: - - aiagent-network - - redis: - image: redis:7-alpine - ports: - - "6380:6379" # 主机 6380 映射到容器 6379,避免与宿主机 6379 冲突 - volumes: - - redis_data:/data - networks: - - aiagent-network - -volumes: - redis_data: - -networks: - aiagent-network: - driver: bridge diff --git a/(红头)Agent搭建通用方法指南.md b/docs/agent-guides/(红头)Agent搭建通用方法指南.md similarity index 100% rename from (红头)Agent搭建通用方法指南.md rename to docs/agent-guides/(红头)Agent搭建通用方法指南.md diff --git a/Agent使用说明.md b/docs/agent-guides/Agent使用说明.md similarity index 100% rename from Agent使用说明.md rename to docs/agent-guides/Agent使用说明.md diff --git a/Agent管理功能使用说明.md b/docs/agent-guides/Agent管理功能使用说明.md similarity index 100% rename from Agent管理功能使用说明.md rename to docs/agent-guides/Agent管理功能使用说明.md diff --git a/使用指南.md b/docs/agent-guides/使用指南.md similarity index 100% rename from 使用指南.md rename to docs/agent-guides/使用指南.md diff --git a/使用文档.md b/docs/agent-guides/使用文档.md similarity index 100% rename from 使用文档.md rename to docs/agent-guides/使用文档.md diff --git a/创建Agent经验.md b/docs/agent-guides/创建Agent经验.md similarity index 100% rename from 创建Agent经验.md rename to docs/agent-guides/创建Agent经验.md diff --git a/创建Agent经验总结.md b/docs/agent-guides/创建Agent经验总结.md similarity index 100% rename from 创建Agent经验总结.md rename to docs/agent-guides/创建Agent经验总结.md diff --git a/创建agent.md b/docs/agent-guides/创建agent.md similarity index 100% rename from 创建agent.md rename to docs/agent-guides/创建agent.md diff --git a/(红头)项目初始文档.md b/docs/architecture/(红头)项目初始文档.md similarity index 100% rename from (红头)项目初始文档.md rename to docs/architecture/(红头)项目初始文档.md diff --git a/(红头)项目核心文档汇总.md b/docs/architecture/(红头)项目核心文档汇总.md similarity index 100% rename from (红头)项目核心文档汇总.md rename to docs/architecture/(红头)项目核心文档汇总.md diff --git a/AI agent改造计划.md b/docs/architecture/AI agent改造计划.md similarity index 100% rename from AI agent改造计划.md rename to docs/architecture/AI agent改造计划.md diff --git a/aiagent项目架构文档.md b/docs/architecture/aiagent项目架构文档.md similarity index 100% rename from aiagent项目架构文档.md rename to docs/architecture/aiagent项目架构文档.md diff --git a/可执行的 90 天演进路线图.md b/docs/architecture/可执行的 90 天演进路线图.md similarity index 100% rename from 可执行的 90 天演进路线图.md rename to docs/architecture/可执行的 90 天演进路线图.md diff --git a/平台待完善功能清单.md b/docs/architecture/平台待完善功能清单.md similarity index 100% rename from 平台待完善功能清单.md rename to docs/architecture/平台待完善功能清单.md diff --git a/开发进度.md b/docs/architecture/开发进度.md similarity index 100% rename from 开发进度.md rename to docs/architecture/开发进度.md diff --git a/方案-优化版.md b/docs/architecture/方案-优化版.md similarity index 100% rename from 方案-优化版.md rename to docs/architecture/方案-优化版.md diff --git a/缺失能力.md b/docs/architecture/缺失能力.md similarity index 100% rename from 缺失能力.md rename to docs/architecture/缺失能力.md diff --git a/自主AI Agent改造完成情况.md b/docs/architecture/自主AI Agent改造完成情况.md similarity index 100% rename from 自主AI Agent改造完成情况.md rename to docs/architecture/自主AI Agent改造完成情况.md diff --git a/解决缺失能力计划.md b/docs/architecture/解决缺失能力计划.md similarity index 100% rename from 解决缺失能力计划.md rename to docs/architecture/解决缺失能力计划.md diff --git a/项目完成情况分析.md b/docs/architecture/项目完成情况分析.md similarity index 100% rename from 项目完成情况分析.md rename to docs/architecture/项目完成情况分析.md diff --git a/401错误解决.md b/docs/bugfixes/401错误解决.md similarity index 100% rename from 401错误解决.md rename to docs/bugfixes/401错误解决.md diff --git a/CORS问题解决.md b/docs/bugfixes/CORS问题解决.md similarity index 100% rename from CORS问题解决.md rename to docs/bugfixes/CORS问题解决.md diff --git a/保存失败问题解决.md b/docs/bugfixes/保存失败问题解决.md similarity index 100% rename from 保存失败问题解决.md rename to docs/bugfixes/保存失败问题解决.md diff --git a/修复说明.md b/docs/bugfixes/修复说明.md similarity index 100% rename from 修复说明.md rename to docs/bugfixes/修复说明.md diff --git a/最终修复说明.md b/docs/bugfixes/最终修复说明.md similarity index 100% rename from 最终修复说明.md rename to docs/bugfixes/最终修复说明.md diff --git a/错误处理优化说明.md b/docs/bugfixes/错误处理优化说明.md similarity index 100% rename from 错误处理优化说明.md rename to docs/bugfixes/错误处理优化说明.md diff --git a/问题解决.md b/docs/bugfixes/问题解决.md similarity index 100% rename from 问题解决.md rename to docs/bugfixes/问题解决.md diff --git a/agent记忆实现方案.md b/docs/chat-assistant/agent记忆实现方案.md similarity index 100% rename from agent记忆实现方案.md rename to docs/chat-assistant/agent记忆实现方案.md diff --git a/智能体聊天助手性能优化方案.md b/docs/chat-assistant/智能体聊天助手性能优化方案.md similarity index 100% rename from 智能体聊天助手性能优化方案.md rename to docs/chat-assistant/智能体聊天助手性能优化方案.md diff --git a/智能体聊天助手记忆存储说明.md b/docs/chat-assistant/智能体聊天助手记忆存储说明.md similarity index 100% rename from 智能体聊天助手记忆存储说明.md rename to docs/chat-assistant/智能体聊天助手记忆存储说明.md diff --git a/智能体聊天助手记忆问题修复.md b/docs/chat-assistant/智能体聊天助手记忆问题修复.md similarity index 100% rename from 智能体聊天助手记忆问题修复.md rename to docs/chat-assistant/智能体聊天助手记忆问题修复.md diff --git a/智能聊天助手性能优化实施报告.md b/docs/chat-assistant/智能聊天助手性能优化实施报告.md similarity index 100% rename from 智能聊天助手性能优化实施报告.md rename to docs/chat-assistant/智能聊天助手性能优化实施报告.md diff --git a/智能需求分析与解决方案生成器_使用说明.md b/docs/chat-assistant/智能需求分析与解决方案生成器_使用说明.md similarity index 100% rename from 智能需求分析与解决方案生成器_使用说明.md rename to docs/chat-assistant/智能需求分析与解决方案生成器_使用说明.md diff --git a/聊天智能体示例.json b/docs/chat-assistant/聊天智能体示例.json similarity index 100% rename from 聊天智能体示例.json rename to docs/chat-assistant/聊天智能体示例.json diff --git a/聊天智能体示例说明.md b/docs/chat-assistant/聊天智能体示例说明.md similarity index 100% rename from 聊天智能体示例说明.md rename to docs/chat-assistant/聊天智能体示例说明.md diff --git a/Python_vs_JavaScript_三大核心特性对比分析报告.md b/docs/comparisons/Python_vs_JavaScript_三大核心特性对比分析报告.md similarity index 100% rename from Python_vs_JavaScript_三大核心特性对比分析报告.md rename to docs/comparisons/Python_vs_JavaScript_三大核心特性对比分析报告.md diff --git a/Python与JavaScript三大核心特性综合对比报告.md b/docs/comparisons/Python与JavaScript三大核心特性综合对比报告.md similarity index 100% rename from Python与JavaScript三大核心特性综合对比报告.md rename to docs/comparisons/Python与JavaScript三大核心特性综合对比报告.md diff --git a/工具链对比报告.md b/docs/comparisons/工具链对比报告.md similarity index 100% rename from 工具链对比报告.md rename to docs/comparisons/工具链对比报告.md diff --git a/WebSocket实时推送说明.md b/docs/features/WebSocket实时推送说明.md similarity index 100% rename from WebSocket实时推送说明.md rename to docs/features/WebSocket实时推送说明.md diff --git a/内置工具列表.md b/docs/features/内置工具列表.md similarity index 100% rename from 内置工具列表.md rename to docs/features/内置工具列表.md diff --git a/前端功能完成说明.md b/docs/features/前端功能完成说明.md similarity index 100% rename from 前端功能完成说明.md rename to docs/features/前端功能完成说明.md diff --git a/功能完成总结.md b/docs/features/功能完成总结.md similarity index 100% rename from 功能完成总结.md rename to docs/features/功能完成总结.md diff --git a/可新增节点类型建议.md b/docs/features/可新增节点类型建议.md similarity index 100% rename from 可新增节点类型建议.md rename to docs/features/可新增节点类型建议.md diff --git a/工作流模板和导入导出功能说明.md b/docs/features/工作流模板和导入导出功能说明.md similarity index 100% rename from 工作流模板和导入导出功能说明.md rename to docs/features/工作流模板和导入导出功能说明.md diff --git a/工作流验证功能说明.md b/docs/features/工作流验证功能说明.md similarity index 100% rename from 工作流验证功能说明.md rename to docs/features/工作流验证功能说明.md diff --git a/数据转换节点功能说明.md b/docs/features/数据转换节点功能说明.md similarity index 100% rename from 数据转换节点功能说明.md rename to docs/features/数据转换节点功能说明.md diff --git a/条件节点功能说明.md b/docs/features/条件节点功能说明.md similarity index 100% rename from 条件节点功能说明.md rename to docs/features/条件节点功能说明.md diff --git a/节点测试功能说明.md b/docs/features/节点测试功能说明.md similarity index 100% rename from 节点测试功能说明.md rename to docs/features/节点测试功能说明.md diff --git a/节点连接使用说明.md b/docs/features/节点连接使用说明.md similarity index 100% rename from 节点连接使用说明.md rename to docs/features/节点连接使用说明.md diff --git a/节点配置页面增强功能完成情况.md b/docs/features/节点配置页面增强功能完成情况.md similarity index 100% rename from 节点配置页面增强功能完成情况.md rename to docs/features/节点配置页面增强功能完成情况.md diff --git a/节点配置页面增强方案-完成情况.md b/docs/features/节点配置页面增强方案-完成情况.md similarity index 100% rename from 节点配置页面增强方案-完成情况.md rename to docs/features/节点配置页面增强方案-完成情况.md diff --git a/节点配置页面增强方案.md b/docs/features/节点配置页面增强方案.md similarity index 100% rename from 节点配置页面增强方案.md rename to docs/features/节点配置页面增强方案.md diff --git a/(红头)上传图片和识别的实现文档.md b/docs/image-ocr/(红头)上传图片和识别的实现文档.md similarity index 100% rename from (红头)上传图片和识别的实现文档.md rename to docs/image-ocr/(红头)上传图片和识别的实现文档.md diff --git a/ADB工具和Android日志Agent搭建总结.md b/docs/integrations/ADB工具和Android日志Agent搭建总结.md similarity index 100% rename from ADB工具和Android日志Agent搭建总结.md rename to docs/integrations/ADB工具和Android日志Agent搭建总结.md diff --git a/ADB工具验证方法.md b/docs/integrations/ADB工具验证方法.md similarity index 100% rename from ADB工具验证方法.md rename to docs/integrations/ADB工具验证方法.md diff --git a/Android日志Agent问题分析.md b/docs/integrations/Android日志Agent问题分析.md similarity index 100% rename from Android日志Agent问题分析.md rename to docs/integrations/Android日志Agent问题分析.md diff --git a/Android日志获取助手使用指南.md b/docs/integrations/Android日志获取助手使用指南.md similarity index 100% rename from Android日志获取助手使用指南.md rename to docs/integrations/Android日志获取助手使用指南.md diff --git a/DeepSeek配置完成.md b/docs/integrations/DeepSeek配置完成.md similarity index 100% rename from DeepSeek配置完成.md rename to docs/integrations/DeepSeek配置完成.md diff --git a/DeepSeek集成说明.md b/docs/integrations/DeepSeek集成说明.md similarity index 100% rename from DeepSeek集成说明.md rename to docs/integrations/DeepSeek集成说明.md diff --git a/OpenAI集成说明.md b/docs/integrations/OpenAI集成说明.md similarity index 100% rename from OpenAI集成说明.md rename to docs/integrations/OpenAI集成说明.md diff --git a/数据库查询工具实现总结.md b/docs/integrations/数据库查询工具实现总结.md similarity index 100% rename from 数据库查询工具实现总结.md rename to docs/integrations/数据库查询工具实现总结.md diff --git a/数据库迁移说明-模板市场.md b/docs/integrations/数据库迁移说明-模板市场.md similarity index 100% rename from 数据库迁移说明-模板市场.md rename to docs/integrations/数据库迁移说明-模板市场.md diff --git a/(红头)Windows服务器启动与重启唯一指南.md b/docs/startup-deploy/(红头)Windows服务器启动与重启唯一指南.md similarity index 100% rename from (红头)Windows服务器启动与重启唯一指南.md rename to docs/startup-deploy/(红头)Windows服务器启动与重启唯一指南.md diff --git a/(红头)前后端服务器启动和停止.md b/docs/startup-deploy/(红头)前后端服务器启动和停止.md similarity index 100% rename from (红头)前后端服务器启动和停止.md rename to docs/startup-deploy/(红头)前后端服务器启动和停止.md diff --git a/(红头)服务器启动的注意事项.md b/docs/startup-deploy/(红头)服务器启动的注意事项.md similarity index 100% rename from (红头)服务器启动的注意事项.md rename to docs/startup-deploy/(红头)服务器启动的注意事项.md diff --git a/Windows启动指南.md b/docs/startup-deploy/Windows启动指南.md similarity index 100% rename from Windows启动指南.md rename to docs/startup-deploy/Windows启动指南.md diff --git a/install_autostart.ps1 b/docs/startup-deploy/install_autostart.ps1 similarity index 100% rename from install_autostart.ps1 rename to docs/startup-deploy/install_autostart.ps1 diff --git a/windows启动和停止用法.md b/docs/startup-deploy/windows启动和停止用法.md similarity index 100% rename from windows启动和停止用法.md rename to docs/startup-deploy/windows启动和停止用法.md diff --git a/docs/startup-deploy/上传git仓.md b/docs/startup-deploy/上传git仓.md new file mode 100644 index 0000000..a2da3a8 --- /dev/null +++ b/docs/startup-deploy/上传git仓.md @@ -0,0 +1,10 @@ +将修改上传到 git 仓 **rjb_win_dev** 分支: + +http://101.43.95.130:3001/admin/aiagent.git + +```powershell +cd D:\aaa\aiagent +git push -u origin rjb_win_dev +``` + +鉴权请使用本机已保存的凭据,或在提示时输入平台账号;**勿在文档中保存明文密码**,也勿将含密码的文件提交到仓库。 diff --git a/启动说明(红头).md b/docs/startup-deploy/启动说明(红头).md similarity index 100% rename from 启动说明(红头).md rename to docs/startup-deploy/启动说明(红头).md diff --git a/数据库初始化说明.md b/docs/startup-deploy/数据库初始化说明.md similarity index 100% rename from 数据库初始化说明.md rename to docs/startup-deploy/数据库初始化说明.md diff --git a/数据库及端口.md b/docs/startup-deploy/数据库及端口.md similarity index 100% rename from 数据库及端口.md rename to docs/startup-deploy/数据库及端口.md diff --git a/防火墙配置说明.md b/docs/startup-deploy/防火墙配置说明.md similarity index 100% rename from 防火墙配置说明.md rename to docs/startup-deploy/防火墙配置说明.md diff --git a/(红头)agent测试用例文档.md b/docs/tests/(红头)agent测试用例文档.md similarity index 100% rename from (红头)agent测试用例文档.md rename to docs/tests/(红头)agent测试用例文档.md diff --git a/(红头)工作流调用测试总结.txt b/docs/tests/(红头)工作流调用测试总结.txt similarity index 100% rename from (红头)工作流调用测试总结.txt rename to docs/tests/(红头)工作流调用测试总结.txt diff --git a/Android日志Agent测试报告.md b/docs/tests/Android日志Agent测试报告.md similarity index 100% rename from Android日志Agent测试报告.md rename to docs/tests/Android日志Agent测试报告.md diff --git a/DeepSeek测试报告.md b/docs/tests/DeepSeek测试报告.md similarity index 100% rename from DeepSeek测试报告.md rename to docs/tests/DeepSeek测试报告.md diff --git a/agent_test_cases.example.json b/docs/tests/agent_test_cases.example.json similarity index 100% rename from agent_test_cases.example.json rename to docs/tests/agent_test_cases.example.json diff --git a/test_node_input_output.md b/docs/tests/test_node_input_output.md similarity index 100% rename from test_node_input_output.md rename to docs/tests/test_node_input_output.md diff --git a/前端测试邮件和消息队列节点.md b/docs/tests/前端测试邮件和消息队列节点.md similarity index 100% rename from 前端测试邮件和消息队列节点.md rename to docs/tests/前端测试邮件和消息队列节点.md diff --git a/前端界面测试指南-邮件和消息队列节点.md b/docs/tests/前端界面测试指南-邮件和消息队列节点.md similarity index 100% rename from 前端界面测试指南-邮件和消息队列节点.md rename to docs/tests/前端界面测试指南-邮件和消息队列节点.md diff --git a/执行功能测试报告.md b/docs/tests/执行功能测试报告.md similarity index 100% rename from 执行功能测试报告.md rename to docs/tests/执行功能测试报告.md diff --git a/条件节点测试报告.md b/docs/tests/条件节点测试报告.md similarity index 100% rename from 条件节点测试报告.md rename to docs/tests/条件节点测试报告.md diff --git a/测试指南-完整版.md b/docs/tests/测试指南-完整版.md similarity index 100% rename from 测试指南-完整版.md rename to docs/tests/测试指南-完整版.md diff --git a/测试指南.md b/docs/tests/测试指南.md similarity index 100% rename from 测试指南.md rename to docs/tests/测试指南.md diff --git a/测试方案总结.md b/docs/tests/测试方案总结.md similarity index 100% rename from 测试方案总结.md rename to docs/tests/测试方案总结.md diff --git a/测试连接.md b/docs/tests/测试连接.md similarity index 100% rename from 测试连接.md rename to docs/tests/测试连接.md diff --git a/知识库问答助手测试总结.md b/docs/tests/知识库问答助手测试总结.md similarity index 100% rename from 知识库问答助手测试总结.md rename to docs/tests/知识库问答助手测试总结.md diff --git a/节点测试按钮无法点击问题排查.md b/docs/tests/节点测试按钮无法点击问题排查.md similarity index 100% rename from 节点测试按钮无法点击问题排查.md rename to docs/tests/节点测试按钮无法点击问题排查.md diff --git a/邮件和消息队列节点测试报告.md b/docs/tests/邮件和消息队列节点测试报告.md similarity index 100% rename from 邮件和消息队列节点测试报告.md rename to docs/tests/邮件和消息队列节点测试报告.md diff --git a/邮件和消息队列节点测试指南.md b/docs/tests/邮件和消息队列节点测试指南.md similarity index 100% rename from 邮件和消息队列节点测试指南.md rename to docs/tests/邮件和消息队列节点测试指南.md diff --git a/工具调用功能完成总结.md b/docs/tool-calling/工具调用功能完成总结.md similarity index 100% rename from 工具调用功能完成总结.md rename to docs/tool-calling/工具调用功能完成总结.md diff --git a/工具调用可视化功能测试报告.md b/docs/tool-calling/工具调用可视化功能测试报告.md similarity index 100% rename from 工具调用可视化功能测试报告.md rename to docs/tool-calling/工具调用可视化功能测试报告.md diff --git a/工具调用可视化实现总结.md b/docs/tool-calling/工具调用可视化实现总结.md similarity index 100% rename from 工具调用可视化实现总结.md rename to docs/tool-calling/工具调用可视化实现总结.md diff --git a/工具调用实施总结.md b/docs/tool-calling/工具调用实施总结.md similarity index 100% rename from 工具调用实施总结.md rename to docs/tool-calling/工具调用实施总结.md diff --git a/工具调用实现方案.md b/docs/tool-calling/工具调用实现方案.md similarity index 100% rename from 工具调用实现方案.md rename to docs/tool-calling/工具调用实现方案.md diff --git a/知你客服14号能力文档.md b/docs/zhini-kefu/知你客服14号能力文档.md similarity index 100% rename from 知你客服14号能力文档.md rename to docs/zhini-kefu/知你客服14号能力文档.md diff --git a/知你客服15号_循环工作流设计.md b/docs/zhini-kefu/知你客服15号_循环工作流设计.md similarity index 100% rename from 知你客服15号_循环工作流设计.md rename to docs/zhini-kefu/知你客服15号_循环工作流设计.md diff --git a/知你客服15号能力文档.md b/docs/zhini-kefu/知你客服15号能力文档.md similarity index 100% rename from 知你客服15号能力文档.md rename to docs/zhini-kefu/知你客服15号能力文档.md diff --git a/知你客服16号能力文档.md b/docs/zhini-kefu/知你客服16号能力文档.md similarity index 100% rename from 知你客服16号能力文档.md rename to docs/zhini-kefu/知你客服16号能力文档.md diff --git a/知你客服17号能力文档.md b/docs/zhini-kefu/知你客服17号能力文档.md similarity index 100% rename from 知你客服17号能力文档.md rename to docs/zhini-kefu/知你客服17号能力文档.md diff --git a/知你客服Agent智能聊天App接入方案.md b/docs/zhini-kefu/知你客服Agent智能聊天App接入方案.md similarity index 100% rename from 知你客服Agent智能聊天App接入方案.md rename to docs/zhini-kefu/知你客服Agent智能聊天App接入方案.md diff --git a/知你客服能力的集成和扩展方案.md b/docs/zhini-kefu/知你客服能力的集成和扩展方案.md similarity index 100% rename from 知你客服能力的集成和扩展方案.md rename to docs/zhini-kefu/知你客服能力的集成和扩展方案.md diff --git a/docs/产品经理/产品经理角色文档.md b/docs/产品经理/产品经理角色文档.md new file mode 100644 index 0000000..d7a645d --- /dev/null +++ b/docs/产品经理/产品经理角色文档.md @@ -0,0 +1,104 @@ +# 产品经理 + +> 所属公司:瑞来兹软件技术有限公司 +> 更新日期:2026-05-04 + +--- + +## 一、角色定义 + +产品经理(Product Manager)是产品的所有者,负责从需求发现到产品上线的全生命周期管理,在用户价值与商业价值之间寻找最优解。 + +--- + +## 二、主要职责 + +### 2.1 需求管理 +- 收集并分析用户反馈、业务需求、市场趋势 +- 撰写 BRD(商业需求文档)和 PRD(产品需求文档) +- 维护产品 Backlog,持续进行需求优先级排序 +- 组织需求评审会,协同技术、设计、测试对齐需求 + +### 2.2 产品规划 +- 制定产品路线图(Roadmap),明确版本迭代节奏 +- 定义产品核心指标(KPI/OKR),跟踪产品数据表现 +- 竞品分析与市场调研,输出竞品分析报告 +- 参与公司战略规划,对齐产品方向与业务目标 + +### 2.3 功能设计 +- 输出产品原型(低保真/高保真)、交互流程图 +- 编写用户故事(User Story)和验收标准(AC) +- 与 UX/UI 设计师协作完成界面设计 +- 定义功能边界、异常状态、权限模型 + +### 2.4 项目推进 +- 参与 Sprint Planning、每日站会、Sprint Review +- 在开发过程中澄清需求细节,及时调整方案 +- 组织 UAT(用户验收测试),确认上线条件 +- 版本发布后撰写 Release Notes,组织功能宣讲 + +### 2.5 数据分析与迭代 +- 上线后跟踪产品数据(DAU、留存率、转化率等) +- 通过 A/B 测试验证功能假设 +- 收集用户反馈,驱动下一轮产品迭代 +- 定期输出产品月报/季报 + +--- + +## 三、常用平台与工具 + +| 分类 | 工具 | 用途 | +|------|------|------| +| **原型设计** | Axure RP | 高保真原型、复杂交互 | +| | Figma | UI 协作设计、原型演示 | +| | 墨刀(Mockplus) | 快速原型、团队协作 | +| | Sketch | Mac 端 UI 设计 | +| **项目管理** | Jira | 需求跟踪、Sprint 管理 | +| | 禅道 | 国产项目管理、Bug 跟踪 | +| | Trello | 轻量看板管理 | +| | Notion | 文档协作、知识库 | +| | Confluence | 需求文档 Wiki | +| | PingCode | 国产研发管理平台 | +| **文档协作** | 飞书文档 | 内部协作与文档 | +| | 语雀 | 知识库与文档 | +| | 石墨文档 | 在线协同编辑 | +| | Google Docs | 国际团队协作 | +| **数据分析** | 神策数据 | 用户行为分析 | +| | GrowingIO | 无埋点数据分析 | +| | Google Analytics | Web 流量分析 | +| | 友盟+ | 移动端数据统计 | +| | Metabase / Superset | BI 自助分析 | +| **沟通协作** | 飞书 / 钉钉 / 企业微信 | 即时通讯 | +| | Slack | 国际团队通讯 | +| | Zoom / 腾讯会议 | 视频会议 | +| **思维导图** | XMind | 需求梳理、功能拆解 | +| | ProcessOn | 流程图、脑图在线 | +| | Draw.io | 架构图、流程图 | + +--- + +## 四、能力模型 + +| 能力 | 要求 | +|------|------| +| 需求洞察 | 能从模糊业务诉求中提炼可执行需求 | +| 逻辑思维 | 严谨的功能边界与异常流梳理 | +| 数据分析 | SQL 查询、指标体系搭建、A/B 实验设计 | +| 沟通协调 | 跨部门(技术/设计/运营/销售)高效沟通 | +| 技术理解 | 了解前后端基础、API 概念、数据库基础 | +| 商业思维 | 理解商业模式、ROI 评估、定价策略 | + +--- + +## 五、产出物清单 + +| 产出物 | 交付节点 | +|--------|----------| +| 竞品分析报告 | 产品立项前 | +| BRD(商业需求文档) | 立项阶段 | +| PRD(产品需求文档) | 需求评审前 | +| 产品原型 | 需求评审前 | +| 用户故事地图 | Sprint 规划前 | +| 产品路线图 | 季度/年度规划 | +| 产品数据月报 | 每月 | +| Release Notes | 每个版本发布 | diff --git a/docs/使用平台创建工单的方法.md b/docs/使用平台创建工单的方法.md new file mode 100644 index 0000000..4fdd5c4 --- /dev/null +++ b/docs/使用平台创建工单的方法.md @@ -0,0 +1,228 @@ +# 使用平台创建工单的方法 + +> 平台:Gitea 1.25.2 · 仓库:admin/aiagent · 地址:http://101.43.95.130:3001 + +--- + +## 前置准备 + +### 账号信息 + +| 项 | 值 | +|----|-----| +| 地址 | http://101.43.95.130:3001 | +| 用户名 | admin | +| 密码 | 123456 | +| 仓库 | admin/aiagent | +| 分支 | rjb_win_dev | + +### 获取 API Token + +```bash +curl -s -X POST "http://101.43.95.130:3001/api/v1/users/admin/tokens" \ + -u "admin:123456" \ + -H "Content-Type: application/json" \ + -d '{"name":"claude-code-api","scopes":["write:issue","write:repository","read:repository","read:user"]}' +``` + +返回示例: + +```json +{ + "id": 3, + "name": "claude-code-api", + "sha1": "fbc9ee7f96635793f4844187eac5c0e573480721", + "token_last_eight": "73480721", + "scopes": ["write:issue", "write:repository", "read:user"] +} +``` + +**Token**:`fbc9ee7f96635793f4844187eac5c0e573480721` + +### Token 管理 + +- 查看已有 Token:登录 Gitea → 头像 → 设置 → 应用 → 管理 Access Token +- 权限最小化原则:创建工单只需要 `write:issue` + `read:repository` + +--- + +## 工单 CRUD + +### 1. 创建工单 + +**端点**:`POST /api/v1/repos/{owner}/{repo}/issues` + +**JSON 模板**: + +```json +{ + "title": "[类别] 标题", + "body": "## 背景\n\n...\n\n## 需求\n\n1. ...\n2. ...\n\n## 涉及模块\n\n- 文件路径\n\n## 优先级\n\n高/中/低", + "assignee": "admin", + "milestone": null +} +``` + +**curl 命令**: + +```bash +TOKEN="fbc9ee7f96635793f4844187eac5c0e573480721" + +# 方式一:直接传 JSON(仅英文/简单内容) +curl -s -X POST "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"title":"测试工单","body":"内容","assignee":"admin"}' + +# 方式二:从文件读取(推荐,支持中文/长文本) +curl -s -X POST "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + --data-binary @/tmp/issue.json +``` + +**返回值**: + +```json +{ + "id": 19, + "url": "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues/1", + "html_url": "http://101.43.95.130:3001/admin/aiagent/issues/1", + "number": 1, + "title": "...", + "state": "open", + "assignee": { "login": "admin" }, + "created_at": "2026-05-04T22:44:20+08:00" +} +``` + +### 2. 查询工单 + +```bash +# 查看所有开放工单 +curl -s "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues?state=open" \ + -H "Authorization: token $TOKEN" + +# 查看单个工单 +curl -s "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues/1" \ + -H "Authorization: token $TOKEN" +``` + +### 3. 修改工单 + +```bash +# 修改标题、内容、指派人 +curl -s -X PATCH "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues/1" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"title":"新标题","body":"新内容","assignee":"admin"}' +``` + +### 4. 关闭工单 + +```bash +curl -s -X PATCH "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues/1" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"state":"closed"}' +``` + +### 5. 添加评论 + +```bash +curl -s -X POST "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues/1/comments" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"body":"已修复,请验证。"}' +``` + +--- + +## 工单标题规范 + +建议使用 `[类别]` 前缀区分工单类型: + +| 前缀 | 用途 | 示例 | +|------|------|------| +| `[Bug]` | 缺陷修复 | `[Bug] SSE 流式降级导致重复空消息` | +| `[Phase N]` | 按阶段规划 | `[Phase 4] 降级/回退链` | +| `[监控]` | 监控告警 | `[监控] 系统监控面板` | +| `[DevOps]` | 运维基础设施 | `[DevOps] Docker 生产环境配置` | +| `[UX]` | 用户体验 | `[UX] 工作流画布自动布局` | +| `[高级]` | 差异化功能 | `[高级] 插件系统` | +| `[质量]` | 测试与安全 | `[质量] 单元测试覆盖率提升` | +| `[Agent]` | Agent 能力 | `[Agent] 多模态 Agent` | + +--- + +## 踩坑记录 + +### 坑1:中文 JSON 编码异常 + +**现象**:curl 的 `-d` 参数直接写中文 JSON 返回 `json: slice unexpected end of JSON input` + +**原因**:shell 对中文和特殊字符的处理不一致 + +**解决**:改用 `--data-binary @文件路径`,将 JSON 先写入文件 + +```bash +# 错误写法 +curl -d '{"title":"中文标题",...}' ... + +# 正确写法 +echo '{"title":"中文标题",...}' > /tmp/issue.json +curl --data-binary @/tmp/issue.json ... +``` + +### 坑2:labels 参数格式 + +Gitea 的 labels 必须传数字 ID(如 `[1, 2]`),不能传字符串名(如 `["bug"]`)。如果不确定 label ID,先查或不传 labels。 + +```bash +# 先查有哪些 labels +curl -s "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/labels" \ + -H "Authorization: token $TOKEN" +``` + +### 坑3:body 中的特殊字符 + +JSON body 中不要使用: +- 中文双引号 `""` → 改用书名号 `「」` 或省略 +- 反引号 `` ` `` → JSON 中需要转义为 `\`` +- 未转义的换行 → JSON 中换行用 `\n` + +--- + +## 批量创建脚本 + +```bash +#!/bin/bash +TOKEN="你的token" +REPO_URL="http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues" + +# 准备多个 JSON 文件 +# /tmp/issues/01.json, /tmp/issues/02.json, ... + +for f in /tmp/issues/*.json; do + num=$(basename "$f" .json) + result=$(curl -s -X POST "$REPO_URL" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + --data-binary @"$f" | grep -o '"number":[0-9]*') + echo " $num → $result" +done +``` + +--- + +## API 参考 + +| 操作 | 方法 | 路径 | +|------|------|------| +| 创建 | POST | `/api/v1/repos/{owner}/{repo}/issues` | +| 列表 | GET | `/api/v1/repos/{owner}/{repo}/issues?state=open` | +| 详情 | GET | `/api/v1/repos/{owner}/{repo}/issues/{id}` | +| 修改 | PATCH | `/api/v1/repos/{owner}/{repo}/issues/{id}` | +| 评论 | POST | `/api/v1/repos/{owner}/{repo}/issues/{id}/comments` | + +完整 API 文档:[http://101.43.95.130:3001/api/swagger](http://101.43.95.130:3001/api/swagger) diff --git a/docs/创建工单总结.md b/docs/创建工单总结.md new file mode 100644 index 0000000..4af2eaa --- /dev/null +++ b/docs/创建工单总结.md @@ -0,0 +1,159 @@ +# 创建工单总结 + +## 整体流程 + +``` +测试页面 → 阅读源码 → 后端API验证 → 前端代码审查 → 定位Bug → 写入Gitea平台 +``` + +--- + +## 1. 测试目标 + +浏览器访问 `http://localhost:3001/agent-chat`,测试其中的 Agent 助手对话功能。 + +## 2. 源码阅读 + +读取出错的关键文件: + +| 文件 | 作用 | +|------|------| +| `src/views/AgentChat.vue` | Agent 对话页面主组件 | +| `src/components/AgentChatPreview.vue` | Agent 预览对话组件 | +| `src/api/index.ts` | 前端 API 封装 (axios) | +| `src/stores/agent.ts` | Agent 状态管理 (Pinia) | +| `src/router/index.ts` | 路由配置 | +| `backend/app/api/agent_chat.py` | 后端对话 API | + +## 3. 后端 API 验证 + +用 curl 逐一测试 7 个 API 端点: + +```bash +# 登录获取 token(后端使用 form-encoded,非 JSON) +curl -s -X POST http://localhost:8037/api/v1/auth/login \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d 'username=admin&password=123456' + +# 测试 bare 对话 +curl -s -X POST http://localhost:8037/api/v1/agent-chat/bare \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"message":"hello"}' + +# 测试带 Agent ID 的对话 +curl -s -X POST "http://localhost:8037/api/v1/agent-chat/{agent_id}" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"message":"hello"}' + +# 测试 SSE 流式 +curl -s -X POST http://localhost:8037/api/v1/agent-chat/bare/stream \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"message":"hello"}' + +# 测试多 Agent 编排 +curl -s -X POST http://localhost:8037/api/v1/agent-chat/orchestrate \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"message":"hello","mode":"debate","agents":[...]}' +``` + +结果:后端 7 个端点全部正常,问题在前端代码。 + +## 4. 前端代码审查(定位Bug) + +逐行审查 `AgentChat.vue` 中 `sendMessage()` 函数的 SSE 流式 + fallback 逻辑: + +``` +第 432 行 → push 占位消息(content: '') +第 440-504 行 → SSE 流读取事件 +第 506 行 → catch { usedStreaming = false } ← 流失败 +第 513-527 行 → if (!usedStreaming) push fallback 结果 ← 重复! +``` + +定位到 5 个 Bug: + +| # | 问题 | 位置 | +|---|------|------| +| 1 | SSE 流失败后占位消息未删除,fallback POST 又 push 一条,导致重复 | :424-527 | +| 2 | 流正常结束后 `streamTimeout` 未清除 | :411 | +| 3 | `final` 事件与 `finally` 之间 loading 状态闪烁 | :496-537 | +| 4 | 前端 SSE 事件类型硬编码,可能与后端 runtime 不一致 | :467-501 | +| 5 | `retryMessage` 在流式失败场景下占位消息残留 | :571-609 | + +## 5. 创建 Gitea 工单 + +### 获取 Token + +```bash +curl -s -X POST "http://101.43.95.130:3001/api/v1/users/admin/tokens" \ + -u "admin:123456" \ + -H "Content-Type: application/json" \ + -d '{"name":"claude-code-api","scopes":["write:issue","write:repository","read:repository","read:user"]}' +``` + +返回 token:`fbc9ee7f96635793f4844187eac5c0e573480721` + +### 查找仓库 + +```bash +curl -s "http://101.43.95.130:3001/api/v1/repos/admin/aiagent" \ + -H "Authorization: token $TOKEN" +``` + +确认仓库为 `admin/aiagent`。 + +### 批量创建工单 + +JSON 模板: + +```json +{ + "title": "[Bug] 问题标题", + "body": "## 问题描述\n\n...\n\n## 涉及文件\n\n...\n\n## 严重程度\n\n...", + "assignee": "admin" +} +``` + +创建命令: + +```bash +curl -s -X POST "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + --data-binary @/tmp/issue.json +``` + +### 踩坑记录 + +- **中文编码问题**:curl `-d` 参数直接写中文 JSON 会编码异常(`json: slice unexpected end of JSON input`),改用 `--data-binary @文件路径` 方式解决 +- **labels 参数**:Gitea 的 labels 必须传数字 ID 而非字符串名,第一批次去掉 labels 规避 +- **Gitea 版本**:使用 `GET /api/v1/version` 确认版本为 1.25.2,API 兼容 GitHub 风格 + +## 6. 验证与关闭 + +```bash +# 查看工单列表 +curl -s "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues?state=open" \ + -H "Authorization: token $TOKEN" + +# 关闭工单 +curl -s -X PATCH "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues/1" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"state":"closed"}' +``` + +## 总结 + +| 步骤 | 耗时 | 工具 | +|------|------|------| +| 测试页面 + 读源码 | 主要时间 | Read, Grep | +| 后端 API 验证 | 快速 | curl | +| 前端代码审查定位 Bug | 主要时间 | Read (逐行审查) | +| 创建工单 | 快速 | curl + Gitea API | +| 关闭工单 | 快速 | curl + Gitea API | + +核心思路:**先验证后端排除服务端问题,再集中审查前端业务逻辑找到根因。** diff --git a/docs/平台资料.md b/docs/平台资料.md new file mode 100644 index 0000000..eeccdb6 --- /dev/null +++ b/docs/平台资料.md @@ -0,0 +1,117 @@ +# 平台资料 + +## 工单平台(Gitea) + +| 项目 | 值 | +|------|-----| +| 平台 | Gitea 1.25.2 | +| 地址 | http://101.43.95.130:3001 | +| 用户名 | admin | +| 密码 | 123456 | +| 仓库 | admin/aiagent | +| 工单页 | http://101.43.95.130:3001/admin/aiagent/issues | +| 分支 | rjb_win_dev | +| API Token | `fbc9ee7f96635793f4844187eac5c0e573480721` | +| API 文档 | http://101.43.95.130:3001/api/swagger | + +## 项目(aiagent) + +| 项目 | 值 | +|------|-----| +| 名称 | 低代码智能体平台 | +| 前端 | `D:\aaa\aiagent\frontend` (Vue 3 + Vite + Element Plus) | +| 后端 | `D:\aaa\aiagent\backend` (FastAPI + SQLAlchemy + Celery) | +| 前端端口 | 3001 (Vite dev) | +| 后端端口 | 8037 (Uvicorn) | + +## 测试账号 + +| 项目 | 值 | +|------|-----| +| 用户名 | admin | +| 密码 | 123456 | +| 角色 | 管理员 | + +## 前端项目结构 + +``` +frontend/src/ +├── api/index.ts # API 封装 (axios),DEV 走 Vite 代理到 8037 +├── main.ts # 入口 +├── router/index.ts # 路由 (/agent-chat, /agents, /workflow 等) +├── stores/ +│ ├── agent.ts # Agent Store (Pinia) +│ ├── user.ts # 用户 Store +│ └── ... +├── views/ +│ ├── AgentChat.vue # Agent 对话页 (核心) +│ ├── AgentConfig.vue # Agent 配置页 +│ ├── Agents.vue # Agent 列表 +│ └── ... +└── components/ + ├── AgentChatPreview.vue # Agent 预览对话组件 + └── ... +``` + +## 后端项目结构 + +``` +backend/app/ +├── main.py # FastAPI 入口 +├── api/ +│ ├── agent_chat.py # Agent 对话 API +│ ├── agents.py # Agent CRUD API +│ ├── auth.py # 认证 API (login/register) +│ └── ... +├── agent_runtime/ +│ ├── core.py # Agent 运行时核心 +│ ├── context.py # 上下文管理 +│ ├── memory.py # 记忆系统 +│ └── orchestrator.py # 多 Agent 编排 +├── core/ +│ ├── config.py # 配置 +│ ├── database.py # 数据库 (MySQL) +│ └── error_handler.py # 异常处理 +└── models/ # SQLAlchemy 模型 +``` + +## API 端点(agent-chat 相关) + +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/api/v1/agent-chat/bare` | 默认 Agent 对话(无配置) | +| POST | `/api/v1/agent-chat/bare/stream` | 默认 Agent 流式对话(SSE) | +| POST | `/api/v1/agent-chat/{agent_id}` | 指定 Agent 对话 | +| POST | `/api/v1/agent-chat/{agent_id}/stream` | 指定 Agent 流式对话(SSE) | +| POST | `/api/v1/agent-chat/orchestrate` | 多 Agent 编排 | +| GET | `/api/v1/agents` | Agent 列表 | +| GET | `/api/v1/agents/{id}` | Agent 详情 | +| POST | `/api/v1/auth/login` | 登录(form-encoded) | + +## 快速验证命令 + +```bash +# 登录获取 token +curl -s -X POST http://localhost:8037/api/v1/auth/login \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d 'username=admin&password=123456' + +# 测试 bare 对话 +curl -s -X POST http://localhost:8037/api/v1/agent-chat/bare \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"message":"你好"}' + +# 测试流式对话 +curl -s -X POST http://localhost:8037/api/v1/agent-chat/bare/stream \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"message":"你好"}' +``` + +## SSH / Git + +| 项目 | 值 | +|------|-----| +| SSH Clone | `ssh://git@101.43.95.130:222/admin/aiagent.git` | +| HTTP Clone | `http://101.43.95.130:3001/admin/aiagent.git` | diff --git a/docs/开发/开发角色文档.md b/docs/开发/开发角色文档.md new file mode 100644 index 0000000..4ff8348 --- /dev/null +++ b/docs/开发/开发角色文档.md @@ -0,0 +1,153 @@ +# 开发 + +> 所属公司:瑞来兹软件技术有限公司 +> 更新日期:2026-05-04 + +--- + +## 一、角色定义 + +开发工程师(Developer)是产品的建造者,负责将需求和设计转化为可运行的软件系统,涵盖前端、后端、移动端、数据等多个方向。 + +--- + +## 二、主要职责 + +### 2.1 编码实现 +- 根据 PRD 和技术方案完成功能开发 +- 编写高质量、可维护、可测试的代码 +- 遵循编码规范和团队约定的设计模式 +- 实现 API 接口、数据库操作、业务逻辑 +- 前端页面还原、交互实现、状态管理 + +### 2.2 代码质量 +- 编写单元测试(Jest/JUnit/pytest),保证核心逻辑覆盖率 +- 代码自测,确保提测质量 +- 参与 Code Review,互相审查代码质量 +- 使用 SonarQube / ESLint 等工具确保代码规范 +- 重构遗留代码,消除技术债务 + +### 2.3 技术方案 +- 参与技术方案评审,评估实现可行性 +- 编写模块级别的详细设计文档 +- 评估需求实现工时(Story Point / 人天) +- 对复杂功能进行技术预研(Spike) + +### 2.4 协作与交付 +- 参与 Sprint 规划、每日站会、评审会 +- 与测试工程师协作定位和修复 Bug +- 与产品经理澄清需求实现细节 +- 配合运维完成服务上线、灰度发布 +- 编写上线 Checklist 和技术 Release Notes + +### 2.5 学习与成长 +- 持续学习新技术、新框架 +- 参与技术分享,沉淀团队知识 +- 阅读优秀开源项目源码 +- 初级工程师接受高级/架构师指导 + +--- + +## 三、常用平台与工具 + +### 3.1 后端开发 + +| 分类 | 工具 | 用途 | +|------|------|------| +| **语言与框架** | Go + Gin / Kratos | 微服务开发 | +| | Java + Spring Boot / Spring Cloud | 企业级后端 | +| | Python + FastAPI / Django | 快速开发 / AI 服务 | +| | Node.js + Nest.js / Express | BFF 层 / 轻量服务 | +| | Rust | 高性能系统 | +| **数据库** | MySQL / PostgreSQL | 关系型数据 | +| | Redis | 缓存、队列、锁 | +| | MongoDB | 文档存储 | +| | Elasticsearch | 全文搜索 | +| **消息队列** | Kafka / RocketMQ / RabbitMQ | 异步解耦 | +| **IDE** | VS Code / IntelliJ IDEA / GoLand | 开发环境 | +| **API 工具** | Postman / Apifox / Apipost | 接口调试 | +| | Swagger / OpenAPI | API 文档 | + +### 3.2 前端开发 + +| 分类 | 工具 | 用途 | +|------|------|------| +| **框架** | React + Next.js | Web 前端 | +| | Vue 3 + Nuxt | Web 前端 | +| | TypeScript | 类型安全 | +| **UI 库** | Ant Design / Element Plus | 中后台组件 | +| | Tailwind CSS | 原子化 CSS | +| | Shadcn/ui | 可定制组件 | +| **构建工具** | Vite / Webpack / Turbopack | 构建打包 | +| **状态管理** | Zustand / Pinia / Redux | 状态管理 | +| **测试** | Vitest / Playwright / Cypress | 单元/端到端测试 | +| **IDE** | VS Code / Cursor | 开发环境 | +| **调试** | Chrome DevTools / React DevTools | 调试工具 | + +### 3.3 移动端开发 + +| 分类 | 工具 | 用途 | +|------|------|------| +| **平台** | Swift + SwiftUI(iOS) | iOS 原生 | +| | Kotlin + Jetpack Compose(Android) | Android 原生 | +| | Flutter / React Native | 跨端开发 | +| **测试** | XCTest(iOS)/ Espresso(Android) | 单元/UI 测试 | +| **分发** | TestFlight / Firebase App Distribution | 测试分发 | +| **性能** | Instruments(iOS)/ Android Profiler | 性能分析 | + +### 3.4 通用工具 + +| 工具 | 用途 | +|------|------| +| Git + GitHub / GitLab / Gitee | 版本控制 | +| Docker | 容器化开发环境 | +| VS Code / JetBrains 系列 | IDE | +| Tmux / iTerm2 / Windows Terminal | 终端 | +| Figma(查看模式) | 设计稿查看 | +| Raycast / Alfred | 效率工具 | + +--- + +## 四、开发流程 + +``` +需求理解 → 技术方案 → 编码实现 → 自测 → 代码评审 → 提测 → 修复Bug → 上线 + │ │ │ │ │ │ │ │ + ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ + PRD阅读 设计文档 分支开发 单元测试 PR/MR 提测单 Bug修复 发布清单 +``` + +--- + +## 五、能力模型 + +| 级别 | 能力要求 | +|------|----------| +| **初级** | 独立完成明确需求的功能开发、Bug 修复 | +| **中级** | 独立负责模块设计与实现、指导初级工程师 | +| **高级** | 主导子系统设计、跨模块优化、Code Review 把控 | +| **资深** | 领域专家、技术难题攻关、架构设计参与 | + +| 通用能力 | 要求 | +|----------|------| +| 编程语言 | 精通至少一门语言及其生态 | +| 数据结构与算法 | 常见数据结构、算法复杂度分析 | +| 设计模式 | 常用 23 种模式,能合理应用 | +| 数据库 | SQL 编写、索引优化、慢查询分析 | +| Linux | 命令行操作、脚本编写、服务部署 | +| Git | 分支管理、冲突解决、Git Flow | +| 调试能力 | 日志分析、断点调试、性能 Profiling | + +--- + +## 六、产出物清单 + +| 产出物 | 交付节点 | +|--------|----------| +| 技术方案/详细设计 | 编码前 | +| 功能代码(含单测) | Sprint 结束 | +| API 接口文档更新 | 接口变更时 | +| 自测报告 | 提测时 | +| Code Review 记录 | 每个 PR/MR | +| 上线 Checklist | 发布前 | +| 技术分享文档 | 不定期 | diff --git a/docs/架构师/架构师角色文档.md b/docs/架构师/架构师角色文档.md new file mode 100644 index 0000000..11fd0f1 --- /dev/null +++ b/docs/架构师/架构师角色文档.md @@ -0,0 +1,130 @@ +# 架构师 + +> 所属公司:瑞来兹软件技术有限公司 +> 更新日期:2026-05-04 + +--- + +## 一、角色定义 + +架构师(Architect)是技术方向的掌舵者,负责系统架构设计、技术选型、非功能需求保障(性能/安全/可扩展性),并在关键决策上提供技术判断力。按领域可细分为:系统架构师、应用架构师、数据架构师、安全架构师、解决方案架构师。 + +--- + +## 二、主要职责 + +### 2.1 架构设计 +- 设计系统整体架构(微服务/单体/混合),产出架构图 +- 定义模块边界、服务拆分、接口契约(API 设计) +- 制定技术选型标准(语言、框架、中间件、数据库) +- 设计数据架构:数据模型、分库分表策略、读写分离 +- 设计部署架构:K8s 集群规划、网络拓扑、容灾方案 + +### 2.2 技术规范 +- 制定编码规范、分支管理策略(Git Flow / Trunk-Based) +- 定义 API 设计规范(RESTful / GraphQL / gRPC) +- 建立技术雷达(Tech Radar),跟踪新兴技术 +- 制定安全编码规范(OWASP Top 10 防护) +- 编写架构决策记录(ADR) + +### 2.3 非功能需求保障 +- 性能优化:系统吞吐量、响应时间、并发能力 +- 高可用设计:多活/主备、故障转移、降级熔断 +- 安全架构:认证授权(OAuth2.0/OIDC)、数据加密、审计日志 +- 可扩展性:水平扩展策略、CQRS、事件驱动 +- 可观测性:日志、指标、链路追踪(OpenTelemetry) + +### 2.4 技术评审与治理 +- 主持技术方案评审会,评估方案可行性 +- 核心模块代码审查,确保架构一致性 +- 识别技术债务,制定偿还计划 +- 参与技术委员会,制定长期技术战略 + +### 2.5 团队赋能 +- 指导高级开发工程师,提升团队技术水位 +- 定期组织技术分享会(Tech Talk) +- 编写架构文档,降低系统认知负荷 +- 帮助团队攻克技术难题(攻关) + +--- + +## 三、常用平台与工具 + +| 分类 | 工具 | 用途 | +|------|------|------| +| **架构设计** | Draw.io | 架构图、流程图、网络拓扑 | +| | PlantUML | 代码生成 UML 图 | +| | C4 Model / Structurizr | 分层架构可视化 | +| | Excalidraw | 手绘风格架构草图 | +| | ArchiMate | 企业架构建模 | +| **技术文档** | Confluence / Notion | 架构文档协作 | +| | ADR Tools | 架构决策记录管理 | +| | Markdown + Git | 轻量文档版本管理 | +| **中间件与基础设施** | Kubernetes (K8s) | 容器编排 | +| | Istio / Linkerd | 服务网格 | +| | Nacos / Consul | 服务注册与配置 | +| | Redis | 缓存 / 分布式锁 | +| | Kafka / RocketMQ / RabbitMQ | 消息队列 | +| | Nginx / APISIX / Kong | API 网关 | +| | Elasticsearch | 搜索与分析引擎 | +| **数据库** | MySQL / PostgreSQL | 关系型数据库 | +| | MongoDB | 文档型 NoSQL | +| | TiDB / CockroachDB | 分布式 SQL | +| | ClickHouse / Doris | OLAP 分析 | +| **监控与可观测** | Prometheus + Grafana | 监控与可视化 | +| | ELK(Elasticsearch + Logstash + Kibana) | 日志管理 | +| | SkyWalking / Jaeger / Zipkin | 链路追踪 | +| | Sentry | 错误追踪 | +| **CI/CD** | Jenkins / GitLab CI / GitHub Actions | 流水线 | +| | ArgoCD | GitOps 部署 | +| | Docker / Containerd | 容器化 | +| **安全** | SonarQube | 代码安全扫描 | +| | Vault | 密钥管理 | +| | OWASP ZAP | 安全测试 | + +--- + +## 四、架构设计原则 + +``` +1. KISS(Keep It Simple, Stupid)—— 简单优于复杂 +2. YAGNI(You Aren't Gonna Need It)—— 不为未来过度设计 +3. 高内聚低耦合 —— 模块内紧密关联,模块间松散依赖 +4. 关注点分离(Separation of Concerns) +5. 面向失败设计(Design for Failure) +6. 无状态优先 —— 有状态服务需特殊设计 +7. 数据一致性权衡 —— CAP 取舍、最终一致性 +8. API First —— 先定义接口,再实现 +9. 12-Factor App —— 云原生应用方法论 +10. 安全左移(Shift Left on Security) +``` + +--- + +## 五、能力模型 + +| 能力 | 要求 | +|------|------| +| 系统设计 | 分布式系统、微服务、领域驱动设计(DDD) | +| 技术广度 | 多语言/多框架/多数据库经验 | +| 抽象思维 | 从业务需求抽象架构模型 | +| 决策能力 | 技术选型与权衡(Trade-off)分析 | +| 沟通表达 | 能用图/文档清晰传达架构意图 | +| 业务理解 | 理解业务战略,支撑业务扩展 | +| 领导力 | 技术驱动,不依赖职级 | + +--- + +## 六、产出物清单 + +| 产出物 | 交付节点 | +|--------|----------| +| 系统架构设计文档 | 项目启动阶段 | +| 技术选型报告 | 方案评审 | +| API 设计规范 | 编码前 | +| 数据库 ER 图与表设计 | 编码前 | +| 部署架构图 | 上线前 | +| 架构评审记录 | 每次评审 | +| ADR(架构决策记录) | 关键决策时 | +| 性能压测报告 | 上线前 | +| 技术雷达 | 季度 | diff --git a/docs/测试/测试角色文档.md b/docs/测试/测试角色文档.md new file mode 100644 index 0000000..5a55708 --- /dev/null +++ b/docs/测试/测试角色文档.md @@ -0,0 +1,162 @@ +# 测试 + +> 所属公司:瑞来兹软件技术有限公司 +> 更新日期:2026-05-04 + +--- + +## 一、角色定义 + +测试工程师(QA Engineer)是产品质量的守门员,通过功能测试、自动化测试、性能测试、安全测试等手段,保障软件交付质量,降低线上故障风险。 + +--- + +## 二、主要职责 + +### 2.1 测试规划 +- 参与需求评审,从测试角度提出可测性建议 +- 编写测试计划,明确测试范围、策略、资源 +- 设计测试用例(功能、异常、边界、兼容性) +- 评估测试工作量,规划测试排期 +- 定义测试准入/准出标准 + +### 2.2 功能测试 +- 执行冒烟测试(Smoke Test),验证提测版本可用性 +- 执行全量测试用例,记录测试结果 +- 发现 Bug 并提交到缺陷管理平台,清晰描述复现步骤 +- 回归测试:验证 Bug 修复,确保无新增问题 +- 探索性测试:发现用例未覆盖的缺陷 + +### 2.3 自动化测试 +- 搭建自动化测试框架(Selenium / Playwright / Appium) +- 编写接口自动化测试脚本(pytest / JMeter / Postman) +- 编写 UI 自动化测试脚本 +- 集成到 CI/CD 流水线,实现自动化回归 +- 维护自动化用例,保证脚本稳定性 + +### 2.4 性能测试 +- 制定性能测试方案,确定压测场景与指标 +- 使用 JMeter / Locust / k6 进行压力测试 +- 分析性能瓶颈(CPU/内存/IO/数据库慢查询) +- 输出性能测试报告,给出优化建议 +- 生产环境容量评估与容量规划 + +### 2.5 安全测试 +- 执行安全扫描(OWASP Top 10) +- SQL 注入、XSS、CSRF 等常见漏洞检测 +- API 接口权限验证、敏感数据泄露检查 +- 配合第三方安全公司进行渗透测试 + +### 2.6 质量度量 +- 统计缺陷密度、Bug 修复率、Bug 严重程度分布 +- 分析线上故障,推动故障复盘 +- 建立质量大盘,可视化质量趋势 +- 推动质量左移,提升提测质量 + +--- + +## 三、常用平台与工具 + +| 分类 | 工具 | 用途 | +|------|------|------| +| **缺陷管理** | Jira | Bug 跟踪与管理 | +| | 禅道 | 国产缺陷管理 | +| | 飞书项目 | Bug 跟踪 | +| | Bugzilla / Mantis | 开源缺陷管理 | +| **测试管理** | TestRail | 测试用例管理 | +| | Xray(Jira 插件) | 测试管理+Jira集成 | +| | TestLink | 开源测试管理 | +| | PingCode | 测试用例与计划 | +| **接口测试** | Postman / Apifox | 手动接口调试 | +| | REST Assured | Java 接口自动化 | +| | pytest + requests | Python 接口自动化 | +| | JMeter | 接口压力测试 | +| **UI 自动化** | Selenium | Web 自动化 | +| | Playwright | 新一代 Web 自动化 | +| | Cypress | 前端 E2E 测试 | +| | Appium | 移动端自动化 | +| | Airtest | 移动端/游戏自动化 | +| **性能测试** | JMeter | 接口压力测试 | +| | Locust | Python 性能测试 | +| | k6 | 云原生性能测试 | +| | Gatling | Scala 性能测试 | +| | wrk / ab | 轻量 HTTP 压测 | +| **安全测试** | OWASP ZAP | Web 安全扫描 | +| | Burp Suite | 渗透测试 | +| | SQLMap | SQL 注入检测 | +| | Nmap | 端口扫描 | +| **抓包与调试** | Charles / Fiddler | HTTP 抓包 | +| | Proxyman | Mac 代理调试 | +| | Whistle | 跨平台抓包 | +| **数据库** | Navicat / DBeaver | 数据库管理 | +| | RedisInsight | Redis 可视化 | +| **CI/CD 集成** | Jenkins / GitLab CI / GitHub Actions | 自动化测试流水线 | +| | Allure | 测试报告可视化 | + +--- + +## 四、测试流程 + +``` +需求评审 → 测试计划 → 用例设计 → 用例评审 → 冒烟测试 → 功能测试 → 回归测试 → 验收测试 → 上线 + │ │ │ │ │ │ │ │ │ + ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ + 提出可测 测试策略 用例库 团队评审 版本准入 执行用例 Bug验证 UAT参与 测试报告 + 性建议 资源排期 TestRail 对齐标准 快速验证 Bug提交 全量回归 验收签字 质量评估 +``` + +--- + +## 五、测试策略分层(测试金字塔) + +``` + ╱ 〰 ╲ + ╱ E2E ╲ 少量端到端测试(全链路验证) + ╱──────────────╲ + ╱ 集成测试 ╲ 中量集成测试(模块联调、API) + ╱──────────────────╲ + ╱ 单元测试 ╲ 大量单元测试(函数/类级别) +╱──────────────────────────╲ + 成本 ⬆️ | 速度 ⬇️ +``` + +- **单元测试**:占 70%,开发编写,CI 自动运行 +- **接口/集成测试**:占 20%,测试+开发共同维护 +- **UI/E2E 测试**:占 10%,覆盖核心业务主流程 + +--- + +## 六、能力模型 + +| 级别 | 能力要求 | +|------|----------| +| **初级** | 执行功能测试,编写测试用例,提交 Bug | +| **中级** | 独立负责模块测试,编写接口自动化脚本,性能测试执行 | +| **高级** | 搭建自动化框架,主导性能/安全测试,制定测试策略 | +| **资深** | 全链路质量体系搭建,测试左移/右移,团队赋能 | + +| 通用能力 | 要求 | +|----------|------| +| 测试方法论 | 黑盒/白盒/灰盒、等价类、边界值、场景法 | +| 编程能力 | Python/Java 至少一种,能写自动化脚本 | +| SQL | 复杂查询、数据验证、造数据 | +| Linux | 查看日志、部署服务、排查问题 | +| 网络协议 | HTTP/HTTPS、TCP/IP、抓包分析 | +| 业务理解 | 深度理解业务场景,设计有效用例 | +| 细心与耐心 | 不放过任何一个异常现象 | + +--- + +## 七、产出物清单 + +| 产出物 | 交付节点 | +|--------|----------| +| 测试计划 | 需求评审后 | +| 测试用例 | 编码阶段 | +| 冒烟测试报告 | 提测后 | +| Bug 报告 | 测试过程中 | +| 功能测试报告 | 测试完成 | +| 性能测试报告 | 性能测试后 | +| 安全测试报告 | 安全测试后 | +| 自动化测试脚本 | 持续维护 | +| 质量月报 | 每月 | diff --git a/docs/规划/规划角色文档.md b/docs/规划/规划角色文档.md new file mode 100644 index 0000000..f2ec138 --- /dev/null +++ b/docs/规划/规划角色文档.md @@ -0,0 +1,117 @@ +# 规划 + +> 所属公司:瑞来兹软件技术有限公司 +> 更新日期:2026-05-04 + +--- + +## 一、角色定义 + +规划岗位(含项目规划师、敏捷教练、技术规划等角色)负责项目的整体规划、资源调度和过程管控,确保项目按时、按质、按预算交付。 + +--- + +## 二、主要职责 + +### 2.1 项目规划 +- 制定项目章程(Project Charter),明确项目范围、目标、干系人 +- 编制 WBS(工作分解结构),拆解可执行的任务单元 +- 制定项目排期(甘特图),确定关键路径与里程碑 +- 评估项目风险,制定风险应对预案 +- 编制项目预算,跟踪成本与投入产出比 + +### 2.2 资源管理 +- 协调跨部门资源(研发、测试、设计、运维) +- 制定人力投入计划,避免资源冲突 +- 管理外部供应商与外包团队 +- 项目物料与软硬件资源的采购跟进 + +### 2.3 进度管控 +- 主持项目例会(周会/日站会),跟踪任务进度 +- 使用燃尽图(Burndown Chart)监控 Sprint 健康度 +- 识别并解决阻塞项(Blocker),及时升级风险 +- 变更管理:评估变更影响,走 CR(Change Request)流程 + +### 2.4 质量与交付 +- 定义 DoD(Definition of Done),确保交付标准 +- 组织阶段评审(需求评审、设计评审、代码评审) +- 管理 UAT 测试与验收交付 +- 项目结项:归档文档、复盘总结(Retrospective) + +### 2.5 敏捷实践 +- 担任 Scrum Master,推动敏捷转型 +- 引导 Sprint Planning、Daily Standup、Sprint Review、Retro +- 度量团队敏捷成熟度,持续优化流程 +- 培训团队敏捷/精益方法论 + +--- + +## 三、常用平台与工具 + +| 分类 | 工具 | 用途 | +|------|------|------| +| **项目管理** | Jira | Sprint 管理、任务跟踪、报表 | +| | Microsoft Project | 专业项目排期、甘特图 | +| | Asana | 轻量团队任务管理 | +| | Monday.com | 可视化项目管理 | +| | 禅道 | 国产全流程项目管理 | +| | PingCode | 研发项目管理 | +| | 飞书项目 | 飞书生态项目管理 | +| | Teambition | 钉钉生态项目管理 | +| | Worktile | 国产协作与项目管理 | +| | OmniPlan | Mac 端专业甘特图 | +| **文档与知识库** | Confluence | 项目文档 Wiki | +| | Notion | 项目知识库 | +| | 语雀 | 团队知识沉淀 | +| **进度可视化** | 甘特图(Excel/Project) | 里程碑计划 | +| | 燃尽图(Burndown) | Sprint 进度跟踪 | +| | Miro / MURAL | 线上白板、Retro 回顾 | +| **沟通协作** | 飞书 / 钉钉 / 企业微信 | 日常沟通 | +| | Slack | 国际团队 | +| | Zoom / 腾讯会议 | 远程会议 | +| **文档处理** | Office 365 / WPS | 文档、表格、演示 | +| | Google Workspace | 在线协作 | +| **时间管理** | Toggl / Clockify | 工时记录 | +| | RescueTime | 时间分析 | + +--- + +## 四、关键流程 + +``` +项目立项 → 需求评审 → 方案设计 → Sprint规划 → 开发实施 → 测试验证 → UAT → 上线发布 → 结项复盘 + │ │ │ │ │ │ │ │ │ + ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ + 立项书 PRD评审 技术方案 Sprint 每日站会 Bug跟踪 验收报告 发布清单 复盘报告 + 评审 Backlog +``` + +--- + +## 五、能力模型 + +| 能力 | 要求 | +|------|------| +| 项目管理 | PMP/PRINCE2/ACP 方法论、敏捷/瀑布混合 | +| 风险管理 | 风险识别、概率评估、应对策略 | +| 沟通协调 | 干系人管理、冲突解决、向上汇报 | +| 时间管理 | 多项目并行管理、关键路径分析 | +| 工具使用 | Jira/Project/甘特图等专业工具 | +| 业务理解 | 快速理解行业背景与业务逻辑 | +| 数据分析 | 项目度量指标、报表输出 | + +--- + +## 六、产出物清单 + +| 产出物 | 交付节点 | +|--------|----------| +| 项目章程 | 立项阶段 | +| WBS 工作分解 | 规划阶段 | +| 项目排期(甘特图) | 规划阶段 | +| 风险管理计划 | 规划阶段 | +| 项目周报 | 每周 | +| Sprint 燃尽图 | 每个 Sprint | +| 阶段评审报告 | 里程碑节点 | +| 变更申请单(CR) | 需求变更时 | +| 项目复盘报告 | 结项 | diff --git a/frontend/src/stores/agent.ts b/frontend/src/stores/agent.ts index 61e1b6f..6d235eb 100644 --- a/frontend/src/stores/agent.ts +++ b/frontend/src/stores/agent.ts @@ -41,10 +41,11 @@ export const useAgentStore = defineStore('agent', () => { if (options?.status) params.status = options.status if (options?.skip !== undefined) params.skip = options.skip if (options?.limit !== undefined) params.limit = options.limit - + const response = await api.get('/api/v1/agents', { params }) agents.value = response.data - return response.data + const total = parseInt(response.headers['x-total-count'] || '0', 10) + return { data: response.data, total } } finally { loading.value = false } diff --git a/frontend/src/views/Agents.vue b/frontend/src/views/Agents.vue index 603f8c5..3c07b7c 100644 --- a/frontend/src/views/Agents.vue +++ b/frontend/src/views/Agents.vue @@ -791,13 +791,13 @@ const handleRefresh = async () => { // 加载Agent列表 const loadAgents = async () => { try { - const agents = await agentStore.fetchAgents({ + const result = await agentStore.fetchAgents({ search: searchText.value || undefined, status: statusFilter.value || undefined, skip: (currentPage.value - 1) * pageSize.value, limit: pageSize.value }) - total.value = agents.length + total.value = result.total } catch (error: any) { ElMessage.error(error.response?.data?.detail || '加载Agent列表失败') } diff --git a/三字经.md b/misc/poetry/三字经.md similarity index 100% rename from 三字经.md rename to misc/poetry/三字经.md diff --git a/三字经里的人之初.md b/misc/poetry/三字经里的人之初.md similarity index 100% rename from 三字经里的人之初.md rename to misc/poetry/三字经里的人之初.md diff --git a/望庐山瀑布.md b/misc/poetry/望庐山瀑布.md similarity index 100% rename from 望庐山瀑布.md rename to misc/poetry/望庐山瀑布.md diff --git a/满江红.md b/misc/poetry/满江红.md similarity index 100% rename from 满江红.md rename to misc/poetry/满江红.md diff --git a/静夜思.md b/misc/poetry/静夜思.md similarity index 100% rename from 静夜思.md rename to misc/poetry/静夜思.md diff --git a/123.md b/misc/test-data/123.md similarity index 100% rename from 123.md rename to misc/test-data/123.md diff --git a/456.md b/misc/test-data/456.md similarity index 100% rename from 456.md rename to misc/test-data/456.md diff --git a/misc/test-data/daily_reminder_config.md b/misc/test-data/daily_reminder_config.md new file mode 100644 index 0000000..91f414d --- /dev/null +++ b/misc/test-data/daily_reminder_config.md @@ -0,0 +1,6 @@ +# ⏰ 每日作业提醒配置 +- **任务名称**: 每日作业任务提醒 +- **提醒时间**: 每天早上 8:00 +- **Cron表达式**: 0 8 * * * +- **创建时间**: 2026-05-02 23:47 +- **状态**: ✅ 待系统激活 diff --git a/index.html b/misc/test-data/index.html similarity index 100% rename from index.html rename to misc/test-data/index.html diff --git a/multi_route_workflow.json b/misc/test-data/multi_route_workflow.json similarity index 100% rename from multi_route_workflow.json rename to misc/test-data/multi_route_workflow.json diff --git a/test_emails.txt b/misc/test-data/test_emails.txt similarity index 100% rename from test_emails.txt rename to misc/test-data/test_emails.txt diff --git a/user_profile.json b/misc/test-data/user_profile.json similarity index 100% rename from user_profile.json rename to misc/test-data/user_profile.json diff --git a/作业.txt b/misc/test-data/作业.txt similarity index 100% rename from 作业.txt rename to misc/test-data/作业.txt diff --git a/restart_backend_celery.ps1 b/restart_backend_celery.ps1 deleted file mode 100644 index 02eeb60..0000000 --- a/restart_backend_celery.ps1 +++ /dev/null @@ -1,56 +0,0 @@ -# Restart backend API (uvicorn) and Celery worker only; does not stop frontend/Redis.$ErrorActionPreference = "Continue" -$RepoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path -$Backend = Join-Path $RepoRoot "backend" -$ApiPort = 8037 - -Write-Host "== Stop API + Celery only ==" -ForegroundColor Cyan - -function Stop-ByCommandLine([string]$pattern, [string]$name) { - $targets = Get-CimInstance Win32_Process | Where-Object { - $_.CommandLine -and $_.CommandLine -match $pattern - } - if (-not $targets) { - Write-Host "[SKIP] ${name}: no matching process" -ForegroundColor DarkGray - return - } - foreach ($p in $targets) { - try { - Stop-Process -Id $p.ProcessId -Force - Write-Host "[OK] stopped ${name} PID=$($p.ProcessId)" -ForegroundColor Green - } catch { - Write-Host "[WARN] failed to stop ${name} PID=$($p.ProcessId)" -ForegroundColor Yellow - } - } -} - -Stop-ByCommandLine "uvicorn\s+app\.main:app" "backend-api" -Stop-ByCommandLine "celery\s+-A\s+app\.core\.celery_app\s+worker" "celery-worker" - -Start-Sleep -Seconds 2 - -Write-Host "== Start API on $ApiPort + Celery ==" -ForegroundColor Cyan - -Start-Process powershell -ArgumentList @( - "-NoExit", - "-NoProfile", - "-ExecutionPolicy", "Bypass", - "-Command", - "Set-Location '$Backend'; .\venv\Scripts\Activate.ps1; python -m uvicorn app.main:app --host 0.0.0.0 --port $ApiPort" -) - -Start-Process powershell -ArgumentList @( - "-NoExit", - "-NoProfile", - "-ExecutionPolicy", "Bypass", - "-Command", - "Set-Location '$Backend'; .\venv\Scripts\Activate.ps1; python -m celery -A app.core.celery_app worker --loglevel=info --pool=threads --concurrency=8" -) - -Write-Host "" -Write-Host "[DONE] 已在新窗口启动 API 与 Celery (请查看弹出的 PowerShell 窗口日志)" -ForegroundColor Green -Write-Host "API: http://127.0.0.1:$ApiPort/docs" -ForegroundColor Cyan - -Start-Sleep -Seconds 2 -Write-Host "" -Write-Host "Port $ApiPort :" -ForegroundColor Cyan -netstat -ano | findstr ":$ApiPort" diff --git a/scripts/create_issues.py b/scripts/create_issues.py new file mode 100644 index 0000000..76db0cd --- /dev/null +++ b/scripts/create_issues.py @@ -0,0 +1,485 @@ +"""批量创建 Gitea 工单 — 项目下一步需求清单""" +import requests +import json + +TOKEN = "fbc9ee7f96635793f4844187eac5c0e573480721" +BASE = "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues" + +HEADERS = { + "Authorization": f"token {TOKEN}", + "Content-Type": "application/json" +} + +ISSUES = [ + # ===== Phase 4: 容错与共享 ===== + { + "title": "[Phase 4.1] 降级/回退链 — 模型/Agent 失败自动切换备用方案", + "body": """## 目标 +主模型调用失败自动切换 fallback_llm,Agent 执行失败自动切换 fallback_agent。 + +## 涉及改动 +- AgentLLMConfig 添加 fallback_llm 字段 +- Agent 节点 data 添加 fallback_agent 字段 +- core.py LLM 调用失败后切换 fallback +- workflow_engine.py Agent 节点失败后切换 fallback_agent + +## 验收 +- 主模型不可用 → 自动切备用模型 +- Agent 执行失败 → 自动切备选 Agent +- 所有备用方案都失败 → 抛出明确错误 + +## 预计工作量 +4-6 小时""", + }, + { + "title": "[Phase 4.2] Agent 间知识共享 — 打破记忆隔离", + "body": """## 目标 +打破 Agent 记忆隔离,Agent A 学到的知识 Agent B 也能检索使用。 + +## 方案 +- 新增 GlobalKnowledge 模型(content, embedding, source_agent_id, tags) +- Agent 执行完成后自动提取关键知识写入全局知识池 +- Agent 初始化时从全局知识池检索相关知识 +- 自主进化创建的 Agent 自动继承创建者的知识 + +## 涉及文件 +- models/agent.py — 新增 GlobalKnowledge 模型 +- agent_runtime/memory.py — initialize() 添加全局知识加载 +- agent_runtime/core.py — 执行完毕提取知识写入全局 + +## 预计工作量 +8-12 小时""", + }, + { + "title": "[Phase 4.3] Agent 异步执行 — 填空 execute_agent_task", + "body": """## 问题 +backend/app/tasks/agent_tasks.py 中 execute_agent_task 当前是空壳占位符(返回 pending),定时调度无法真正异步执行 Agent。 + +## 方案 +1. 从 DB 加载 Agent 配置 +2. 构造 AgentRuntime +3. 异步执行并更新 execution 记录 +4. 完成通知(飞书/WebSocket/站内通知) + +## 涉及文件 +- tasks/agent_tasks.py — 实现 execute_agent_task +- tasks/scheduler_tasks.py — 调度触发真正的异步执行 + +## 预计工作量 +4-6 小时""", + }, + + # ===== 监控告警前端 ===== + { + "title": "[监控] 系统监控面板 — CPU/内存/磁盘 + 执行统计", + "body": """## 当前状态 +后端 API 已完成:GET /api/v1/monitoring/overview、GET /api/v1/monitoring/statistics +前端界面缺失。 + +## 需要完成 +- 系统资源监控(CPU、内存、磁盘) +- 执行统计图表(成功率、执行时间、错误率) +- 实时执行状态看板 + +## 预计工作量 +4-6 小时""", + }, + { + "title": "[监控] 告警规则管理页面", + "body": """## 当前状态 +后端 API 已完成:GET/POST /api/v1/alert-rules、PUT/DELETE /api/v1/alert-rules/{id} +前端界面缺失。 + +## 需要完成 +- 告警规则列表 +- 告警规则创建/编辑表单(条件、阈值、通知方式) +- 启用/禁用开关 + +## 预计工作量 +4-6 小时""", + }, + { + "title": "[监控] 告警日志查看页面", + "body": """## 当前状态 +后端 API 已完成:GET /api/v1/alert-rules/{id}/logs +前端界面缺失。 + +## 需要完成 +- 告警历史列表(时间、规则、级别、状态) +- 告警详情查看 + 筛选分页 +- 告警通知配置(邮件、Webhook、飞书) + +## 预计工作量 +4-6 小时""", + }, + + # ===== DevOps / 生产就绪 ===== + { + "title": "[DevOps] Docker 生产环境配置 + 多环境管理", + "body": """## 目标 +从开发环境升级到生产就绪的容器化部署。 + +## 需要完成 +- Docker Compose 生产配置(资源限制、健康检查、重启策略) +- 多环境配置管理(dev / staging / prod) +- 配置文件加密(敏感信息不提交 Git) +- Nginx 反向代理 + HTTPS + +## 预计工作量 +8-12 小时""", + }, + { + "title": "[DevOps] Prometheus + Grafana 监控集成", + "body": """## 目标 +建立生产级指标收集和可视化。 + +## 需要完成 +- Prometheus 指标收集(业务指标:执行数/成功率/耗时 + 系统指标:CPU/内存/网络) +- Grafana 仪表板(系统监控 + 业务监控) +- FastAPI metrics 端点暴露 +- Celery worker 指标收集 + +## 预计工作量 +8-12 小时""", + }, + { + "title": "[DevOps] ELK 日志聚合系统", + "body": """## 目标 +集中化日志管理,替代本地 backend.log 文件。 + +## 需要完成 +- Elasticsearch + Logstash + Kibana 容器化部署 +- FastAPI 日志接入 Logstash +- Celery 任务日志接入 +- 日志查询和分析界面(Kibana) + +## 预计工作量 +8-12 小时""", + }, + { + "title": "[DevOps] CI/CD 流水线 — GitHub Actions", + "body": """## 目标 +自动化测试→构建→部署流程。 + +## 需要完成 +- 自动化测试流程(pytest + vitest) +- 代码质量检查(ESLint + Pylint + Black) +- 自动化 Docker 镜像构建 +- 自动化部署到 staging 环境 + +## 预计工作量 +10-15 小时""", + }, + { + "title": "[安全] API 限流保护", + "body": """## 目标 +防止 API 滥用,保护后端服务。 + +## 需要完成 +- 全局限流(每 IP 每分钟 N 次) +- 用户级别限流(按 token/user_id) +- Agent 对话接口更严格限流 +- 限流响应头(X-RateLimit-*) + +## 技术方案 +可使用 slowapi(FastAPI 限流库)或 Redis + 滑动窗口。 + +## 预计工作量 +4-6 小时""", + }, + { + "title": "[安全] 密钥管理 — 敏感信息加密存储", + "body": """## 问题 +当前 API Key、数据库密码等敏感信息明文存储在 .env 文件中。 + +## 方案 +- 使用环境变量 + Docker secrets 管理生产密钥 +- 开发环境使用 .env.local(加入 .gitignore) +- 数据库存储的 API Key 使用 AES 加密 + +## 预计工作量 +4-6 小时""", + }, + + # ===== 用户体验 ===== + { + "title": "[UX] 工作流编辑器优化 — 自动布局 + 节点搜索", + "body": """## 目标 +提升复杂工作流的编辑效率。 + +## 需要完成 +- 节点自动布局(一键美化排列) +- 节点搜索/筛选(按名称、类型快速定位) +- 画布小地图(minimap)导航 +- 节点分组/折叠功能 + +## 预计工作量 +8-10 小时""", + }, + { + "title": "[UX] Agent 快速测试功能", + "body": """## 目标 +在 Agent 配置页面直接测试,不需跳转到对话页。 + +## 需要完成 +- Agent 配置页嵌入测试对话面板 +- 测试结果实时显示(思考链+工具调用+最终回答) +- 测试历史记录 +- 快速切换模型对比测试 + +## 预计工作量 +6-8 小时""", + }, + { + "title": "[UX] Agent 使用统计和分析", + "body": """## 目标 +量化每个 Agent 的使用情况。 + +## 需要完成 +- 每个 Agent 调用次数、成功率、平均耗时 +- Token 消耗统计 +- 工具调用频次排行 +- 按时间维度(日/周/月)展示趋势图 + +## 预计工作量 +6-8 小时""", + }, + { + "title": "[UX] 移动端适配", + "body": """## 目标 +核心功能支持移动端访问。 + +## 需要完成 +- 响应式布局优化 +- 移动端 Agent 对话页面 +- 移动端执行状态查看(只读) +- 移动端工作流查看(只读) + +## 预计工作量 +15-20 小时""", + }, + + # ===== 高级功能 ===== + { + "title": "[高级] 主控台/应用商店 — 面向业务用户的一站式入口", + "body": """## 目标 +让业务用户选模板→填参数→执行→看结果,不需了解工作流细节。 + +## 需要完成 +- MainConsole 主入口页面 +- TemplateMarket 模板市场(浏览、搜索、评分、收藏) +- ExecutionBoard 父子执行链看板 +- 一键从模板创建并执行 + +## 预计工作量 +15-20 小时""", + }, + { + "title": "[高级] 统一 DSL — 场景可编程输入", + "body": """## 目标 +让不同模板复用统一的输入契约,降低场景迁移成本。 + +## 方案 +- 新增 scenario_dsl.py 定义标准输入:目标、约束、产物、验收标准 +- 工作流入口处做 DSL 校验与标准化映射 +- 所有模板脚本迁移到 DSL 输入 + +## 预计工作量 +8-12 小时""", + }, + { + "title": "[高级] 成本预算治理 — 按 Agent 设置预算阈值", + "body": """## 目标 +防止 Agent 执行失控,支持组织级运营。 + +## 方案 +- 增加预算控制:max_steps、工具调用上限、token 估算上限 +- 新增预算配置模型,按租户/项目/Agent 维度配置 +- 预算超限告警与熔断 + +## 预计工作量 +8-12 小时""", + }, + { + "title": "[高级] 插件系统 — 第三方自定义节点 + 插件市场", + "body": """## 目标 +让外部开发者可以开发自定义节点插件,扩展平台能力。 + +## 方案 +- 插件注册机制(插件清单、入口文件、依赖声明) +- 自定义节点插件开发框架(SDK + 脚手架) +- 插件市场(上传、下载、评分、版本管理) +- 插件安全沙箱 + +## 预计工作量 +30-40 小时(大型功能,可分期交付)""", + }, + { + "title": "[高级] 多租户支持 — SaaS 化", + "body": """## 目标 +支持多个组织/团队独立使用平台,数据隔离。 + +## 方案 +- 租户模型和 API +- 数据隔离(数据库级 / Schema 级 / 行级) +- 资源配额管理 +- 租户管理界面 + +## 预计工作量 +20-30 小时(大型功能)""", + }, + + # ===== 质量保障 ===== + { + "title": "[测试] 单元测试覆盖率提升至 80%+", + "body": """## 当前状态 +测试框架已搭建(pytest + vitest),但覆盖率较低。 + +## 需要完成 +- 后端核心模块覆盖率提升(workflow_engine、agent_runtime、core) +- 前端核心组件测试(AgentChat、WorkflowDesigner) +- API 端点测试完善 + +## 预计工作量 +20-30 小时(持续投入)""", + }, + { + "title": "[测试] E2E 测试 — Playwright 核心流程", + "body": """## 目标 +用 Playwright 覆盖核心用户流程的端到端测试。 + +## 测试场景 +- 用户登录→创建 Agent→对话→查看结果 +- 用户登录→创建工作流→添加节点→执行→查看日志 +- Agent 编排→多 Agent 协作→查看执行链 + +## 预计工作量 +10-15 小时""", + }, + { + "title": "[测试] 性能压测 — Locust/k6", + "body": """## 目标 +找到系统性能瓶颈,验证并发能力。 + +## 需要完成 +- Locust/k6 压测脚本编写 +- 压测场景:并发对话、并发工作流执行、并发 API 调用 +- 性能瓶颈分析 + 输出性能测试报告 + +## 预计工作量 +8-12 小时""", + }, + { + "title": "[测试] 安全扫描 — OWASP ZAP + 依赖漏洞检查", + "body": """## 目标 +发现并修复安全漏洞。 + +## 需要完成 +- OWASP ZAP 扫描 Web 应用 +- 依赖漏洞检查(pip-audit / npm audit) +- Docker 镜像漏洞扫描 +- API 接口权限验证 + +## 预计工作量 +8-12 小时""", + }, + + # ===== Agent 能力拓展 ===== + { + "title": "[Agent] 多模态 Agent — 图片识别 + 语音输入", + "body": """## 目标 +让 Agent 支持图片和语音输入,不只限于文本。 + +## 当前基础 +tessdata 目录已存在(OCR),图片上传功能已有。 + +## 需要完成 +- 图片理解 Agent(上传图片→OCR 提取文字→Agent 分析) +- 语音转文字输入(Whisper API 集成) +- 前端输入框添加图片/语音入口 + +## 预计工作量 +12-16 小时""", + }, + { + "title": "[Agent] Agent 协作工作台 — 可视化多 Agent 编排", + "body": """## 目标 +用可视化方式设计和执行多 Agent 协作流程。 + +## 需要完成 +- 协作流程图编辑器(拖拽 Agent 节点+连线) +- 4 种协作模式可视化配置 +- 协作结果总览面板 +- 协作模板保存和复用 + +## 预计工作量 +12-16 小时""", + }, + { + "title": "[Agent] Agent 技能商店 — 公共市场共享", + "body": """## 目标 +让用户发布和共享 Agent、工具、工作流模板。 + +## 需要完成 +- Agent 发布到公共市场(名称、描述、评分、下载数) +- 工具和工作流模板发布 +- 评分和评论系统 +- 分类浏览和搜索 + +## 预计工作量 +15-20 小时""", + }, +] + + +def main(): + print(f"准备创建 {len(ISSUES)} 个工单到 admin/aiagent ...\n") + + created = [] + failed = [] + + for i, issue in enumerate(ISSUES): + payload = { + "title": issue["title"], + "body": issue["body"], + "assignee": "admin", + } + try: + r = requests.post(BASE, headers=HEADERS, json=payload, timeout=15) + if r.status_code == 201: + data = r.json() + num = data["number"] + print(f" [{i+1:2d}/{len(ISSUES)}] #{num} {issue['title']}") + created.append(num) + else: + print(f" [{i+1:2d}/{len(ISSUES)}] FAIL ({r.status_code}): {issue['title']}") + print(f" {r.text[:200]}") + failed.append(issue["title"]) + except Exception as e: + print(f" [{i+1:2d}/{len(ISSUES)}] ERROR: {e}") + failed.append(issue["title"]) + + print(f"\n=== 完成 ===") + print(f"成功: {len(created)} 个") + print(f"失败: {len(failed)} 个") + if created: + print(f"工单编号: #{created[0]} ~ #{created[-1]}") + if failed: + print(f"失败列表: {failed}") + + # 验证 + print("\n--- 验证:当前所有 Open 工单 ---") + r = requests.get( + f"{BASE}?state=open&limit=50", + headers=HEADERS, + timeout=10 + ) + if r.status_code == 200: + issues = r.json() + print(f"共 {len(issues)} 个开放工单:") + for iss in issues: + print(f" #{iss['number']} [{iss['state']}] {iss['title']}") + + +if __name__ == "__main__": + main() diff --git a/start.sh b/scripts/startup/start.sh old mode 100755 new mode 100644 similarity index 100% rename from start.sh rename to scripts/startup/start.sh diff --git a/start_windows.cmd b/scripts/startup/start_windows.cmd similarity index 100% rename from start_windows.cmd rename to scripts/startup/start_windows.cmd diff --git a/start_windows.ps1 b/scripts/startup/start_windows.ps1 similarity index 100% rename from start_windows.ps1 rename to scripts/startup/start_windows.ps1 diff --git a/stop.sh b/scripts/startup/stop.sh old mode 100755 new mode 100644 similarity index 100% rename from stop.sh rename to scripts/startup/stop.sh diff --git a/view_logs.sh b/scripts/startup/view_logs.sh old mode 100755 new mode 100644 similarity index 100% rename from view_logs.sh rename to scripts/startup/view_logs.sh diff --git a/test_adb_tool.py b/scripts/testing/test_adb_tool.py similarity index 100% rename from test_adb_tool.py rename to scripts/testing/test_adb_tool.py diff --git a/test_agent_execution.py b/scripts/testing/test_agent_execution.py old mode 100755 new mode 100644 similarity index 100% rename from test_agent_execution.py rename to scripts/testing/test_agent_execution.py diff --git a/test_coding_agent_execution.py b/scripts/testing/test_coding_agent_execution.py similarity index 100% rename from test_coding_agent_execution.py rename to scripts/testing/test_coding_agent_execution.py diff --git a/test_database_query_tool.py b/scripts/testing/test_database_query_tool.py similarity index 100% rename from test_database_query_tool.py rename to scripts/testing/test_database_query_tool.py diff --git a/test_memory_functionality.py b/scripts/testing/test_memory_functionality.py old mode 100755 new mode 100644 similarity index 100% rename from test_memory_functionality.py rename to scripts/testing/test_memory_functionality.py diff --git a/test_output_variable_extraction.py b/scripts/testing/test_output_variable_extraction.py similarity index 100% rename from test_output_variable_extraction.py rename to scripts/testing/test_output_variable_extraction.py diff --git a/test_tool_calling_visualization.py b/scripts/testing/test_tool_calling_visualization.py old mode 100755 new mode 100644 similarity index 100% rename from test_tool_calling_visualization.py rename to scripts/testing/test_tool_calling_visualization.py diff --git a/test_workflow_data_flow.py b/scripts/testing/test_workflow_data_flow.py old mode 100755 new mode 100644 similarity index 100% rename from test_workflow_data_flow.py rename to scripts/testing/test_workflow_data_flow.py diff --git a/test_workflow_tool.py b/scripts/testing/test_workflow_tool.py old mode 100755 new mode 100644 similarity index 100% rename from test_workflow_tool.py rename to scripts/testing/test_workflow_tool.py diff --git a/test_zhini_kefu_6.py b/scripts/testing/test_zhini_kefu_6.py similarity index 100% rename from test_zhini_kefu_6.py rename to scripts/testing/test_zhini_kefu_6.py diff --git a/check_execution_logs.py b/scripts/tools/check_execution_logs.py old mode 100755 new mode 100644 similarity index 100% rename from check_execution_logs.py rename to scripts/tools/check_execution_logs.py diff --git a/check_switch_logs.py b/scripts/tools/check_switch_logs.py old mode 100755 new mode 100644 similarity index 100% rename from check_switch_logs.py rename to scripts/tools/check_switch_logs.py diff --git a/scripts/tools/daily_work_summary.py b/scripts/tools/daily_work_summary.py new file mode 100644 index 0000000..ae5d9ec --- /dev/null +++ b/scripts/tools/daily_work_summary.py @@ -0,0 +1,403 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +=========================================== +📋 每日工作总结自动生成器 +功能:每天早上9点 → 生成今日工作总结 → 推送到指定渠道 +=========================================== +""" + +import json +import os +import sys +import datetime +import smtplib +import requests +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from pathlib import Path + +# =========================================== +# 📝 第一部分:配置区(请按需修改) +# =========================================== + +class Config: + """所有配置集中管理,方便修改""" + + # --- 数据来源方式 --- + # mode = "manual" → 手动输入今日事项 + # mode = "file" → 从文件读取今日事项 + DATA_MODE = "manual" + + # 当 DATA_MODE = "file" 时,读取此文件 + DATA_FILE = "today_work_items.txt" + + # --- 推送方式(可多选)--- + # 支持:wecom(企业微信)、dingtalk(钉钉)、email(邮件) + PUSH_CHANNELS = ["wecom"] # 可改为 ["dingtalk"] 或 ["email"] + + # ----- 企业微信机器人 ----- + WECOM_WEBHOOK = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY_HERE" + + # ----- 钉钉机器人 ----- + DINGTALK_WEBHOOK = "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN_HERE" + + # ----- 邮件配置 ----- + SMTP_SERVER = "smtp.qq.com" + SMTP_PORT = 465 + SMTP_USER = "your_email@qq.com" + SMTP_PASSWORD = "your_auth_code" # 邮件授权码,非密码 + MAIL_TO = ["boss@company.com", "yourself@company.com"] + + # --- 总结模板 --- + SUMMARY_TEMPLATE = """ +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📅 今日工作总结 · {date} +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +👤 姓名:{name} +🏢 部门:{department} + +📌 今日工作事项: +{work_items} + +📊 工作总结: +{summary} + +✅ 明日计划: +{tomorrow_plan} + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⏰ 生成时间:{generate_time} +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +""" + + +# =========================================== +# 🔧 第二部分:核心功能 +# =========================================== + +class WorkSummaryGenerator: + """工作总结生成器""" + + def __init__(self, name="张三", department="技术部"): + self.name = name + self.department = department + self.today = datetime.date.today() + self.tomorrow = self.today + datetime.timedelta(days=1) + + def get_work_items_from_input(self): + """方式一:手动输入今日工作事项""" + print("\n📝 请输入你今天的工作事项(每行一条,输入空行结束):") + print("(例:完成了XX功能开发 / 修复了XXbug / 参加了XX会议)\n") + items = [] + while True: + line = input(" ➜ ").strip() + if not line: + break + items.append(line) + return items + + def get_work_items_from_file(self, filepath): + """方式二:从文件读取今日工作事项""" + try: + with open(filepath, "r", encoding="utf-8") as f: + items = [line.strip() for line in f if line.strip()] + return items + except FileNotFoundError: + print(f"⚠️ 文件 {filepath} 不存在,请先创建!") + return [] + + def generate_summary(self, work_items): + """生成工作总结(基于规则+模板,你也可以接入AI API)""" + if not work_items: + return "今日暂无工作记录。" + + # 统计信息 + total_items = len(work_items) + + # 简单分类统计(关键词匹配) + categories = { + "开发": 0, "修复": 0, "会议": 0, "文档": 0, + "测试": 0, "沟通": 0, "其他": 0 + } + keywords_map = { + "开发": ["开发", "编码", "编程", "实现", "搭建", "构建"], + "修复": ["修复", "解决", "处理", "修改", "bug", "Bug"], + "会议": ["会议", "评审", "讨论", "沟通会", "晨会", "周会"], + "文档": ["文档", "文档", "方案", "设计", "PPT", "报告"], + "测试": ["测试", "调试", "自测", "联调", "验证"], + "沟通": ["沟通", "对齐", "同步", "协调", "对接"], + } + + for item in work_items: + matched = False + for cat, keywords in keywords_map.items(): + if any(kw in item for kw in keywords): + categories[cat] += 1 + matched = True + break + if not matched: + categories["其他"] += 1 + + # 生成总结文本 + summary_parts = [f"今日共完成 {total_items} 项工作,具体如下:\n"] + + # 分类汇总 + active_cats = {k: v for k, v in categories.items() if v > 0} + if active_cats: + summary_parts.append("📊 工作类型分布:") + for cat, count in active_cats.items(): + bar = "█" * count + summary_parts.append(f" {cat}:{bar} {count}项") + summary_parts.append("") + + # 逐项列出 + summary_parts.append("📋 详细工作记录:") + for i, item in enumerate(work_items, 1): + summary_parts.append(f" {i}. {item}") + + # 评价与建议 + summary_parts.append(f"\n💡 今日小结:今日工作效率{'较高' if total_items >= 5 else '正常'}," + f"建议明日继续保持{'/优化时间管理' if total_items < 3 else ''}。") + + return "\n".join(summary_parts) + + def generate_tomorrow_plan(self): + """生成明日计划(可扩展为从配置文件读取)""" + plan_items = [ + "继续推进当前开发任务", + "同步项目进度", + "代码审查", + ] + return "\n".join(f" {i+1}. {item}" for i, item in enumerate(plan_items)) + + def build_summary_text(self, work_items, summary): + """组装完整的总结文本""" + tomorrow_plan = self.generate_tomorrow_plan() + formatted_items = "\n".join(f" • {item}" for item in work_items) + + now_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + return Config.SUMMARY_TEMPLATE.format( + date=self.today.strftime("%Y年%m月%d日"), + name=self.name, + department=self.department, + work_items=formatted_items if formatted_items else " (暂无记录)", + summary=summary, + tomorrow_plan=tomorrow_plan, + generate_time=now_str + ) + + def run(self): + """主运行流程""" + print(f"\n{'='*50}") + print(f"📅 今日工作总结生成器") + print(f"📆 {self.today.strftime('%Y年%m月%d日')}") + print(f"{'='*50}\n") + + # 1️⃣ 获取工作事项 + if Config.DATA_MODE == "file": + items = self.get_work_items_from_file(Config.DATA_FILE) + else: + items = self.get_work_items_from_input() + + if not items: + print("⚠️ 没有输入任何工作事项,将生成空总结。") + + # 2️⃣ 生成总结 + summary = self.generate_summary(items) + final_text = self.build_summary_text(items, summary) + + # 3️⃣ 本地保存 + self.save_to_file(final_text) + + # 4️⃣ 推送 + pusher = MessagePusher() + pusher.push(final_text) + + return final_text + + def save_to_file(self, content): + """保存到本地文件""" + filename = f"工作总结_{self.today.strftime('%Y%m%d')}.md" + with open(filename, "w", encoding="utf-8") as f: + f.write(content) + print(f"\n✅ 已保存到本地文件:{os.path.abspath(filename)}") + + +# =========================================== +# 📨 第三部分:消息推送 +# =========================================== + +class MessagePusher: + """消息推送器""" + + def push(self, content): + """根据配置推送消息到各个渠道""" + for channel in Config.PUSH_CHANNELS: + if channel == "wecom": + self.push_wecom(content) + elif channel == "dingtalk": + self.push_dingtalk(content) + elif channel == "email": + self.push_email(content) + else: + print(f"⚠️ 未知推送渠道: {channel}") + + def push_wecom(self, content): + """推送到企业微信机器人""" + url = Config.WECOM_WEBHOOK + if "YOUR_KEY_HERE" in url: + print("⚠️ 跳过企业微信推送:未配置Webhook地址") + return + + data = { + "msgtype": "markdown", + "markdown": {"content": content} + } + try: + resp = requests.post(url, json=data, timeout=10) + result = resp.json() + if result.get("errcode") == 0: + print("✅ 企业微信推送成功!") + else: + print(f"⚠️ 企业微信推送失败: {result}") + except Exception as e: + print(f"⚠️ 企业微信推送异常: {e}") + + def push_dingtalk(self, content): + """推送到钉钉机器人""" + url = Config.DINGTALK_WEBHOOK + if "YOUR_TOKEN_HERE" in url: + print("⚠️ 跳过钉钉推送:未配置Webhook地址") + return + + data = { + "msgtype": "markdown", + "markdown": { + "title": "今日工作总结", + "text": content + } + } + try: + resp = requests.post(url, json=data, timeout=10) + result = resp.json() + if result.get("errcode") == 0: + print("✅ 钉钉推送成功!") + else: + print(f"⚠️ 钉钉推送失败: {result}") + except Exception as e: + print(f"⚠️ 钉钉推送异常: {e}") + + def push_email(self, content): + """通过邮件推送""" + if "your_email" in Config.SMTP_USER: + print("⚠️ 跳过邮件推送:未配置邮箱信息") + return + + try: + msg = MIMEMultipart() + msg["From"] = Config.SMTP_USER + msg["To"] = ", ".join(Config.MAIL_TO) + msg["Subject"] = f"今日工作总结 - {datetime.date.today().strftime('%Y-%m-%d')}" + + msg.attach(MIMEText(content, "plain", "utf-8")) + + with smtplib.SMTP_SSL(Config.SMTP_SERVER, Config.SMTP_PORT) as server: + server.login(Config.SMTP_USER, Config.SMTP_PASSWORD) + server.sendmail(Config.SMTP_USER, Config.MAIL_TO, msg.as_string()) + + print("✅ 邮件推送成功!") + except Exception as e: + print(f"⚠️ 邮件推送异常: {e}") + + +# =========================================== +# 🚀 第四部分:程序入口 +# =========================================== + +def setup_windows_task(): + """生成Windows任务计划程序导入脚本""" + script_path = os.path.abspath(sys.argv[0]) + python_path = sys.executable + + cmd = f'''@echo off +echo ======================================== +echo 设置每日9点自动运行工作总结脚本 +echo ======================================== + +REM 请以管理员身份运行此.bat文件 + +schtasks /create /tn "每日工作总结" /tr "{python_path} {script_path}" /sc daily /st 09:00 /f + +if %errorlevel%==0 ( + echo ✅ 定时任务创建成功!每天早上9:00自动运行。 +) else ( + echo ❌ 创建失败,请尝试以管理员身份运行。 +) + +pause +''' + + bat_path = "setup_windows_task.bat" + with open(bat_path, "w", encoding="utf-8") as f: + f.write(cmd) + print(f"\n📁 Windows定时任务脚本已生成:{os.path.abspath(bat_path)}") + print(" → 右键点击该文件,选择「以管理员身份运行」即可设置定时任务!") + + +def setup_linux_cron(): + """输出Linux crontab配置说明""" + script_path = os.path.abspath(sys.argv[0]) + python_path = sys.executable + + print("\n" + "="*50) + print("🐧 Linux/macOS 定时任务设置(crontab)") + print("="*50) + print(f"在终端中执行以下命令添加定时任务:") + print(f"\n crontab -e") + print(f"\n然后添加一行:") + print(f"\n 0 9 * * * {python_path} {script_path}") + print(f"\n保存退出即可!📌") + + +if __name__ == "__main__": + import sys + + # 检查是否有命令行参数 + if len(sys.argv) > 1: + arg = sys.argv[1].lower() + if arg == "--setup-win": + setup_windows_task() + sys.exit(0) + elif arg == "--setup-linux": + setup_linux_cron() + sys.exit(0) + + print(""" +╔══════════════════════════════════════════╗ +║ 🍊 每日工作总结生成器 ║ +║ ║ +║ 用法: ║ +║ python daily_work_summary.py ║ +║ python daily_work_summary.py --setup-win ║ +║ python daily_work_summary.py --setup-linux ║ +╚══════════════════════════════════════════╝ +""") + + # 运行主程序 + generator = WorkSummaryGenerator(name="张三", department="技术部") + result = generator.run() + + print("\n" + "="*50) + print("🎉 工作总结生成完成!") + print("="*50) + + # 生成本地定时任务设置脚本 + if sys.platform == "win32": + setup_windows_task() + else: + setup_linux_cron() + + print("\n💡 小提示:修改 config.py 中的配置可自定义推送到不同渠道~") diff --git a/debug_switch_node.py b/scripts/tools/debug_switch_node.py similarity index 100% rename from debug_switch_node.py rename to scripts/tools/debug_switch_node.py diff --git a/publish_agent.py b/scripts/tools/publish_agent.py similarity index 100% rename from publish_agent.py rename to scripts/tools/publish_agent.py diff --git a/run_agent_test_cases.py b/scripts/tools/run_agent_test_cases.py similarity index 100% rename from run_agent_test_cases.py rename to scripts/tools/run_agent_test_cases.py diff --git a/开放端口脚本.sh b/scripts/tools/开放端口脚本.sh old mode 100755 new mode 100644 similarity index 100% rename from 开放端口脚本.sh rename to scripts/tools/开放端口脚本.sh diff --git a/执行数据库迁移.sh b/scripts/tools/执行数据库迁移.sh old mode 100755 new mode 100644 similarity index 100% rename from 执行数据库迁移.sh rename to scripts/tools/执行数据库迁移.sh diff --git a/start_aiagent.ps1 b/start_aiagent.ps1 deleted file mode 100644 index 948ac18..0000000 --- a/start_aiagent.ps1 +++ /dev/null @@ -1,90 +0,0 @@ -param( - [int]$ApiPort = 8037, - [int]$FallbackApiPort = 8041, - [int]$FrontendPort = 3001 -) - -$ErrorActionPreference = "Stop" -$RepoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path -$Backend = Join-Path $RepoRoot "backend" -$Frontend = Join-Path $RepoRoot "frontend" -$RedisDir = Join-Path $Backend "redis" -$RedisExe = Join-Path $RedisDir "redis-server.exe" -$RedisCli = Join-Path $RedisDir "redis-cli.exe" - -function Test-PortListening([int]$Port) { - $line = netstat -ano | Select-String ":$Port\s+.*LISTENING" | Select-Object -First 1 - return [bool]$line -} - -function Ensure-Redis { - if (Test-PortListening 6379) { - Write-Host '[OK] Redis already listening on 6379' -ForegroundColor Green - return - } - if (-not (Test-Path $RedisExe)) { - throw "Redis executable not found: $RedisExe" - } - Write-Host '[RUN] Starting Redis on 6379 ...' -ForegroundColor Yellow - Start-Process -FilePath $RedisExe -ArgumentList "--port 6379" -WorkingDirectory $RedisDir | Out-Null - Start-Sleep -Seconds 2 - if (-not (Test-PortListening 6379)) { - throw "Redis failed: port 6379 not listening" - } - if (Test-Path $RedisCli) { - & $RedisCli -p 6379 ping | Out-Null - } - Write-Host '[OK] Redis started' -ForegroundColor Green -} - -function Resolve-ApiPort { - if (-not (Test-PortListening $ApiPort)) { - return $ApiPort - } - Write-Host ('[WARN] Port {0} is occupied, switching to {1}' -f $ApiPort, $FallbackApiPort) -ForegroundColor Yellow - if (Test-PortListening $FallbackApiPort) { - throw "Ports $ApiPort and $FallbackApiPort are in use; free one first" - } - return $FallbackApiPort -} - -Write-Host '== AIAgent one-click start ==' -ForegroundColor Cyan -Write-Host "Repo: $RepoRoot" - -Ensure-Redis -$RealApiPort = Resolve-ApiPort -$ApiBase = "http://127.0.0.1:$RealApiPort" - -Write-Host ('[RUN] Starting backend API on {0} ...' -f $RealApiPort) -ForegroundColor Yellow -Start-Process powershell -ArgumentList @( - "-NoExit", - "-Command", - "cd '$Backend'; .\venv\Scripts\Activate.ps1; python -m uvicorn app.main:app --host 0.0.0.0 --port $RealApiPort" -) - -Write-Host '[RUN] Starting Celery worker ...' -ForegroundColor Yellow -Start-Process powershell -ArgumentList @( - "-NoExit", - "-Command", - "cd '$Backend'; .\venv\Scripts\Activate.ps1; python -m celery -A app.core.celery_app worker --loglevel=info --pool=threads --concurrency=8" -) - -Write-Host '[RUN] Starting Celery Beat (scheduler) ...' -ForegroundColor Yellow -Start-Process powershell -ArgumentList @( - "-NoExit", - "-Command", - "cd '$Backend'; .\venv\Scripts\Activate.ps1; python -m celery -A app.core.celery_app beat --loglevel=info" -) - -Write-Host ('[RUN] Starting frontend on {0} (proxy -> {1}) ...' -f $FrontendPort, $ApiBase) -ForegroundColor Yellow -Start-Process powershell -ArgumentList @( - "-NoExit", - "-Command", - "`$env:AIAGENT_API_PROXY='$ApiBase'; cd '$Frontend'; pnpm dev --port $FrontendPort" -) - -Write-Host "" -Write-Host '[DONE] Start commands issued (check new PowerShell windows)' -ForegroundColor Green -Write-Host "Frontend: http://localhost:$FrontendPort" -ForegroundColor Cyan -Write-Host "API docs: $ApiBase/docs" -ForegroundColor Cyan -Write-Host "Redis: 127.0.0.1:6379" -ForegroundColor Cyan diff --git a/start_aiagent_background.ps1 b/start_aiagent_background.ps1 deleted file mode 100644 index b0aded8..0000000 --- a/start_aiagent_background.ps1 +++ /dev/null @@ -1,111 +0,0 @@ -# 静默后台启动脚本(无弹窗,日志写文件) -# 用于 Windows 任务计划程序开机自启 - -$ErrorActionPreference = "Continue" -$RepoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path -$Backend = Join-Path $RepoRoot "backend" -$Frontend = Join-Path $RepoRoot "frontend" -$RedisDir = Join-Path $Backend "redis" -$RedisExe = Join-Path $RedisDir "redis-server.exe" -$RedisCli = Join-Path $RedisDir "redis-cli.exe" -$LogDir = Join-Path $RepoRoot "logs" -$LogFile = Join-Path $LogDir "autostart_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" - -if (-not (Test-Path $LogDir)) { - New-Item -ItemType Directory -Path $LogDir -Force | Out-Null -} - -function Write-Log($msg) { - $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss" - "$ts $msg" | Out-File -Append -FilePath $LogFile -Encoding UTF8 -} - -Write-Log "========== AIAgent background start ==========" -Write-Log "Repo: $RepoRoot" - -# ── 1) Redis ────────────────────────────────── -$redisPort = 6379 -$redisListening = netstat -ano | Select-String ":$redisPort\s+.*LISTENING" -if ($redisListening) { - Write-Log "Redis already listening on $redisPort" -} else { - if (-not (Test-Path $RedisExe)) { - Write-Log "ERROR: Redis not found at $RedisExe" - } else { - Write-Log "Starting Redis on $redisPort ..." - $redisProc = Start-Process -FilePath $RedisExe ` - -ArgumentList "--port $redisPort" ` - -WorkingDirectory $RedisDir ` - -WindowStyle Hidden ` - -PassThru - Start-Sleep -Seconds 2 - if (Test-Path $RedisCli) { - & $RedisCli -p $redisPort ping 2>&1 | Out-File -Append $LogFile -Encoding UTF8 - } - Write-Log "Redis PID=$($redisProc.Id) started" - } -} - -# ── 2) Backend API ──────────────────────────── -$apiPort = 8037 -$apiListening = netstat -ano | Select-String ":$apiPort\s+.*LISTENING" -if ($apiListening) { - Write-Log "Backend API already listening on $apiPort" -} else { - Write-Log "Starting backend API on $apiPort ..." - $apiLog = Join-Path $LogDir "api.log" - Start-Process powershell ` - -WindowStyle Hidden ` - -ArgumentList @( - "-NoProfile", "-ExecutionPolicy", "Bypass", - "-Command", - "Set-Location '$Backend'; .\venv\Scripts\Activate.ps1; python -m uvicorn app.main:app --host 0.0.0.0 --port $apiPort 2>&1 | Out-File -Append '$apiLog' -Encoding UTF8" - ) - Write-Log "Backend API starting (log: $apiLog)" -} - -# ── 3) Celery Worker ────────────────────────── -Write-Log "Starting Celery worker ..." -$celeryLog = Join-Path $LogDir "celery.log" -Start-Process powershell ` - -WindowStyle Hidden ` - -ArgumentList @( - "-NoProfile", "-ExecutionPolicy", "Bypass", - "-Command", - "Set-Location '$Backend'; .\venv\Scripts\Activate.ps1; python -m celery -A app.core.celery_app worker --loglevel=info --pool=threads --concurrency=8 2>&1 | Out-File -Append '$celeryLog' -Encoding UTF8" - ) -Write-Log "Celery worker starting (log: $celeryLog)" - -# ── 3.5) Celery Beat (scheduler) ────────────── -Write-Log "Starting Celery Beat ..." -$beatLog = Join-Path $LogDir "celery_beat.log" -Start-Process powershell ` - -WindowStyle Hidden ` - -ArgumentList @( - "-NoProfile", "-ExecutionPolicy", "Bypass", - "-Command", - "Set-Location '$Backend'; .\venv\Scripts\Activate.ps1; python -m celery -A app.core.celery_app beat --loglevel=info 2>&1 | Out-File -Append '$beatLog' -Encoding UTF8" - ) -Write-Log "Celery Beat starting (log: $beatLog)" - -# ── 4) Frontend (pnpm dev) ──────────────────── -$frontendPort = 3001 -$frontendListening = netstat -ano | Select-String ":$frontendPort\s+.*LISTENING" -if ($frontendListening) { - Write-Log "Frontend already listening on $frontendPort" -} else { - Write-Log "Starting frontend on $frontendPort ..." - $frontendLog = Join-Path $LogDir "frontend.log" - Start-Process powershell ` - -WindowStyle Hidden ` - -ArgumentList @( - "-NoProfile", "-ExecutionPolicy", "Bypass", - "-Command", - "`$env:AIAGENT_API_PROXY='http://127.0.0.1:$apiPort'; Set-Location '$Frontend'; pnpm dev --port $frontendPort 2>&1 | Out-File -Append '$frontendLog' -Encoding UTF8" - ) - Write-Log "Frontend starting (log: $frontendLog)" -} - -Write-Log "========== All services issued ==========" -Write-Log "Frontend: http://localhost:$frontendPort" -Write-Log "API docs: http://127.0.0.1:$apiPort/docs" diff --git a/stop_aiagent.ps1 b/stop_aiagent.ps1 deleted file mode 100644 index 792419d..0000000 --- a/stop_aiagent.ps1 +++ /dev/null @@ -1,65 +0,0 @@ -$ErrorActionPreference = "SilentlyContinue" - -Write-Host "== AIAgent stop ==" -ForegroundColor Cyan - -function Get-PidsListeningOnPort([int]$Port) { - $pids = New-Object System.Collections.Generic.HashSet[int] - try { - netstat -ano | ForEach-Object { - $ln = $_.Trim() - if ($ln -notmatch "LISTENING") { return } - # 匹配 :8037 或 :3001 等端口后的 LISTENING 行末 PID - if ($ln -match ":$Port\s+.*LISTENING\s+(\d+)\s*$") { - [void]$pids.Add([int]$Matches[1]) - } - } - } catch { } - return @($pids) -} - -function Stop-OnPorts([int[]]$ports, [string]$name) { - $all = New-Object System.Collections.Generic.HashSet[int] - foreach ($p in $ports) { - foreach ($pid in (Get-PidsListeningOnPort $p)) { - [void]$all.Add($pid) - } - } - if ($all.Count -eq 0) { - Write-Host "[SKIP] ${name}: no listener on ports $($ports -join ',')" -ForegroundColor DarkGray - return - } - foreach ($pid in $all) { - if ($pid -le 4) { continue } - try { - Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue - Write-Host "[OK] stopped ${name} PID=$pid" -ForegroundColor Green - } catch { - Write-Host "[WARN] failed to stop ${name} PID=$pid" -ForegroundColor Yellow - } - } -} - -# 后端 API(8037 / 8041 备用) -Stop-OnPorts @(8037, 8041) "backend-api" - -# 前端 Vite(3001) -Stop-OnPorts @(3001) "frontend-dev" - -# Redis(6379,仅当监听在本地开发端口时结束;若与其它项目共用请谨慎) -Stop-OnPorts @(6379) "redis" - -Start-Sleep -Milliseconds 600 - -Write-Host "" -Write-Host "Port check:" -ForegroundColor Cyan -foreach ($port in 3001, 8037, 8041, 6379) { - $line = netstat -ano | Select-String ":$port\s+.*LISTENING" | Select-Object -First 1 - if ($line) { - Write-Host " - ${port}: LISTEN" -ForegroundColor Yellow - } else { - Write-Host " - ${port}: free" -ForegroundColor Green - } -} - -Write-Host "" -Write-Host "DONE: stop script finished" -ForegroundColor Green