|
@@ -1,8 +1,9 @@
|
|
|
import json
|
|
import json
|
|
|
import unicodedata
|
|
import unicodedata
|
|
|
from collections.abc import Mapping
|
|
from collections.abc import Mapping
|
|
|
|
|
+from enum import StrEnum
|
|
|
from types import MappingProxyType
|
|
from types import MappingProxyType
|
|
|
-from typing import Literal, TypeAlias
|
|
|
|
|
|
|
+from typing import Annotated, Literal, TypeAlias
|
|
|
from urllib.parse import urlsplit
|
|
from urllib.parse import urlsplit
|
|
|
|
|
|
|
|
import httpx
|
|
import httpx
|
|
@@ -17,29 +18,24 @@ from agent_lab.domain.events import ToolCallEvent
|
|
|
from agent_lab.domain.messages import StreamItem
|
|
from agent_lab.domain.messages import StreamItem
|
|
|
|
|
|
|
|
|
|
|
|
|
-BenchmarkMode: TypeAlias = Literal["dual_agent", "chat_agent_tools"]
|
|
|
|
|
-BenchmarkCaseId: TypeAlias = Literal[
|
|
|
|
|
- "ordinary_chat",
|
|
|
|
|
- "device_volume_silent",
|
|
|
|
|
- "calendar_schedule_template",
|
|
|
|
|
- "web_search_two_answers",
|
|
|
|
|
- "session_terminate",
|
|
|
|
|
- "parallel_volume_schedule",
|
|
|
|
|
-]
|
|
|
|
|
|
|
+class BenchmarkMode(StrEnum):
|
|
|
|
|
+ DUAL_AGENT = "dual_agent"
|
|
|
|
|
+ CHAT_AGENT_TOOLS = "chat_agent_tools"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class BenchmarkCaseId(StrEnum):
|
|
|
|
|
+ ORDINARY_CHAT = "ordinary_chat"
|
|
|
|
|
+ DEVICE_VOLUME_SILENT = "device_volume_silent"
|
|
|
|
|
+ CALENDAR_SCHEDULE_TEMPLATE = "calendar_schedule_template"
|
|
|
|
|
+ WEB_SEARCH_TWO_ANSWERS = "web_search_two_answers"
|
|
|
|
|
+ SESSION_TERMINATE = "session_terminate"
|
|
|
|
|
+ PARALLEL_VOLUME_SCHEDULE = "parallel_volume_schedule"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
JsonScalar: TypeAlias = str | int | float | bool | None
|
|
JsonScalar: TypeAlias = str | int | float | bool | None
|
|
|
|
|
|
|
|
-BENCHMARK_MODES: tuple[BenchmarkMode, ...] = (
|
|
|
|
|
- "dual_agent",
|
|
|
|
|
- "chat_agent_tools",
|
|
|
|
|
-)
|
|
|
|
|
-BENCHMARK_CASE_IDS: tuple[BenchmarkCaseId, ...] = (
|
|
|
|
|
- "ordinary_chat",
|
|
|
|
|
- "device_volume_silent",
|
|
|
|
|
- "calendar_schedule_template",
|
|
|
|
|
- "web_search_two_answers",
|
|
|
|
|
- "session_terminate",
|
|
|
|
|
- "parallel_volume_schedule",
|
|
|
|
|
-)
|
|
|
|
|
|
|
+BENCHMARK_MODES: tuple[BenchmarkMode, ...] = tuple(BenchmarkMode)
|
|
|
|
|
+BENCHMARK_CASE_IDS: tuple[BenchmarkCaseId, ...] = tuple(BenchmarkCaseId)
|
|
|
|
|
|
|
|
BENCHMARK_SYSTEM_PROMPT = "Exercise the requested business scenario."
|
|
BENCHMARK_SYSTEM_PROMPT = "Exercise the requested business scenario."
|
|
|
BENCHMARK_MAX_TOKENS = 128
|
|
BENCHMARK_MAX_TOKENS = 128
|
|
@@ -109,11 +105,11 @@ class BenchmarkTarget(_StrictBenchmarkModel):
|
|
|
class BenchmarkConfig(BenchmarkTarget):
|
|
class BenchmarkConfig(BenchmarkTarget):
|
|
|
schema_version: Literal[1]
|
|
schema_version: Literal[1]
|
|
|
runs_per_case: int = Field(default=1, ge=1)
|
|
runs_per_case: int = Field(default=1, ge=1)
|
|
|
- modes: list[BenchmarkMode] = Field(
|
|
|
|
|
|
|
+ modes: list[Annotated[BenchmarkMode, Field(strict=False)]] = Field(
|
|
|
default_factory=lambda: list(BENCHMARK_MODES),
|
|
default_factory=lambda: list(BENCHMARK_MODES),
|
|
|
min_length=1,
|
|
min_length=1,
|
|
|
)
|
|
)
|
|
|
- cases: list[BenchmarkCaseId] = Field(
|
|
|
|
|
|
|
+ cases: list[Annotated[BenchmarkCaseId, Field(strict=False)]] = Field(
|
|
|
default_factory=lambda: list(BENCHMARK_CASE_IDS),
|
|
default_factory=lambda: list(BENCHMARK_CASE_IDS),
|
|
|
min_length=1,
|
|
min_length=1,
|
|
|
)
|
|
)
|
|
@@ -127,7 +123,10 @@ class BenchmarkConfig(BenchmarkTarget):
|
|
|
|
|
|
|
|
@field_validator("modes", "cases")
|
|
@field_validator("modes", "cases")
|
|
|
@classmethod
|
|
@classmethod
|
|
|
- def reject_duplicates(cls, values: list[str]) -> list[str]:
|
|
|
|
|
|
|
+ def reject_duplicates(
|
|
|
|
|
+ cls,
|
|
|
|
|
+ values: list[BenchmarkMode | BenchmarkCaseId],
|
|
|
|
|
+ ) -> list[BenchmarkMode | BenchmarkCaseId]:
|
|
|
if len(values) != len(set(values)):
|
|
if len(values) != len(set(values)):
|
|
|
raise ValueError("values must not contain duplicates")
|
|
raise ValueError("values must not contain duplicates")
|
|
|
return values
|
|
return values
|
|
@@ -211,8 +210,8 @@ _SCHEDULE_CONFIRMATION = (
|
|
|
|
|
|
|
|
BENCHMARK_CASE_CATALOG: Mapping[BenchmarkCaseId, BenchmarkCase] = MappingProxyType(
|
|
BENCHMARK_CASE_CATALOG: Mapping[BenchmarkCaseId, BenchmarkCase] = MappingProxyType(
|
|
|
{
|
|
{
|
|
|
- "ordinary_chat": BenchmarkCase(
|
|
|
|
|
- case_id="ordinary_chat",
|
|
|
|
|
|
|
+ BenchmarkCaseId.ORDINARY_CHAT: BenchmarkCase(
|
|
|
|
|
+ case_id=BenchmarkCaseId.ORDINARY_CHAT,
|
|
|
user_message="Hello.",
|
|
user_message="Hello.",
|
|
|
enabled_tools=(
|
|
enabled_tools=(
|
|
|
"session.terminate",
|
|
"session.terminate",
|
|
@@ -230,8 +229,8 @@ BENCHMARK_CASE_CATALOG: Mapping[BenchmarkCaseId, BenchmarkCase] = MappingProxyTy
|
|
|
mock_visible_messages=("Ordinary answer.",),
|
|
mock_visible_messages=("Ordinary answer.",),
|
|
|
mock_events=(),
|
|
mock_events=(),
|
|
|
),
|
|
),
|
|
|
- "device_volume_silent": BenchmarkCase(
|
|
|
|
|
- case_id="device_volume_silent",
|
|
|
|
|
|
|
+ BenchmarkCaseId.DEVICE_VOLUME_SILENT: BenchmarkCase(
|
|
|
|
|
+ case_id=BenchmarkCaseId.DEVICE_VOLUME_SILENT,
|
|
|
user_message="set volume to 30",
|
|
user_message="set volume to 30",
|
|
|
enabled_tools=("device.volume.adjust",),
|
|
enabled_tools=("device.volume.adjust",),
|
|
|
expectation=BenchmarkExpectation(
|
|
expectation=BenchmarkExpectation(
|
|
@@ -250,8 +249,8 @@ BENCHMARK_CASE_CATALOG: Mapping[BenchmarkCaseId, BenchmarkCase] = MappingProxyTy
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
- "calendar_schedule_template": BenchmarkCase(
|
|
|
|
|
- case_id="calendar_schedule_template",
|
|
|
|
|
|
|
+ BenchmarkCaseId.CALENDAR_SCHEDULE_TEMPLATE: BenchmarkCase(
|
|
|
|
|
+ case_id=BenchmarkCaseId.CALENDAR_SCHEDULE_TEMPLATE,
|
|
|
user_message=(
|
|
user_message=(
|
|
|
'schedule "Design review" at 2026-07-14T09:30:00+08:00 '
|
|
'schedule "Design review" at 2026-07-14T09:30:00+08:00 '
|
|
|
"timezone Asia/Shanghai"
|
|
"timezone Asia/Shanghai"
|
|
@@ -273,8 +272,8 @@ BENCHMARK_CASE_CATALOG: Mapping[BenchmarkCaseId, BenchmarkCase] = MappingProxyTy
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
- "web_search_two_answers": BenchmarkCase(
|
|
|
|
|
- case_id="web_search_two_answers",
|
|
|
|
|
|
|
+ BenchmarkCaseId.WEB_SEARCH_TWO_ANSWERS: BenchmarkCase(
|
|
|
|
|
+ case_id=BenchmarkCaseId.WEB_SEARCH_TWO_ANSWERS,
|
|
|
user_message="event batch safety",
|
|
user_message="event batch safety",
|
|
|
enabled_tools=("knowledge.web.search",),
|
|
enabled_tools=("knowledge.web.search",),
|
|
|
expectation=BenchmarkExpectation(
|
|
expectation=BenchmarkExpectation(
|
|
@@ -293,8 +292,8 @@ BENCHMARK_CASE_CATALOG: Mapping[BenchmarkCaseId, BenchmarkCase] = MappingProxyTy
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
- "session_terminate": BenchmarkCase(
|
|
|
|
|
- case_id="session_terminate",
|
|
|
|
|
|
|
+ BenchmarkCaseId.SESSION_TERMINATE: BenchmarkCase(
|
|
|
|
|
+ case_id=BenchmarkCaseId.SESSION_TERMINATE,
|
|
|
user_message="end this session",
|
|
user_message="end this session",
|
|
|
enabled_tools=("session.terminate",),
|
|
enabled_tools=("session.terminate",),
|
|
|
expectation=BenchmarkExpectation(
|
|
expectation=BenchmarkExpectation(
|
|
@@ -307,8 +306,8 @@ BENCHMARK_CASE_CATALOG: Mapping[BenchmarkCaseId, BenchmarkCase] = MappingProxyTy
|
|
|
mock_visible_messages=("Goodbye.",),
|
|
mock_visible_messages=("Goodbye.",),
|
|
|
mock_events=(_mock_event("terminate-1", "session.terminate"),),
|
|
mock_events=(_mock_event("terminate-1", "session.terminate"),),
|
|
|
),
|
|
),
|
|
|
- "parallel_volume_schedule": BenchmarkCase(
|
|
|
|
|
- case_id="parallel_volume_schedule",
|
|
|
|
|
|
|
+ BenchmarkCaseId.PARALLEL_VOLUME_SCHEDULE: BenchmarkCase(
|
|
|
|
|
+ case_id=BenchmarkCaseId.PARALLEL_VOLUME_SCHEDULE,
|
|
|
user_message=(
|
|
user_message=(
|
|
|
'set volume to 30 and schedule "Design review" at '
|
|
'set volume to 30 and schedule "Design review" at '
|
|
|
"2026-07-14T09:30:00+08:00 timezone Asia/Shanghai"
|
|
"2026-07-14T09:30:00+08:00 timezone Asia/Shanghai"
|
|
@@ -350,13 +349,19 @@ BENCHMARK_CASE_CATALOG: Mapping[BenchmarkCaseId, BenchmarkCase] = MappingProxyTy
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _normalize_benchmark_mode(mode: BenchmarkMode | str) -> BenchmarkMode:
|
|
|
|
|
+ try:
|
|
|
|
|
+ return BenchmarkMode(mode)
|
|
|
|
|
+ except ValueError as exc:
|
|
|
|
|
+ raise ValueError(f"unsupported benchmark mode: {mode}") from exc
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def build_benchmark_request(
|
|
def build_benchmark_request(
|
|
|
case: BenchmarkCase,
|
|
case: BenchmarkCase,
|
|
|
- mode: BenchmarkMode,
|
|
|
|
|
|
|
+ mode: BenchmarkMode | str,
|
|
|
model: str,
|
|
model: str,
|
|
|
) -> DebugRunRequest:
|
|
) -> DebugRunRequest:
|
|
|
- if mode not in BENCHMARK_MODES:
|
|
|
|
|
- raise ValueError(f"unsupported benchmark mode: {mode}")
|
|
|
|
|
|
|
+ normalized_mode = _normalize_benchmark_mode(mode)
|
|
|
validated_model = _validate_model(model)
|
|
validated_model = _validate_model(model)
|
|
|
return DebugRunRequest(
|
|
return DebugRunRequest(
|
|
|
user_message=case.user_message,
|
|
user_message=case.user_message,
|
|
@@ -376,16 +381,15 @@ def build_benchmark_request(
|
|
|
max_parallel_events=BENCHMARK_MAX_PARALLEL_EVENTS,
|
|
max_parallel_events=BENCHMARK_MAX_PARALLEL_EVENTS,
|
|
|
batch_timeout_seconds=BENCHMARK_BATCH_TIMEOUT_SECONDS,
|
|
batch_timeout_seconds=BENCHMARK_BATCH_TIMEOUT_SECONDS,
|
|
|
),
|
|
),
|
|
|
- tool_invocation_mode=mode,
|
|
|
|
|
|
|
+ tool_invocation_mode=normalized_mode.value,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_mock_rounds(
|
|
def build_mock_rounds(
|
|
|
case: BenchmarkCase,
|
|
case: BenchmarkCase,
|
|
|
- mode: BenchmarkMode,
|
|
|
|
|
|
|
+ mode: BenchmarkMode | str,
|
|
|
) -> list[list[StreamItem]]:
|
|
) -> list[list[StreamItem]]:
|
|
|
- if mode not in BENCHMARK_MODES:
|
|
|
|
|
- raise ValueError(f"unsupported benchmark mode: {mode}")
|
|
|
|
|
|
|
+ normalized_mode = _normalize_benchmark_mode(mode)
|
|
|
|
|
|
|
|
first_round: list[StreamItem] = []
|
|
first_round: list[StreamItem] = []
|
|
|
if case.mock_visible_messages:
|
|
if case.mock_visible_messages:
|
|
@@ -404,7 +408,7 @@ def build_mock_rounds(
|
|
|
)
|
|
)
|
|
|
event_item = (
|
|
event_item = (
|
|
|
StreamItem.text_event(event)
|
|
StreamItem.text_event(event)
|
|
|
- if mode == "dual_agent"
|
|
|
|
|
|
|
+ if normalized_mode is BenchmarkMode.DUAL_AGENT
|
|
|
else StreamItem.provider_tool_call(event)
|
|
else StreamItem.provider_tool_call(event)
|
|
|
)
|
|
)
|
|
|
first_round.append(event_item)
|
|
first_round.append(event_item)
|