feat:意图识别优化
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
"""意图门控的单一来源:根据分类结果、风险和澄清次数决定最终意图"""
|
||||
|
||||
from app.schemas.decision import ChoiceResult
|
||||
from app.schemas.requirement import Intent
|
||||
|
||||
# 会修改大屏的意图,判错的代价高,使用较严格的阈值;问答判错代价低,使用宽松阈值
|
||||
ACTION_INTENTS = ("create_screen", "modify_screen")
|
||||
|
||||
|
||||
def resolve_intent(
|
||||
decision: ChoiceResult,
|
||||
*,
|
||||
action_min_probability: float,
|
||||
qa_min_probability: float,
|
||||
clarified_rounds: int,
|
||||
max_clarify_rounds: int,
|
||||
option_selected: bool,
|
||||
) -> Intent:
|
||||
"""按选中项自身的概率门控,confidence 只表示分布集中度,不作为判据
|
||||
|
||||
- 用户刚点选澄清选项时,选择本身已表达意图,直接沿用模型判断
|
||||
- 模型判定 unclear 时始终澄清;仅因概率不足而存疑时,澄清次数用尽后退回问答
|
||||
"""
|
||||
choice = decision.choice
|
||||
# 缺少该选项的概率时退回 confidence,避免因协议缺字段而误判
|
||||
probability = decision.probabilities.get(choice, decision.confidence)
|
||||
minimum = action_min_probability if choice in ACTION_INTENTS else qa_min_probability
|
||||
|
||||
# 模型明确判定语义不明时始终澄清,直到用户说清或取消
|
||||
if choice == "unclear":
|
||||
return "unclear"
|
||||
if option_selected or probability >= minimum:
|
||||
return choice # type: ignore[return-value]
|
||||
# 只是把握不足时,澄清次数用尽后安全地退回问答,而不是继续追问
|
||||
if clarified_rounds >= max_clarify_rounds:
|
||||
return "qa"
|
||||
return "unclear"
|
||||
@@ -13,6 +13,7 @@ from app.processor.state import QueryState
|
||||
from app.prompt.action import build_action_prompt
|
||||
from app.schemas.action_validation import ActionValidationError, schema_errors
|
||||
from app.schemas.actions import ActionBatch, AddAction
|
||||
from app.schemas.operations import is_direct_operation
|
||||
|
||||
|
||||
def can_generate_actions(state: QueryState) -> bool:
|
||||
@@ -47,12 +48,7 @@ def action_block_reason(state: QueryState) -> str | None:
|
||||
unsupported = [
|
||||
f"{change.target_scope}/{change.operation}"
|
||||
for change in requirement.changes
|
||||
if not (
|
||||
change.target_scope == "nodes"
|
||||
and change.operation in ("move", "resize", "update", "delete")
|
||||
or change.target_scope == "canvas"
|
||||
and change.operation in ("update", "resize", "update_canvas")
|
||||
)
|
||||
if not is_direct_operation(change.target_scope, change.operation)
|
||||
]
|
||||
|
||||
# 如果存在不支持的操作,按出现顺序去重后返回原因,便于展示失败信息
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
"""意图识别节点"""
|
||||
|
||||
from typing import cast
|
||||
|
||||
from app.clients.decision import close_decision_engine, get_decision_engine
|
||||
from app.processor.base import BaseNode
|
||||
from app.processor.intent_policy import resolve_intent
|
||||
from app.processor.state import QueryState
|
||||
from app.prompt.intent import build_intent_question
|
||||
from app.schemas.base import JsonObject
|
||||
from app.schemas.decision import DecisionRequest
|
||||
from app.schemas.input_context import InputContext
|
||||
from app.schemas.requirement import Intent
|
||||
|
||||
|
||||
class IntentNode(BaseNode):
|
||||
@@ -101,15 +99,17 @@ class IntentNode(BaseNode):
|
||||
# 从响应中取出 intent 对应的判断结果
|
||||
decision = decision_res.results["intent"]
|
||||
|
||||
# 如果模型无法明确意图,或置信度低于配置阈值,则转入澄清流程
|
||||
if (
|
||||
decision.choice == "unclear"
|
||||
or decision.confidence < self.config.intent_min_confidence
|
||||
):
|
||||
intent: Intent = "unclear"
|
||||
else:
|
||||
# 置信度达标时沿用模型选项,cast 仅作类型提示,不进行运行时转换或校验
|
||||
intent = cast(Intent, decision.choice)
|
||||
# 用户刚点选了澄清选项时,选择本身已表达明确意图
|
||||
history = context.clarification_history
|
||||
# 按选中项概率、风险和澄清轮数确定最终意图,规则集中在 intent_policy
|
||||
intent = resolve_intent(
|
||||
decision,
|
||||
action_min_probability=self.config.intent_min_probability,
|
||||
qa_min_probability=self.config.intent_qa_min_probability,
|
||||
clarified_rounds=len(history),
|
||||
max_clarify_rounds=self.config.intent_max_clarify_rounds,
|
||||
option_selected=bool(history and history[-1].get("selected_option")),
|
||||
)
|
||||
|
||||
# 完整保留原始决策,便于查看模型选项及其置信度
|
||||
updated_state["intent_decision"] = decision_res
|
||||
|
||||
@@ -10,6 +10,7 @@ from app.actions.plan import plan_materials
|
||||
from app.clients.llm import get_llm
|
||||
from app.processor.base import BaseNode
|
||||
from app.processor.state import QueryState
|
||||
from app.schemas.capabilities import USER_FAILURE_MESSAGE
|
||||
from app.prompt.reflection import REFLECTION_SYSTEM_PROMPT
|
||||
from app.schemas.action_repair import ActionRepairPlan
|
||||
|
||||
@@ -71,8 +72,12 @@ class ReflectionNode(BaseNode):
|
||||
updated_state["repair_plan"] = plan
|
||||
# 仅在建议允许重试时将次数加一,缺少历史计数时从零开始,不可重试时保持原次数
|
||||
updated_state["retry_count"] = state.get("retry_count", 0) + int(plan.retryable)
|
||||
# 可以重试时清空旧失败原因,否则使用建议中的原因说明为何停止修正
|
||||
updated_state["failure_reason"] = None if plan.retryable else plan.reason
|
||||
# 可以重试时清空旧失败原因;不可重试时建议中的分析含内部术语,只记录日志,对用户使用统一说明
|
||||
if not plan.retryable:
|
||||
self.logger.warning("动作无法修正: {}", plan.reason)
|
||||
updated_state["failure_reason"] = (
|
||||
None if plan.retryable else USER_FAILURE_MESSAGE
|
||||
)
|
||||
|
||||
# 返回修正建议、重试次数和失败原因,由后续路由决定重新生成动作还是结束
|
||||
return updated_state
|
||||
|
||||
@@ -56,6 +56,27 @@ class RequirementNode(BaseNode):
|
||||
# 如果存在无效目标,立即抛出异常,并排序输出 ID 以便定位问题
|
||||
raise ValueError(f"需求引用了不存在的节点: {sorted(unknown_ids)}")
|
||||
|
||||
# 纯新增需求缺少位置、尺寸等信息时不澄清,采用默认值先执行,用户之后可再次提出调整
|
||||
# 提示词无法保证模型每次都遵守,因此在这里兜底;含待确认目标的需求不受影响
|
||||
if (
|
||||
requirement.needs_clarification
|
||||
and requirement.changes
|
||||
and all(
|
||||
change.target_scope == "screen" and change.operation == "add"
|
||||
for change in requirement.changes
|
||||
)
|
||||
):
|
||||
requirement = requirement.model_copy(
|
||||
update={
|
||||
"needs_clarification": False,
|
||||
"clarification_question": None,
|
||||
"assumptions": [
|
||||
*requirement.assumptions,
|
||||
"未指定的位置、尺寸和数据来源采用默认值,数据使用物料示例数据",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
# 保存通过结构及目标校验的需求,供后续澄清或范围判断使用
|
||||
updated_state["requirement"] = requirement
|
||||
# 返回更新后的状态,由图继续执行后续节点
|
||||
|
||||
@@ -8,6 +8,7 @@ from app.processor.base import BaseNode
|
||||
from app.processor.state import QueryState
|
||||
from app.schemas.action_validation import ActionValidationError, schema_errors
|
||||
from app.schemas.actions import ActionBatch, AddAction, UpdateCanvasAction
|
||||
from app.schemas.operations import is_canvas_operation
|
||||
|
||||
|
||||
class ValidatorNode(BaseNode):
|
||||
@@ -76,8 +77,7 @@ class ValidatorNode(BaseNode):
|
||||
allowed.update(
|
||||
("canvas", "", "update_canvas")
|
||||
for change in requirement.changes
|
||||
if change.target_scope == "canvas"
|
||||
and change.operation in ("update", "resize", "update_canvas")
|
||||
if is_canvas_operation(change.target_scope, change.operation)
|
||||
)
|
||||
|
||||
# 画布没有组件 ID,使用独立作用域避免与同名组件混淆
|
||||
|
||||
Reference in New Issue
Block a user