576 lines
20 KiB
Python
576 lines
20 KiB
Python
import json
|
||||
|
|
import unittest
|
|||
|
|
from types import SimpleNamespace
|
|||
|
|
from unittest.mock import AsyncMock, patch
|
|||
|
|
|
|||
|
|
import httpx
|
|||
|
|
from fastapi import HTTPException
|
|||
|
|
|
|||
|
|
from db.models import ModelResource, TestRun
|
|||
|
|
from models import (
|
|||
|
|
AssistantConfig,
|
|||
|
|
RuntimeModelResource,
|
|||
|
|
RuntimeTool,
|
|||
|
|
RuntimeToolBinding,
|
|||
|
|
)
|
|||
|
|
from routes.test_runs import create_prompt_improvement
|
|||
|
|
from services.test_runs.prompt_improvement import (
|
|||
|
|
PROMPT_IMPROVEMENT_SNAPSHOT_KEY,
|
|||
|
|
PromptImprovementAgent,
|
|||
|
|
PromptImprovementError,
|
|||
|
|
_GeneratedImprovement,
|
|||
|
|
build_prompt_improvement_context,
|
|||
|
|
build_prompt_improvement_snapshot,
|
|||
|
|
public_test_run_config,
|
|||
|
|
)
|
|||
|
|
from test_schemas import PromptImprovementOut
|
|||
|
|
|
|||
|
|
|
|||
|
|
def evaluator_resource() -> RuntimeModelResource:
|
|||
|
|
return RuntimeModelResource(
|
|||
|
|
id="model_eval",
|
|||
|
|
name="测试评估模型",
|
|||
|
|
capability="LLM",
|
|||
|
|
interface_type="openai-llm",
|
|||
|
|
values={
|
|||
|
|
"apiUrl": "https://model.example/v1",
|
|||
|
|
"modelId": "judge-model",
|
|||
|
|
},
|
|||
|
|
secrets={"apiKey": "model-secret"},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def failed_result() -> dict:
|
|||
|
|
return {
|
|||
|
|
"startup": {
|
|||
|
|
"completed": True,
|
|||
|
|
"greeting": "您好",
|
|||
|
|
"assistantOutputs": ["请问需要什么帮助?"],
|
|||
|
|
"runtimeRecords": [{"privateNoise": "must-not-be-forwarded"}],
|
|||
|
|
},
|
|||
|
|
"turns": [
|
|||
|
|
{
|
|||
|
|
"id": "turn_1",
|
|||
|
|
"index": 0,
|
|||
|
|
"userInput": "查询订单",
|
|||
|
|
"assistantReply": "请提供订单号",
|
|||
|
|
"toolCalls": [
|
|||
|
|
{
|
|||
|
|
"functionName": "query_order",
|
|||
|
|
"argumentsJson": '{"orderId":"A1"}',
|
|||
|
|
"resultJson": '{"status":"ok"}',
|
|||
|
|
"outcome": "success",
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"runtimeRecords": [{"privateNoise": "must-not-be-forwarded"}],
|
|||
|
|
"evaluations": [
|
|||
|
|
{
|
|||
|
|
"label": "回复 · 关键词",
|
|||
|
|
"status": "fail",
|
|||
|
|
"expected": "应包含:查询成功",
|
|||
|
|
"actual": "请提供订单号",
|
|||
|
|
"reason": "没有告知查询结果",
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"overallCriteria": [],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def missing_tool_result() -> dict:
|
|||
|
|
return {
|
|||
|
|
"startup": {"completed": True},
|
|||
|
|
"turns": [
|
|||
|
|
{
|
|||
|
|
"id": "turn_photo",
|
|||
|
|
"index": 0,
|
|||
|
|
"userInput": "1",
|
|||
|
|
"assistantReply": "现在开始拍照,请拍车辆前方。",
|
|||
|
|
"toolCalls": [],
|
|||
|
|
"evaluations": [
|
|||
|
|
{
|
|||
|
|
"id": "behavior_photo",
|
|||
|
|
"kind": "tool_call",
|
|||
|
|
"label": "工具 · set_photo_button_visible",
|
|||
|
|
"status": "fail",
|
|||
|
|
"expected": "应调用 set_photo_button_visible 1 次以上",
|
|||
|
|
"actual": "实际调用 0 次 set_photo_button_visible",
|
|||
|
|
"reason": "工具调用次数不符合要求。",
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"overallCriteria": [],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def missing_tool_case() -> dict:
|
|||
|
|
return {
|
|||
|
|
"name": "进入拍照阶段",
|
|||
|
|
"turns": [
|
|||
|
|
{
|
|||
|
|
"id": "turn_photo",
|
|||
|
|
"userInput": "1",
|
|||
|
|
"behaviors": [
|
|||
|
|
{
|
|||
|
|
"id": "behavior_photo",
|
|||
|
|
"type": "tool_call",
|
|||
|
|
"toolId": "tool_photo",
|
|||
|
|
"functionName": "set_photo_button_visible",
|
|||
|
|
"expectation": "called",
|
|||
|
|
"minCalls": 1,
|
|||
|
|
"maxCalls": None,
|
|||
|
|
"paramAssertions": [
|
|||
|
|
{
|
|||
|
|
"name": "visible",
|
|||
|
|
"matchMode": "exact",
|
|||
|
|
"value": "true",
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def missing_tool_snapshot() -> dict:
|
|||
|
|
return {
|
|||
|
|
"assistantType": "prompt",
|
|||
|
|
"runtimeMode": "pipeline",
|
|||
|
|
"prompt": "信息确认后立即调用 `set_photo_button_visible(true)`。",
|
|||
|
|
"greeting": "您好",
|
|||
|
|
"dynamicVariableNames": [],
|
|||
|
|
"tools": [
|
|||
|
|
{
|
|||
|
|
"toolId": "tool_photo",
|
|||
|
|
"functionName": "set_photo_button_visible",
|
|||
|
|
"type": "client",
|
|||
|
|
"parameters": [{"name": "visible", "type": "boolean"}],
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PromptImprovementSnapshotTests(unittest.TestCase):
|
|||
|
|
def test_snapshot_keeps_model_visible_tool_settings_without_secrets(self):
|
|||
|
|
config = AssistantConfig(
|
|||
|
|
type="prompt",
|
|||
|
|
runtimeMode="pipeline",
|
|||
|
|
prompt="服务 {{customer_name}}",
|
|||
|
|
greeting="您好",
|
|||
|
|
dynamic_variable_definitions={"customer_name": {"type": "string"}},
|
|||
|
|
startup={
|
|||
|
|
"openingMode": "confirmation",
|
|||
|
|
"entryMode": "wait_user",
|
|||
|
|
"openingMessage": {
|
|||
|
|
"title": "提示",
|
|||
|
|
"message": "请确认",
|
|||
|
|
"confirmLabel": "确认",
|
|||
|
|
},
|
|||
|
|
"actions": [
|
|||
|
|
{
|
|||
|
|
"phase": "opening",
|
|||
|
|
"required": True,
|
|||
|
|
"toolBinding": {
|
|||
|
|
"toolId": "tool_order",
|
|||
|
|
"argumentBindings": {
|
|||
|
|
"token": {
|
|||
|
|
"source": "literal",
|
|||
|
|
"value": "literal-secret",
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
tools=[
|
|||
|
|
RuntimeTool(
|
|||
|
|
id="tool_order",
|
|||
|
|
name="查询订单",
|
|||
|
|
function_name="query_order",
|
|||
|
|
type="http",
|
|||
|
|
description="按订单号查询",
|
|||
|
|
definition={
|
|||
|
|
"type": "http",
|
|||
|
|
"config": {
|
|||
|
|
"url": "https://private.example/orders",
|
|||
|
|
"headers": {"Authorization": "header-secret"},
|
|||
|
|
"parameters": [
|
|||
|
|
{
|
|||
|
|
"name": "order_id",
|
|||
|
|
"type": "string",
|
|||
|
|
"location": "body",
|
|||
|
|
"description": "订单号",
|
|||
|
|
"required": True,
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
secrets={"token": "tool-secret"},
|
|||
|
|
)
|
|||
|
|
],
|
|||
|
|
tool_bindings=[
|
|||
|
|
RuntimeToolBinding(
|
|||
|
|
tool_id="tool_order",
|
|||
|
|
argument_bindings={
|
|||
|
|
"order_id": {"source": "model"},
|
|||
|
|
"token": {"source": "literal", "value": "binding-secret"},
|
|||
|
|
},
|
|||
|
|
result_assignments={"status": "order_status"},
|
|||
|
|
)
|
|||
|
|
],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
snapshot = build_prompt_improvement_snapshot(config)
|
|||
|
|
serialized = json.dumps(snapshot, ensure_ascii=False)
|
|||
|
|
|
|||
|
|
self.assertEqual(snapshot["prompt"], "服务 {{customer_name}}")
|
|||
|
|
self.assertEqual(snapshot["tools"][0]["functionName"], "query_order")
|
|||
|
|
self.assertEqual(snapshot["tools"][0]["parameters"][0]["name"], "order_id")
|
|||
|
|
self.assertEqual(
|
|||
|
|
snapshot["tools"][0]["binding"]["argumentSources"]["token"],
|
|||
|
|
{"source": "literal"},
|
|||
|
|
)
|
|||
|
|
for secret in (
|
|||
|
|
"private.example",
|
|||
|
|
"header-secret",
|
|||
|
|
"tool-secret",
|
|||
|
|
"literal-secret",
|
|||
|
|
"binding-secret",
|
|||
|
|
):
|
|||
|
|
self.assertNotIn(secret, serialized)
|
|||
|
|
|
|||
|
|
def test_internal_snapshot_is_hidden_from_public_run_config(self):
|
|||
|
|
public = public_test_run_config(
|
|||
|
|
{
|
|||
|
|
"timeoutSecs": 60,
|
|||
|
|
PROMPT_IMPROVEMENT_SNAPSHOT_KEY: {"prompt": "secret context"},
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
self.assertEqual(public, {"timeoutSecs": 60})
|
|||
|
|
|
|||
|
|
def test_context_contains_failure_trajectory_but_not_runtime_records(self):
|
|||
|
|
context = build_prompt_improvement_context(
|
|||
|
|
{
|
|||
|
|
"assistantType": "prompt",
|
|||
|
|
"runtimeMode": "pipeline",
|
|||
|
|
"prompt": "你是订单助手",
|
|||
|
|
"greeting": "您好",
|
|||
|
|
"startup": {},
|
|||
|
|
"tools": [],
|
|||
|
|
"dynamicVariableNames": [],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"name": "订单查询",
|
|||
|
|
"description": "查询已存在订单",
|
|||
|
|
"contextTurns": [{"role": "user", "content": "之前的消息"}],
|
|||
|
|
"turns": [{"id": "turn_1", "userInput": "查询订单"}],
|
|||
|
|
},
|
|||
|
|
failed_result(),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
self.assertIn("你是订单助手", context)
|
|||
|
|
self.assertIn("没有告知查询结果", context)
|
|||
|
|
self.assertIn("query_order", context)
|
|||
|
|
self.assertNotIn("must-not-be-forwarded", context)
|
|||
|
|
|
|||
|
|
def test_context_keeps_structured_missing_tool_assertion(self):
|
|||
|
|
context = json.loads(
|
|||
|
|
build_prompt_improvement_context(
|
|||
|
|
missing_tool_snapshot(),
|
|||
|
|
missing_tool_case(),
|
|||
|
|
missing_tool_result(),
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
failure = context["failedEvaluations"][0]
|
|||
|
|
self.assertEqual(failure["kind"], "tool_call")
|
|||
|
|
self.assertEqual(failure["turnIndex"], 1)
|
|||
|
|
self.assertEqual(failure["actualCallCount"], 0)
|
|||
|
|
self.assertEqual(
|
|||
|
|
failure["expectedBehavior"]["functionName"],
|
|||
|
|
"set_photo_button_visible",
|
|||
|
|
)
|
|||
|
|
self.assertEqual(
|
|||
|
|
failure["expectedBehavior"]["paramAssertions"][0],
|
|||
|
|
{"name": "visible", "matchMode": "exact", "value": "true"},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PromptImprovementAgentTests(unittest.IsolatedAsyncioTestCase):
|
|||
|
|
async def test_valid_change_preserves_existing_variable(self):
|
|||
|
|
agent = PromptImprovementAgent(evaluator_resource())
|
|||
|
|
generated = _GeneratedImprovement.model_validate(
|
|||
|
|
{
|
|||
|
|
"diagnosis": "缺少查询后的回复要求",
|
|||
|
|
"promptChange": {
|
|||
|
|
"after": "服务 {{customer_name}}。查询完成后说明结果。",
|
|||
|
|
"reason": "补充回复规则",
|
|||
|
|
"evidence": ["实际回复没有查询结果"],
|
|||
|
|
},
|
|||
|
|
"manualRecommendations": [],
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
with patch.object(agent, "_request", AsyncMock(return_value=generated)):
|
|||
|
|
result = await agent.improve(
|
|||
|
|
snapshot={
|
|||
|
|
"prompt": "服务 {{customer_name}}。",
|
|||
|
|
"greeting": "您好",
|
|||
|
|
"dynamicVariableNames": ["customer_name"],
|
|||
|
|
"tools": [],
|
|||
|
|
},
|
|||
|
|
case_snapshot={"name": "订单", "turns": []},
|
|||
|
|
result=failed_result(),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
self.assertEqual(result.prompt_change.before, "服务 {{customer_name}}。")
|
|||
|
|
self.assertIn("说明结果", result.prompt_change.after)
|
|||
|
|
|
|||
|
|
async def test_missing_tool_noop_is_retried_with_focused_instruction(self):
|
|||
|
|
agent = PromptImprovementAgent(evaluator_resource())
|
|||
|
|
before = missing_tool_snapshot()["prompt"]
|
|||
|
|
noop = _GeneratedImprovement.model_validate(
|
|||
|
|
{
|
|||
|
|
"diagnosis": "Prompt 已经要求调用工具",
|
|||
|
|
"promptChange": {
|
|||
|
|
"after": before,
|
|||
|
|
"reason": "无需修改",
|
|||
|
|
"evidence": ["已有调用要求"],
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
repaired = _GeneratedImprovement.model_validate(
|
|||
|
|
{
|
|||
|
|
"diagnosis": "助手用文字进入下一阶段,漏掉实际工具调用",
|
|||
|
|
"promptChange": {
|
|||
|
|
"after": (
|
|||
|
|
f"{before}\n调用成功前不得输出拍照提示,"
|
|||
|
|
"不得用自然语言代替工具调用。"
|
|||
|
|
),
|
|||
|
|
"reason": "将工具调用设为进入下一阶段的前置门槛",
|
|||
|
|
"evidence": ["实际调用 0 次 set_photo_button_visible"],
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
request = AsyncMock(side_effect=[noop, repaired])
|
|||
|
|
|
|||
|
|
with patch.object(agent, "_request", request):
|
|||
|
|
result = await agent.improve(
|
|||
|
|
snapshot=missing_tool_snapshot(),
|
|||
|
|
case_snapshot=missing_tool_case(),
|
|||
|
|
result=missing_tool_result(),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
self.assertEqual(request.await_count, 2)
|
|||
|
|
retry_instruction = request.await_args_list[1].kwargs["retry_instruction"]
|
|||
|
|
self.assertIn("实际调用 0 次", retry_instruction)
|
|||
|
|
self.assertIn("调用成功前", result.prompt_change.after)
|
|||
|
|
|
|||
|
|
async def test_missing_tool_uses_safe_fallback_after_two_noop_results(self):
|
|||
|
|
agent = PromptImprovementAgent(evaluator_resource())
|
|||
|
|
before = missing_tool_snapshot()["prompt"]
|
|||
|
|
noop = _GeneratedImprovement.model_validate(
|
|||
|
|
{
|
|||
|
|
"diagnosis": "Prompt 已包含工具调用要求",
|
|||
|
|
"promptChange": {
|
|||
|
|
"after": before,
|
|||
|
|
"reason": "保持原文",
|
|||
|
|
"evidence": ["已有调用要求"],
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
with patch.object(
|
|||
|
|
agent,
|
|||
|
|
"_request",
|
|||
|
|
AsyncMock(side_effect=[noop, noop]),
|
|||
|
|
):
|
|||
|
|
result = await agent.improve(
|
|||
|
|
snapshot=missing_tool_snapshot(),
|
|||
|
|
case_snapshot=missing_tool_case(),
|
|||
|
|
result=missing_tool_result(),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
self.assertIn("# 工具调用执行约束", result.prompt_change.after)
|
|||
|
|
self.assertIn("不得用自然语言", result.prompt_change.after)
|
|||
|
|
self.assertIn("没有实际调用", result.diagnosis)
|
|||
|
|
self.assertEqual(
|
|||
|
|
result.prompt_change.evidence,
|
|||
|
|
[
|
|||
|
|
"应调用 set_photo_button_visible 1 次以上;"
|
|||
|
|
"实际调用 0 次 set_photo_button_visible;"
|
|||
|
|
"工具调用次数不符合要求。"
|
|||
|
|
],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def test_unknown_variable_is_rejected(self):
|
|||
|
|
agent = PromptImprovementAgent(evaluator_resource())
|
|||
|
|
generated = _GeneratedImprovement.model_validate(
|
|||
|
|
{
|
|||
|
|
"diagnosis": "需要修改",
|
|||
|
|
"promptChange": {
|
|||
|
|
"after": "服务 {{unknown_name}}。",
|
|||
|
|
"reason": "错误建议",
|
|||
|
|
"evidence": [],
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
with patch.object(agent, "_request", AsyncMock(return_value=generated)):
|
|||
|
|
with self.assertRaisesRegex(PromptImprovementError, "未知动态变量"):
|
|||
|
|
await agent.improve(
|
|||
|
|
snapshot={
|
|||
|
|
"prompt": "服务用户。",
|
|||
|
|
"greeting": "您好",
|
|||
|
|
"dynamicVariableNames": [],
|
|||
|
|
"tools": [],
|
|||
|
|
},
|
|||
|
|
case_snapshot={"name": "订单", "turns": []},
|
|||
|
|
result=failed_result(),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def test_non_prompt_root_cause_can_return_manual_recommendation(self):
|
|||
|
|
agent = PromptImprovementAgent(evaluator_resource())
|
|||
|
|
generated = _GeneratedImprovement.model_validate(
|
|||
|
|
{
|
|||
|
|
"diagnosis": "工具描述与测试预期不一致",
|
|||
|
|
"promptChange": None,
|
|||
|
|
"manualRecommendations": [
|
|||
|
|
{
|
|||
|
|
"category": "tool",
|
|||
|
|
"title": "检查工具描述",
|
|||
|
|
"detail": "明确工具的适用条件。",
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
with patch.object(agent, "_request", AsyncMock(return_value=generated)):
|
|||
|
|
result = await agent.improve(
|
|||
|
|
snapshot={
|
|||
|
|
"prompt": "服务用户。",
|
|||
|
|
"greeting": "您好",
|
|||
|
|
"dynamicVariableNames": [],
|
|||
|
|
"tools": [],
|
|||
|
|
},
|
|||
|
|
case_snapshot={"name": "订单", "turns": []},
|
|||
|
|
result=failed_result(),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
self.assertIsNone(result.prompt_change)
|
|||
|
|
self.assertEqual(result.manual_recommendations[0].category, "tool")
|
|||
|
|
|
|||
|
|
async def test_model_timeout_is_reported_as_gateway_timeout(self):
|
|||
|
|
agent = PromptImprovementAgent(evaluator_resource())
|
|||
|
|
fake_client = AsyncMock()
|
|||
|
|
fake_client.__aenter__.return_value = fake_client
|
|||
|
|
fake_client.post.side_effect = httpx.ReadTimeout("timeout")
|
|||
|
|
|
|||
|
|
with patch(
|
|||
|
|
"services.test_runs.prompt_improvement.httpx.AsyncClient",
|
|||
|
|
return_value=fake_client,
|
|||
|
|
):
|
|||
|
|
with self.assertRaises(PromptImprovementError) as raised:
|
|||
|
|
await agent._request("{}")
|
|||
|
|
|
|||
|
|
self.assertEqual(raised.exception.status_code, 504)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class FakeScalarResult:
|
|||
|
|
def __init__(self, values):
|
|||
|
|
self._values = values
|
|||
|
|
|
|||
|
|
def scalars(self):
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def all(self):
|
|||
|
|
return self._values
|
|||
|
|
|
|||
|
|
|
|||
|
|
class FakePromptImprovementSession:
|
|||
|
|
def __init__(self, run, run_case, evaluator):
|
|||
|
|
self.run = run
|
|||
|
|
self.run_case = run_case
|
|||
|
|
self.evaluator = evaluator
|
|||
|
|
|
|||
|
|
async def get(self, model, identity):
|
|||
|
|
if model is TestRun and identity == self.run.id:
|
|||
|
|
return self.run
|
|||
|
|
if model is ModelResource and identity == self.evaluator.id:
|
|||
|
|
return self.evaluator
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
async def execute(self, _query):
|
|||
|
|
return FakeScalarResult([self.run_case])
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PromptImprovementRouteTests(unittest.IsolatedAsyncioTestCase):
|
|||
|
|
def make_session(self, *, status="fail", run_status="completed"):
|
|||
|
|
snapshot = {
|
|||
|
|
"assistantType": "prompt",
|
|||
|
|
"runtimeMode": "pipeline",
|
|||
|
|
"prompt": "你是订单助手",
|
|||
|
|
"dynamicVariableNames": [],
|
|||
|
|
}
|
|||
|
|
run = SimpleNamespace(
|
|||
|
|
id="run_1",
|
|||
|
|
status=run_status,
|
|||
|
|
config={
|
|||
|
|
"evaluatorModelResourceId": "model_eval",
|
|||
|
|
PROMPT_IMPROVEMENT_SNAPSHOT_KEY: snapshot,
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
run_case = SimpleNamespace(
|
|||
|
|
status=status,
|
|||
|
|
position=0,
|
|||
|
|
case_snapshot={"name": "订单查询", "turns": []},
|
|||
|
|
result=failed_result(),
|
|||
|
|
)
|
|||
|
|
evaluator = SimpleNamespace(
|
|||
|
|
id="model_eval",
|
|||
|
|
name="测试评估模型",
|
|||
|
|
capability="LLM",
|
|||
|
|
interface_type="openai-llm",
|
|||
|
|
values={"apiUrl": "https://model.example/v1", "modelId": "judge"},
|
|||
|
|
secrets={"apiKey": "secret"},
|
|||
|
|
support_image_input=False,
|
|||
|
|
enabled=True,
|
|||
|
|
)
|
|||
|
|
return FakePromptImprovementSession(run, run_case, evaluator)
|
|||
|
|
|
|||
|
|
async def test_route_returns_generated_result_without_writing_assistant(self):
|
|||
|
|
expected = PromptImprovementOut.model_validate(
|
|||
|
|
{
|
|||
|
|
"diagnosis": "缺少结果说明",
|
|||
|
|
"promptChange": {
|
|||
|
|
"before": "你是订单助手",
|
|||
|
|
"after": "你是订单助手。查询后说明结果。",
|
|||
|
|
"reason": "补充规则",
|
|||
|
|
"evidence": [],
|
|||
|
|
},
|
|||
|
|
"evaluatorName": "测试评估模型",
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
session = self.make_session()
|
|||
|
|
with patch.object(
|
|||
|
|
PromptImprovementAgent,
|
|||
|
|
"improve",
|
|||
|
|
AsyncMock(return_value=expected),
|
|||
|
|
):
|
|||
|
|
result = await create_prompt_improvement("run_1", session=session)
|
|||
|
|
|
|||
|
|
self.assertEqual(result, expected)
|
|||
|
|
|
|||
|
|
async def test_route_rejects_execution_error(self):
|
|||
|
|
session = self.make_session(status="error")
|
|||
|
|
|
|||
|
|
with self.assertRaises(HTTPException) as raised:
|
|||
|
|
await create_prompt_improvement("run_1", session=session)
|
|||
|
|
|
|||
|
|
self.assertEqual(raised.exception.status_code, 409)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
unittest.main()
|