From 6a5926ba41649ff248efc7d206ea9eb6c99c4d4f Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Tue, 2 Jun 2026 20:08:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E4=BB=A5=E6=B8=85=E7=90=86=E5=92=8C=E9=87=8D?= =?UTF-8?q?=E5=90=AF=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 14 +++++- .../yuxi/agents/buildin/chatbot/graph.py | 2 +- backend/package/yuxi/agents/context.py | 10 +++++ .../yuxi/repositories/agent_repository.py | 44 +++++++++++++++++++ backend/server/utils/lifespan.py | 4 +- 5 files changed, 71 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 30c9f9fa..8e60d449 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -.PHONY: up up-lite down logs lint format seed +.PHONY: up up-lite down logs lint format seed reset PYTEST_ARGS ?= @@ -13,6 +13,18 @@ up: down: docker compose down +reset: + @if [ ! -f .env ]; then \ + echo "Error: .env file not found. Please create it from .env.template"; \ + exit 1; \ + fi + docker compose down + rm -rf docker/volumes + docker compose up -d + @echo "Waiting for api to be ready..." + @until docker compose exec -T api true >/dev/null 2>&1; do sleep 2; done + $(MAKE) seed + up-lite: @if [ ! -f .env ]; then \ echo "Error: .env file not found. Please create it from .env.template"; \ diff --git a/backend/package/yuxi/agents/buildin/chatbot/graph.py b/backend/package/yuxi/agents/buildin/chatbot/graph.py index 0e8948e5..e3624513 100644 --- a/backend/package/yuxi/agents/buildin/chatbot/graph.py +++ b/backend/package/yuxi/agents/buildin/chatbot/graph.py @@ -48,7 +48,7 @@ async def _build_middlewares(context): summary_middleware, TodoListMiddleware(system_prompt=TODO_MID_PROMPT), PatchToolCallsMiddleware(), - ModelRetryMiddleware(), + ModelRetryMiddleware(max_retries=getattr(context, "model_retry_times", 2)), ] ) return middlewares diff --git a/backend/package/yuxi/agents/context.py b/backend/package/yuxi/agents/context.py index 54f2575e..713250df 100644 --- a/backend/package/yuxi/agents/context.py +++ b/backend/package/yuxi/agents/context.py @@ -196,6 +196,16 @@ class BaseContext: }, ) + model_retry_times: int = field( + default=2, + metadata={ + "name": "模型重试次数", + "description": "模型调用失败时的最大重试次数,默认值为 2。", + "type": "number", + "auth": "admin", + }, + ) + @classmethod def get_configurable_items(cls, user_role: str | None = None): """实现一个可配置的参数列表,在 UI 上配置时使用""" diff --git a/backend/package/yuxi/repositories/agent_repository.py b/backend/package/yuxi/repositories/agent_repository.py index 1bed386c..a5bf040e 100644 --- a/backend/package/yuxi/repositories/agent_repository.py +++ b/backend/package/yuxi/repositories/agent_repository.py @@ -16,6 +16,24 @@ DEFAULT_AGENT_BACKEND_ID = "ChatbotAgent" SUB_AGENT_BACKEND_ID = "SubAgentBackend" DEFAULT_AGENT_DESCRIPTION = "基础的对话机器人,可以回答问题,可在配置中启用需要的工具。" DEFAULT_SHARE_CONFIG = {"access_level": "global", "department_ids": [], "user_uids": []} + +WEB_SEARCH_AGENT_SLUG = "web-search" +WEB_SEARCH_AGENT_NAME = "网页检索" +WEB_SEARCH_AGENT_DESCRIPTION = "围绕检索目标持续搜索网页,返回带引用来源的摘要资料。" +WEB_SEARCH_SYSTEM_PROMPT = """你是「网页检索」子智能体,由主智能体通过 task 工具调用,专注于面向目标的网页信息检索。 + +你的职责:围绕调用方给定的检索目标,使用网页搜索工具持续检索,直到收集到足以回答目标的信息。 + +工作方式: +1. 拆解目标,确定需要检索的关键问题与检索词。 +2. 多轮调用网页搜索工具:依据上一轮结果调整检索词、补充遗漏角度、交叉验证关键事实,直到信息充分或确认无法获取更多有效信息。 +3. 优先采信权威、时效性强且彼此印证的来源;对存在冲突的信息要说明分歧。 + +输出要求: +- 返回一份结构化的摘要资料,按主题或要点组织。 +- 每条关键结论后使用 $INDEX 标注引用来源,$INDEX 从 1 开始递增。 +- 在结尾汇总「参考来源」列表,逐条列出标题与 URL。 +- 不要编造来源或链接;无法验证的信息要明确标注。""" ACCESS_LEVELS = {"global", "department", "user"} ADMIN_ROLES = {"admin", "superadmin"} @@ -159,6 +177,32 @@ class AgentRepository: await self.db.refresh(agent) return agent + async def ensure_web_search_subagent(self, *, created_by: str | None = None) -> Agent: + agent = await self.get_by_slug(WEB_SEARCH_AGENT_SLUG) + if agent: + return agent + + agent = Agent( + slug=WEB_SEARCH_AGENT_SLUG, + backend_id=SUB_AGENT_BACKEND_ID, + name=WEB_SEARCH_AGENT_NAME, + description=WEB_SEARCH_AGENT_DESCRIPTION, + icon=None, + pics=[], + config_json={"context": {"system_prompt": WEB_SEARCH_SYSTEM_PROMPT}}, + share_config=DEFAULT_SHARE_CONFIG.copy(), + is_default=False, + is_subagent=True, + created_by=created_by, + updated_by=created_by, + created_at=utc_now_naive(), + updated_at=utc_now_naive(), + ) + self.db.add(agent) + await self.db.commit() + await self.db.refresh(agent) + return agent + async def list_visible(self, *, user: User, include_subagents: bool = False) -> list[Agent]: stmt = select(Agent) if not include_subagents: diff --git a/backend/server/utils/lifespan.py b/backend/server/utils/lifespan.py index 1e7f04cb..1da21cbb 100644 --- a/backend/server/utils/lifespan.py +++ b/backend/server/utils/lifespan.py @@ -45,7 +45,9 @@ async def lifespan(app: FastAPI): from yuxi.repositories.agent_repository import AgentRepository async with pg_manager.get_async_session_context() as session: - await AgentRepository(session).ensure_default_agent() + repository = AgentRepository(session) + await repository.ensure_default_agent() + await repository.ensure_web_search_subagent() except Exception as e: logger.error(f"Failed to ensure default agent during startup: {e}")