|
@@ -17,6 +17,10 @@ from agent_lab.application.contracts import (
|
|
|
DebugRunRequest,
|
|
DebugRunRequest,
|
|
|
EventAgentParams,
|
|
EventAgentParams,
|
|
|
)
|
|
)
|
|
|
|
|
+from agent_lab.application.events import (
|
|
|
|
|
+ InMemoryCalendarScheduleAdapter,
|
|
|
|
|
+ InMemoryDeviceVolumeAdapter,
|
|
|
|
|
+)
|
|
|
from agent_lab.application.runtime import DebugRuntime
|
|
from agent_lab.application.runtime import DebugRuntime
|
|
|
from agent_lab.application.queues import RuntimeQueues
|
|
from agent_lab.application.queues import RuntimeQueues
|
|
|
from agent_lab.application.tools import build_default_tool_registry
|
|
from agent_lab.application.tools import build_default_tool_registry
|
|
@@ -50,6 +54,7 @@ BENCHMARK_MAX_TOKENS = 128
|
|
|
BENCHMARK_MAX_EVENT_LOOPS = 1
|
|
BENCHMARK_MAX_EVENT_LOOPS = 1
|
|
|
BENCHMARK_MAX_PARALLEL_EVENTS = 2
|
|
BENCHMARK_MAX_PARALLEL_EVENTS = 2
|
|
|
BENCHMARK_BATCH_TIMEOUT_SECONDS = 1.0
|
|
BENCHMARK_BATCH_TIMEOUT_SECONDS = 1.0
|
|
|
|
|
+BENCHMARK_PARALLEL_BARRIER_TIMEOUT_SECONDS = 0.05
|
|
|
|
|
|
|
|
|
|
|
|
|
class _StrictBenchmarkModel(BaseModel):
|
|
class _StrictBenchmarkModel(BaseModel):
|
|
@@ -174,6 +179,79 @@ class BenchmarkRunResult(_StrictBenchmarkModel):
|
|
|
semantic_failures: list[str]
|
|
semantic_failures: list[str]
|
|
|
error: str | None
|
|
error: str | None
|
|
|
model_calls: list[BenchmarkModelCallTiming]
|
|
model_calls: list[BenchmarkModelCallTiming]
|
|
|
|
|
+ parallel_events_overlapped: bool | None = None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class BenchmarkParallelEventProbe:
|
|
|
|
|
+ _EXPECTED_EVENTS = frozenset(
|
|
|
|
|
+ {"device.volume.adjust", "calendar.schedule.create"}
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(
|
|
|
|
|
+ self,
|
|
|
|
|
+ *,
|
|
|
|
|
+ barrier_timeout_seconds: float = BENCHMARK_PARALLEL_BARRIER_TIMEOUT_SECONDS,
|
|
|
|
|
+ ) -> None:
|
|
|
|
|
+ self.barrier_timeout_seconds = barrier_timeout_seconds
|
|
|
|
|
+ self._volume = InMemoryDeviceVolumeAdapter()
|
|
|
|
|
+ self._calendar = InMemoryCalendarScheduleAdapter()
|
|
|
|
|
+ self._active: set[str] = set()
|
|
|
|
|
+ self._release = asyncio.Event()
|
|
|
|
|
+ self._lock = asyncio.Lock()
|
|
|
|
|
+ self.overlapped = False
|
|
|
|
|
+
|
|
|
|
|
+ async def adjust(
|
|
|
|
|
+ self,
|
|
|
|
|
+ event_id: str,
|
|
|
|
|
+ *,
|
|
|
|
|
+ mode: str,
|
|
|
|
|
+ value: int | None = None,
|
|
|
|
|
+ delta: int | None = None,
|
|
|
|
|
+ ) -> dict[str, Any]:
|
|
|
|
|
+ await self._await_overlap("device.volume.adjust")
|
|
|
|
|
+ return self._volume.adjust(
|
|
|
|
|
+ event_id,
|
|
|
|
|
+ mode=mode,
|
|
|
|
|
+ value=value,
|
|
|
|
|
+ delta=delta,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ async def create(
|
|
|
|
|
+ self,
|
|
|
|
|
+ event_id: str,
|
|
|
|
|
+ *,
|
|
|
|
|
+ title: str,
|
|
|
|
|
+ start_at: str,
|
|
|
|
|
+ timezone: str,
|
|
|
|
|
+ recurrence: str | None = None,
|
|
|
|
|
+ reminder_minutes: int | None = None,
|
|
|
|
|
+ ) -> dict[str, Any]:
|
|
|
|
|
+ await self._await_overlap("calendar.schedule.create")
|
|
|
|
|
+ return self._calendar.create(
|
|
|
|
|
+ event_id,
|
|
|
|
|
+ title=title,
|
|
|
|
|
+ start_at=start_at,
|
|
|
|
|
+ timezone=timezone,
|
|
|
|
|
+ recurrence=recurrence,
|
|
|
|
|
+ reminder_minutes=reminder_minutes,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ async def _await_overlap(self, event_name: str) -> None:
|
|
|
|
|
+ async with self._lock:
|
|
|
|
|
+ self._active.add(event_name)
|
|
|
|
|
+ if self._EXPECTED_EVENTS.issubset(self._active):
|
|
|
|
|
+ self.overlapped = True
|
|
|
|
|
+ self._release.set()
|
|
|
|
|
+ try:
|
|
|
|
|
+ await asyncio.wait_for(
|
|
|
|
|
+ self._release.wait(),
|
|
|
|
|
+ timeout=self.barrier_timeout_seconds,
|
|
|
|
|
+ )
|
|
|
|
|
+ except TimeoutError:
|
|
|
|
|
+ pass
|
|
|
|
|
+ finally:
|
|
|
|
|
+ async with self._lock:
|
|
|
|
|
+ self._active.discard(event_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
class TimingChatClient:
|
|
class TimingChatClient:
|
|
@@ -202,14 +280,36 @@ class TimingChatClient:
|
|
|
first_item_kind: str | None = None
|
|
first_item_kind: str | None = None
|
|
|
first_message_delta_at: float | None = None
|
|
first_message_delta_at: float | None = None
|
|
|
usage: TokenUsage | None = None
|
|
usage: TokenUsage | None = None
|
|
|
|
|
+ provider_elapsed_seconds = 0.0
|
|
|
|
|
+ iterator: AsyncIterator[StreamItem] | None = None
|
|
|
try:
|
|
try:
|
|
|
stream_chat = getattr(self.inner, "stream_chat")
|
|
stream_chat = getattr(self.inner, "stream_chat")
|
|
|
- async for item in stream_chat(
|
|
|
|
|
|
|
+ iterator = stream_chat(
|
|
|
messages=messages,
|
|
messages=messages,
|
|
|
tools=tools,
|
|
tools=tools,
|
|
|
params=params,
|
|
params=params,
|
|
|
tool_choice=tool_choice,
|
|
tool_choice=tool_choice,
|
|
|
- ):
|
|
|
|
|
|
|
+ ).__aiter__()
|
|
|
|
|
+ while True:
|
|
|
|
|
+ wait_started_at = self.clock()
|
|
|
|
|
+ try:
|
|
|
|
|
+ item = await iterator.__anext__()
|
|
|
|
|
+ except StopAsyncIteration:
|
|
|
|
|
+ provider_elapsed_seconds += max(
|
|
|
|
|
+ 0.0,
|
|
|
|
|
+ self.clock() - wait_started_at,
|
|
|
|
|
+ )
|
|
|
|
|
+ break
|
|
|
|
|
+ except BaseException:
|
|
|
|
|
+ provider_elapsed_seconds += max(
|
|
|
|
|
+ 0.0,
|
|
|
|
|
+ self.clock() - wait_started_at,
|
|
|
|
|
+ )
|
|
|
|
|
+ raise
|
|
|
|
|
+ provider_elapsed_seconds += max(
|
|
|
|
|
+ 0.0,
|
|
|
|
|
+ self.clock() - wait_started_at,
|
|
|
|
|
+ )
|
|
|
item_at = self.clock()
|
|
item_at = self.clock()
|
|
|
if first_item_at is None:
|
|
if first_item_at is None:
|
|
|
first_item_at = item_at
|
|
first_item_at = item_at
|
|
@@ -224,24 +324,29 @@ class TimingChatClient:
|
|
|
usage = item.usage
|
|
usage = item.usage
|
|
|
yield item
|
|
yield item
|
|
|
finally:
|
|
finally:
|
|
|
- timing = BenchmarkModelCallTiming(
|
|
|
|
|
- call_index=call_index,
|
|
|
|
|
- call_kind=(
|
|
|
|
|
- "argument_fallback"
|
|
|
|
|
- if tool_choice is not None
|
|
|
|
|
- else "chat_completion"
|
|
|
|
|
- ),
|
|
|
|
|
- first_item_kind=first_item_kind,
|
|
|
|
|
- provider_ttft_ms=self._elapsed_ms(started_at, first_item_at),
|
|
|
|
|
- visible_ttft_ms=self._elapsed_ms(
|
|
|
|
|
- started_at,
|
|
|
|
|
- first_message_delta_at,
|
|
|
|
|
- ),
|
|
|
|
|
- elapsed_ms=self._elapsed_ms(started_at, self.clock()),
|
|
|
|
|
- usage=usage,
|
|
|
|
|
- )
|
|
|
|
|
- self.timings.append(timing)
|
|
|
|
|
- self.timings.sort(key=lambda item: item.call_index)
|
|
|
|
|
|
|
+ try:
|
|
|
|
|
+ close = getattr(iterator, "aclose", None)
|
|
|
|
|
+ if close is not None:
|
|
|
|
|
+ await close()
|
|
|
|
|
+ finally:
|
|
|
|
|
+ timing = BenchmarkModelCallTiming(
|
|
|
|
|
+ call_index=call_index,
|
|
|
|
|
+ call_kind=(
|
|
|
|
|
+ "argument_fallback"
|
|
|
|
|
+ if tool_choice is not None
|
|
|
|
|
+ else "chat_completion"
|
|
|
|
|
+ ),
|
|
|
|
|
+ first_item_kind=first_item_kind,
|
|
|
|
|
+ provider_ttft_ms=self._elapsed_ms(started_at, first_item_at),
|
|
|
|
|
+ visible_ttft_ms=self._elapsed_ms(
|
|
|
|
|
+ started_at,
|
|
|
|
|
+ first_message_delta_at,
|
|
|
|
|
+ ),
|
|
|
|
|
+ elapsed_ms=max(0, round(provider_elapsed_seconds * 1000)),
|
|
|
|
|
+ usage=usage,
|
|
|
|
|
+ )
|
|
|
|
|
+ self.timings.append(timing)
|
|
|
|
|
+ self.timings.sort(key=lambda item: item.call_index)
|
|
|
|
|
|
|
|
async def aclose(self) -> None:
|
|
async def aclose(self) -> None:
|
|
|
close = getattr(self.inner, "aclose", None)
|
|
close = getattr(self.inner, "aclose", None)
|
|
@@ -607,6 +712,7 @@ class MockBenchmarkChatClient:
|
|
|
|
|
|
|
|
|
|
|
|
|
BenchmarkClientFactory: TypeAlias = Callable[[BenchmarkConfig, str], object]
|
|
BenchmarkClientFactory: TypeAlias = Callable[[BenchmarkConfig, str], object]
|
|
|
|
|
+BenchmarkParallelProbeFactory: TypeAlias = Callable[[], BenchmarkParallelEventProbe]
|
|
|
|
|
|
|
|
|
|
|
|
|
class _BenchmarkOutputQueue(asyncio.Queue[dict[str, Any]]):
|
|
class _BenchmarkOutputQueue(asyncio.Queue[dict[str, Any]]):
|
|
@@ -634,6 +740,7 @@ class BenchmarkRunner:
|
|
|
api_key: str | None,
|
|
api_key: str | None,
|
|
|
mock: bool = False,
|
|
mock: bool = False,
|
|
|
client_factory: BenchmarkClientFactory | None = None,
|
|
client_factory: BenchmarkClientFactory | None = None,
|
|
|
|
|
+ parallel_probe_factory: BenchmarkParallelProbeFactory | None = None,
|
|
|
clock: Callable[[], float] = time.perf_counter,
|
|
clock: Callable[[], float] = time.perf_counter,
|
|
|
) -> None:
|
|
) -> None:
|
|
|
if not mock and not (api_key or "").strip():
|
|
if not mock and not (api_key or "").strip():
|
|
@@ -642,6 +749,9 @@ class BenchmarkRunner:
|
|
|
self.api_key = (api_key or "").strip()
|
|
self.api_key = (api_key or "").strip()
|
|
|
self.mock = mock
|
|
self.mock = mock
|
|
|
self.client_factory = client_factory or self._default_client_factory
|
|
self.client_factory = client_factory or self._default_client_factory
|
|
|
|
|
+ self.parallel_probe_factory = (
|
|
|
|
|
+ parallel_probe_factory or BenchmarkParallelEventProbe
|
|
|
|
|
+ )
|
|
|
self.clock = clock
|
|
self.clock = clock
|
|
|
|
|
|
|
|
async def run(self) -> list[BenchmarkRunResult]:
|
|
async def run(self) -> list[BenchmarkRunResult]:
|
|
@@ -662,6 +772,7 @@ class BenchmarkRunner:
|
|
|
store = SQLiteSessionStore(":memory:")
|
|
store = SQLiteSessionStore(":memory:")
|
|
|
runtime: DebugRuntime | None = None
|
|
runtime: DebugRuntime | None = None
|
|
|
timing_client: TimingChatClient | None = None
|
|
timing_client: TimingChatClient | None = None
|
|
|
|
|
+ parallel_probe: BenchmarkParallelEventProbe | None = None
|
|
|
result: BenchmarkRunResult | None = None
|
|
result: BenchmarkRunResult | None = None
|
|
|
close_error: Exception | None = None
|
|
close_error: Exception | None = None
|
|
|
try:
|
|
try:
|
|
@@ -678,10 +789,18 @@ class BenchmarkRunner:
|
|
|
self.config.model,
|
|
self.config.model,
|
|
|
).model_copy(update={"session_id": session_id})
|
|
).model_copy(update={"session_id": session_id})
|
|
|
output_queue = _BenchmarkOutputQueue(self.clock)
|
|
output_queue = _BenchmarkOutputQueue(self.clock)
|
|
|
|
|
+ if case.case_id is BenchmarkCaseId.PARALLEL_VOLUME_SCHEDULE:
|
|
|
|
|
+ parallel_probe = self.parallel_probe_factory()
|
|
|
|
|
+ registry = build_default_tool_registry(
|
|
|
|
|
+ device_volume_port=parallel_probe,
|
|
|
|
|
+ calendar_schedule_port=parallel_probe,
|
|
|
|
|
+ )
|
|
|
|
|
+ else:
|
|
|
|
|
+ registry = build_default_tool_registry()
|
|
|
runtime = DebugRuntime(
|
|
runtime = DebugRuntime(
|
|
|
timing_client,
|
|
timing_client,
|
|
|
queues=RuntimeQueues(output=output_queue),
|
|
queues=RuntimeQueues(output=output_queue),
|
|
|
- registry=build_default_tool_registry(),
|
|
|
|
|
|
|
+ registry=registry,
|
|
|
session_store=store,
|
|
session_store=store,
|
|
|
clock=self.clock,
|
|
clock=self.clock,
|
|
|
)
|
|
)
|
|
@@ -704,6 +823,11 @@ class BenchmarkRunner:
|
|
|
timings=timing_client.timings,
|
|
timings=timing_client.timings,
|
|
|
visible_ttft_ms=visible_ttft_ms,
|
|
visible_ttft_ms=visible_ttft_ms,
|
|
|
runtime_error=runtime_error,
|
|
runtime_error=runtime_error,
|
|
|
|
|
+ parallel_events_overlapped=(
|
|
|
|
|
+ parallel_probe.overlapped
|
|
|
|
|
+ if parallel_probe is not None
|
|
|
|
|
+ else None
|
|
|
|
|
+ ),
|
|
|
)
|
|
)
|
|
|
except Exception as exc:
|
|
except Exception as exc:
|
|
|
result = self._failed_result(
|
|
result = self._failed_result(
|
|
@@ -774,6 +898,7 @@ class BenchmarkRunner:
|
|
|
timings: list[BenchmarkModelCallTiming],
|
|
timings: list[BenchmarkModelCallTiming],
|
|
|
visible_ttft_ms: int | None,
|
|
visible_ttft_ms: int | None,
|
|
|
runtime_error: str | None,
|
|
runtime_error: str | None,
|
|
|
|
|
+ parallel_events_overlapped: bool | None = None,
|
|
|
) -> BenchmarkRunResult:
|
|
) -> BenchmarkRunResult:
|
|
|
session_usage = usage["session"]
|
|
session_usage = usage["session"]
|
|
|
calls = usage["calls"]
|
|
calls = usage["calls"]
|
|
@@ -808,6 +933,7 @@ class BenchmarkRunner:
|
|
|
timing_fallback_count = sum(
|
|
timing_fallback_count = sum(
|
|
|
timing.call_kind == "argument_fallback" for timing in timings
|
|
timing.call_kind == "argument_fallback" for timing in timings
|
|
|
)
|
|
)
|
|
|
|
|
+ token_totals, missing_usage_count = self._token_totals(timings)
|
|
|
tool_count = int(session_usage["tool_count"])
|
|
tool_count = int(session_usage["tool_count"])
|
|
|
semantic_failures = self._semantic_failures(
|
|
semantic_failures = self._semantic_failures(
|
|
|
case=case,
|
|
case=case,
|
|
@@ -824,6 +950,8 @@ class BenchmarkRunner:
|
|
|
tool_count=tool_count,
|
|
tool_count=tool_count,
|
|
|
timings=timings,
|
|
timings=timings,
|
|
|
check_mock_text=self.mock,
|
|
check_mock_text=self.mock,
|
|
|
|
|
+ missing_usage_count=missing_usage_count,
|
|
|
|
|
+ parallel_events_overlapped=parallel_events_overlapped,
|
|
|
)
|
|
)
|
|
|
initial_call = next(
|
|
initial_call = next(
|
|
|
(timing for timing in timings if timing.call_kind == "chat_completion"),
|
|
(timing for timing in timings if timing.call_kind == "chat_completion"),
|
|
@@ -840,10 +968,10 @@ class BenchmarkRunner:
|
|
|
),
|
|
),
|
|
|
visible_ttft_ms=visible_ttft_ms,
|
|
visible_ttft_ms=visible_ttft_ms,
|
|
|
turn_wall_time_ms=session_usage["turn_wall_time_ms"],
|
|
turn_wall_time_ms=session_usage["turn_wall_time_ms"],
|
|
|
- prompt_tokens=int(session_usage["prompt_tokens"]),
|
|
|
|
|
- completion_tokens=int(session_usage["completion_tokens"]),
|
|
|
|
|
- total_tokens=int(session_usage["total_tokens"]),
|
|
|
|
|
- cached_tokens=int(session_usage["cached_tokens"]),
|
|
|
|
|
|
|
+ prompt_tokens=token_totals[0],
|
|
|
|
|
+ completion_tokens=token_totals[1],
|
|
|
|
|
+ total_tokens=token_totals[2],
|
|
|
|
|
+ cached_tokens=token_totals[3],
|
|
|
model_call_count=timing_model_call_count,
|
|
model_call_count=timing_model_call_count,
|
|
|
fallback_count=timing_fallback_count,
|
|
fallback_count=timing_fallback_count,
|
|
|
tool_count=tool_count,
|
|
tool_count=tool_count,
|
|
@@ -856,6 +984,7 @@ class BenchmarkRunner:
|
|
|
semantic_failures=semantic_failures,
|
|
semantic_failures=semantic_failures,
|
|
|
error=runtime_error,
|
|
error=runtime_error,
|
|
|
model_calls=list(timings),
|
|
model_calls=list(timings),
|
|
|
|
|
+ parallel_events_overlapped=parallel_events_overlapped,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
def _semantic_failures(
|
|
def _semantic_failures(
|
|
@@ -875,6 +1004,8 @@ class BenchmarkRunner:
|
|
|
tool_count: int,
|
|
tool_count: int,
|
|
|
timings: list[BenchmarkModelCallTiming],
|
|
timings: list[BenchmarkModelCallTiming],
|
|
|
check_mock_text: bool,
|
|
check_mock_text: bool,
|
|
|
|
|
+ missing_usage_count: int = 0,
|
|
|
|
|
+ parallel_events_overlapped: bool | None = None,
|
|
|
) -> list[str]:
|
|
) -> list[str]:
|
|
|
failures: list[str] = []
|
|
failures: list[str] = []
|
|
|
answers, first_answer_index, first_tool_index = self._visible_answers(outputs)
|
|
answers, first_answer_index, first_tool_index = self._visible_answers(outputs)
|
|
@@ -977,6 +1108,15 @@ class BenchmarkRunner:
|
|
|
failures.append(
|
|
failures.append(
|
|
|
f"fallback_count ledger={fallback_count}, timing={timing_fallback_count}"
|
|
f"fallback_count ledger={fallback_count}, timing={timing_fallback_count}"
|
|
|
)
|
|
)
|
|
|
|
|
+ if missing_usage_count:
|
|
|
|
|
+ failures.append(
|
|
|
|
|
+ f"model usage missing for {missing_usage_count} call(s)"
|
|
|
|
|
+ )
|
|
|
|
|
+ if (
|
|
|
|
|
+ case.case_id is BenchmarkCaseId.PARALLEL_VOLUME_SCHEDULE
|
|
|
|
|
+ and parallel_events_overlapped is not True
|
|
|
|
|
+ ):
|
|
|
|
|
+ failures.append("parallel event handlers did not overlap")
|
|
|
terminal = any(audit["event"] == "terminal_completed" for audit in audits)
|
|
terminal = any(audit["event"] == "terminal_completed" for audit in audits)
|
|
|
if terminal is not expected.terminal:
|
|
if terminal is not expected.terminal:
|
|
|
failures.append(
|
|
failures.append(
|
|
@@ -1031,17 +1171,7 @@ class BenchmarkRunner:
|
|
|
fallback_count = sum(
|
|
fallback_count = sum(
|
|
|
timing.call_kind == "argument_fallback" for timing in timings
|
|
timing.call_kind == "argument_fallback" for timing in timings
|
|
|
)
|
|
)
|
|
|
- usages = [timing.usage for timing in timings if timing.usage is not None]
|
|
|
|
|
- prompt_tokens = (
|
|
|
|
|
- sum(usage.prompt_tokens for usage in usages) if usages else None
|
|
|
|
|
- )
|
|
|
|
|
- completion_tokens = (
|
|
|
|
|
- sum(usage.completion_tokens for usage in usages) if usages else None
|
|
|
|
|
- )
|
|
|
|
|
- total_tokens = sum(usage.total_tokens for usage in usages) if usages else None
|
|
|
|
|
- cached_tokens = (
|
|
|
|
|
- sum(usage.cached_tokens for usage in usages) if usages else None
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ token_totals, missing_usage_count = BenchmarkRunner._token_totals(timings)
|
|
|
return BenchmarkRunResult(
|
|
return BenchmarkRunResult(
|
|
|
case_id=case_id,
|
|
case_id=case_id,
|
|
|
mode=mode,
|
|
mode=mode,
|
|
@@ -1052,10 +1182,10 @@ class BenchmarkRunner:
|
|
|
),
|
|
),
|
|
|
visible_ttft_ms=None,
|
|
visible_ttft_ms=None,
|
|
|
turn_wall_time_ms=None,
|
|
turn_wall_time_ms=None,
|
|
|
- prompt_tokens=prompt_tokens,
|
|
|
|
|
- completion_tokens=completion_tokens,
|
|
|
|
|
- total_tokens=total_tokens,
|
|
|
|
|
- cached_tokens=cached_tokens,
|
|
|
|
|
|
|
+ prompt_tokens=token_totals[0],
|
|
|
|
|
+ completion_tokens=token_totals[1],
|
|
|
|
|
+ total_tokens=token_totals[2],
|
|
|
|
|
+ cached_tokens=token_totals[3],
|
|
|
model_call_count=model_call_count,
|
|
model_call_count=model_call_count,
|
|
|
fallback_count=fallback_count,
|
|
fallback_count=fallback_count,
|
|
|
tool_count=None,
|
|
tool_count=None,
|
|
@@ -1065,11 +1195,35 @@ class BenchmarkRunner:
|
|
|
event_sources=[],
|
|
event_sources=[],
|
|
|
tool_statuses=[],
|
|
tool_statuses=[],
|
|
|
tool_latencies_ms=[],
|
|
tool_latencies_ms=[],
|
|
|
- semantic_failures=[],
|
|
|
|
|
|
|
+ semantic_failures=(
|
|
|
|
|
+ [f"model usage missing for {missing_usage_count} call(s)"]
|
|
|
|
|
+ if missing_usage_count
|
|
|
|
|
+ else []
|
|
|
|
|
+ ),
|
|
|
error=error,
|
|
error=error,
|
|
|
model_calls=list(timings),
|
|
model_calls=list(timings),
|
|
|
|
|
+ parallel_events_overlapped=None,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _token_totals(
|
|
|
|
|
+ timings: list[BenchmarkModelCallTiming],
|
|
|
|
|
+ ) -> tuple[tuple[int | None, int | None, int | None, int | None], int]:
|
|
|
|
|
+ if not timings:
|
|
|
|
|
+ return (None, None, None, None), 0
|
|
|
|
|
+ missing_usage_count = sum(timing.usage is None for timing in timings)
|
|
|
|
|
+ if missing_usage_count:
|
|
|
|
|
+ return (None, None, None, None), missing_usage_count
|
|
|
|
|
+ usages = [timing.usage for timing in timings]
|
|
|
|
|
+ assert all(usage is not None for usage in usages)
|
|
|
|
|
+ complete_usages = [usage for usage in usages if usage is not None]
|
|
|
|
|
+ return (
|
|
|
|
|
+ sum(usage.prompt_tokens for usage in complete_usages),
|
|
|
|
|
+ sum(usage.completion_tokens for usage in complete_usages),
|
|
|
|
|
+ sum(usage.total_tokens for usage in complete_usages),
|
|
|
|
|
+ sum(usage.cached_tokens for usage in complete_usages),
|
|
|
|
|
+ ), 0
|
|
|
|
|
+
|
|
|
@staticmethod
|
|
@staticmethod
|
|
|
def _default_client_factory(
|
|
def _default_client_factory(
|
|
|
config: BenchmarkConfig,
|
|
config: BenchmarkConfig,
|