fix(ai写作): 修复内网技能检不出东西(技能目录路径 + 模型 + 兜底 + 开关)
对比主智能体与 AI 写作两份调用日志定位到根因:真实 agent 路径只注入了 SKILL.md 内容、没告诉模型技能在容器内的目录,模型用相对路径跑脚本 (python3 scripts/search)→ No such file。主智能体则 cd 进技能绝对目录再跑。 - _skill_agent:注入 skill.get_container_path() 的绝对目录,要求先 cd 再执行 脚本/用绝对路径(对齐主智能体 skill_view 拿 location 后的行为)。 - researcher:跑技能的 agent 改用用户选定的写作主模型(model_name_fallback=None), 不再用常配成外网的 keyword_model,避免技能 agent 连不上自己的模型。 - researcher:所有技能都 0 条时,最终兜底直连内网 ES 接口(IntranetSearchProvider), 受 ai_writing.intranet_search_url 控制。 - config.yaml:开 researcher_skill_agent=true(内网技能必须走真实运行时), 新增 intranet_search_url / intranet_knowledge_base 兜底配置项。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d8b43e7579
commit
c0305c9269
3 files changed
+71
-2
No files matched your search
@@ -425,6 +425,16 @@ ai_writing:
|
||||
keyword_model: deepseek-chat # 检索词生成专用模型,留空回退写作主模型
|
||||
rank_model: deepseek-chat # 素材排序专用模型,留空回退写作主模型
|
||||
max_materials: 20 # 合并去重后的素材总量上限,范围 [1, 100],默认 20
|
||||
# 用「真实智能体运行时」跑配置技能(read SKILL.md + 全套工具 + 沙箱),让技能的真实
|
||||
# 检索逻辑(调内网接口/MCP/读文件)真正执行,而不是塞进通用 web/ES 检索 provider。
|
||||
# 内网技能必须开 true,否则技能会走通用 provider、连不上内网而报 ConnectError。
|
||||
# 仅对非「知识库检索(knowledge-base-search)」的配置技能生效;失败回退直连检索。
|
||||
researcher_skill_agent: true
|
||||
# 最终兜底:当所有技能都没检索到素材时,直连内网知识库 ES 接口兜底检索(避免素材全空)。
|
||||
# 配上你内网 ES 接口地址即启用;留空则不启用该兜底。
|
||||
# 接口契约:POST {url} 体 {"query","knowledge_base_name","ts":"yes"} → [{"page_content","metadata":{m_title,content,recUuid,m_publish,source1}}]
|
||||
intranet_search_url: "" # 例如 http://10.106.57.180:7863/knowledge_base/search_docs_es
|
||||
intranet_knowledge_base: "samples" # 兜底检索用的知识库名
|
||||
# 四阶段「内置功能型智能体」开关(按阶段灰度)。true = 该阶段节点改为加载
|
||||
# .deer-flow/agents/ai-writing-<stage>/ 的 SOUL.md + config(admin 可在智能体
|
||||
# 管理页编辑人设/模型),false / 缺失 = 走旧的硬编码 prompt 路径。
|
||||
|
||||
+19
-1
@@ -114,7 +114,25 @@ async def run_skill_via_real_agent(
|
||||
logger.warning("skill real-agent[%s]: build tools failed: %s", skill_name, e)
|
||||
return [], f"工具初始化失败: {e}"
|
||||
|
||||
system_prompt = (soul + "\n\n" if soul else "") + _MATERIAL_OUTPUT_CONTRACT
|
||||
# 技能在容器内的目录:注入 SKILL.md 时模型只有内容、不知道脚本在哪,会用相对路径
|
||||
# (python3 scripts/xxx.py)在错误的 CWD 下跑 → No such file。把绝对目录告诉它,让它
|
||||
# 先 cd 进去再执行(与「别的智能体」里 skill_view/read_file 拿到 location 后的行为一致)。
|
||||
try:
|
||||
base_path = getattr(getattr(app_config, "skills", None), "container_path", None) or "/mnt/skills"
|
||||
except Exception: # noqa: BLE001
|
||||
base_path = "/mnt/skills"
|
||||
try:
|
||||
skill_dir = skill.get_container_path(base_path)
|
||||
except Exception: # noqa: BLE001
|
||||
skill_dir = ""
|
||||
skill_loc_hint = (
|
||||
f"\n\n【技能目录(重要)】技能「{skill_name}」在容器内的目录是:{skill_dir}\n"
|
||||
f"- 运行该技能自带的脚本时,**务必先 `cd {skill_dir}` 再执行**"
|
||||
f"(例如 `cd {skill_dir} && python3 scripts/xxx.py ...`),或对脚本用绝对路径;\n"
|
||||
"- **不要用相对路径**(如 `python3 scripts/xxx.py`),否则当前目录不对会报 No such file。"
|
||||
) if skill_dir else ""
|
||||
|
||||
system_prompt = (soul + "\n\n" if soul else "") + _MATERIAL_OUTPUT_CONTRACT + skill_loc_hint
|
||||
|
||||
task_lines = [f"写作意图:{intent or '(无)'}"]
|
||||
if user_query:
|
||||
|
||||
+42
-1
@@ -725,7 +725,11 @@ async def researcher_node(state: AIWritingState, config: RunnableConfig) -> dict
|
||||
user_query=effective_user_query if is_user_query_search else "",
|
||||
config=config,
|
||||
state=state,
|
||||
model_name_fallback=keyword_model,
|
||||
# 跑技能的 agent 用**用户选定的写作主模型**(state.model_name),不要用
|
||||
# keyword_model:那是「检索词生成快模型」,常配成外网模型(如 deepseek-chat
|
||||
# → api.deepseek.com),内网跑技能 agent 会连不上模型本身。传 None 让
|
||||
# resolve_agent_model_name 回退到 state.model_name(用户在表单选的、确定可达)。
|
||||
model_name_fallback=None,
|
||||
max_results=per_skill,
|
||||
timeout_s=_SKILL_SEARCH_TIMEOUT_SECONDS,
|
||||
emit_query=_emit_q,
|
||||
@@ -810,6 +814,43 @@ async def researcher_node(state: AIWritingState, config: RunnableConfig) -> dict
|
||||
# 兜底已拿到素材即止,不再无谓尝试后续技能
|
||||
break
|
||||
|
||||
# ── 最终兜底:技能(含兜底技能)都没检索到 → 直连内网知识库接口(ES)兜一把 ──
|
||||
# 用户诉求:技能因网络/配置连不上而 0 条时,至少用内网 ES 接口直检,避免素材全空。
|
||||
# 需在 config.yaml 的 ai_writing 段配 intranet_search_url 才启用;接口契约见
|
||||
# IntranetSearchProvider(POST {query,knowledge_base_name,ts} → [{page_content,metadata}])。
|
||||
if not new_results:
|
||||
intranet_url = (ai_writing_config.get("intranet_search_url") or "").strip()
|
||||
if intranet_url:
|
||||
from ..search.intranet_stub import IntranetSearchProvider
|
||||
|
||||
_emit_skill_step({
|
||||
"type": "fallback",
|
||||
"message": "技能均未检索到素材,改为直连内网知识库接口兜底检索…",
|
||||
})
|
||||
intranet = IntranetSearchProvider(
|
||||
base_url=intranet_url,
|
||||
knowledge_base_name=ai_writing_config.get("intranet_knowledge_base", "samples"),
|
||||
)
|
||||
fb_keywords = keywords[:keyword_count] or [intent]
|
||||
fb_gathered = await asyncio.gather(
|
||||
*[intranet.search(kw, max_results=per_skill) for kw in fb_keywords],
|
||||
return_exceptions=True,
|
||||
)
|
||||
fb_added = 0
|
||||
for fb_results in fb_gathered:
|
||||
if isinstance(fb_results, Exception):
|
||||
search_errors.append(f"{type(fb_results).__name__}: {fb_results}")
|
||||
continue
|
||||
fb_added += _collect(fb_results)
|
||||
search_errors.extend(intranet.errors)
|
||||
# 让下方 notice 拿到内网接口的原始响应体(解析失败时便于定位)
|
||||
provider = intranet
|
||||
_emit_skill_step({
|
||||
"type": "skill_result",
|
||||
"message": f"内网知识库接口兜底检索到 {fb_added} 条新素材",
|
||||
"matched_count": fb_added,
|
||||
})
|
||||
|
||||
# 合并去重
|
||||
# - 首次检索 / 普通 re_search(无输入,用户只想重出):旧素材丢弃,覆盖式替换
|
||||
# - 素材不足求助 supplement_search:保留旧素材 + 追加新检索结果
|
||||
|
||||
Reference in new issue
Block a user