Merge branch 'ji' into master (操作日志 Y-Log 上报)

# Conflicts:
#	CLAUDE.md
This commit is contained in:
Administrator committed 2026-06-23 19:22:53 +08:00
commit 053d3e300e
35 files changed
+1182 -125

No files matched your search

@@ -26,3 +26,10 @@ async def get_task_deeplink_config() -> dict:
"""返回 ``config.yaml`` 的 ``task_deeplink`` 段(含 test/delete_test/各 URL/mock)。"""
config = get_app_config()
return config.task_deeplink.model_dump()
@router.get("/y-log")
async def get_y_log_config() -> dict:
"""返回 ``config.yaml`` 的 ``y_log`` 段(enabled / write_url),无密钥,公开只读。"""
config = get_app_config()
return config.y_log.model_dump()
@@ -314,7 +314,7 @@ def build_run_config(
the LangGraph Platform-compatible HTTP API and the IM channel path behave
identically.
"""
config: dict[str, Any] = {"recursion_limit": 100}
config: dict[str, Any] = {"recursion_limit": 250}
if request_config:
# LangGraph >= 0.6.0 introduced ``context`` as the preferred way to
# pass thread-level data and rejects requests that include both
+4 -1
View File
@@ -431,11 +431,14 @@ task_deeplink:
delete_test: true # 删除接口走 mock.delete 假数据(该接口尚未开发完)
action_test: true # 行动→任务解析走 mock.action_detail 假数据(xdfx 行动id→任务id)
purpose_detail_url: "https://ch1.b.uat.4.cn/consumer/taskAnalyseSearch/getTaskPurposeDetail"
delete_url: "https://ch1.b.uat.4.cn/consumer/taskAnalyseSearch/delete"
# rwfx 与 xdfx 共用同一删除接口:rwfx 追加 ?taskId=(不传 actionId);xdfx 追加 ?actionId=(不传 taskId,直接不出现)。
delete_url: "https://ch1.b.uat.4.cn/bwjt/dropSixOrSevenData"
treemap_edit_url: "https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-mode-treemap-full-edit"
treemap_next_url: "https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-mode-treemap-full-next"
# xdfx 深链传入的是「行动id」,先调该接口(追加 /{actionId})拿 data.taskId 解析出真正「任务id」。
action_detail_url: "https://ch1.b.uat.4.cn/consumer/plan/action"
# xdfx 删除前「行动历史是否存在」预检接口(追加 ?actionId=);地址待提供,留空则前端视作无历史、不阻断删除。
action_history_url: ""
# rwfx 目的树面板「3q详情」链接的目标页(地址待定,留空则前端提示未配置);跳转追加 ?id=任务id。
detail_3q_url: ""
mock:
@@ -28,12 +28,12 @@ from langgraph.runtime import Runtime
logger = logging.getLogger(__name__)
# Defaults — can be overridden via constructor
_DEFAULT_WARN_THRESHOLD = 3 # inject warning after 3 identical calls
_DEFAULT_HARD_LIMIT = 5 # force-stop after 5 identical calls
_DEFAULT_WINDOW_SIZE = 20 # track last N tool calls
_DEFAULT_WARN_THRESHOLD = 8 # inject warning after 8 identical calls
_DEFAULT_HARD_LIMIT = 20 # force-stop after 20 identical calls
_DEFAULT_WINDOW_SIZE = 60 # track last N tool calls (must be >= hard_limit)
_DEFAULT_MAX_TRACKED_THREADS = 100 # LRU eviction limit
_DEFAULT_TOOL_FREQ_WARN = 30 # warn after 30 calls to the same tool type
_DEFAULT_TOOL_FREQ_HARD_LIMIT = 50 # force-stop after 50 calls to the same tool type
_DEFAULT_TOOL_FREQ_WARN = 80 # warn after 80 calls to the same tool type
_DEFAULT_TOOL_FREQ_HARD_LIMIT = 120 # force-stop after 120 calls to the same tool type
def _normalize_tool_call_args(raw_args: object) -> tuple[dict, str | None]:
@@ -142,19 +142,19 @@ class LoopDetectionMiddleware(AgentMiddleware[AgentState]):
Args:
warn_threshold: Number of identical tool call sets before injecting
a warning message. Default: 3.
a warning message. Default: 8.
hard_limit: Number of identical tool call sets before stripping
tool_calls entirely. Default: 5.
tool_calls entirely. Default: 20.
window_size: Size of the sliding window for tracking calls.
Default: 20.
Default: 60.
max_tracked_threads: Maximum number of threads to track before
evicting the least recently used. Default: 100.
tool_freq_warn: Number of calls to the same tool *type* (regardless
of arguments) before injecting a frequency warning. Catches
cross-file read loops that hash-based detection misses.
Default: 30.
Default: 80.
tool_freq_hard_limit: Number of calls to the same tool type before
forcing a stop. Default: 50.
forcing a stop. Default: 120.
"""
def __init__(
@@ -39,6 +39,7 @@ from deerflow.config.token_usage_config import TokenUsageConfig
from deerflow.config.tool_config import ToolConfig, ToolGroupConfig
from deerflow.config.tool_search_config import ToolSearchConfig, load_tool_search_config_from_dict
from deerflow.config.vision_config import VisionConfig
from deerflow.config.y_log_config import YLogConfig
load_dotenv()
@@ -141,6 +142,7 @@ class AppConfig(BaseModel):
knowledge: KnowledgeConfig = Field(default_factory=KnowledgeConfig, description="Product knowledge base (obsidian-wiki compatible vault) configuration")
knowledge_ingest: KnowledgeIngestConfig = Field(default_factory=KnowledgeIngestConfig, description="External knowledge-ingest endpoint configuration")
task_deeplink: TaskDeeplinkConfig = Field(default_factory=TaskDeeplinkConfig, description="任务深链(rwfx)目的树闸门与跳转地址配置(前端经 /api/public/config/task-deeplink 读取)")
y_log: YLogConfig = Field(default_factory=YLogConfig, description="操作日志上报配置(前端经 /api/public/config/y-log 读取)")
@classmethod
def resolve_config_path(cls, config_path: str | None = None) -> Path:
@@ -8,8 +8,13 @@
- ``test`` → 目的树查询(``purpose_detail_url``)走 ``mock.purpose_detail`` 假数据;
同时 rwfx 进入「3q 是否完成」预检(``selectSqReport``,外部接口不可用)走 ``mock.sq_report`` 假数据。
- ``delete_test`` → 删除接口(``delete_url``)走 ``mock.delete`` 假数据(该接口尚未开发完时打开)。
rwfx 与 xdfx 共用同一个删除接口 ``dropSixOrSevenData``:rwfx 传 ``?taskId=``、xdfx 传 ``?actionId=``
(另一参数直接不传,不出现在 URL 里)。
- ``action_test`` → 行动详情接口(``action_detail_url``)走 ``mock.action_detail`` 假数据。
xdfx 深链传入的是「行动id」,需先调该接口拿 ``data.taskId`` 解析出真正的「任务id」。
``action_history_url`` 为 xdfx 删除前「行动历史是否存在」预检接口,地址待提供:留空则前端视作
「无历史/不查询」,不阻断删除(后续配上真实地址即生效)。
"""
from __future__ import annotations
@@ -44,13 +49,17 @@ class TaskDeeplinkConfig(BaseModel):
default="",
description="行动详情接口基址(xdfx 行动→任务),调用时追加 /{actionId} 路径段;返回 data.taskId 为真正任务id",
)
action_history_url: str = Field(
default="",
description="xdfx「行动历史是否存在」查询完整 URL(删除前预检),调用时追加 ?actionId=;地址待提供,留空则前端视作「无历史/不查询」(不阻断删除)",
)
detail_3q_url: str = Field(
default="",
description="「3q详情」详情页 URL(rwfx 目的树面板的链接),跳转时追加 ?id=(任务id)",
)
delete_url: str = Field(
default="",
description="删除完整 URL,调用时追加 ?taskId=",
description="删除完整 URL(rwfx 与 xdfx 共用同一个删除接口 dropSixOrSevenData):rwfx 调用追加 ?taskId=(不传 actionId),xdfx 调用追加 ?actionId=(不传 taskId)——另一参数直接不出现在 URL 里,不是空值",
)
treemap_edit_url: str = Field(
default="",
@@ -0,0 +1,20 @@
"""操作日志(Y-Log)上报配置。
前端经公开只读接口 ``GET /api/public/config/y-log`` 读取本段(地址 + 开关),
据此决定是否把用户的关键操作上报到外部日志接口。段内只有地址与开关,无密钥,
公开只读。仿 :mod:`deerflow.config.log_system_config` 的写法。
"""
from __future__ import annotations
from pydantic import BaseModel, Field
class YLogConfig(BaseModel):
"""操作日志(Y-Log)上报配置:前端经 /api/public/config/y-log 读取。"""
enabled: bool = Field(default=False, description="是否启用操作日志上报")
write_url: str = Field(
default="",
description="日志上报外部接口完整地址(POST),如 https://ch1.b.uat.4.cn/api/y-log/write",
)
+12 -1
View File
@@ -308,6 +308,14 @@ log_system:
url: "https://your-log-system.example.com"
label: "日志系统"
# ───────────────────────────────────────────────────────────────────────────
# 操作日志(Y-Log)上报:前端直连外部接口记录用户的关键操作(审计/统计)。
# 仅在「token 登录」(存在 token1)时上报;前端经公开只读接口
# GET /api/public/config/y-log 读取本段,改这里即时生效(Gateway mtime 热重载)。
y_log:
enabled: true
write_url: https://ch1.b.uat.4.cn/api/y-log/write
summarization:
# 开关已移到聊天框下方(参考文献下面)的「上下文压缩」按钮,默认关闭。
# 这里 enabled 仅作为非 Web 路径(渠道/定时任务)的后备开关;下面的 trigger/keep
@@ -341,11 +349,14 @@ task_deeplink:
delete_test: true # 删除接口走 mock.delete 假数据(该接口尚未开发完)
action_test: true # 行动→任务解析走 mock.action_detail 假数据(xdfx 行动id→任务id)
purpose_detail_url: "https://ch1.b.uat.4.cn/consumer/taskAnalyseSearch/getTaskPurposeDetail"
delete_url: "https://ch1.b.uat.4.cn/consumer/taskAnalyseSearch/delete"
# rwfx 与 xdfx 共用同一删除接口:rwfx 追加 ?taskId=(不传 actionId);xdfx 追加 ?actionId=(不传 taskId,直接不出现)。
delete_url: "https://ch1.b.uat.4.cn/bwjt/dropSixOrSevenData"
treemap_edit_url: "https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-mode-treemap-full-edit"
treemap_next_url: "https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-mode-treemap-full-next"
# xdfx 深链传入的是「行动id」,先调该接口(追加 /{actionId})拿 data.taskId 解析出真正「任务id」。
action_detail_url: "https://ch1.b.uat.4.cn/consumer/plan/action"
# xdfx 删除前「行动历史是否存在」预检接口(追加 ?actionId=);地址待提供,留空则前端视作无历史、不阻断删除。
action_history_url: ""
# rwfx 目的树面板「3q详情」链接的目标页(地址待定,留空则前端提示未配置);跳转追加 ?id=任务id。
detail_3q_url: ""
mock: