PMFlow/backend/tests/test_pmflow_ai_agent_action...

160 lines
7.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from app.schemas.pmflow_ai_agent_runtime import (
PMFlowAIAgentActionCandidateLedgerEntry,
PMFlowAIAgentClaim,
PMFlowAIAgentCriticResult,
PMFlowAIAgentGoalFrame,
PMFlowAIAgentStepProposal,
PMFlowAIAgentWorkspace,
)
from app.services.pmflow_ai_agent_runtime.agent_answer_runtime import build_agent_terminal_answer_state
from app.services.pmflow_ai_agent_runtime.agent_deliverable_runtime import PMFlowAIAgentDeliverableRuntime
from app.services.pmflow_ai_agent_runtime.agent_payload_gate import build_agent_answer_canvas
def test_goal_authored_project_launch_affordance_reaches_customer_canvas() -> None:
question = "我想做一个帮助独立开发者管理产品验证和变现推进的工具"
workspace = PMFlowAIAgentWorkspace(
current_goal=question,
goal_frame=PMFlowAIAgentGoalFrame(
user_goal=question,
desired_output="给出可执行的产品推进蓝图,并能进入 PMFlow 下一步确认链。",
source_policy="internal_customer_safe",
freshness_policy="not_required",
answer_objective={"user_success": "拿到可落地的 MVP 推进方向"},
dynamic_rubric=[
{
"criterion": "回答必须覆盖目标用户、MVP 边界、验证动作和 PMFlow 承接动作",
"why_it_matters": "用户要从想法进入执行",
"evidence_expectation": "受控假设即可",
}
],
deliverable_shape={
"primary_shape": "产品执行蓝图",
"must_include": ["MVP 边界", "验证动作", "PMFlow 下一步"],
"artifact_slots": [
{
"slot_key": "mvp_scope",
"display_goal": "明确首版 MVP 的边界",
"must_cover": ["核心闭环", "先不做什么"],
"depth": "standard",
}
],
"pmflow_system_actions": [
{
"capability_key": "project_launch_draft",
"title": "生成项目草案待确认",
"summary": "本轮方向已经可以进入 Project Launch 生成结构化草案。",
"input_seed": question,
"completion_criteria": "看到可审查的草案后再决定是否创建项目。",
}
],
},
),
claim_ledger=[
PMFlowAIAgentClaim(
claim_type="controlled_hypothesis_basis",
claim_text="可以先把这个方向收敛成一个待验证的产品执行蓝图,再进入 PMFlow 草案确认链。",
customer_value="用户可以从想法进入结构化推进。",
confidence="medium",
allowed_to_state=True,
relevance_to_user_goal="direct",
claim_kind="directional_judgement",
)
],
)
terminal = build_agent_terminal_answer_state(
question=question,
workspace=workspace,
proposal=PMFlowAIAgentStepProposal(action="answer"),
observation=None,
critic_result=PMFlowAIAgentCriticResult(ready_to_answer=True, retry_would_change_state=False, recover_or_continue="compose_answer"),
)
assert terminal.action_candidates
candidate = terminal.action_candidates[0]
assert candidate.action_type == "create_draft"
assert candidate.safety == "prepare_only"
assert candidate.requires_confirmation is True
assert candidate.href == "/project-launch?source=pmflow-ai"
assert candidate.action_payload["input"] == question
assert candidate.action_payload["launch_seed"]["packet_version"] == "pmflow_project_launch_seed.v1"
assert candidate.action_payload["launch_seed"]["source_contract"] == "same_understanding_materialization"
assert candidate.action_payload["launch_seed"]["materialization_policy"]["do_not_reinterpret_as_new_idea"] is True
assert candidate.action_payload["launch_seed"]["customer_safe_claims"][0]["claim_text"].startswith("可以先把这个方向")
assert len(candidate.href) <= 300
deliverable = PMFlowAIAgentDeliverableRuntime().build(
question=question,
workspace=workspace,
proposal=PMFlowAIAgentStepProposal(action="answer"),
observation=None,
critic_result=PMFlowAIAgentCriticResult(ready_to_answer=True, retry_would_change_state=False, recover_or_continue="compose_answer"),
terminal_state=terminal,
answer_text="这个方向可以先做成一个围绕验证和变现推进的 MVP,并进入 PMFlow 生成项目草案待确认。",
)
canvas = build_agent_answer_canvas(answer=deliverable.answer_text, workspace=workspace, terminal_state=terminal, deliverable_packet=deliverable)
assert deliverable.artifacts
assert deliverable.artifacts[0].artifact_kind == "product_blueprint"
assert deliverable.artifacts[0].slots[0]["title"] == "明确首版 MVP 的边界"
assert canvas["artifacts"][0]["artifact_kind"] == "product_blueprint"
assert canvas["artifacts"][0]["slots"][0]["title"] == "明确首版 MVP 的边界"
assert canvas["action_candidates"][0]["action_type"] == "create_draft"
assert canvas["action_candidates"][0]["href"] == "/project-launch?source=pmflow-ai"
assert canvas["action_candidates"][0]["action_payload"]["input"] == question
assert canvas["action_candidates"][0]["action_payload"]["launch_seed"]["idea_input"] == question
def test_goal_authored_project_launch_action_wins_over_llm_generated_href() -> None:
question = "我想做个AI漫剧的工具"
workspace = PMFlowAIAgentWorkspace(
current_goal=question,
goal_frame=PMFlowAIAgentGoalFrame(
user_goal=question,
desired_output="生成可确认的项目草案入口。",
deliverable_shape={
"pmflow_system_actions": [
{
"capability_key": "project_launch_draft",
"title": "启动“AI漫剧工具”项目草案",
"summary": "进入 Project Launch 生成待确认草案。",
"input_seed": question,
}
]
},
),
action_candidate_ledger=[
PMFlowAIAgentActionCandidateLedgerEntry(
title="启动“AI漫剧工具”项目草案",
summary="模型建议进入项目草案。",
action_type="navigate",
href="/pmflow/项目列表/new?seed=ai_comic_tool_draft",
safety="prepare_only",
)
],
)
terminal = build_agent_terminal_answer_state(
question=question,
workspace=workspace,
proposal=PMFlowAIAgentStepProposal(action="answer"),
observation=None,
critic_result=PMFlowAIAgentCriticResult(ready_to_answer=True, retry_would_change_state=False, recover_or_continue="compose_answer"),
)
deliverable = PMFlowAIAgentDeliverableRuntime().build(
question=question,
workspace=workspace,
proposal=PMFlowAIAgentStepProposal(action="answer"),
observation=None,
critic_result=PMFlowAIAgentCriticResult(ready_to_answer=True, retry_would_change_state=False, recover_or_continue="compose_answer"),
terminal_state=terminal,
answer_text="可以进入 PMFlow 生成项目草案待确认。",
)
canvas = build_agent_answer_canvas(answer=deliverable.answer_text, workspace=workspace, terminal_state=terminal, deliverable_packet=deliverable)
assert canvas["action_candidates"][0]["action_type"] == "create_draft"
assert canvas["action_candidates"][0]["href"] == "/project-launch?source=pmflow-ai"
assert canvas["action_candidates"][0]["action_payload"]["input"] == question
assert canvas["action_candidates"][0]["action_payload"]["launch_seed"]["source"] == "space_ai_turn"