diff --git a/apps/jingrow/jingrow/__init__.py b/apps/jingrow/jingrow/__init__.py index 8ebac57..e249374 100644 --- a/apps/jingrow/jingrow/__init__.py +++ b/apps/jingrow/jingrow/__init__.py @@ -6,12 +6,17 @@ import functools import inspect import logging import os +import threading from jingrow.model.page import Page from jingrow.config import Config from jingrow.utils.jingrow_api import upload_file_to_jingrow _local = {} +# 【通用执行上下文标记】用于标记当前调用链的来源,防止循环触发等场景 +# 使用 thread-local 存储,轻量且精确(同一线程内有效) +_update_source_context = threading.local() + # 统一 Jingrow 日志记录器(仅为本模块及调用方提供最小可用输出,不修改全局 root logger) _root_logger = logging.getLogger("jingrow") @@ -62,6 +67,38 @@ def update_pg(pagetype: str, name: str, data: Dict[str, Any]): return updated +def skip_hooks(source: str = "agent"): + """ + 设置上下文标记,表示当前调用链来自指定来源(如智能体执行), + 用于跳过某些钩子防止循环触发等场景。 + """ + try: + _update_source_context.source = source + except Exception: + pass + + +def restore_hooks(): + """ + 清除上下文标记,恢复正常的钩子执行。 + """ + try: + if hasattr(_update_source_context, 'source'): + delattr(_update_source_context, 'source') + except Exception: + pass + + +def get_hook_source() -> Optional[str]: + """ + 获取当前调用链的来源标识,用于判断是否跳过某些钩子。 + """ + try: + return getattr(_update_source_context, 'source', None) + except Exception: + return None + + def delete_pg(pagetype: str, name: str) -> bool: pg = Page(pagetype) res = pg.delete(name) diff --git a/apps/jingrow/jingrow/ai/nodes/update_record/update_record.py b/apps/jingrow/jingrow/ai/nodes/update_record/update_record.py index b5aec2c..64cf3ef 100644 --- a/apps/jingrow/jingrow/ai/nodes/update_record/update_record.py +++ b/apps/jingrow/jingrow/ai/nodes/update_record/update_record.py @@ -160,7 +160,13 @@ def execute(context=None, inputs=None, config=None): # 调用 Jingrow 更新记录(仅当有更新) if updated: - server_record = jingrow.update_pg(record_type, record_name, mock_record) + # 【防无限循环】设置上下文标记,表示当前更新来自智能体执行 + # 这样在 on_update 钩子中就不会再次触发智能体,防止循环 + jingrow.skip_hooks("agent") + try: + server_record = jingrow.update_pg(record_type, record_name, mock_record) + finally: + jingrow.restore_hooks() if server_record is False: return {"success": False, "error": "更新记录失败"} if server_record is True: diff --git a/apps/jingrow/jingrow/ai/pagetype/local_ai_agent/__init__.py b/apps/jingrow/jingrow/ai/pagetype/local_ai_agent/__init__.py index d072c84..b132070 100644 --- a/apps/jingrow/jingrow/ai/pagetype/local_ai_agent/__init__.py +++ b/apps/jingrow/jingrow/ai/pagetype/local_ai_agent/__init__.py @@ -38,12 +38,17 @@ def run_agent(pg=None, method=None, event=None, page=None, **kwargs): """本地版 Local Ai Agent 事件驱动机制。 - 支持按 pagetype 与 module 分组、合并去重 - condition 支持 Jinja2 表达式 - - 使用本地任务队列,避免链式重复触发 + - 使用上下文标记防止无限循环 """ # 兼容多种调用方式 pg = pg or page method = event or method or kwargs.get("event") + # 【防无限循环】检查上下文标记:如果当前调用来自智能体执行,跳过 + # 使用轻量级 thread-local 上下文标记,防止循环触发 + if jingrow.get_hook_source() == "agent": + return + # 支持的事件 event_list = [ "on_update", @@ -91,7 +96,6 @@ def run_agent(pg=None, method=None, event=None, page=None, **kwargs): merged_agents = list({a.get("name"): a for a in agents_by_pagetype + agents_by_module}.values()) if not merged_agents: - jingrow.log_error("LocalAIAgent", "No Local Ai Agents matched for this page") return # 一次性准备渲染上下文(仅当至少一个 agent 有 condition 时才构建) @@ -112,6 +116,7 @@ def run_agent(pg=None, method=None, event=None, page=None, **kwargs): for agent in merged_agents: if agent.get("event_type") == method: + agent_name = agent.get("name") trigger = False if not agent.get("condition"): @@ -131,7 +136,7 @@ def run_agent(pg=None, method=None, event=None, page=None, **kwargs): trigger = False if trigger: - enqueue_local_ai_agent(pg, agent.get('name')) + enqueue_local_ai_agent(pg, agent_name) def enqueue_local_ai_agent(pg, agent_name): """后台实际入队执行 Local Ai Agent。"""