修复更新同一条记录触发智能体无限循环的问题

This commit is contained in:
jingrow 2025-11-01 03:32:45 +08:00
parent 20aedb7347
commit 80a037ad82
3 changed files with 77 additions and 3 deletions

View File

@ -6,12 +6,17 @@ import functools
import inspect import inspect
import logging import logging
import os import os
import threading
from jingrow.model.page import Page from jingrow.model.page import Page
from jingrow.config import Config from jingrow.config import Config
from jingrow.utils.jingrow_api import upload_file_to_jingrow from jingrow.utils.jingrow_api import upload_file_to_jingrow
_local = {} _local = {}
# 【通用执行上下文标记】用于标记当前调用链的来源,防止循环触发等场景
# 使用 thread-local 存储,轻量且精确(同一线程内有效)
_update_source_context = threading.local()
# 统一 Jingrow 日志记录器(仅为本模块及调用方提供最小可用输出,不修改全局 root logger # 统一 Jingrow 日志记录器(仅为本模块及调用方提供最小可用输出,不修改全局 root logger
_root_logger = logging.getLogger("jingrow") _root_logger = logging.getLogger("jingrow")
@ -62,6 +67,62 @@ def update_pg(pagetype: str, name: str, data: Dict[str, Any]):
return updated return updated
def skip_hooks(source: str = "agent"):
"""
设置上下文标记表示当前调用链来自指定来源如智能体执行
用于跳过某些钩子防止循环触发等场景
Args:
source: 来源标识默认为 "agent"
Example:
skip_hooks("agent") # 标记来自智能体
try:
update_pg(...) # 在 on_update 钩子中会检查此标记
finally:
restore_hooks()
"""
try:
_update_source_context.source = source
except Exception:
pass
def restore_hooks():
"""
清除上下文标记恢复正常的钩子执行
Example:
skip_hooks()
try:
update_pg(...)
finally:
restore_hooks()
"""
try:
if hasattr(_update_source_context, 'source'):
delattr(_update_source_context, 'source')
except Exception:
pass
def get_hook_source() -> Optional[str]:
"""
获取当前调用链的来源标识用于判断是否跳过某些钩子
Returns:
来源标识 "agent"如果未设置则返回 None
Example:
if get_hook_source() == "agent":
return # 跳过智能体触发
"""
try:
return getattr(_update_source_context, 'source', None)
except Exception:
return None
def delete_pg(pagetype: str, name: str) -> bool: def delete_pg(pagetype: str, name: str) -> bool:
pg = Page(pagetype) pg = Page(pagetype)
res = pg.delete(name) res = pg.delete(name)

View File

@ -160,7 +160,13 @@ def execute(context=None, inputs=None, config=None):
# 调用 Jingrow 更新记录(仅当有更新) # 调用 Jingrow 更新记录(仅当有更新)
if updated: 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: if server_record is False:
return {"success": False, "error": "更新记录失败"} return {"success": False, "error": "更新记录失败"}
if server_record is True: if server_record is True:

View File

@ -39,11 +39,18 @@ def run_agent(pg=None, method=None, event=None, page=None, **kwargs):
- 支持按 pagetype module 分组合并去重 - 支持按 pagetype module 分组合并去重
- condition 支持 Jinja2 表达式 - condition 支持 Jinja2 表达式
- 使用本地任务队列避免链式重复触发 - 使用本地任务队列避免链式重复触发
- 使用上下文标记防止无限循环
""" """
# 兼容多种调用方式 # 兼容多种调用方式
pg = pg or page pg = pg or page
method = event or method or kwargs.get("event") method = event or method or kwargs.get("event")
# 【防无限循环】检查上下文标记:如果当前调用来自智能体执行,跳过
# 使用轻量级 thread-local 上下文标记,防止循环触发
if jingrow.get_hook_source() == "agent":
# 当前调用链来自智能体执行,跳过,防止循环触发
return
# 支持的事件 # 支持的事件
event_list = [ event_list = [
"on_update", "on_update",
@ -91,7 +98,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()) merged_agents = list({a.get("name"): a for a in agents_by_pagetype + agents_by_module}.values())
if not merged_agents: if not merged_agents:
jingrow.log_error("LocalAIAgent", "No Local Ai Agents matched for this page")
return return
# 一次性准备渲染上下文(仅当至少一个 agent 有 condition 时才构建) # 一次性准备渲染上下文(仅当至少一个 agent 有 condition 时才构建)
@ -112,6 +118,7 @@ def run_agent(pg=None, method=None, event=None, page=None, **kwargs):
for agent in merged_agents: for agent in merged_agents:
if agent.get("event_type") == method: if agent.get("event_type") == method:
agent_name = agent.get("name")
trigger = False trigger = False
if not agent.get("condition"): if not agent.get("condition"):
@ -131,7 +138,7 @@ def run_agent(pg=None, method=None, event=None, page=None, **kwargs):
trigger = False trigger = False
if trigger: 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): def enqueue_local_ai_agent(pg, agent_name):
"""后台实际入队执行 Local Ai Agent。""" """后台实际入队执行 Local Ai Agent。"""