fix: resume confirmed analysis handoff

This commit is contained in:
potaser
2026-08-11 06:37:24 +08:00
parent 4a80793b4a
commit 77011a21b7
4 changed files with 130 additions and 1 deletions
@@ -299,6 +299,7 @@ class BoundRecognizeAnalysisIntentArgs(BaseModel):
model_config = ConfigDict(extra="forbid")
query: str
prefix: str = ""
language: str = "zh"
_prefix_is_literal = field_validator("prefix")(_validate_prefix)
+1 -1
View File
@@ -1405,7 +1405,7 @@ class RouterRuntime:
tool_trace=decision_trace,
interaction_kind=decision.interaction_kind,
)
if decision.goal == "new_analysis":
if decision.goal == "new_analysis" and confirmed_proposal is None:
proposal_query = self.tools.resolve_analysis_handoff_request(
effective_query,
workflow_id=decision.workflow_id,
@@ -1,5 +1,7 @@
import asyncio
import json
from gfa_core.mas.agents.intent_v2 import IntentAgentV2
from gfa_core.mas.agents.conversation_tools import build_plantai_conversation_tools
from gfa_core.mas.llm import LLMManager
from gfa_core.mas.workflow_models import GoalLedger
@@ -83,3 +85,32 @@ def test_next_step_question_is_not_rewritten_to_genome_prepare(tmp_path):
assert resolved.startswith("接下来需要做什么?\n")
assert "执行数据准备(genome_prepare)" not in resolved
def test_bound_analysis_intent_uses_default_language(monkeypatch, tmp_path):
captured_queries = []
async def fake_execute(_self, state, message):
captured_queries.append(message.payload["query"])
state.results["results"] = {
"intent": {
"gene_family": "SPL",
"stages": ["ppi", "kaks"],
"primary_stage": "ppi",
}
}
return state
monkeypatch.setattr(IntentAgentV2, "execute", fake_execute)
result = json.loads(
asyncio.run(
_registry(tmp_path).dispatch(
"recognize_analysis_intent",
json.dumps({"query": "执行Ft的PPI和kaks分析"}, ensure_ascii=False),
)
)
)
assert result["ok"] is True
assert result["data"]["stages"] == ["ppi", "kaks"]
assert captured_queries[0].startswith("执行Ft的PPI和kaks分析\n")
@@ -1,14 +1,18 @@
from __future__ import annotations
import asyncio
import json
from typing import Any
import pytest
from pydantic import BaseModel
from gfa_core.mas.agents.conversation_tools import ConversationToolRegistry
from gfa_core.mas.agents.router_runtime import (
RouteDecision,
RouterRuntime,
)
from gfa_core.mas.llm import LLMResponse, LLMToolCall
from gfa_core.mas.workflow_models import GoalLedger
@@ -39,6 +43,28 @@ class _ContextEnrichingTools:
)
class _IntentArgs(BaseModel):
query: str
class _SequenceLLM:
def __init__(self, responses: list[LLMResponse]) -> None:
self.responses = list(responses)
async def acall(self, *_args: Any, **_kwargs: Any) -> LLMResponse:
return self.responses.pop(0)
def _response(content: str = "", *, tool_calls: list[LLMToolCall] | None = None) -> LLMResponse:
return LLMResponse(
content=content,
model="test-model",
provider="test-provider",
usage={},
tool_calls=list(tool_calls or []),
)
@pytest.mark.parametrize(
("query", "workflow_id", "clarification_question"),
(
@@ -134,6 +160,77 @@ def test_context_enrichment_does_not_replace_original_analysis_handoff() -> None
assert outcome.proposal["original_request"] == "执行kaks分析"
def test_confirmed_analysis_proposal_skips_second_confirmation() -> None:
job_id = "job-confirmed-analysis-handoff"
enriched_request = (
"执行Ft的PPI和kaks分析\n"
"已确认上下文:基因家族 SPL;目标物种 Ft;参考物种 At。"
)
recognized_queries: list[str] = []
registry = ConversationToolRegistry()
registry.register(
name="recognize_analysis_intent",
description="Recognize an analysis intent.",
args_model=_IntentArgs,
handler=lambda args: recognized_queries.append(args.query)
or {
"gene_family": "SPL",
"stages": ["ppi", "kaks"],
"primary_stage": "ppi",
"params": {"targetSpecies": ["Ft"], "referenceSpecies": ["At"]},
},
permission="pure",
)
llm = _SequenceLLM(
[
_response(
tool_calls=[
LLMToolCall(
id="recognize-confirmed-analysis-1",
name="recognize_analysis_intent",
arguments=json.dumps(
{"query": enriched_request}, ensure_ascii=False
),
)
]
),
_response(
'{"status":"ok","action":"propose_analysis",'
'"message":"已交给现有分析系统。"}'
),
]
)
ledger = GoalLedger(
conversation_id="session-confirmed-analysis-handoff",
revision=1,
user_terms={
"active_job_id": job_id,
"pending_proposal": {
"proposal_id": "proposal-confirmed-analysis",
"request": enriched_request,
"original_request": "执行Ft的PPI和kaks分析",
"subject": "执行Ft的PPI和kaks分析",
"job_id": job_id,
},
},
)
outcome = asyncio.run(
RouterRuntime(llm, registry, max_iterations=5).run(
query="继续",
history=[],
session_id="session-confirmed-analysis-handoff",
job_id=job_id,
goal_ledger=ledger,
)
)
assert outcome.action == "propose_analysis"
assert outcome.intent is not None
assert outcome.intent["stages"] == ["ppi", "kaks"]
assert recognized_queries == [enriched_request]
def test_new_analysis_decision_discards_main_agent_workflow_and_clarification() -> None:
decision = RouteDecision(
goal="new_analysis",