feat(agent): 完备性判断改LLM裁决(2026-09-08用户定夺:判断归LLM,契约归数据,硬编码降级为兜底)

- judge_completeness:①模型有配套技能(skills/models/{model}/SKILL.md)→LLM按
  技能输入契约vs用户已给内容判断缺什么;②无技能→LLM按能力语义(输入→输出
  模态)判断;③LLM失败→_check_completeness_code能力表兜底(闸门不因裁决故障失效)
- load_model_skill读配套技能;_resolve_capability补返回description供裁决
- 裁决只在invoke_model(贵动作)发生,不是每条消息;宁放行不误拦,可选参数不拦
This commit is contained in:
ymq 2026-09-08 12:31:22 +08:00
parent ba65d586b1
commit 83d11772d5

View File

@ -91,12 +91,12 @@ def _biz_media(biz, key):
return bool(str(v or "").strip())
def check_completeness(capability, task, biz):
"""完备性门禁:该能力契约要求的输入是否齐备
def _check_completeness_code(capability, task, biz):
"""能力契约代码表校验(**兜底层**2026-09-08 用户定夺后降级)
返回 '' = 完备可执行否则返回需要向用户追问的话术QUESTION 内容
契约未登记的能力 embedding/新能力不拦宁可放行也别误伤
但生成类主流能力全部登记在册
判断主体是 LLMjudge_completeness本函数只在 LLM 裁决失败
调用异常/输出不可解析时兜底花钱闸门不能因裁决故障失效
返回 '' = 完备否则返回追问话术
"""
cap = (capability or "").strip().lower()
required = _CAP_REQUIRED.get(cap)
@ -120,6 +120,139 @@ def check_completeness(capability, task, biz):
return ""
# ─────────────────────────────────────────────────────────────────
# 模型配套技能2026-09-08 用户定夺):
# 「每个模型的加入需要有配套的 skill模型需要什么样的输入、用户已经
# 提供的内容缺什么,应该由 LLM 判断而不是硬编码;没有 skill 的,按
# 能力的输入要求对照用户已给输入决策是否需要补充。」
#
# 判断分层(判断归 LLM契约归数据代码表只兜底
# ① 模型有配套技能skills/models/{model}/SKILL.md上线时自动生成
# → LLM 按技能里的输入契约 vs 用户已给内容判断缺什么
# ② 无技能 → LLM 按能力语义(能力字典 输入→输出模态vs 用户输入判断
# ③ LLM 裁决失败(异常/输出不可解析)→ _check_completeness_code 代码表
# 兜底——花钱闸门不能因裁决故障失效
# ─────────────────────────────────────────────────────────────────
_CAP_MODALS = {
"t2t": "文本→文本", "i2t": "图像→文本", "m2t": "多媒体→文本",
"t2i": "文本→图像", "i2i": "图像→图像", "t2v": "文本→视频",
"i2v": "图像→视频", "r2v": "参考媒体(图/视频/音频)→视频",
"tts": "文本→语音", "asr": "语音→文本",
"embedding": "文本→向量", "rerank": "候选集→排序",
}
def load_model_skill(model_name):
"""读模型配套技能正文skills/models/{model}/SKILL.md
查找顺序全局模板get_skills_base()应用目录 机构运行时目录
/d/pipeline/workspaces/{org}/skills 由各调用方 base_dir 决定模型
技能是平台级资产放全局模板即可无技能返回 ''
目录约定skills/models/ 不在 SkillLoader 扫描范围global/pipelines/
orgs/users不会污染 agent 技能目录层
"""
import os
try:
from pipeline_core.skill_pack import get_skills_base
base = get_skills_base()
except Exception:
base = os.path.join(os.getcwd(), "skills")
safe = "".join(ch for ch in (model_name or "")
if ch.isalnum() or ch in "-_.")
if not safe:
return ""
path = os.path.join(base, "models", safe, "SKILL.md")
try:
if os.path.isfile(path):
with open(path, encoding="utf-8") as f:
return f.read()[:4000]
except Exception as e:
logger.warning("load_model_skill(%s) 失败: %s", model_name, e)
return ""
async def judge_completeness(model_name, capability, task, biz,
description="", org_id="0", user_id=""):
"""完备性判断主体LLM 裁决2026-09-08 用户定夺)。
模型有配套技能 按技能输入契约判断 无技能 按能力语义判断
LLM 失败 代码表兜底
返回 '' = 完备可执行否则返回追问话术QUESTION 内容
"""
cap = (capability or "").strip().lower()
skill = load_model_skill(model_name)
if skill:
contract = ("该模型有配套技能(权威输入契约),按其「输入要求」判断:\n"
+ skill[:3000])
else:
modal = _CAP_MODALS.get(cap, "")
contract = ("该模型无配套技能,按能力语义判断。能力 %s%s%s"
% (cap or "?", modal or "模态未知",
"必备输入=箭头左侧模态的素材/文本;右侧是产物。"
if modal else "无法确定契约,宽松放行。"))
got = ["任务描述: " + (task or "(空)")]
for k in ("image_files", "audio_files", "video_files"):
if _biz_media(biz or {}, k):
v = biz.get(k)
n = len(v) if isinstance(v, (list, tuple)) else 1
got.append("已提供 %s: %d" % (k, n))
extra = {k: str(v)[:60] for k, v in (biz or {}).items()
if k not in ("image_files", "audio_files", "video_files")}
if extra:
got.append("其他业务参数: " + json.dumps(extra, ensure_ascii=False)[:300])
prompt = (
"你是平台模型调用的完备性裁决器。即将调用模型「%s」(能力 %s%s)完成生成任务,"
"调用会产生真实费用。请判断当前输入是否足以完成该任务。\n\n"
"%s\n\n模型描述:%s\n\n用户已提供的输入:\n%s\n\n"
"判断规则:\n"
"1. 只把「没有就无法完成本能力任务」的输入判为缺失(如文生图没有画面描述、"
"图生视频没有输入图)。\n"
"2. 有默认值/可选的偏好参数(尺寸/风格/时长/音色等)缺失**不算**不完备,不要拦。\n"
"3. 用户已提供的内容里隐含了该输入的(如任务描述里已含图片 URL算已提供。\n"
"4. 判断不了就放行complete=true宁可放行也别误拦。\n\n"
"只返回 JSON{\"complete\": true/false, \"ask\": \"缺失时向用户追问的话术"
"(说清缺什么、要什么格式、一句话),完备时空串\"}"
% (model_name, cap or "?",
("" + _CAP_DESC.get(cap, "")) if cap in _CAP_DESC else "",
contract, (description or "(无)")[:200], "\n".join(got)))
try:
from pipeline_service.llm_bridge import llm_call_msgs
raw = await llm_call_msgs(
[{"role": "user", "content": prompt}],
temperature=0, org_id=org_id or "0", user_id=user_id or "",
purpose="utility", timeout=60)
except Exception as e:
logger.warning("judge_completeness LLM 失败(走代码表兜底): %s", repr(e)[:150])
return _check_completeness_code(cap, task, biz)
import re as _re
txt = (raw or "").strip()
m = _re.search(r"\{[\s\S]*\}", txt)
if not m:
logger.warning("judge_completeness 输出非 JSON走代码表兜底: %s", txt[:120])
return _check_completeness_code(cap, task, biz)
try:
obj = json.loads(m.group(0))
except Exception:
return _check_completeness_code(cap, task, biz)
if not isinstance(obj, dict):
return _check_completeness_code(cap, task, biz)
if obj.get("complete") is True:
return ""
ask = str(obj.get("ask") or "").strip()
if not ask:
# LLM 判不完备但没给追问话术 → 代码表兜底生成话术(追问必须可行动)
return _check_completeness_code(cap, task, biz)
return ("调用「%s」模型前完备性检查未通过,已停在调用前、未产生任何费用。%s"
% (model_name or (_CAP_DESC.get(cap, cap)), ask))
async def tool_list_platform_models(params, org_id):
"""列出平台可用模型(本机构+平台owner机构含能力类型与描述"""
cap = str((params or {}).get("capability") or "").strip().lower()
@ -148,19 +281,20 @@ async def tool_list_platform_models(params, org_id):
async def _resolve_capability(model_name, org_id):
"""按模型注册名反查能力类型(门禁需要知道该查哪份输入契约)。
models_catalog与推理链同一机构可见性查不到返回 ''
"""按模型注册名反查 (能力类型, 描述)(门禁需知道查哪份输入契约;
描述供 LLM 裁决参考 models_catalog与推理链同一机构可见性
查不到返回 ('', '')
"""
try:
from pipeline_llm.selection import models_catalog
models = await models_catalog(org_id or "0", capabilities=())
for m in models:
if model_name in (m.get("name"), m.get("vendor_model_id")):
return (m.get("capability") or "").strip().lower()
return ((m.get("capability") or "").strip().lower(),
m.get("description") or "")
except Exception as e:
logger.warning("_resolve_capability(%s) 失败: %s", model_name, e)
return ""
return "", ""
async def tool_invoke_model(params, org_id, user_id=""):
@ -214,12 +348,17 @@ async def tool_invoke_model(params, org_id, user_id=""):
except Exception as e:
return "ERROR: 自动选型失败: " + str(e)[:300]
# ── 完备性硬门禁2026-09-08 用户定夺一C+二A)──
# ── 完备性门禁2026-09-08 用户定夺:判断归 LLM代码表只兜底)──
# 必须在 llm_infer 之前:缺输入直接 QUESTION 追问用户,上游一分钱不花。
# capability 未显式给出时按选中模型反查(门禁需知道查哪份输入契约)。
if not cap:
cap = await _resolve_capability(model, org_id)
gap = check_completeness(cap, task, biz)
# 判断分层:① 模型配套技能skills/models/{model}/SKILL.md→ LLM 按契约判;
# ② 无技能 → LLM 按能力语义判;③ LLM 失败 → 能力契约代码表兜底。
# capability/description 未显式给出时按选中模型反查。
desc = ""
cap2, desc = await _resolve_capability(model, org_id)
cap = cap or cap2
gap = await judge_completeness(model, cap, task, biz,
description=desc, org_id=org_id or "0",
user_id=user_id or "")
if gap:
logger.info("invoke_model 完备性门禁拦截(%s: %s", cap or "?", gap[:80])
return "QUESTION: " + gap