fix: resume confirmed analysis handoff
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user