From 89df10bc4ee62931dbb4b9f4c2c7943dc7c224b2 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Mon, 7 Sep 2026 02:54:52 +0800 Subject: [PATCH 01/10] Complete phase two benchmarks, plot previews and static export workflow --- backend/app/agent/markdown_tools.py | 7 +- backend/app/benchmarks/agent.py | 137 +++++ backend/app/benchmarks/datasets.py | 19 +- backend/app/benchmarks/rag.py | 1 + backend/app/benchmarks/service.py | 9 + backend/app/contracts.py | 58 +- backend/app/export/assets.py | 140 +++++ backend/app/export/exporters/docx.py | 34 +- backend/app/export/exporters/html.py | 12 + backend/app/export/exporters/pdf.py | 21 +- backend/app/export/fonts.py | 22 + backend/app/export/service.py | 13 +- backend/app/export/themes.py | 1 + backend/app/local_models/runtime.py | 25 +- backend/app/main.py | 4 + backend/app/plot/model.py | 4 +- backend/app/plot/render.py | 41 +- backend/app/plot/render_reportlab.py | 9 +- backend/app/plot/renderer.py | 2 +- backend/app/plot_routes.py | 34 ++ backend/app/retrieval/engine.py | 8 +- backend/app/routes.py | 16 +- backend/data/benchmarks/agent-core-v1.json | 89 +++ backend/data/benchmarks/corpus/phase2-v1.json | 18 + backend/data/benchmarks/rag-phase2-v1.json | 368 ++++++++++++ backend/pyproject.toml | 1 + backend/scripts/agent-task-stress.py | 6 +- backend/scripts/phase2-context.py | 50 ++ backend/scripts/phase2-integration.py | 56 ++ backend/scripts/phase2-live.py | 64 ++ backend/scripts/phase2-quality.py | 69 +++ backend/tests/test_export.py | 11 +- backend/tests/test_phase2_completion.py | 166 ++++++ backend/tests/test_plot.py | 7 +- backend/uv.lock | 546 ++++++++++++++++++ frontend/scripts/phase2-benchmark-browser.cjs | 22 + frontend/scripts/phase2-browser-check.cjs | 94 +++ .../components/common/DiagramInteractions.vue | 5 + .../src/components/common/MarkdownContent.vue | 2 +- .../src/components/common/PrimarySidebar.vue | 1 + .../src/features/benchmarks/BenchmarkView.vue | 65 +++ frontend/src/features/editor/EditorHeader.vue | 4 + frontend/src/features/editor/ExportDialog.vue | 56 ++ .../features/editor/VisualMarkdownEditor.vue | 14 +- .../src/features/editor/mermaidPreview.ts | 13 +- .../features/editor/shikiCodeMirror.spec.ts | 2 +- .../src/features/editor/shikiCodeMirror.ts | 1 + frontend/src/router/index.ts | 6 +- frontend/src/services/benchmarkService.ts | 14 + frontend/src/services/exportService.spec.ts | 18 + frontend/src/services/exportService.ts | 68 +++ .../src/services/functionPlotService.spec.ts | 22 + frontend/src/services/functionPlotService.ts | 21 + frontend/src/services/mermaidService.ts | 18 +- frontend/src/styles/tokens.css | 38 ++ frontend/src/utils/markdown.ts | 27 +- frontend/tests/visual/mermaid-matrix.html | 14 + frontend/tests/visual/phase2.html | 20 + frontend/vite.config.ts | 5 +- 59 files changed, 2535 insertions(+), 83 deletions(-) create mode 100644 backend/app/benchmarks/agent.py create mode 100644 backend/app/export/assets.py create mode 100644 backend/app/export/fonts.py create mode 100644 backend/app/plot_routes.py create mode 100644 backend/data/benchmarks/agent-core-v1.json create mode 100644 backend/data/benchmarks/corpus/phase2-v1.json create mode 100644 backend/data/benchmarks/rag-phase2-v1.json create mode 100644 backend/scripts/phase2-context.py create mode 100644 backend/scripts/phase2-integration.py create mode 100644 backend/scripts/phase2-live.py create mode 100644 backend/scripts/phase2-quality.py create mode 100644 backend/tests/test_phase2_completion.py create mode 100644 frontend/scripts/phase2-benchmark-browser.cjs create mode 100644 frontend/scripts/phase2-browser-check.cjs create mode 100644 frontend/src/features/benchmarks/BenchmarkView.vue create mode 100644 frontend/src/features/editor/ExportDialog.vue create mode 100644 frontend/src/services/benchmarkService.ts create mode 100644 frontend/src/services/exportService.spec.ts create mode 100644 frontend/src/services/exportService.ts create mode 100644 frontend/src/services/functionPlotService.spec.ts create mode 100644 frontend/src/services/functionPlotService.ts create mode 100644 frontend/tests/visual/phase2.html diff --git a/backend/app/agent/markdown_tools.py b/backend/app/agent/markdown_tools.py index ee6d627..a7738e1 100644 --- a/backend/app/agent/markdown_tools.py +++ b/backend/app/agent/markdown_tools.py @@ -6,7 +6,7 @@ from pydantic import BaseModel, ConfigDict, Field from app.contracts import ToolDefinition from app.services import note_service -Format = Literal['heading', 'paragraph', 'bold', 'italic', 'strikethrough', 'inline-code', 'bullet-list', 'ordered-list', 'task-list', 'blockquote', 'callout', 'code-block', 'mermaid', 'inline-math', 'math-block', 'link', 'image', 'table', 'horizontal-rule', 'hard-break', 'reference-link', 'html', 'metadata'] +Format = Literal['heading', 'paragraph', 'bold', 'italic', 'strikethrough', 'inline-code', 'bullet-list', 'ordered-list', 'task-list', 'blockquote', 'callout', 'code-block', 'mermaid', 'function-plot', 'inline-math', 'math-block', 'link', 'image', 'table', 'horizontal-rule', 'hard-break', 'reference-link', 'html', 'metadata'] CALLOUTS = ['note', 'abstract', 'summary', 'tldr', 'info', 'todo', 'tip', 'hint', 'important', 'success', 'check', 'done', 'question', 'help', 'faq', 'warning', 'caution', 'attention', 'failure', 'fail', 'missing', 'danger', 'error', 'bug', 'example', 'quote', 'cite'] @@ -56,7 +56,7 @@ def compose(arguments: ComposeArguments, _): elif kind == 'inline-code': marker = '`' * (max([0, *(len(m[0]) for m in re.finditer(r'`+', text))]) + 1) result = marker + ' ' + text.replace('\n', ' ') + ' ' + marker - elif kind in ('code-block', 'mermaid'): result = fenced(text, 'mermaid' if kind == 'mermaid' else a.language) + elif kind in ('code-block', 'mermaid', 'function-plot'): result = fenced(text, kind if kind != 'code-block' else a.language) elif kind in ('bullet-list', 'ordered-list', 'task-list'): result = '\n'.join((f'{i + 1}. ' if kind == 'ordered-list' else '- [ ] ' if kind == 'task-list' else '- ') + item.replace('\n', '\n ') for i, item in enumerate(a.items)) elif kind == 'blockquote': result = '\n'.join('> ' + line for line in text.split('\n')) @@ -90,7 +90,8 @@ def catalog(_, __): from typing import get_args return {'formats': list(get_args(Format)), 'callouts': CALLOUTS, 'workflow': 'Use markdown.compose, then notes.create or notes.patch_markdown to persist. Read notes.read.content_hash before patching. metadata composition replaces the frontmatter only when you explicitly patch it; do not prepend duplicate frontmatter.', - 'rendering': 'Math, Mermaid, callouts and auto-links depend on editor preferences. HTML is sanitized; scripts are not supported. Heading folding, font size, undo and redo are UI state, not Markdown document syntax. Callout collapsed=null is static, true is folded, false is expanded.'} + 'function_plot': 'Use a function-plot fenced block: domain: -4, 4 followed by y = x^2 and y = sin(x). At most 16 expressions per block, 16 plots and 8000 total AST nodes per exported document. No arbitrary code execution.', + 'rendering': 'Function plots, Math, Mermaid, callouts and auto-links depend on editor preferences. HTML is sanitized; scripts are not supported. Heading folding, font size, undo and redo are UI state, not Markdown document syntax. Callout collapsed=null is static, true is folded, false is expanded.'} async def patch(arguments: PatchArguments, _): diff --git a/backend/app/benchmarks/agent.py b/backend/app/benchmarks/agent.py new file mode 100644 index 0000000..12cec3f --- /dev/null +++ b/backend/app/benchmarks/agent.py @@ -0,0 +1,137 @@ +"""Standard task evaluation over AgentRuntime, never a scripted substitute runner.""" +import asyncio +from time import perf_counter +from uuid import uuid4 +from app.contracts import (AgentBenchmarkRequest, AgentCaseResult, AgentRunCreateRequest, + BenchmarkRun, BenchmarkReport, BenchmarkKind, BenchmarkStatus, BenchmarkEvent, BenchmarkEventType) +from app.benchmarks import datasets, service +from app.errors import ApiError + +INVALID = {'TOOL_NOT_FOUND', 'TOOL_NOT_ALLOWED', 'TOOL_ARGUMENT_INVALID', 'TOOL_VALIDATION_ERROR'} + +def score(case, run, events, latency, repeat): + calls = [e.data for e in events if e.event.value == 'ToolCall'] + unmatched = list(calls) + selected = accurate = 0 + for expected in case.expected_tools: + candidates = [c for c in unmatched if c.get('name') == expected.name] + if not candidates: + continue + exact = next((c for c in candidates if all(k in c.get('arguments', {}) and c['arguments'][k] == v for k,v in expected.arguments.items())), None) + chosen = exact or candidates[0] + unmatched.remove(chosen); selected += 1; accurate += int(exact is not None) + results = run.tool_results + checks = { + 'completed': run.status.value == 'completed', + 'tools_selected': selected == len(case.expected_tools), + 'tool_arguments': accurate == len(case.expected_tools), + 'no_extra_calls': len(calls) <= len(case.expected_tools), + 'tool_results': all(r.success for r in results), + 'output': all(text.casefold() in (run.output or '').casefold() for text in case.output_contains), + 'citation': not case.citation_required or bool(run.citations), + 'tasks_created': case.tasks_created is None or sum(r.success and r.name == 'tasks.create' for r in results) == case.tasks_created, + } + return AgentCaseResult(case_id=case.case_id, repeat=repeat, agent_run_id=run.run_id, + success=all(checks.values()), tool_calls=len(calls), expected_calls=len(case.expected_tools), + selected_calls=selected, accurate_calls=accurate, invalid_calls=sum(r.error_code in INVALID for r in results), + steps=run.current_step, latency_ms=latency, token_usage=run.token_usage, checks=checks, error_code=run.error_code) + +def aggregate(cases, planned_total=None): + total = len(cases) if planned_total is None else planned_total + calls = sum(c.tool_calls for c in cases) + expected = sum(c.expected_calls for c in cases) + # Micro accuracy penalizes omitted AND unnecessary calls; no-call cases are N/A. + denominator = max(calls, expected) + return {'total_cases': total, 'evaluated_cases': len(cases), 'task_success_rate': sum(c.success for c in cases)/total if total else 0, + 'tool_selection_accuracy': sum(c.selected_calls for c in cases)/denominator if denominator else None, + 'tool_argument_accuracy': sum(c.accurate_calls for c in cases)/denominator if denominator else None, + 'invalid_tool_call_rate': sum(c.invalid_calls for c in cases)/calls if calls else None, + 'average_steps': sum(c.steps for c in cases)/total if total else 0, + 'average_latency_ms': sum(c.latency_ms for c in cases)/total if total else 0, + 'token_usage': sum(c.token_usage for c in cases), 'tool_calls': calls, 'expected_calls': expected} + +async def create_run(request: AgentBenchmarkRequest): + from app.container import container + from app.providers.registry import ProviderNotFoundError + try: + provider = container.providers.get(request.provider_id) + except ProviderNotFoundError as exc: + raise ApiError(404, 'PROVIDER_NOT_FOUND', 'Provider not found or disabled.') from exc + is_mock = provider.config.provider_type.value == 'mock' + if request.offline and not is_mock: + raise ApiError(422, 'BENCHMARK_OFFLINE_PROVIDER_REQUIRED', 'Offline regression only accepts a mock provider.') + if is_mock and not request.offline: + raise ApiError(422, 'BENCHMARK_REAL_PROVIDER_REQUIRED', 'Select a real provider or explicitly mark offline regression.') + dataset = datasets.load_dataset(request.dataset_id, BenchmarkKind.agent) + if not service._evict_terminal(): + raise ApiError(429, 'BENCHMARK_CAPACITY_EXCEEDED', 'Benchmark capacity exceeded.') + run_id = 'benchmark_' + uuid4().hex[:12] + snapshot = {**request.model_dump(), 'dataset_hash': dataset.content_hash, + 'dataset_version': dataset.version, 'execution': 'offline' if request.offline else 'real_agent_runtime', + 'provider_type': provider.config.provider_type, 'scoring_version': '1.0', 'permission_policy': 'runtime_user_decision'} + run = BenchmarkRun(run_id=run_id, kind=BenchmarkKind.agent, dataset_id=dataset.dataset_id, + dataset_hash=dataset.content_hash, status=BenchmarkStatus.queued, created_at=service._now(), config_snapshot=snapshot) + service._runs[run_id] = run + service._events[run_id] = [] + service._subscribers[run_id] = [] + service._cancel_flags[run_id] = asyncio.Event() + service._tasks[run_id] = asyncio.create_task(execute(run_id, request, dataset, container.agent)) + return run + +async def execute(run_id, request, dataset, runtime): + flag = service._cancel_flags[run_id] + results = []; active = None + def emit(kind, data): + event = BenchmarkEvent(event=kind, run_id=run_id, sequence=len(service._events[run_id]), data=data, timestamp=service._now()) + service._events[run_id].append(event) + for queue in service._subscribers.get(run_id, []): queue.put_nowait(event) + status = BenchmarkStatus.completed + error = None + try: + service._runs[run_id] = service._runs[run_id].model_copy(update={'status': BenchmarkStatus.running, 'started_at': service._now()}) + emit(BenchmarkEventType.run_started, {'dataset_id': dataset.dataset_id}) + for case in dataset.cases: + for repeat in range(request.repeat): + if flag.is_set(): + status = BenchmarkStatus.cancelled; break + started = perf_counter() + active = await runtime.create_run(AgentRunCreateRequest(input=case.prompt, provider_id=request.provider_id, + model=request.model, allowed_tools=case.allowed_tools, max_steps=request.max_steps, + token_budget=request.token_budget, run_timeout_seconds=request.timeout_seconds, + tool_timeout_seconds=min(30, request.timeout_seconds), allow_network=request.allow_network, + metadata={'benchmark_run_id': run_id, 'case_id': case.case_id})) + # Surface the real Trace/permission entry while the case is still executing. + service._runs[run_id].config_snapshot['active_agent_run_id'] = active.run_id + wait = asyncio.create_task(runtime.wait(active.run_id)) + cancel = asyncio.create_task(flag.wait()) + try: + done, _ = await asyncio.wait([wait, cancel], return_when=asyncio.FIRST_COMPLETED) + if cancel in done: + await runtime.cancel(active.run_id) + status = BenchmarkStatus.cancelled + finished = await wait + finally: + cancel.cancel(); await asyncio.gather(cancel, return_exceptions=True) + events = [event async for event in runtime.events(active.run_id)] + result = score(case, finished, events, (perf_counter()-started)*1000, repeat) + results.append(result); active = None + service._runs[run_id].progress = len(results)/(len(dataset.cases)*request.repeat) + emit(BenchmarkEventType.case_completed, result.model_dump(mode='json')) + if status == BenchmarkStatus.cancelled: break + except asyncio.CancelledError: + status = BenchmarkStatus.cancelled + except Exception: + status = BenchmarkStatus.failed; error = 'BENCHMARK_RUN_FAILED' + finally: + if active: + await runtime.cancel(active.run_id) + await runtime.wait(active.run_id) + metrics = aggregate(results, len(dataset.cases)*request.repeat) + run = service._runs[run_id] + service._runs[run_id] = run.model_copy(update={'status':status, 'metrics':metrics, 'completed_at':service._now(), 'error_code':error}) + service._reports[run_id] = BenchmarkReport(run_id=run_id, kind=BenchmarkKind.agent, + dataset_id=dataset.dataset_id, dataset_hash=dataset.content_hash, status=status, + config_snapshot=run.config_snapshot, cases=results, metrics=metrics, error_code=error) + emit({BenchmarkStatus.completed: BenchmarkEventType.run_completed, BenchmarkStatus.failed: BenchmarkEventType.run_failed, + BenchmarkStatus.cancelled: BenchmarkEventType.run_cancelled}[status], {'metrics':metrics, 'error_code':error}) + service._cancel_flags.pop(run_id, None); service._subscribers.pop(run_id, None) diff --git a/backend/app/benchmarks/datasets.py b/backend/app/benchmarks/datasets.py index fff0661..6b2901e 100644 --- a/backend/app/benchmarks/datasets.py +++ b/backend/app/benchmarks/datasets.py @@ -17,20 +17,20 @@ from app.config import get_settings from app.contracts import ( BenchmarkDatasetInfo, BenchmarkKind, - RAGDatasetCase, + RAGDatasetCase, AgentDatasetCase, ) from app.errors import ApiError @dataclass class RAGDataset: - """内存中的 RAG 数据集:元信息 + 已校验的 Case 列表 + 内容哈希。""" + """内存中的 RAG / Agent 数据集:元信息 + 已校验的 Case 列表 + 内容哈希。""" dataset_id: str kind: BenchmarkKind version: str description: str - cases: list[RAGDatasetCase] = field(default_factory=list) + cases: list[RAGDatasetCase | AgentDatasetCase] = field(default_factory=list) content_hash: str = "" @@ -104,10 +104,10 @@ def _dataset_from_raw(raw: dict, raw_bytes: bytes, kind: BenchmarkKind) -> RAGDa {"dataset_id": dataset_id}, ) - cases: list[RAGDatasetCase] = [] + cases: list[RAGDatasetCase | AgentDatasetCase] = [] for index, case in enumerate(raw_cases): try: - parsed = RAGDatasetCase.model_validate(case) + parsed = (AgentDatasetCase if kind == BenchmarkKind.agent else RAGDatasetCase).model_validate(case) except ValidationError as exc: raise ApiError( 422, @@ -115,6 +115,13 @@ def _dataset_from_raw(raw: dict, raw_bytes: bytes, kind: BenchmarkKind) -> RAGDa f"Dataset case #{index} is invalid.", {"dataset_id": dataset_id, "case_index": index, "errors": exc.errors()}, ) from exc + if kind == BenchmarkKind.agent: + if not (parsed.expected_tools or parsed.output_contains or parsed.citation_required or parsed.tasks_created is not None): + raise ApiError(422, 'BENCHMARK_DATASET_INVALID', 'Agent case requires objective expectations.') + if any(tool.name not in parsed.allowed_tools for tool in parsed.expected_tools): + raise ApiError(422, 'BENCHMARK_DATASET_INVALID', 'Expected tools must be allowed.') + cases.append(parsed) + continue # 每个 Case 至少要声明一个期望 id,否则无法计算命中/召回 if not parsed.expected_note_ids and not parsed.expected_block_ids: raise ApiError( @@ -133,6 +140,8 @@ def _dataset_from_raw(raw: dict, raw_bytes: bytes, kind: BenchmarkKind) -> RAGDa ) cases.append(parsed) + if len(cases) > 100 or len({c.case_id for c in cases}) != len(cases): + raise ApiError(422, 'BENCHMARK_DATASET_INVALID', 'Dataset case IDs must be unique; maximum 100 cases.') return RAGDataset( dataset_id=dataset_id, kind=kind, diff --git a/backend/app/benchmarks/rag.py b/backend/app/benchmarks/rag.py index e364f46..6f53067 100644 --- a/backend/app/benchmarks/rag.py +++ b/backend/app/benchmarks/rag.py @@ -86,6 +86,7 @@ async def _evaluate_one( limit=request.retrieval.top_k, include_snippet=False, rrf_k=request.retrieval.rrf_k, + fusion=request.retrieval.fusion, rerank=request.retrieval.rerank, rerank_candidates=request.retrieval.rerank_candidates, score_threshold=request.retrieval.score_threshold, diff --git a/backend/app/benchmarks/service.py b/backend/app/benchmarks/service.py index f06f3ca..3ba2bbd 100644 --- a/backend/app/benchmarks/service.py +++ b/backend/app/benchmarks/service.py @@ -352,3 +352,12 @@ async def wait_for_run(run_id: str) -> BenchmarkRun: if task is not None: await task return _runs.get(run_id) + + +async def shutdown(): + loop = asyncio.get_running_loop() + active = {rid: task for rid, task in _tasks.items() if not task.done() and task.get_loop() is loop} + for rid in active: + flag = _cancel_flags.get(rid) + if flag: flag.set() + await asyncio.gather(*active.values(), return_exceptions=True) diff --git a/backend/app/contracts.py b/backend/app/contracts.py index 05e0608..dc3fd30 100644 --- a/backend/app/contracts.py +++ b/backend/app/contracts.py @@ -155,6 +155,7 @@ class SearchRequest(Contract): include_snippet: bool = True # 检索调优参数(Benchmark 与 Skill 共用):控制 RRF / 精排 / 候选池 / 分数阈值。 # rerank_candidates=None 表示对全部候选精排(保留原有行为),Benchmark 传显式值。 + fusion: Literal['rrf', 'weighted'] = 'rrf' rrf_k: int = Field(default=60, ge=1) rerank: bool = True rerank_candidates: int | None = Field(default=None, ge=1) @@ -1221,6 +1222,7 @@ class RAGRetrievalConfig(Contract): 其余参数透传到 SearchRequest,由检索引擎实际执行。""" top_k: int = Field(default=10, ge=1, le=100) + fusion: Literal['rrf', 'weighted'] = 'rrf' rrf_k: int = Field(default=60, ge=1) rerank: bool = True rerank_candidates: int = Field(default=20, ge=1) @@ -1329,6 +1331,51 @@ class RAGCaseResult(Contract): error_code: str | None = None +class ExpectedToolCall(Contract): + name: str = Field(min_length=1) + arguments: dict[str, Any] = Field(default_factory=dict) + + +class AgentDatasetCase(Contract): + case_id: str = Field(min_length=1) + prompt: str = Field(min_length=1, max_length=20000) + allowed_tools: list[str] = Field(default_factory=list, max_length=30) + expected_tools: list[ExpectedToolCall] = Field(default_factory=list, max_length=30) + output_contains: list[str] = Field(default_factory=list) + citation_required: bool = False + tasks_created: int | None = Field(default=None, ge=0, le=20) + tags: list[str] = Field(default_factory=list) + + +class AgentBenchmarkRequest(Contract): + dataset_id: str = Field(min_length=1) + provider_id: str + model: str = Field(min_length=1) + max_steps: int = Field(default=6, ge=1, le=20) + timeout_seconds: int = Field(default=90, ge=1, le=300) + token_budget: int = Field(default=6000, ge=1, le=30000) + repeat: int = Field(default=1, ge=1, le=3) + allow_network: bool = False + offline: bool = False + + +class AgentCaseResult(Contract): + case_id: str + repeat: int + agent_run_id: str | None = None + success: bool = False + tool_calls: int = 0 + expected_calls: int = 0 + selected_calls: int = 0 + accurate_calls: int = 0 + invalid_calls: int = 0 + steps: int = 0 + latency_ms: float = 0 + token_usage: int = 0 + checks: dict[str, bool] = Field(default_factory=dict) + error_code: str | None = None + + class BenchmarkReport(Contract): run_id: str kind: BenchmarkKind @@ -1337,7 +1384,7 @@ class BenchmarkReport(Contract): status: BenchmarkStatus config_snapshot: dict[str, Any] = Field(default_factory=dict) metrics: dict[str, Any] = Field(default_factory=dict) - cases: list[RAGCaseResult] = Field(default_factory=list) + cases: list[RAGCaseResult | AgentCaseResult] = Field(default_factory=list) error: str | None = None error_code: str | None = None @@ -1366,6 +1413,7 @@ class ExportSource(Contract): """导出源:note 引用已索引笔记,markdown 用于未保存预览(不持久化)。""" type: ExportSourceType + file_path: str | None = Field(default=None, max_length=1024) note_id: str | None = None markdown: str | None = None @@ -1386,7 +1434,15 @@ class ExportOptions(Contract): code_theme: str = "github-light" +class ExportAsset(Contract): + kind: Literal['mermaid', 'math_block', 'math_inline', 'image'] + source_hash: str = Field(pattern=r'^[a-f0-9]{64}$') + png_base64: str = Field(max_length=2800000) + + class ExportRequest(Contract): + assets: list[ExportAsset] = Field(default_factory=list, max_length=64) + title: str = Field(default="", max_length=200) source: ExportSource format: ExportFormat options: ExportOptions = Field(default_factory=ExportOptions) diff --git a/backend/app/export/assets.py b/backend/app/export/assets.py new file mode 100644 index 0000000..5415345 --- /dev/null +++ b/backend/app/export/assets.py @@ -0,0 +1,140 @@ +"""Bounded raster-only resource boundary. No URLs, XML or filesystem paths accepted.""" +import base64 +import hashlib +import threading +from io import BytesIO +from PIL import Image +from app.errors import ApiError + +_math_lock = threading.Lock() + +def enrich_document(document, file_path=None): + """Embed local vault images and bounded MathText. Unsupported TeX stays explicit.""" + from app.config import get_settings + from urllib.parse import unquote, urlsplit + vault = get_settings().vault_path.resolve() + base = (vault / (file_path or '')).parent if file_path else vault + warnings = [] + count = total = pixels = 0 + def visit(node): + nonlocal count, total, pixels + if node.type in {'image','math_block','math_inline'} or node.attributes.get('static_png'): + count += 1 + try: + if count > 64: raise ValueError('resource count') + if node.attributes.get('static_png'): + raw = node.attributes['static_png'] + elif node.type == 'image': + src = str(node.attributes.get('src','')) + if urlsplit(src).scheme or src.startswith('//'): raise ValueError('remote image') + path = (base / unquote(src)).resolve() + if not path.is_relative_to(vault) or path.suffix.lower() not in {'.png','.jpg','.jpeg','.webp'} or path.stat().st_size > 2_000_000: + raise ValueError('image path or budget') + raw = path.read_bytes() + else: + source = node.text + depth = 0 + for char in source: + depth += (char == '{') - (char == '}') + if depth > 20: raise ValueError('math depth') + if len(source) > 512 or depth != 0: raise ValueError('math budget') + from matplotlib.mathtext import math_to_image + with _math_lock: + out = BytesIO() + math_to_image('$'+source+'$', out, dpi=180, format='png', color='black') + raw = out.getvalue() + with Image.open(BytesIO(raw)) as image: + pixels += image.width * image.height + if pixels > 16_000_000: raise ValueError('document pixels') + if image.width * image.height > 4_000_000: raise ValueError('image dimensions') + out = BytesIO() + # Flatten alpha on white for portable print/Word output. + rgba=image.convert('RGBA'); background=Image.new('RGBA',rgba.size,'white') + background.alpha_composite(rgba); background.convert('RGB').save(out,'PNG') + png=out.getvalue();total += len(png) + if total > 8_000_000: raise ValueError('resource bytes') + node.attributes['static_png']=png + except Exception: + node.attributes.pop('static_png', None) + warnings.append('图片无法内嵌(仅支持 Vault 内 PNG/JPEG/WebP),已保留替代文字' if node.type=='image' + else '公式超出 MathText 语法或资源预算,已保留源码' if node.type.startswith('math') + else '静态图表超过文档资源预算,已保留源码') + for child in node.children: visit(child) + for child in document.children: visit(child) + return warnings + +def source_hash(source): + return hashlib.sha256(source.strip().encode()).hexdigest() + +def validate_assets(assets): + result = {} + total = pixels = 0 + for asset in assets: + try: + raw = base64.b64decode(asset.png_base64, validate=True) + total += len(raw) + if total > 8 * 1024 * 1024: + raise ValueError('asset budget') + with Image.open(BytesIO(raw)) as image: + pixels += image.width * image.height + if pixels > 16_000_000: raise ValueError('document pixel budget') + if image.format != 'PNG' or image.width * image.height > 4_000_000: + raise ValueError('image budget') + image.load() + out = BytesIO() + rgba = image.convert('RGBA') + background = Image.new('RGBA', rgba.size, 'white') + background.alpha_composite(rgba) + background.convert('RGB').save(out, 'PNG') + key = (asset.kind, asset.source_hash) + if key in result: + raise ValueError('duplicate asset') + result[key] = out.getvalue() + except Exception as exc: + raise ApiError(422, 'EXPORT_ASSET_INVALID', 'Invalid PNG or resource budget exceeded.') from exc + return result + +def attach_assets(document, assets): + def visit(node): + source = node.attributes.get('src', '') if node.type == 'image' else node.text + key = (node.type, source_hash(source)) + if key in assets: + node.attributes['static_png'] = assets[key] + for child in node.children: + visit(child) + for child in document.children: + visit(child) + +def plot_png(plot): + """DOCX consumes the same clipped geometry as SVG/PDF, rendered at 2x.""" + from app.plot.render import compute_geometry, _sx, _sy, _fmt_num + from PIL import ImageDraw, ImageFont + geo = compute_geometry(plot) + image = Image.new('RGB', (geo.width * 2, (geo.height + ((len(plot.expressions)+1)//2)*24) * 2), 'white') + draw = ImageDraw.Draw(image) + from app.export.fonts import FONT_PATH + font = ImageFont.truetype(str(FONT_PATH), 20) if FONT_PATH else ImageFont.load_default(size=20) + def line(points, color, width=2): + draw.line([(x * 2, y * 2) for x, y in points], fill=color, width=width) + sx = lambda x: _sx(x, geo.xmin, geo.xmax) + sy = lambda y: _sy(y, geo.ymin, geo.ymax) + for x in geo.xticks: + if geo.grid: line([(sx(x),52),(sx(x),428)], '#d0d7de') + draw.text((sx(x)*2, sy(geo.x_axis_y)*2+8), _fmt_num(x), fill='#57606a', font=font) + for y in geo.yticks: + if geo.grid: line([(52,sy(y)),(588,sy(y))], '#d0d7de') + draw.text((max(0,sx(geo.y_axis_x)*2-75),sy(y)*2), _fmt_num(y), fill='#57606a', font=font) + line([(52,sy(geo.x_axis_y)),(588,sy(geo.x_axis_y))], '#57606a') + line([(sx(geo.y_axis_x),52),(sx(geo.y_axis_x),428)], '#57606a') + for segments, color in zip(geo.polylines,geo.colors): + for segment in segments: + if len(segment)>1: line(segment,color,3) + if geo.xlabel: + draw.text((geo.width, (geo.height - 18)*2), geo.xlabel, fill='#1f2328', font=font, anchor='mm') + if geo.ylabel: + # Horizontal at the upper-left margin keeps CJK labels readable in Word. + draw.text((24, 24), geo.ylabel, fill='#1f2328', font=font) + for index, expression in enumerate(plot.expressions): + draw.text((48+(index%2)*620,geo.height*2+index//2*48),expression.label or 'y = '+expression.expression,fill=geo.colors[index],font=font) + out=BytesIO(); image.save(out,'PNG') + return out.getvalue(), geo.warnings diff --git a/backend/app/export/exporters/docx.py b/backend/app/export/exporters/docx.py index d3f03c0..12cefcb 100644 --- a/backend/app/export/exporters/docx.py +++ b/backend/app/export/exporters/docx.py @@ -1,7 +1,7 @@ """DocxExporter:Document AST → DOCX(python-docx)。 -v1 为文本优先:标题/段落/行内强调与链接/列表/引用/表格/代码块/数学文本均可导出; -function_plot 与 mermaid 保留源码占位并记 warning。中文字体通过 Normal 样式挂载 +标题、段落、列表、表格等使用原生 Word 元素;函数图、已准备的 Mermaid、 +受支持的公式与 Vault 图片使用静态图片,无法表示的资源保留源码并记 warning。中文字体通过 Normal 样式挂载 w:eastAsia=宋体,保证 Word 打开时中文正常显示;bold/italic 由 Word 原生渲染。 """ @@ -50,6 +50,8 @@ class DocxExporter: def render(self, document: Document, options: ExportOptions) -> ExportResult: """同步渲染;CPU 密集,调用方应放入线程执行,避免阻塞事件循环。""" + from app.export.exporters._common import FunctionPlotBudget + self._plot_budget = FunctionPlotBudget() self._doc = DocxDocument() self._configure_normal_style() self._configure_page(options) @@ -109,6 +111,13 @@ class DocxExporter: self._render_block(child, warnings) def _render_block(self, node: DocumentNode, warnings: list[str]) -> None: + if node.attributes.get('static_png'): + from PIL import Image + png = node.attributes['static_png'] + with Image.open(BytesIO(png)) as image: + width = min(5.8, image.width / (180 if node.type == 'math_block' else 96)) + self._doc.add_picture(BytesIO(png), width=Inches(width)) + return handler = getattr(self, f"_block_{node.type}", None) if handler is not None: handler(node, warnings) @@ -270,7 +279,20 @@ class DocxExporter: self._block_code_block(node, warnings) def _block_function_plot(self, node: DocumentNode, warnings: list[str]) -> None: - warnings.append(PLOT_PLACEHOLDER_WARNING) + from app.plot.parser import parse_source + from app.export.assets import plot_png + over = self._plot_budget.check_count() + if not over: + parsed = parse_source(node.text) + warnings.extend(d.message for d in parsed.diagnostics) + if parsed.plot: + over = self._plot_budget.check_nodes(parsed.plot.node_count) + if not over: + png, messages = plot_png(parsed.plot) + warnings.extend(messages) + self._doc.add_picture(BytesIO(png), width=Inches(5.8)) + return + warnings.append(over or '函数图像无法绘制,已保留源码') self._block_code_block(node, warnings) def _block_math_block(self, node: DocumentNode, warnings: list[str]) -> None: @@ -303,6 +325,12 @@ class DocxExporter: bold: bool = False, italic: bool = False, ) -> None: + if node.attributes.get('static_png'): + from PIL import Image + with Image.open(BytesIO(node.attributes['static_png'])) as image: + width = min(5.8, image.width / (180 if node.type.startswith('math') else 96)) + paragraph.add_run().add_picture(BytesIO(node.attributes['static_png']), width=Inches(width)) + return t = node.type if t == "text": self._add_run(paragraph, node.text, bold=bold, italic=italic) diff --git a/backend/app/export/exporters/html.py b/backend/app/export/exporters/html.py index ac8c8e8..5ccd364 100644 --- a/backend/app/export/exporters/html.py +++ b/backend/app/export/exporters/html.py @@ -151,6 +151,16 @@ class HtmlExporter: return "".join(self._render_node(child, warnings) for child in children) def _render_node(self, node: DocumentNode, warnings: list[str]) -> str: + if node.attributes.get('static_png'): + import base64 + data = base64.b64encode(node.attributes['static_png']).decode() + from PIL import Image + from io import BytesIO + width = '' + if node.type.startswith('math'): + with Image.open(BytesIO(node.attributes['static_png'])) as image: + width = f'width:{image.width*96/180:.1f}px;vertical-align:middle;' + return f'{html.escape(node.text or node.type)}' handler = getattr(self, f"_render_{node.type}", None) if handler is not None: return handler(node, warnings) @@ -257,6 +267,8 @@ class HtmlExporter: warnings.append(f"函数图像:解析或渲染失败,已回退占位({exc})") return f'
{html.escape(node.text)}
' warnings.extend(rendered.warnings) + from app.plot.render import theme_svg + rendered.content = theme_svg(rendered.content, self._options.theme_id) return f'
{rendered.content}
' def _render_math_block(self, node: DocumentNode, warnings: list[str]) -> str: diff --git a/backend/app/export/exporters/pdf.py b/backend/app/export/exporters/pdf.py index 2f44d22..2ba7949 100644 --- a/backend/app/export/exporters/pdf.py +++ b/backend/app/export/exporters/pdf.py @@ -42,8 +42,7 @@ from app.export.exporters._common import ( from app.plot.render_reportlab import render_drawing from app.plot.renderer import FunctionPlotStaticRenderer, StaticRenderRequest -_FONT = "STSong-Light" -pdfmetrics.registerFont(UnicodeCIDFont(_FONT)) +from app.export.fonts import FONT as _FONT _MIME = "application/pdf" @@ -122,6 +121,7 @@ class PdfExporter: self._styles = _make_styles() warnings: list[str] = [] print_theme_warning(options, warnings, "PDF") + if _FONT == "STSong-Light": warnings.append("PDF 使用 CID 字体,阅读器需提供中文字体;可配置 APP_EXPORT_FONT 嵌入 TrueType 字体") page = _PAGE_SIZES.get((options.page_size or "A4").lower(), A4) self._options = options @@ -169,6 +169,14 @@ class PdfExporter: self._render_block(child, story, warnings) def _render_block(self, node: DocumentNode, story: list, warnings: list[str]) -> None: + if node.attributes.get('static_png'): + from reportlab.platypus import Image + image = Image(BytesIO(node.attributes['static_png'])) + scale = min(1, self._plot_width / image.imageWidth, 600 / image.imageHeight) + image.drawWidth = image.imageWidth * scale + image.drawHeight = image.imageHeight * scale + story.append(image) + return handler = getattr(self, f"_block_{node.type}", None) if handler is not None: handler(node, story, warnings) @@ -362,6 +370,15 @@ class PdfExporter: return "".join(self._render_inline_node(child, warnings) for child in children) def _render_inline_node(self, node: DocumentNode, warnings: list[str]) -> str: + if node.attributes.get('static_png'): + import base64 + from PIL import Image as PILImage + raw = node.attributes['static_png'] + with PILImage.open(BytesIO(raw)) as image: + scale = min(.4 if node.type.startswith('math') else 1, 350/image.width, 160/image.height) + width, height = image.width*scale, image.height*scale + data = base64.b64encode(raw).decode() + return f'' t = node.type if t == "text": return _html.escape(node.text) diff --git a/backend/app/export/fonts.py b/backend/app/export/fonts.py new file mode 100644 index 0000000..df2440f --- /dev/null +++ b/backend/app/export/fonts.py @@ -0,0 +1,22 @@ +"""Embed an available CJK TrueType font; retain the portable CID fallback.""" +import os +from pathlib import Path +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont +from reportlab.pdfbase.cidfonts import UnicodeCIDFont + +def register_font(): + candidates = [os.getenv('APP_EXPORT_FONT',''), + str(Path(os.getenv('WINDIR','C:/Windows'))/'Fonts/simsun.ttc'), + '/usr/share/fonts/truetype/arphic/uming.ttc'] + for candidate in candidates: + if candidate and Path(candidate).is_file(): + try: + pdfmetrics.registerFont(TTFont('NotesExportCJK',candidate,subfontIndex=0)) + return 'NotesExportCJK', Path(candidate) + except Exception: + continue + pdfmetrics.registerFont(UnicodeCIDFont('STSong-Light')) + return 'STSong-Light', None + +FONT, FONT_PATH = register_font() diff --git a/backend/app/export/service.py b/backend/app/export/service.py index 10666eb..dfe7b31 100644 --- a/backend/app/export/service.py +++ b/backend/app/export/service.py @@ -182,12 +182,15 @@ async def _resolve_source(source: ExportSource) -> tuple[str, str, dict | None]: f"markdown source exceeds {MAX_MARKDOWN_CHARS} characters", {"size": len(markdown), "limit": MAX_MARKDOWN_CHARS}, ) - return markdown, "", None + return markdown, "", {"file_path": source.file_path} if source.file_path else None async def create_export(request: ExportRequest) -> ExportJob: """创建导出任务,立即返回 queued 的 ExportJob,由后台 Task 渲染。""" markdown, title, metadata = await _resolve_source(request.source) + title = request.title or title + from app.export.assets import validate_assets + assets = await asyncio.to_thread(validate_assets, request.assets) if not _evict_terminal(): raise ApiError( @@ -207,7 +210,7 @@ async def create_export(request: ExportRequest) -> ExportJob: _jobs[job_id] = job _cancel_flags[job_id] = asyncio.Event() _tasks[job_id] = asyncio.create_task( - _execute(job_id, request.format, markdown, title, metadata, request.options) + _execute(job_id, request.format, markdown, title, metadata, request.options, assets) ) return job @@ -245,6 +248,7 @@ async def _execute( title: str, metadata: dict | None, options: ExportOptions, + assets: dict | None = None, ) -> None: """后台渲染:排队 → 解析 → 导出 → 写文件 → 挂载产物元信息。""" cancel_event = _cancel_flags[job_id] @@ -273,10 +277,15 @@ async def _execute( # 使运行中的取消能在渲染边界生效;写文件前再次检查取消。 document = await asyncio.to_thread(parse_document, markdown) document.attributes["title"] = title + from app.export.assets import attach_assets + attach_assets(document, assets or {}) if metadata: document.attributes["metadata"] = metadata + from app.export.assets import enrich_document + resource_warnings = await asyncio.to_thread(enrich_document, document, (metadata or {}).get('file_path')) result = await asyncio.to_thread(_render_document, document, options, format) + result.warnings[:0] = resource_warnings if cancel_event.is_set(): raise ExportCancelled() if len(result.content) > MAX_EXPORT_BYTES: diff --git a/backend/app/export/themes.py b/backend/app/export/themes.py index 94e07ee..bf833cc 100644 --- a/backend/app/export/themes.py +++ b/backend/app/export/themes.py @@ -1,5 +1,6 @@ """Export palettes are fixed data; arbitrary theme CSS is never executed.""" PALETTES = { + 'ocean-blue': ('#edf5fa','#ffffff','#183a50','#46667a','#e6f1f8','#a6c5d9','#086b9c'), 'light': ('#f6f7f9','#ffffff','#1f2328','#57606a','#eaeef2','#d0d7de','#0969da'), 'dark': ('#010409','#0d1117','#e6edf3','#b1bac4','#21262d','#57606a','#79c0ff'), 'sepia': ('#eee5d2','#faf4e6','#463b2d','#6b5943','#eae0cd','#b5a58b','#80532a'), diff --git a/backend/app/local_models/runtime.py b/backend/app/local_models/runtime.py index 7bcb7e2..63766d1 100644 --- a/backend/app/local_models/runtime.py +++ b/backend/app/local_models/runtime.py @@ -5,6 +5,8 @@ import asyncio import json import os import time +import hashlib +from collections import OrderedDict from contextlib import closing from contextvars import ContextVar from functools import wraps @@ -239,6 +241,12 @@ class Runtime: runtime = Runtime() +# Bounded in-memory reuse of deterministic single-text local embeddings. The key +# includes the data/model location, immutable model revision and frozen runtime +# configuration. No remote API response or unavailable-model fallback is cached. +_embedding_cache = OrderedDict() +_EMBEDDING_CACHE_TTL = 600 + class LocalEmbedding: dim = 384 @@ -264,9 +272,24 @@ class LocalEmbedding: async def embed_documents(self, texts): config = (self._config or configuration()).model_copy(deep=True) + from app.retrieval.provenance import record_embedding + cache_key = None + if len(texts) == 1 and read_state(config.embedding_model)['status'] == 'installed' and interpreter(config).is_file(): + cache_key = (str(model_path(config.embedding_model).resolve()), config.model_dump_json(), + hashlib.sha256(texts[0].encode()).hexdigest()) + cached = _embedding_cache.get(cache_key) + if cached and time.monotonic() - cached[0] < _EMBEDDING_CACHE_TTL: + _embedding_cache.move_to_end(cache_key) + record_embedding(query_embedding_cache='hit') + return [list(cached[1])] + record_embedding(query_embedding_cache='miss') token = runtime_context.set(config) try: - return await runtime.infer(config.embedding_model, "embedding", {"texts": texts}, priority=embedding_priority.get()) + vectors = await runtime.infer(config.embedding_model, "embedding", {"texts": texts}, priority=embedding_priority.get()) + if cache_key and len(vectors) == 1: + _embedding_cache[cache_key] = (time.monotonic(), tuple(vectors[0])) + while len(_embedding_cache) > 128: _embedding_cache.popitem(last=False) + return vectors finally: runtime_context.reset(token) diff --git a/backend/app/main.py b/backend/app/main.py index c9e0651..6426954 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -35,6 +35,8 @@ async def lifespan(_: FastAPI): try: yield finally: + from app.benchmarks import service as benchmark_service + await benchmark_service.shutdown() await container.agent.shutdown() from app.services import index_service await index_service.shutdown() @@ -75,6 +77,8 @@ app.include_router(local_model_router) app.include_router(usage_router) app.include_router(provider_preview_router) app.include_router(log_router) +from app.plot_routes import router as plot_router +app.include_router(plot_router) @app.middleware('http') diff --git a/backend/app/plot/model.py b/backend/app/plot/model.py index f11273e..63ba52e 100644 --- a/backend/app/plot/model.py +++ b/backend/app/plot/model.py @@ -1,7 +1,7 @@ """Function Plot 内部数据模型。 -契约 §12.2 的 FunctionPlot 结构与 §10.4 的 StaticRenderResult 只在导出链路的后端内部 -流转,不进入 HTTP 契约,因此与 Document AST 一样放在独立包内,不进 contracts.py。 +FunctionPlot 供预览和导出共享;StaticRenderResult 同时是交互预览端点的响应内容。 +模型保留在独立包内,由 plot_routes 中的请求与响应类型注册 OpenAPI。 """ from __future__ import annotations diff --git a/backend/app/plot/render.py b/backend/app/plot/render.py index baee2be..40f494c 100644 --- a/backend/app/plot/render.py +++ b/backend/app/plot/render.py @@ -413,12 +413,12 @@ def _grid_svg(geo: PlotGeometry) -> str: for x in geo.xticks: parts.append( f'' + f'y2="{sy(geo.ymax):.2f}" stroke="#eaeef2" class="plot-grid"/>' ) for y in geo.yticks: parts.append( f'' + f'y2="{sy(y):.2f}" stroke="#eaeef2" class="plot-grid"/>' ) return "".join(parts) @@ -430,11 +430,11 @@ def _axes_svg(geo: PlotGeometry) -> str: # 坐标轴:过原点则画在原点,否则贴边,保证始终有参照系 parts.append( f'' + f'y2="{sy(geo.x_axis_y):.2f}" stroke="#57606a" class="plot-axis"/>' ) parts.append( f'' + f'y2="{sy(geo.ymax):.2f}" stroke="#57606a" class="plot-axis"/>' ) # x 轴刻度数字(画在轴下方) for x in geo.xticks: @@ -453,10 +453,10 @@ def _axes_svg(geo: PlotGeometry) -> str: def _polylines_svg(geo: PlotGeometry) -> str: parts: list[str] = [] - for segments, color in zip(geo.polylines, geo.colors): + for index, (segments, color) in enumerate(zip(geo.polylines, geo.colors)): for seg in segments: points = " ".join(f"{px:.2f},{py:.2f}" for px, py in seg) - parts.append(f'') + parts.append(f'') return "".join(parts) @@ -476,22 +476,43 @@ def _labels_svg(geo: PlotGeometry) -> str: return "".join(parts) -def render_svg(plot: FunctionPlot) -> StaticRenderResult: +def render_svg(plot: FunctionPlot, theme_id: str = 'light') -> StaticRenderResult: """把已解析的 FunctionPlot 渲染为内嵌 SVG。""" geo = compute_geometry(plot) + legend_height = ((len(plot.expressions) + 1) // 2) * 24 + height = geo.height + legend_height parts: list[str] = [ - f'' + f'' ] if geo.grid: parts.append(_grid_svg(geo)) parts.append(_axes_svg(geo)) parts.append(_polylines_svg(geo)) parts.append(_labels_svg(geo)) + for index, expression in enumerate(plot.expressions): + x = 24 + (index % 2) * 310 + y = geo.height + 18 + (index // 2) * 24 + label = html.escape(expression.label or ('y = ' + expression.expression)) + parts.append(f'{label}') parts.append("") return StaticRenderResult( - content="".join(parts), + content=theme_svg("".join(parts), theme_id), width=geo.width, - height=geo.height, + height=height, warnings=geo.warnings, ) + + +def theme_svg(svg: str, theme_id: str) -> str: + from app.export.themes import PALETTES + palette = PALETTES.get(theme_id, PALETTES['light']) + for source, target in [('#eaeef2', palette[5]), ('#57606a', palette[3]), ('#1f2328', palette[2])]: + svg = svg.replace(source, target) + if theme_id in {'dark', 'midnight-purple'}: + for source, target in zip(_PALETTE, ['#79c0ff','#ff9b9b','#7ee787','#d2a8ff','#f2cc60','#ffa657']): + svg = svg.replace(source, target) + background = '' + if re.search(r'', svg): + return re.sub(r'', background, svg, count=1) + return svg.replace('role="img" class="function-plot-svg">', 'role="img" class="function-plot-svg">' + background) diff --git a/backend/app/plot/render_reportlab.py b/backend/app/plot/render_reportlab.py index 3bd5452..576dc9f 100644 --- a/backend/app/plot/render_reportlab.py +++ b/backend/app/plot/render_reportlab.py @@ -17,9 +17,7 @@ from reportlab.pdfbase.cidfonts import UnicodeCIDFont from app.plot.model import FunctionPlot from app.plot.render import PlotGeometry, _fmt_num, _sx, _sy, compute_geometry -_FONT = "STSong-Light" -if _FONT not in pdfmetrics.getRegisteredFontNames(): - pdfmetrics.registerFont(UnicodeCIDFont(_FONT)) +from app.export.fonts import FONT as _FONT _GRID_COLOR = HexColor("#eaeef2") _AXIS_COLOR = HexColor("#57606a") @@ -115,6 +113,11 @@ def render_drawing(plot: FunctionPlot, width: float | None = None) -> Drawing: """ geo = compute_geometry(plot) drawing = _build_drawing(geo) + legend_height = ((len(plot.expressions)+1)//2)*24 + drawing.height += legend_height + for index, expression in enumerate(plot.expressions): + drawing.add(String(24+(index%2)*310,geo.height+legend_height-18-(index//2)*24, + expression.label or 'y = '+expression.expression,fontName=_FONT,fontSize=12,fillColor=HexColor(geo.colors[index]))) if width is not None and width > 0: drawing.renderScale = min(1.0, width / geo.width) return drawing diff --git a/backend/app/plot/renderer.py b/backend/app/plot/renderer.py index 78ee049..45f1165 100644 --- a/backend/app/plot/renderer.py +++ b/backend/app/plot/renderer.py @@ -47,7 +47,7 @@ class FunctionPlotStaticRenderer: parsed = self.parse(request) if parsed.plot is None: raise ValueError("function-plot source has no valid plot") - return self.render_plot(parsed.plot) + return render_svg(parsed.plot, request.theme or 'light') def render_plot(self, plot: FunctionPlot) -> StaticRenderResult: return render_svg(plot) diff --git a/backend/app/plot_routes.py b/backend/app/plot_routes.py new file mode 100644 index 0000000..0163820 --- /dev/null +++ b/backend/app/plot_routes.py @@ -0,0 +1,34 @@ +"""Interactive previews use the same bounded parser and geometry as exports.""" +import asyncio +from fastapi import APIRouter +from pydantic import BaseModel, Field +from app.plot.parser import parse_source +from app.plot.render import render_svg +from app.plot.model import PlotDiagnostic, StaticRenderResult + +router = APIRouter(prefix='/api/plots', tags=['Function Plot']) +_slots = asyncio.Semaphore(2) + +class PlotRequest(BaseModel): + source: str = Field(max_length=20000) + theme_id: str = Field(default='light', max_length=100) + +class PlotResponse(BaseModel): + result: StaticRenderResult | None = None + diagnostics: list[PlotDiagnostic] = Field(default_factory=list) + node_count: int = 0 + +def preview(request): + parsed = parse_source(request.source) + if parsed.plot is None: + return PlotResponse(diagnostics=parsed.diagnostics) + if parsed.plot.node_count > 8000: + return PlotResponse(node_count=parsed.plot.node_count, diagnostics=[PlotDiagnostic( + severity='error', code='PLOT_BUDGET_EXCEEDED', message='图表累计表达式节点超过 8000 上限')]) + return PlotResponse(result=render_svg(parsed.plot, request.theme_id), + diagnostics=parsed.diagnostics, node_count=parsed.plot.node_count) + +@router.post('/function', response_model=PlotResponse) +async def render_function(request: PlotRequest): + async with _slots: + return await asyncio.to_thread(preview, request) diff --git a/backend/app/retrieval/engine.py b/backend/app/retrieval/engine.py index cc6f0e5..bf743a5 100644 --- a/backend/app/retrieval/engine.py +++ b/backend/app/retrieval/engine.py @@ -114,7 +114,13 @@ class RetrievalEngine: elif request.mode == SearchMode.vector: candidate_scores = vec_scores else: # hybrid:RRF 融合 - candidate_scores = rrf_fuse([fts_ranked, vec_ranked], k=request.rrf_k) + if request.fusion == 'weighted': + fts_normal = dict(normalize_scores(list(fts_scores.items()))) + vec_normal = dict(normalize_scores(list(vec_scores.items()))) + candidate_scores = {bid: .5 * fts_normal.get(bid, 0) + .5 * vec_normal.get(bid, 0) + for bid in dict.fromkeys(fts_ranked + vec_ranked)} + else: + candidate_scores = rrf_fuse([fts_ranked, vec_ranked], k=request.rrf_k) if not candidate_scores: return self._empty(request) diff --git a/backend/app/routes.py b/backend/app/routes.py index 0978b10..aa91fd7 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -562,7 +562,7 @@ async def create_agent_run(request: AgentRunCreateRequest) -> AgentRun: tags=["Agent"], ) async def get_agent_run(run_id: str) -> AgentRun: - return agent_run_or_404(run_id) + return await asyncio.to_thread(agent_run_or_404, run_id) @router.post( @@ -571,7 +571,7 @@ async def get_agent_run(run_id: str) -> AgentRun: tags=["Agent"], ) async def cancel_agent_run(run_id: str) -> OperationResponse: - agent_run_or_404(run_id) + await asyncio.to_thread(agent_run_or_404, run_id) run = await container.agent.cancel(run_id) return OperationResponse( status="completed", @@ -596,7 +596,7 @@ async def agent_events( after_sequence: int | None = Query(default=None, ge=-1), last_event_id: str | None = Header(default=None, alias="Last-Event-ID"), ) -> StreamingResponse: - agent_run_or_404(run_id) + await asyncio.to_thread(agent_run_or_404, run_id) cursor = after_sequence if cursor is None and last_event_id is not None: try: @@ -658,7 +658,7 @@ async def get_agent_trace( async def decide_agent_permission( run_id: str, request_id: str, request: PermissionDecisionRequest ) -> OperationResponse: - agent_run_or_404(run_id) + await asyncio.to_thread(agent_run_or_404, run_id) if not await container.agent.resolve_permission(run_id, request_id, request.decision): raise ApiError( 404, @@ -1622,3 +1622,11 @@ async def get_global_persona(): @router.put("/settings/persona", response_model=PersonaSettings, tags=["Settings"]) async def put_global_persona(request: PersonaSettings): return save_persona(request) + + +from app.contracts import AgentBenchmarkRequest +from app.benchmarks import agent as agent_benchmark + +@router.post('/benchmarks/agent/runs', response_model=BenchmarkRun, status_code=202, tags=['Benchmark']) +async def create_agent_benchmark(request: AgentBenchmarkRequest): + return await agent_benchmark.create_run(request) diff --git a/backend/data/benchmarks/agent-core-v1.json b/backend/data/benchmarks/agent-core-v1.json new file mode 100644 index 0000000..febb836 --- /dev/null +++ b/backend/data/benchmarks/agent-core-v1.json @@ -0,0 +1,89 @@ +{ + "dataset_id": "agent-core-v1", + "kind": "agent", + "version": "1.0.0", + "description": "受限真实 Runtime 工具选择、参数、无需调用和 Markdown 目录基线;不等同于复杂任务验收", + "cases": [ + { + "case_id": "arithmetic", + "prompt": "必须调用 math.add 计算 17 + 25,并报告结果。", + "allowed_tools": [ + "math.add", + "system.echo" + ], + "expected_tools": [ + { + "name": "math.add", + "arguments": { + "left": 17, + "right": 25 + } + } + ], + "output_contains": [ + "42" + ], + "tags": [ + "tool-selection", + "arguments" + ] + }, + { + "case_id": "echo", + "prompt": "调用 system.echo 原样回显字符串 phase2-check,然后回答原文。", + "allowed_tools": [ + "math.add", + "system.echo" + ], + "expected_tools": [ + { + "name": "system.echo", + "arguments": { + "text": "phase2-check" + } + } + ], + "output_contains": [ + "phase2-check" + ], + "tags": [ + "exact-arguments" + ] + }, + { + "case_id": "no-tool", + "prompt": "不调用任何工具,只回答:验收就绪", + "allowed_tools": [ + "math.add", + "system.echo" + ], + "expected_tools": [], + "output_contains": [ + "验收就绪" + ], + "tags": [ + "unnecessary-tools" + ] + }, + { + "case_id": "markdown-catalog", + "prompt": "使用 markdown.catalog 查询支持的 Markdown 语法,指出函数图像围栏的名称。", + "allowed_tools": [ + "markdown.catalog" + ], + "expected_tools": [ + { + "name": "markdown.catalog", + "arguments": {} + } + ], + "output_contains": [ + "function-plot" + ], + "tags": [ + "markdown", + "integration" + ] + } + ] +} diff --git a/backend/data/benchmarks/corpus/phase2-v1.json b/backend/data/benchmarks/corpus/phase2-v1.json new file mode 100644 index 0000000..7d8be71 --- /dev/null +++ b/backend/data/benchmarks/corpus/phase2-v1.json @@ -0,0 +1,18 @@ +{ + "version": "1.0.0", + "description": "手工编写的中文工程笔记检索集;每篇含关键词与改写问法,混淆主题分别建篇。用于小规模质量对照,不代表生产分布。", + "notes": [ + {"id":"deadlock","title":"死锁的必要条件","text":"死锁需要互斥、占有并等待、不可抢占、循环等待四个条件同时成立。规定所有线程按相同顺序申请锁,可以破坏循环等待条件。","queries":["死锁有哪些必要条件","几个线程各占一把锁并等待对方释放,怎样避免一直卡住"]}, + {"id":"starvation","title":"饥饿与公平调度","text":"饥饿指某个任务长期得不到资源,即使其他任务仍能运行。优先级老化会逐渐提高等待任务的优先级;公平队列可以减少长期等待。饥饿不等于所有进程互相等待的死锁。","queries":["优先级老化怎样缓解饥饿","系统一直有任务在跑,但一个低优先级任务永远轮不到怎么办"]}, + {"id":"rrf","title":"RRF 排名融合","text":"RRF 使用每个候选在各通道中的名次进行融合,单通道贡献为 1/(k+rank)。它避免直接比较全文检索与向量余弦相似度的原始分数。k 越大,头部名次的差距越平缓。","queries":["RRF 的融合公式是什么","全文分数和向量分数尺度不同,如何按排名合并结果"]}, + {"id":"rerank","title":"召回后的重排","text":"重排只重新排列已召回的候选,不能找回不在候选池中的相关段落。扩大候选池可能提高质量,但会增加精排成本。LexicalReranker 根据词面重合打分,不是 Cross-Encoder 神经模型。","queries":["重排能否找回未召回的文档","精排前候选池太小会导致什么问题"]}, + {"id":"optimistic","title":"笔记的乐观并发控制","text":"保存笔记时携带读取时的内容摘要。服务端比较当前摘要,若已变化则拒绝覆盖并报告冲突。用户应重新加载或合并修改,避免把另一个窗口的新内容静默覆盖。","queries":["保存时为什么比较内容摘要","两个窗口同时修改同一篇笔记,怎样避免后保存者覆盖新内容"]}, + {"id":"idempotency","title":"重复提交的幂等键","text":"客户端为一次逻辑上传创建唯一幂等键。网络重试使用相同的键和内容,服务端返回原附件编号;同键不同内容必须拒绝,防止错误复用。新的逻辑上传使用新的键。","queries":["幂等键如何处理重复上传","上传成功但响应丢失,重试怎样不生成两个附件"]}, + {"id":"sse","title":"事件流断线续读","text":"SSE 事件携带递增 sequence。客户端保存最后接收的序号,重连后请求后续事件并去重。终止事件只能出现一次;连接中断本身不代表后台任务被取消。","queries":["SSE 重连如何去重","页面断网后任务仍在运行,如何恢复之前错过的进度"]}, + {"id":"cancel","title":"后台任务取消边界","text":"取消标志由运行循环和工具边界检查。排队任务可以立即结束;正在同步渲染的工作应在安全边界检查取消,并丢弃产物。取消后不能发布完成事件或允许下载未完成文件。","queries":["导出任务取消后怎样处理产物","用户停止渲染时工作线程还没返回,应当如何收尾"]}, + {"id":"embedding","title":"向量空间隔离","text":"不同 Embedding 模型或维度产生的向量属于不同空间,不能直接比较。索引按模型、版本、维度隔离;切换模型后需要重建对应索引。向量不可用时的全文回退必须在报告中明确记录。","queries":["Embedding 模型切换后为什么要重建索引","两个模型生成的向量长度一样就能混着搜索吗"]}, + {"id":"citation","title":"引用定位与块标识","text":"引用记录笔记编号、块编号以及起止偏移。点击引用可定位原文。候选搜索结果不等于回答实际引用的来源;引用质量需要核对正文标记对应的支持性内容。","queries":["引用如何定位到原文","搜索返回十段资料,是否都应该算作回答已引用的来源"]}, + {"id":"zip","title":"ZIP 安装路径检查","text":"解压前检查每个条目的规范路径,拒绝绝对路径、父目录穿越、符号链接和超出解压大小预算的条目。安装完成保存包摘要,重启时复核,包被修改后重新审查。","queries":["ZIP 安装如何阻止路径穿越","扩展包里有指向安装目录外的文件名,为什么必须拒绝"]}, + {"id":"plot","title":"函数图像的安全解析","text":"function-plot 围栏支持 y = x^2 和 y = sin(x),可以设置 domain 和 range。解析器只允许数学语法,不执行任意代码。函数采样应限制表达式节点和求值次数,渐近线处断开曲线。","queries":["函数图像怎样处理渐近线","让用户输入公式绘图时如何避免执行任意程序"]} + ] +} diff --git a/backend/data/benchmarks/rag-phase2-v1.json b/backend/data/benchmarks/rag-phase2-v1.json new file mode 100644 index 0000000..97bbe51 --- /dev/null +++ b/backend/data/benchmarks/rag-phase2-v1.json @@ -0,0 +1,368 @@ +{ + "dataset_id": "rag-phase2-v1", + "kind": "rag", + "version": "1.0.0", + "description": "手工编写的中文工程笔记检索集;每篇含关键词与改写问法,混淆主题分别建篇。用于小规模质量对照,不代表生产分布。", + "cases": [ + { + "case_id": "deadlock-0", + "query": "死锁有哪些必要条件", + "expected_note_ids": [ + "note_894ec7d0760d0cd6" + ], + "expected_block_ids": [ + "blk_748b1be4cee7cb9b" + ], + "citation_required": true, + "tags": [ + "keyword", + "deadlock" + ] + }, + { + "case_id": "deadlock-1", + "query": "几个线程各占一把锁并等待对方释放,怎样避免一直卡住", + "expected_note_ids": [ + "note_894ec7d0760d0cd6" + ], + "expected_block_ids": [ + "blk_748b1be4cee7cb9b" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "deadlock" + ] + }, + { + "case_id": "starvation-0", + "query": "优先级老化怎样缓解饥饿", + "expected_note_ids": [ + "note_da790c1c3b905f26" + ], + "expected_block_ids": [ + "blk_d15c420ab15ba221" + ], + "citation_required": true, + "tags": [ + "keyword", + "starvation" + ] + }, + { + "case_id": "starvation-1", + "query": "系统一直有任务在跑,但一个低优先级任务永远轮不到怎么办", + "expected_note_ids": [ + "note_da790c1c3b905f26" + ], + "expected_block_ids": [ + "blk_d15c420ab15ba221" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "starvation" + ] + }, + { + "case_id": "rrf-0", + "query": "RRF 的融合公式是什么", + "expected_note_ids": [ + "note_1acd666aa1e79f96" + ], + "expected_block_ids": [ + "blk_abe4534c5c35b694" + ], + "citation_required": true, + "tags": [ + "keyword", + "rrf" + ] + }, + { + "case_id": "rrf-1", + "query": "全文分数和向量分数尺度不同,如何按排名合并结果", + "expected_note_ids": [ + "note_1acd666aa1e79f96" + ], + "expected_block_ids": [ + "blk_abe4534c5c35b694" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "rrf" + ] + }, + { + "case_id": "rerank-0", + "query": "重排能否找回未召回的文档", + "expected_note_ids": [ + "note_df8b1e8216af7f9a" + ], + "expected_block_ids": [ + "blk_064caf4b9c2e2518" + ], + "citation_required": true, + "tags": [ + "keyword", + "rerank" + ] + }, + { + "case_id": "rerank-1", + "query": "精排前候选池太小会导致什么问题", + "expected_note_ids": [ + "note_df8b1e8216af7f9a" + ], + "expected_block_ids": [ + "blk_064caf4b9c2e2518" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "rerank" + ] + }, + { + "case_id": "optimistic-0", + "query": "保存时为什么比较内容摘要", + "expected_note_ids": [ + "note_04142ad0124ae76d" + ], + "expected_block_ids": [ + "blk_3f8c19cf88a03805" + ], + "citation_required": true, + "tags": [ + "keyword", + "optimistic" + ] + }, + { + "case_id": "optimistic-1", + "query": "两个窗口同时修改同一篇笔记,怎样避免后保存者覆盖新内容", + "expected_note_ids": [ + "note_04142ad0124ae76d" + ], + "expected_block_ids": [ + "blk_3f8c19cf88a03805" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "optimistic" + ] + }, + { + "case_id": "idempotency-0", + "query": "幂等键如何处理重复上传", + "expected_note_ids": [ + "note_cdc416a180e5099b" + ], + "expected_block_ids": [ + "blk_b68f6945024f098d" + ], + "citation_required": true, + "tags": [ + "keyword", + "idempotency" + ] + }, + { + "case_id": "idempotency-1", + "query": "上传成功但响应丢失,重试怎样不生成两个附件", + "expected_note_ids": [ + "note_cdc416a180e5099b" + ], + "expected_block_ids": [ + "blk_b68f6945024f098d" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "idempotency" + ] + }, + { + "case_id": "sse-0", + "query": "SSE 重连如何去重", + "expected_note_ids": [ + "note_5cf7aef15e17bd32" + ], + "expected_block_ids": [ + "blk_f6328254ea8624a1" + ], + "citation_required": true, + "tags": [ + "keyword", + "sse" + ] + }, + { + "case_id": "sse-1", + "query": "页面断网后任务仍在运行,如何恢复之前错过的进度", + "expected_note_ids": [ + "note_5cf7aef15e17bd32" + ], + "expected_block_ids": [ + "blk_f6328254ea8624a1" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "sse" + ] + }, + { + "case_id": "cancel-0", + "query": "导出任务取消后怎样处理产物", + "expected_note_ids": [ + "note_48f96c75ea97d552" + ], + "expected_block_ids": [ + "blk_efa6b9f6210964c2" + ], + "citation_required": true, + "tags": [ + "keyword", + "cancel" + ] + }, + { + "case_id": "cancel-1", + "query": "用户停止渲染时工作线程还没返回,应当如何收尾", + "expected_note_ids": [ + "note_48f96c75ea97d552" + ], + "expected_block_ids": [ + "blk_efa6b9f6210964c2" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "cancel" + ] + }, + { + "case_id": "embedding-0", + "query": "Embedding 模型切换后为什么要重建索引", + "expected_note_ids": [ + "note_6e13fbe17c9f7a30" + ], + "expected_block_ids": [ + "blk_dc2b0771ea50c3a4" + ], + "citation_required": true, + "tags": [ + "keyword", + "embedding" + ] + }, + { + "case_id": "embedding-1", + "query": "两个模型生成的向量长度一样就能混着搜索吗", + "expected_note_ids": [ + "note_6e13fbe17c9f7a30" + ], + "expected_block_ids": [ + "blk_dc2b0771ea50c3a4" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "embedding" + ] + }, + { + "case_id": "citation-0", + "query": "引用如何定位到原文", + "expected_note_ids": [ + "note_a0b8289f7f334952" + ], + "expected_block_ids": [ + "blk_5eac426f6220ca29" + ], + "citation_required": true, + "tags": [ + "keyword", + "citation" + ] + }, + { + "case_id": "citation-1", + "query": "搜索返回十段资料,是否都应该算作回答已引用的来源", + "expected_note_ids": [ + "note_a0b8289f7f334952" + ], + "expected_block_ids": [ + "blk_5eac426f6220ca29" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "citation" + ] + }, + { + "case_id": "zip-0", + "query": "ZIP 安装如何阻止路径穿越", + "expected_note_ids": [ + "note_4a26a95db9b832fc" + ], + "expected_block_ids": [ + "blk_ce08f371b1dcf2c5" + ], + "citation_required": true, + "tags": [ + "keyword", + "zip" + ] + }, + { + "case_id": "zip-1", + "query": "扩展包里有指向安装目录外的文件名,为什么必须拒绝", + "expected_note_ids": [ + "note_4a26a95db9b832fc" + ], + "expected_block_ids": [ + "blk_ce08f371b1dcf2c5" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "zip" + ] + }, + { + "case_id": "plot-0", + "query": "函数图像怎样处理渐近线", + "expected_note_ids": [ + "note_bfd65995af95a6a6" + ], + "expected_block_ids": [ + "blk_c4644f51b0853ef2" + ], + "citation_required": true, + "tags": [ + "keyword", + "plot" + ] + }, + { + "case_id": "plot-1", + "query": "让用户输入公式绘图时如何避免执行任意程序", + "expected_note_ids": [ + "note_bfd65995af95a6a6" + ], + "expected_block_ids": [ + "blk_c4644f51b0853ef2" + ], + "citation_required": true, + "tags": [ + "paraphrase", + "plot" + ] + } + ] +} \ No newline at end of file diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 4837b93..f6a52ef 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -17,6 +17,7 @@ dependencies = [ "referencing>=0.36,<1.0", "sqlite-vec>=0.1.9", "uvicorn[standard]>=0.35,<1.0", + "matplotlib>=3.9,<4", ] [dependency-groups] diff --git a/backend/scripts/agent-task-stress.py b/backend/scripts/agent-task-stress.py index e5a9559..6758eaa 100644 --- a/backend/scripts/agent-task-stress.py +++ b/backend/scripts/agent-task-stress.py @@ -96,11 +96,13 @@ async def main(output): assert all(sequences) recovered = AgentRuntime(container.providers, container.tools, container.permissions, trace_repository=runtime.trace_repository) - assert all(recovered.get_run(run_id).status == AgentRunStatus.completed for run_id in ids) + recovery_started = time.perf_counter() + assert await asyncio.to_thread(lambda: all(recovered.get_run(run_id).status == AgentRunStatus.completed for run_id in ids)) + recovery_read_ms = (time.perf_counter() - recovery_started) * 1000 assert not any(record.subscribers for record in runtime._records.values()) return {"concurrency": concurrency, "runs": len(ids), "latency": stats(durations), "completed": statuses.count('completed'), "ordered_events_and_replay": True, - "terminal_recovery": True, "retained_records": len(runtime._records)} + "terminal_recovery": True, "recovery_read_ms": round(recovery_read_ms,2), "retained_records": len(runtime._records)} save('agent_tool_runs', await measured(batch)) # Hold model calls so all 200 records remain active while testing admission. diff --git a/backend/scripts/phase2-context.py b/backend/scripts/phase2-context.py new file mode 100644 index 0000000..d76ff4b --- /dev/null +++ b/backend/scripts/phase2-context.py @@ -0,0 +1,50 @@ +"""Two bounded live calls for context summary + answer; never changes saved config.""" +import argparse, asyncio, json, sys +from pathlib import Path +sys.path.insert(0,str(Path(__file__).resolve().parents[1])) +async def main(args): + from app.container import container + from app.contracts import ModelRequest, ModelContextPolicy + from app.providers.factory import ProviderFactory + from app.providers.context_budget import prepare_context, estimate + from app.providers.base import ProviderError + from app.services.usage_service import connection + provider=container.providers.get(args.provider) + model=provider.config.default_model + request=ModelRequest(provider_id=args.provider,model=model,max_tokens=1024,messages=[ + {'role':'user','content':'项目事实:笔记保存在 Vault,导出使用点击时的快照。'*50}, + {'role':'assistant','content':'已记录。'}, {'role':'user','content':'请保持中文。'}, + {'role':'assistant','content':'好的。'}, {'role':'user','content':'笔记保存在什么地方?一句话回答。'}]) + original=request.model_dump() + config=provider.config.model_copy(deep=True) + config.context_policies=[ModelContextPolicy(model=model,context_window=8192,output_reserve=512,threshold=.1,mode='detect')] + calls=0 + async def complete(value): + nonlocal calls + calls+=1 + return await provider.adapter.complete(value) + results={'model':model,'configured_test_window':8192,'vendor_max_context_tested':False} + try: + try: await prepare_context(request,config,complete) + except ProviderError as exc: results['detect']={'error_code':exc.code,'network_calls':calls} + config.context_policies[0].mode='compress' + config.context_policies[0].prompt='把以下历史资料压缩成一句中文,只保留笔记存储位置和导出快照规则。' + prepared=await asyncio.wait_for(prepare_context(request,config,complete),60) + turn=await asyncio.wait_for(complete(prepared),60) + results['compression']={'passed':'vault' in (turn.text or '').lower(),'before_estimate':estimate(request), + 'after_estimate':estimate(prepared),'archive_unchanged':request.model_dump()==original,'network_calls':calls, + 'answer_input_tokens':turn.input_tokens,'answer_output_tokens':turn.output_tokens} + with connection() as conn: + rows=[json.loads(row[0]) for row in conn.execute('SELECT counters_json FROM model_usage WHERE provider_id=?',(args.provider,))] + results['observed_provider_cache']={'requests':len(rows),'reporting_requests':sum(x.get('cache_hit_tokens') is not None for x in rows), + 'positive_hit_requests':sum((x.get('cache_hit_tokens') or 0)>0 for x in rows), + 'positive_miss_requests':sum((x.get('cache_miss_tokens') or 0)>0 for x in rows), + 'scope':'provider reported usage across this isolated acceptance session; not deterministic cache control'} + finally: + args.output.write_text(json.dumps(results,ensure_ascii=False,indent=2),encoding='utf-8') + print(json.dumps(results,ensure_ascii=False)) + await container.agent.shutdown();container.mcp_servers.shutdown();container.plugins.shutdown() +if __name__=='__main__': + p=argparse.ArgumentParser();p.add_argument('--provider',required=True);p.add_argument('--output',type=Path,required=True);p.add_argument('--execute',action='store_true');args=p.parse_args() + if not args.execute:p.error('--execute required; two requests use existing quota') + asyncio.run(main(args)) diff --git a/backend/scripts/phase2-integration.py b/backend/scripts/phase2-integration.py new file mode 100644 index 0000000..eac3e85 --- /dev/null +++ b/backend/scripts/phase2-integration.py @@ -0,0 +1,56 @@ +"""Explicit isolated Demo: retrieval → read → three tasks, then real read-only MCP. + +Approves only this run's tasks.write tickets. Requires the quality fixture Vault. +Calls public API contracts; never writes completion state into SQLite. +""" +import argparse, json, time +from pathlib import Path +from urllib.request import Request,urlopen + +def main(args): + def api(path,body=None): + req=Request(args.base_url+'/api'+path,data=json.dumps(body).encode() if body is not None else None, + headers={'Content-Type':'application/json'}) + with urlopen(req,timeout=60) as response:return json.load(response) + directory=args.data_dir.resolve() + if not (directory/'vault/.phase2-fixture').exists():raise SystemExit('Isolated fixture Vault required') + core=json.loads((directory/'benchmarks/rag-phase2-v1.json').read_text(encoding='utf-8')) + note=next(c for c in core['cases'] if c['case_id']=='deadlock-0')['expected_note_ids'][0] + cases=[{'case_id':'retrieval-tasks','prompt':f'按顺序执行:1. 用 rag.search 搜索“死锁”,明确使用 mode=fts;2. 用 notes.read 读取笔记 {note};3. 根据内容用 tasks.create 分别创建且仅创建三个任务,标题严格为“验收-互斥条件”、“验收-循环等待”、“验收-锁顺序”;4. 总结死锁条件并引用搜索来源。请不要调用其他工具,不重复创建。', + 'allowed_tools':['rag.search','notes.read','tasks.create'], + 'expected_tools':[{'name':'rag.search','arguments':{'query':'死锁','mode':'fts'}},{'name':'notes.read','arguments':{'note_id':note}}]+ + [{'name':'tasks.create','arguments':{'title':title}} for title in ['验收-互斥条件','验收-循环等待','验收-锁顺序']], + 'citation_required':True,'tasks_created':3,'output_contains':['死锁'],'tags':['rag','notes','tasks','permissions']}] + tools=api('/tools')['items']; mcp=next((t for t in tools if t['name'].endswith('.web_search') and t['name'].startswith('mcp.')),None) + if mcp: + cases.append({'case_id':'mcp-search','prompt':f'调用一次 {mcp["name"]},query 严格使用 Python official documentation tutorial。根据工具真实返回给出一句总结。', + 'allowed_tools':[mcp['name']], 'expected_tools':[{'name':mcp['name'],'arguments':{'query':'Python official documentation tutorial'}}], 'tags':['mcp','real-network']}) + dataset={'dataset_id':'agent-integration-v1','kind':'agent','version':'1.0.0','description':'Isolated phase2 cross-module Demo; existing MCP binding captured','cases':cases} + (directory/'benchmarks/agent-integration-v1.json').write_text(json.dumps(dataset,ensure_ascii=False,indent=2),encoding='utf-8') + providers=api('/providers')['items']; provider=next(p for p in providers if p['enabled'] and p['provider_type']!='mock') + run=api('/benchmarks/agent/runs',{'dataset_id':dataset['dataset_id'],'provider_id':provider['provider_id'],'model':provider['default_model'], + 'max_steps':10,'timeout_seconds':150,'token_budget':10000,'allow_network':True}) + approved=set();deadline=time.monotonic()+360 + while run['status'] in ['queued','running']: + if time.monotonic()>deadline: + api('/benchmarks/runs/'+run['run_id']+'/cancel',{});raise RuntimeError('Demo deadline') + active=run['config_snapshot'].get('active_agent_run_id') + if active: + trace=api('/agent/runs/'+active+'/trace') + for event in trace['items']: + data=event['data'];ticket=data.get('request_id') + if event['event']=='PermissionRequired' and data.get('permission')=='tasks.write' and ticket not in approved: + api('/agent/runs/'+active+'/permissions/'+ticket,{'decision':'allow_once'});approved.add(ticket) + time.sleep(.3);run=api('/benchmarks/runs/'+run['run_id']) + report=api('/benchmarks/runs/'+run['run_id']+'/report') + report['permission_approvals']=len(approved) + report['mcp_present']=bool(mcp) + report['tasks']= [{'task_id':t['task_id'],'title':t['title']} for t in api('/tasks')['items'] if t['title'].startswith('验收-')] + args.output.write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8') + print(json.dumps({'metrics':report['metrics'],'cases':report['cases'],'permission_approvals':len(approved)},ensure_ascii=False)) + +if __name__=='__main__': + p=argparse.ArgumentParser();p.add_argument('--base-url',default='http://127.0.0.1:8017');p.add_argument('--data-dir',type=Path,required=True);p.add_argument('--output',type=Path,required=True);p.add_argument('--execute',action='store_true');args=p.parse_args() + if not args.execute:p.error('--execute required; creates three tasks only in the isolated fixture application') + if args.base_url not in {'http://127.0.0.1:8017','http://localhost:8017'}:p.error('Use the isolated local acceptance server on port 8017') + main(args) diff --git a/backend/scripts/phase2-live.py b/backend/scripts/phase2-live.py new file mode 100644 index 0000000..7ffde97 --- /dev/null +++ b/backend/scripts/phase2-live.py @@ -0,0 +1,64 @@ +"""Bounded real protocol/Agent checks using existing configuration; no secret output. + +Requires --execute; at most 5 direct model requests plus one 4-case Agent dataset +(6 steps and 6000 tokens per case). No provisioning or external writes. +""" +import argparse, asyncio, json, sys +from pathlib import Path +from time import perf_counter +sys.path.insert(0,str(Path(__file__).resolve().parents[1])) + +async def main(args): + from app.container import container + from app.contracts import ModelRequest, AgentBenchmarkRequest + from app.providers.base import ProviderError + from app.benchmarks import agent,service + provider=container.providers.get(args.provider) + adapter=provider.adapter; model=provider.config.default_model + results={'provider_id':args.provider,'protocol':provider.config.provider_type.value,'model':model,'checks':{}, + 'unconfigured_protocols':['openai_responses','anthropic_messages','ollama'], + 'not_tested':['provider_context_limit','cache_hit_miss','context_compression'],'max_direct_requests':5} + def request(prompt='Reply OK only.', **changes): + return ModelRequest(provider_id=args.provider,model=model,messages=[{'role':'user','content':prompt}],max_tokens=128,**changes) + async def check(name, fn): + started=perf_counter() + try: results['checks'][name]=await asyncio.wait_for(fn(),60) + except ProviderError as exc: results['checks'][name]={'passed':False,'error_code':exc.code} + except Exception as exc: results['checks'][name]={'passed':False,'error_type':type(exc).__name__} + results['checks'][name]['elapsed_ms']=round((perf_counter()-started)*1000,2) + print(name,results['checks'][name],flush=True) + async def discover(): + models=await adapter.list_models();return {'passed':bool(models),'count':len(models)} + async def complete(): + turn=await adapter.complete(request());return {'passed':bool(turn.text),'input_tokens':turn.input_tokens,'output_tokens':turn.output_tokens} + async def stream(): + events=[e async for e in adapter.stream(request())] + names=[e.event.value for e in events] + return {'passed':names.count('Done')==1 and 'TextDelta' in names,'events':sorted(set(names)), + 'usage_reported':'Usage' in names,'reasoning_observed':'ThinkingDelta' in names} + async def cancel(): + iterator=adapter.stream(request('List the integers 1 through 1000.')) + first=await anext(iterator);started=perf_counter();await iterator.aclose() + return {'passed':True,'first_event':first.event.value,'close_ms':(perf_counter()-started)*1000, + 'scope':'client stream resource close; provider billing cessation not observable'} + async def invalid_model(): + try: await adapter.complete(request().model_copy(update={'model':'notesagent-nonexistent-acceptance-model'})) + except ProviderError as exc:return {'passed':True,'error_code':exc.code} + return {'passed':False,'reason':'provider accepted unknown model'} + try: + for name,fn in [('discovery',discover),('normal_chat',complete),('stream_usage_reasoning',stream),('stream_cancel',cancel),('error_mapping',invalid_model)]:await check(name,fn) + created=await agent.create_run(AgentBenchmarkRequest(dataset_id='agent-core-v1',provider_id=args.provider,model=model)) + await service.wait_for_run(created.run_id) + results['agent']=service.get_report(created.run_id).model_dump(mode='json') + print('agent',results['agent']['metrics'],flush=True) + servers=container.mcp_servers.list() + results['mcp']=[{'server_id':s.server_id,'name':s.name,'state':s.status.value if hasattr(s.status,'value') else s.status} for s in servers] + finally: + args.output.parent.mkdir(parents=True,exist_ok=True) + args.output.write_text(json.dumps(results,ensure_ascii=False,indent=2,default=str),encoding='utf-8') + await container.agent.shutdown();container.mcp_servers.shutdown();container.plugins.shutdown() + +if __name__=='__main__': + p=argparse.ArgumentParser();p.add_argument('--provider',required=True);p.add_argument('--output',type=Path,required=True);p.add_argument('--execute',action='store_true');args=p.parse_args() + if not args.execute:p.error('--execute required; uses existing provider quota') + asyncio.run(main(args)) diff --git a/backend/scripts/phase2-quality.py b/backend/scripts/phase2-quality.py new file mode 100644 index 0000000..2464344 --- /dev/null +++ b/backend/scripts/phase2-quality.py @@ -0,0 +1,69 @@ +"""Reproducible real-model quality run in an explicitly isolated APP_DATA_DIR. + +Uses the application index/benchmark services. Never injects vectors or completion rows. +Existing local weights/runtime must be configured; inference does not download models. +""" +import argparse +import asyncio +import hashlib +import json +import os +import sys +from pathlib import Path +from datetime import datetime, timezone +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +async def main(args): + from app.config import get_settings + from app.knowledge.parser import parse_note + from app.services import index_service + from app.benchmarks import service + from app.contracts import IndexRebuildRequest, RAGRunRequest + settings=get_settings() + if not all(os.getenv(name) for name in ['APP_DATA_DIR','APP_DB_PATH','APP_VAULT_PATH']): + raise SystemExit('Explicit isolated APP_DATA_DIR/APP_DB_PATH/APP_VAULT_PATH required') + fixture=Path(__file__).resolve().parents[1]/'data/benchmarks/corpus/phase2-v1.json' + payload=json.loads(fixture.read_text(encoding='utf-8')); cases=[] + settings.vault_path.mkdir(parents=True,exist_ok=True) + if list(settings.vault_path.glob('*.md')) and not (settings.vault_path/'.phase2-fixture').exists(): + raise SystemExit('Refusing to overwrite a non-fixture vault') + now=datetime(2026,9,7,tzinfo=timezone.utc) + for note in payload['notes']: + name=note['id']+'.md'; markdown='# '+note['title']+'\n\n'+note['text']+'\n' + (settings.vault_path/name).write_text(markdown,encoding='utf-8') + parsed=parse_note(markdown=markdown,file_path=name,folder='',created_at=now,updated_at=now) + for index,query in enumerate(note['queries']): + cases.append({'case_id':note['id']+'-'+str(index),'query':query,'expected_note_ids':[parsed.note_id], + 'expected_block_ids':[parsed.blocks[-1].block_id],'citation_required':True, + 'tags':['keyword' if index==0 else 'paraphrase',note['id']]}) + (settings.vault_path/'.phase2-fixture').touch() + dataset={'dataset_id':'rag-phase2-v1','kind':'rag','version':payload['version'],'description':payload['description'],'cases':cases} + settings.benchmark_datasets_path.mkdir(parents=True,exist_ok=True) + (settings.benchmark_datasets_path/'rag-phase2-v1.json').write_text(json.dumps(dataset,ensure_ascii=False,indent=2),encoding='utf-8') + if not args.reuse_index: + job=await index_service.rebuild(IndexRebuildRequest()) + if job.status != 'completed': raise RuntimeError('Index did not complete: '+str(job.status)) + from app import repository + expected_blocks = {bid for case in cases for bid in case['expected_block_ids']} + if {hit.block_id for hit in repository.get_block_hits(list(expected_blocks))} != expected_blocks: + raise RuntimeError('Frozen corpus does not match the index; rerun without --reuse-index') + reports={} + for label, mode, fusion, rerank, k in [('fts','fts','rrf',False,60),('vector','vector','rrf',False,60), + ('hybrid-weighted','hybrid','weighted',False,60),('hybrid-rrf','hybrid','rrf',False,60), + ('hybrid-rerank','hybrid','rrf',True,60),('rrf-k20','hybrid','rrf',False,20)]: + run=await service.create_rag_run(RAGRunRequest(dataset_id='rag-phase2-v1',modes=[mode],repeat=2, + retrieval={'top_k':5,'fusion':fusion,'rerank':rerank,'rrf_k':k,'rerank_candidates':20}, + metadata={'corpus_sha256':hashlib.sha256(fixture.read_bytes()).hexdigest(),'split':'development; no held-out production claim'})) + await service.wait_for_run(run.run_id) + reports[label]=service.get_report(run.run_id).model_dump(mode='json') + print(label, json.dumps(reports[label]['metrics']),flush=True) + args.output.parent.mkdir(parents=True,exist_ok=True) + args.output.write_text(json.dumps(reports,ensure_ascii=False,indent=2),encoding='utf-8') + args.output.parent.mkdir(parents=True,exist_ok=True) + args.output.write_text(json.dumps(reports,ensure_ascii=False,indent=2),encoding='utf-8') + from app.container import container + await container.agent.shutdown(); container.mcp_servers.shutdown(); container.plugins.shutdown() + +if __name__=='__main__': + parser=argparse.ArgumentParser(); parser.add_argument('--output',type=Path,required=True); parser.add_argument('--reuse-index',action='store_true') + asyncio.run(main(parser.parse_args())) diff --git a/backend/tests/test_export.py b/backend/tests/test_export.py index b5bdc09..affd158 100644 --- a/backend/tests/test_export.py +++ b/backend/tests/test_export.py @@ -321,7 +321,7 @@ def test_pdf_exporter_embeds_function_plot_and_marks_mermaid() -> None: # function_plot 已内嵌为矢量图,不再产生「函数图像占位」warning assert not any("函数图像" in w for w in result.warnings) # 绘图用 STSong-Light 渲染刻度/标签,字体应嵌入 PDF - assert b"STSong-Light" in result.content + assert b"STSong-Light" in result.content or b"/FontFile2" in result.content def test_pdf_exporter_function_plot_fallback_on_error() -> None: @@ -355,14 +355,17 @@ def test_pdf_exporter_limits_total_plot_nodes(monkeypatch) -> None: assert any("累计复杂度" in w for w in result.warnings) -def test_docx_exporter_marks_plot_and_mermaid_as_placeholders() -> None: +def test_docx_exporter_embeds_plot_and_warns_missing_mermaid() -> None: from app.export.exporters.docx import DocxExporter md = "```mermaid\ngraph LR\n```\n\n```function_plot\ny = x\n```" result = asyncio.run(DocxExporter().export(parse_document(md), ExportOptions())) assert result.content[:2] == b"PK" assert any("mermaid" in w for w in result.warnings) - assert any("函数图像" in w for w in result.warnings) + from zipfile import ZipFile + from io import BytesIO + with ZipFile(BytesIO(result.content)) as archive: + assert any(name.startswith('word/media/') for name in archive.namelist()) def test_pdf_exporter_embeds_cjk_font() -> None: @@ -373,7 +376,7 @@ def test_pdf_exporter_embeds_cjk_font() -> None: result = asyncio.run(PdfExporter().export(doc, ExportOptions(include_title=True))) assert result.content[:4] == b"%PDF" # 中文字体通过 STSong-Light CID 字体嵌入,PDF 内应引用该 BaseFont - assert b"STSong-Light" in result.content + assert b"STSong-Light" in result.content or b"/FontFile2" in result.content def test_docx_exporter_contains_cjk_text() -> None: diff --git a/backend/tests/test_phase2_completion.py b/backend/tests/test_phase2_completion.py new file mode 100644 index 0000000..0a1b8ad --- /dev/null +++ b/backend/tests/test_phase2_completion.py @@ -0,0 +1,166 @@ +import asyncio +import base64 +import json +from io import BytesIO +from zipfile import ZipFile +from pathlib import Path +import pytest +from PIL import Image +from app.contracts import ExportAsset, ExportRequest, AgentBenchmarkRequest, BenchmarkStatus +from app.export import service as exports +from app.export.assets import validate_assets, source_hash +from app.errors import ApiError + +def asset(source='flowchart LR\n A --> B'): + buf=BytesIO(); Image.new('RGB',(60,40),'blue').save(buf,'PNG') + return ExportAsset(kind='mermaid', source_hash=source_hash(source), png_base64=base64.b64encode(buf.getvalue()).decode()) + +@pytest.mark.parametrize('format',['html','pdf','docx']) +def test_static_mermaid_in_export(format): + async def run(): + job=await exports.create_export(ExportRequest(source={'type':'markdown','markdown':'```mermaid\nflowchart LR\n A --> B\n```'},format=format,assets=[asset()],title='snapshot')) + finished=await exports.wait_for_export(job.job_id) + assert finished.status.value=='completed' + assert not any('mermaid' in w for w in finished.warnings) + data=exports.get_export_file(job.job_id).read_bytes() + if format=='html': assert b'data:image/png;base64,' in data + elif format=='pdf': assert b'/Subtype /Image' in data + else: + with ZipFile(BytesIO(data)) as archive: assert any(n.startswith('word/media/') for n in archive.namelist()) + asyncio.run(run()) + +def test_asset_invalid_and_duplicate(): + with pytest.raises(ApiError): validate_assets([asset().model_copy(update={'png_base64':'not png'})]) + with pytest.raises(ApiError): validate_assets([asset(),asset()]) + +def test_stale_asset_does_not_replace_source(): + from app.export.assets import attach_assets + from app.export.markdown import parse_document + document=parse_document('```mermaid\nflowchart LR\n X --> Y\n```') + attach_assets(document,validate_assets([asset()])) + assert 'static_png' not in document.children[0].attributes + +@pytest.mark.parametrize('format',['html','pdf','docx']) +def test_math_and_local_image_export(format): + from app.config import get_settings + vault=get_settings().vault_path; vault.mkdir(parents=True) + Image.new('RGB',(100,50),'green').save(vault/'figure.png') + async def run(): + job=await exports.create_export(ExportRequest(source={'type':'markdown','file_path':'demo.md', + 'markdown':'Formula $\\frac{x^2}{2}$\n\n![figure](figure.png)'},format=format)) + done=await exports.wait_for_export(job.job_id) + assert done.status.value=='completed' + assert not any('公式' in w or '图片' in w for w in done.warnings) + data=exports.get_export_file(job.job_id).read_bytes() + if format=='html': assert data.count(b'data:image/png;base64,')==2 + if format=='docx': + with ZipFile(BytesIO(data)) as archive: assert len([n for n in archive.namelist() if n.startswith('word/media/')])==2 + asyncio.run(run()) + +def test_local_image_path_escape_and_tex_fallback(): + from app.export.assets import enrich_document + from app.export.markdown import parse_document + document=parse_document('![no](../outside.png)\n\n$\\unknownmacro{x}$') + warnings=enrich_document(document,'demo.md') + assert len(warnings)==2 + +@pytest.mark.parametrize('theme',['light','dark','sepia','paper-moments','ocean-blue','midnight-purple']) +def test_function_preview_theme_and_parser(theme): + from app.plot_routes import PlotRequest, preview + result=preview(PlotRequest(source='y = x^2\ny = sin(x)',theme_id=theme)) + assert ' B\n```\n\n')*65) + attach_assets(document, validate_assets([asset()])) + warnings = enrich_document(document) + assert sum(bool(node.attributes.get('static_png')) for node in document.children) == 64 + assert any('预算' in warning for warning in warnings) diff --git a/backend/tests/test_plot.py b/backend/tests/test_plot.py index 433e07c..9b6571f 100644 --- a/backend/tests/test_plot.py +++ b/backend/tests/test_plot.py @@ -107,7 +107,7 @@ def test_render_svg_contains_polyline_and_axes() -> None: assert " None: @@ -300,7 +300,7 @@ def test_function_plot_static_renderer_renders_svg() -> None: assert " None: @@ -359,7 +359,8 @@ def test_render_reportlab_builds_drawing() -> None: kinds = {type(c).__name__ for c in drawing.contents} assert {"Line", "PolyLine", "String", "Group"} <= kinds strings = [c for c in drawing.contents if isinstance(c, String)] - assert any(s.fontName == "STSong-Light" for s in strings) + from app.export.fonts import FONT + assert any(s.fontName == FONT for s in strings) assert any(s.text == "时间" for s in strings) # ylabel 在旋转 Group 内 groups = [c for c in drawing.contents if isinstance(c, Group)] diff --git a/backend/uv.lock b/backend/uv.lock index ea26b2b..63f14cf 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version < '3.12'", +] [[package]] name = "annotated-doc" @@ -314,6 +318,89 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + [[package]] name = "cryptography" version = "50.0.1" @@ -370,6 +457,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/44/711e61f7d014be825ef79b285b047292d1bf893732ac1bc030a351fb517f/cryptography-50.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b", size = 3824006, upload-time = "2026-08-25T19:45:37.281Z" }, ] +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + [[package]] name = "fastapi" version = "0.141.1" @@ -386,6 +482,71 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" }, ] +[[package]] +name = "fonttools" +version = "4.64.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/41/0f072a712dc74496e03710e462a18a4cfd8a258ad055a4e22d28b43a7abd/fonttools-4.64.0.tar.gz", hash = "sha256:ecb2e59a7bc692fee64dda6010deb66222335693b30046f15cccf81233aa715f", size = 3664266, upload-time = "2026-08-31T15:44:33.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/f7/a222d1e20d460a09d08fe0b612a6f373235168c6c3228ff6a913cc8ff9ea/fonttools-4.64.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dac25768be4c03a990c359f408cb7e8958ed0e93061e495b3642ce7909761205", size = 3087538, upload-time = "2026-08-31T15:42:26.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/fd/571331ac2b9ea43403ba43f27f5b45427a13b5c559d4379c99f3f9437b59/fonttools-4.64.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d652592c71683941b768306fa1c7c6ce1bb9b072505043feafe86305d71030b7", size = 2582847, upload-time = "2026-08-31T15:42:28.664Z" }, + { url = "https://files.pythonhosted.org/packages/dd/bc/583ad5e4d6fbc600b1d9eb3c2e8b4eac5ca1c204fa29e4ef0ff344d8aa60/fonttools-4.64.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:801fd04899d72eab34f02ab78d0451525621b3bd589da9d2d480dfffe951b643", size = 5493826, upload-time = "2026-08-31T15:42:30.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/3d/4edf079bdb01791abe87753b7c8fdca4e8ec709276e7b030a1aa84a88a51/fonttools-4.64.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff7aff4637fbf71394df139c63ccfe08a47aa4252d2f91224ddb3335c716c925", size = 5455609, upload-time = "2026-08-31T15:42:33.149Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b6/e36ea109c8cfdb0f174c09d8464b4ddf6b446f97313118d69b6a28cd7189/fonttools-4.64.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f521d79d6acda4923b264805541696f452079db0952a5bb96f9ff742f50629ec", size = 5460840, upload-time = "2026-08-31T15:42:35.423Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bf/0d8bb1fbb96c621c1da5f93e288bec8424802df10265e790dddc66626b1f/fonttools-4.64.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a0afa8bac675445dc0e2ba2891ecbedd9be89cb437afa94c823e0290cc2c4bc5", size = 5591341, upload-time = "2026-08-31T15:42:37.874Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e1/6f8a1a01e5ed4408fdffb934c77ce65d978a2df6fd6eaaec5e6b864b10f5/fonttools-4.64.0-cp311-cp311-win32.whl", hash = "sha256:c3c1fb656063a2f762db5378ea8d38ad5f7836b4f3fb8c4652270ded43df2935", size = 2439647, upload-time = "2026-08-31T15:42:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/21/41/b575f14a653911f33f17ef62bbeaa818c94bc7e694579cc9ddec6935d5b2/fonttools-4.64.0-cp311-cp311-win_amd64.whl", hash = "sha256:e63b63b8b5fdb8e29318dff2b15c5f852be46e972775b466f75b848f6eed4502", size = 2497436, upload-time = "2026-08-31T15:42:42.36Z" }, + { url = "https://files.pythonhosted.org/packages/82/23/4ea251977fef70ed14193785e1b2949355f1f5927dc0ef1dada675c0bbb2/fonttools-4.64.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9ecb2b206b5b2386f6968721a0770226b66bdd54adc4279bfff3ddf62873eed8", size = 3095501, upload-time = "2026-08-31T15:42:44.299Z" }, + { url = "https://files.pythonhosted.org/packages/c4/32/943e9034f49797e1a25dbbd60c8047ce0c38c3585c5d1b2db38ce64059c5/fonttools-4.64.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8c631303bb1fd7be3067c47536a30ff1fcb4846d6008c112bc52a03f7cd6965", size = 2583091, upload-time = "2026-08-31T15:42:46.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4b/332cb3105d5d550cb5728e669151601c4f7698cbedafd134154eb806ef83/fonttools-4.64.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2763e452b025ee8e990f0462e76052de9bb094ebc21d296f62c6dfe958886b4", size = 5423449, upload-time = "2026-08-31T15:42:48.615Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8f/e5f2906ea833d362916c64a2f6b1922a07b19442744fd3cd89563f429bff/fonttools-4.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:66a83f93579fb3493e458c4449d1d566a7b2a1c7b19915cd0fa3c9b8b5a8540b", size = 5400981, upload-time = "2026-08-31T15:42:50.862Z" }, + { url = "https://files.pythonhosted.org/packages/fe/46/1b325ebb20aef8bb05f803052ea65931d73638ae3e0d02a6658e3d14e0f8/fonttools-4.64.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf67f96dc0bfe9607f5f2b734cedfbe2f6f995231adee4ccefa12872044d452d", size = 5359588, upload-time = "2026-08-31T15:42:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d3/ac56fa880e01339aa6ad0bcb457cc30bb4d6c5ca79922e1bd650e4a3a396/fonttools-4.64.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6786bed88581e19bc4f28ea7a64ad531e8f54acf50327fddca942688824a60bd", size = 5520698, upload-time = "2026-08-31T15:42:55.919Z" }, + { url = "https://files.pythonhosted.org/packages/d7/40/1251fef04c308836a3a7db523703a7ca8ab010dc1e3b9a9bf08e0861c7a8/fonttools-4.64.0-cp312-cp312-win32.whl", hash = "sha256:da4c9bdeaf6b06c12d13d0addfc8ef15aa9695d26574a6dc10751258bef72f30", size = 2430689, upload-time = "2026-08-31T15:42:58.48Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/ce0e89183c0235ff6cfbf0603d593023a5afa5333efb35ff569cc0be9ce2/fonttools-4.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:06b6409b868494556a831ae33b2d9a090476c37516b38d70f45a9720b460d423", size = 2482071, upload-time = "2026-08-31T15:43:00.598Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/49b0054a79b9ed918c018e70e09b56eb5678ee8b44e59e126c79de4d8d73/fonttools-4.64.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9443eefff58aad558608f352092e1be6d278980e8c3b4e8621fcbfda97818500", size = 3092750, upload-time = "2026-08-31T15:43:02.705Z" }, + { url = "https://files.pythonhosted.org/packages/83/e2/08e73bd2f6e6248f071d3e9debe4c2b4cecda3a7dc057c56b44e255421c3/fonttools-4.64.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:09657817b75575822bcd6098ef0ebf0386f34430839ee53109e70fd40a7f6539", size = 2583001, upload-time = "2026-08-31T15:43:04.923Z" }, + { url = "https://files.pythonhosted.org/packages/2f/58/6dc0ff0963fc1e12f53bac59175617ec5927342ee7a4ffb871b44d56d81b/fonttools-4.64.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d7995b906666037d7114c20a5566a372902747452af7d5bd4cd6bca8f1a2550", size = 5392765, upload-time = "2026-08-31T15:43:07.096Z" }, + { url = "https://files.pythonhosted.org/packages/7a/dd/4d3911049680da7b3aecf97cb5094e3a05554f5d94829358fc7664640456/fonttools-4.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c42237b7e8c6813643e57d3efed3be094d4c06339dc2166b626e2cc5c12ee93", size = 5373969, upload-time = "2026-08-31T15:43:09.617Z" }, + { url = "https://files.pythonhosted.org/packages/48/0a/a2cf94121fd3ca9166bdb4863f71d6db32a5606b223c81e2ba99832a0612/fonttools-4.64.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:498f02ea92c9ca18c0f9c581ea93184a9d56c25b0af14189b0767adaf34235d8", size = 5333854, upload-time = "2026-08-31T15:43:11.985Z" }, + { url = "https://files.pythonhosted.org/packages/44/e2/632f4a8b94e6d7eea41e6d82f0ed337f64f5fb96f9910e2ac6b1689910e2/fonttools-4.64.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8252f20108e557532f91d7d6dd9af87c16ed6fa930f65516aa480fa2cfed3363", size = 5492828, upload-time = "2026-08-31T15:43:14.288Z" }, + { url = "https://files.pythonhosted.org/packages/ff/25/7fee1978fdedc1a2d978b76dddbea88785416f5774e7222327b867712d71/fonttools-4.64.0-cp313-cp313-win32.whl", hash = "sha256:45e3ecc3888f1637094fd75cd8fc727f3a4b06d1ddf89181126c071e244fd2a5", size = 2429011, upload-time = "2026-08-31T15:43:16.463Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ed/bb1c217c9e1fbfa59f42b266af57df937a703882a73e59a309773752228f/fonttools-4.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4812f71c39d77ec5041348dafa400532adf7bf8f1fffa9aa6495fce5876d7b8", size = 2480199, upload-time = "2026-08-31T15:43:18.774Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0a/59b9074b8ab165cc28d3e08bcf7a8eff8e1dc5932a5a41c87b15952fa3af/fonttools-4.64.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6f1ce9ef9a1b13098efdc2e43a2ed96d9851bbde7b31c652a87552c4efe9b422", size = 3096790, upload-time = "2026-08-31T15:43:21.125Z" }, + { url = "https://files.pythonhosted.org/packages/17/39/af1077a36feefe79f36d67d75aee637a96673c0a74eeb2c6f24266f5a20b/fonttools-4.64.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:83cc48d1411d2ff388dab99973dca81172cc9ceae9c9799da9548d494cfb38cb", size = 2584288, upload-time = "2026-08-31T15:43:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/00/c4/1ea58af0eb78264d28e5871bba0810bf0943cd93d641fdeae92553ad3410/fonttools-4.64.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e412767d1c9765cf1b82f7b00f1686c6ca5809ebb77af363b3f9f2325a465c01", size = 5378061, upload-time = "2026-08-31T15:43:25.193Z" }, + { url = "https://files.pythonhosted.org/packages/93/99/5c7e36d770407b66f7fc8378d2bb47f1530d083d8b9247ff011ec9b4dd70/fonttools-4.64.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b4a7af455ffed980925bc0ebf5b8d6239e6c3e797d9d755b6db192fb3080d614", size = 5319458, upload-time = "2026-08-31T15:43:27.696Z" }, + { url = "https://files.pythonhosted.org/packages/a8/09/89e8d600e92723309d4ddd3944c4ca565197a5df0236ae4115ccbe797375/fonttools-4.64.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:398b14f89ca950b288bd290875f07e4e10685644fa4ac668546fb107b1ada4d4", size = 5316656, upload-time = "2026-08-31T15:43:29.842Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b3/56719ab37e1592ceac89724574146622bedec036a207be80f3c7b1c14cbe/fonttools-4.64.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dc96150f99e05a317cb1f042b92c4cf8bc93cdb1f9f85717322e202ecdf2e505", size = 5448380, upload-time = "2026-08-31T15:43:32.311Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b0/15951b7e3006073f3260da4f8983732d18903905cfe26daa5138a0bbf682/fonttools-4.64.0-cp314-cp314-win32.whl", hash = "sha256:1c3661324f3f0fa4539a32288a3e0711a5f3ccf020036e760bb558ae9811a16f", size = 2432778, upload-time = "2026-08-31T15:43:34.421Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f3/0c97402b29411f4f6d31b32f2af5b4bcd5babe5a93c64558a8fd3654f140/fonttools-4.64.0-cp314-cp314-win_amd64.whl", hash = "sha256:043f6c572bf236f2a76e762c25f841daea11e8fc03e78088d7be66e0c5b4e4c0", size = 2485394, upload-time = "2026-08-31T15:43:36.506Z" }, + { url = "https://files.pythonhosted.org/packages/00/da/82192c7bee5314f04129a068dddae11082a63295d4e3b5ce08657de96608/fonttools-4.64.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4691a122b8c1d0d82d6e7510ce59d5c42146518240274b53e912e255573924f7", size = 3170118, upload-time = "2026-08-31T15:43:38.579Z" }, + { url = "https://files.pythonhosted.org/packages/42/2d/0b4f608d754f625fecc7d94b3b26af6f65f6ab4527b386bb0c6141cdd9ea/fonttools-4.64.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3200180abc69639483cf54a17cca2e13c31ede5f665979ea0a9c829d093f372f", size = 2617333, upload-time = "2026-08-31T15:43:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/5e/df/9f7448c38dea05458acfee81c443b14ef97d6d66c636af053d41cf8a32dd/fonttools-4.64.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53eee22af5b5a305c1ee2652955ed46b148e881456fcec1e7f0eb27f642f6bb4", size = 5542345, upload-time = "2026-08-31T15:43:43.114Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/cbf4a81e8be322bc4bee4ba31cd33a7b6336ef8030fa13b53b3e9c09fbf9/fonttools-4.64.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08f172961e11f4eb4f80f2f20049e09b0ea8e044fa6d456fed8346eb8588f360", size = 5349981, upload-time = "2026-08-31T15:43:45.363Z" }, + { url = "https://files.pythonhosted.org/packages/d8/dc/106fd9e93e962dbb60482d30c0913af3d108f5762429dfe93265a850bdc5/fonttools-4.64.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6eae4376adb104c2acfa76fd9ea0cb12b572ca1d70eceac709871f638ff76e93", size = 5409249, upload-time = "2026-08-31T15:43:47.817Z" }, + { url = "https://files.pythonhosted.org/packages/b0/2d/c7be990abf74c9c5c5367ae3dc65953ffce49fe00f62abd129cf725a2397/fonttools-4.64.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2730946ca8f12c356bd98eb9b2b095c8e761ed05bed5afb0d5b380cebe4f6370", size = 5443781, upload-time = "2026-08-31T15:43:49.909Z" }, + { url = "https://files.pythonhosted.org/packages/9b/cc/bffec3dccb9e3f4f6097a2baedaef5a8dec2cb816522ee163e4bb7f54b44/fonttools-4.64.0-cp314-cp314t-win32.whl", hash = "sha256:d30c966bea2deffa19c738c81776f7182da5ccabd97e666bae4f3d6ba87341d9", size = 2466542, upload-time = "2026-08-31T15:43:52.088Z" }, + { url = "https://files.pythonhosted.org/packages/96/49/e31f97dfc94e0648999f04bfccb37a97062d978f87df60654f496942c89f/fonttools-4.64.0-cp314-cp314t-win_amd64.whl", hash = "sha256:917fd520bb60809d83c14d43cfe48d5ad2516abaf2c073d65a431800dade2d29", size = 2516703, upload-time = "2026-08-31T15:43:53.954Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ec/e39b9e56db7dcc859983caa24cf2a36209dd71f6cc8ddbbf00d7342d7988/fonttools-4.64.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:8dd18fdff0ac9759b8d67a714730abee07b2312e3656c20ba5affb0107094762", size = 3091150, upload-time = "2026-08-31T15:43:56.309Z" }, + { url = "https://files.pythonhosted.org/packages/a8/e1/3e6a409d6f99efb66c2f6d0a71986432ff9cc9aca0df9ff4a8bed1d850f6/fonttools-4.64.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:5af87d1a6d247d7467ee082ae977a5443b2c45f8cd4d59375b6daa38d523c2de", size = 2582833, upload-time = "2026-08-31T15:43:58.348Z" }, + { url = "https://files.pythonhosted.org/packages/91/7e/e8aec0e6eaf93267450535c1be9c9ae63e1d9c49659c2ffdb52c19982114/fonttools-4.64.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:769fb64412ca237547ca73f111a64252d9e32c9d938bed51ed537bc9146a8f54", size = 5375327, upload-time = "2026-08-31T15:44:00.417Z" }, + { url = "https://files.pythonhosted.org/packages/61/d1/fa9ce5c1c3c0ef858a2cbe5632e9b6b137deca010b28d5d9a677e4aa89a7/fonttools-4.64.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e662f874ab2c7da9861584db44a13573e0936df087215f63013138f6e5eba083", size = 5338647, upload-time = "2026-08-31T15:44:02.62Z" }, + { url = "https://files.pythonhosted.org/packages/0d/13/e4d4fa3c166f7f5a8f9b6403ad9dcb3887cd05f8b9d2638bd2b43d2889ef/fonttools-4.64.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:2524a26f8fdb9051b0d778d052f5d238285ca9f91a7dc004514c7d6cf38d35f4", size = 5313557, upload-time = "2026-08-31T15:44:05.037Z" }, + { url = "https://files.pythonhosted.org/packages/91/c8/5a352d69608ea7606f677080ede971090fc7340b9f143c0b4fa00cfbf63e/fonttools-4.64.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1e4e84b47839d35be24dbf476845a34f2ccf99707b66df125c1c414d3e86d25d", size = 5462363, upload-time = "2026-08-31T15:44:07.13Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f1/b15b845f66b559a24a9c40859391bbd3048af3ac0fd04e45b57dca11c4c7/fonttools-4.64.0-cp315-cp315-win32.whl", hash = "sha256:be084d19a3ac0c8b2aba696680642d703118d3b1f18cf83f5b7dbaf0ffc62ab6", size = 2431682, upload-time = "2026-08-31T15:44:09.293Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b2/909beff0d2e2e1adac896e36cff7f9d76fd154bcd57e303d8f09aee8f72b/fonttools-4.64.0-cp315-cp315-win_amd64.whl", hash = "sha256:de8acaa5f4160f537a3cf41b031171d51004b9f4aebfa6c194f18dffa9533d03", size = 2484393, upload-time = "2026-08-31T15:44:11.117Z" }, + { url = "https://files.pythonhosted.org/packages/40/47/06a51becf651cc071daa88e18ecb9f45ace9e3a570d8191ed8b3cae353fa/fonttools-4.64.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:5b90ad6637237b636d15c9ae8b7c4a7a1c194f33def378677e468c13fd4542f8", size = 3162235, upload-time = "2026-08-31T15:44:13.182Z" }, + { url = "https://files.pythonhosted.org/packages/dd/96/8b3faf58fd7ec3bb11943126658ad15b3f385068b23248deffccf2327ac1/fonttools-4.64.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:fa75c7970bc6bca340cc6e20f20f069201bfcb50094c31a536fd99724d1d01ca", size = 2613507, upload-time = "2026-08-31T15:44:15.123Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3a/223d1437e72d79f1549be7bafb3cc08f397e1d9c354a241a8ec8573b7d33/fonttools-4.64.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:236e59bc7e2a63557a4d7b013f9cb9e28d9aebc45bc09f85e545e6bf091db626", size = 5518005, upload-time = "2026-08-31T15:44:17.359Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9f/6f1a4b40ae533b9e907b7e627e397be632d0d9fd6f6be7756cf458870279/fonttools-4.64.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a515f664cad988f2295056833a59f62220bc3e46afdaffe389a29060f6712355", size = 5341654, upload-time = "2026-08-31T15:44:19.869Z" }, + { url = "https://files.pythonhosted.org/packages/52/f6/9ca30ba98730a22b527bcc1e02034f0b13b3be2ede764bb0fe8b8e0408d6/fonttools-4.64.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:5bfdaada437e7730c17d366bd7bb8c4a16639963ddbfc1b2f302a68a17a290e7", size = 5385994, upload-time = "2026-08-31T15:44:21.979Z" }, + { url = "https://files.pythonhosted.org/packages/09/27/baf1b61bff983bfec72cbb7b32162c4b68d76dc824aaaa48a1c26fa10b6b/fonttools-4.64.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:c60be0aed97a32c6ba8cee21f0d0477136e495451bd97910f589ac892db120d4", size = 5432407, upload-time = "2026-08-31T15:44:24.469Z" }, + { url = "https://files.pythonhosted.org/packages/21/ff/2eab37d43f2e2ccc0993959d8f2605d9a68470082c592684ac98611ee70a/fonttools-4.64.0-cp315-cp315t-win32.whl", hash = "sha256:f8669ce37851b597d3435b91fefa51139e58d506ca449ca0e5bb68c63b8b6d2b", size = 2463614, upload-time = "2026-08-31T15:44:27.198Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/8bdff3ea656592197c8ef5062774221885468ffa01d8b0dc782dae23a83a/fonttools-4.64.0-cp315-cp315t-win_amd64.whl", hash = "sha256:89356c0793b474af7e49ec90d39fb2363e2341516a90460e38231df5ebe8acd5", size = 2512422, upload-time = "2026-08-31T15:44:29.478Z" }, + { url = "https://files.pythonhosted.org/packages/82/f8/7188153c4b265c899cd035de6a062677d51f67118a4ba640902bd9683e90/fonttools-4.64.0-py3-none-any.whl", hash = "sha256:4a05783ff54ce4c7a28f18e5772efdf63c219374bd9ffc55452182e1cef8be60", size = 1195327, upload-time = "2026-08-31T15:44:31.741Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -511,6 +672,136 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] +[[package]] +name = "kiwisolver" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/07/bd78e6a8fae171ea041ef5bba3ed21a003522fa088834b069b1909981f30/kiwisolver-1.5.1.tar.gz", hash = "sha256:f1303ef2eec81262a4b708c3e858afe58d7c75ad91c1c05266eda7673369859a", size = 104395, upload-time = "2026-08-28T10:28:27.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/7b/2de6908edc668427c149af5f93112e931f87e1fa4cab80bac32c5844dccc/kiwisolver-1.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b3d78f7bb2b9d9a30345be1474b9aaa8685430b54afb51ba3639b5c6c11e9ed6", size = 123364, upload-time = "2026-08-28T10:25:04.359Z" }, + { url = "https://files.pythonhosted.org/packages/8a/24/e70914415c77c97be7e22c80a0740869cb7428768cc380fdcdf6703e7084/kiwisolver-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5025e36fb4fb275cef0a4e30dbb11cb4ae61d1c83deb90189cb5d7e4cafd6b55", size = 66558, upload-time = "2026-08-28T10:25:05.506Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2b/8b08b11833db4d475b8ef1f36174f8d8a7abd31bedd7e794be78e8814b48/kiwisolver-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc1a26b8e53395a01c2c611e58602fa47461f136fba7cd5542e6db6d64be1839", size = 64071, upload-time = "2026-08-28T10:25:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/89/00/05c2d0369ac322d22d5c05f84b5c4a6856fa6207fbae42869108a28f0383/kiwisolver-1.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:95a02752aa032eef4aed01cda6d9b687c669bd0396bf4519eef8bba22a286720", size = 1438206, upload-time = "2026-08-28T10:25:08.254Z" }, + { url = "https://files.pythonhosted.org/packages/c0/05/c941a139f27438c1910d630fdc3ccfdab7c8407c72052299ead12ece086e/kiwisolver-1.5.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:719a35fa1156db3640555f95ebb94f60a444e64d1c69626b0edef5df78eba225", size = 1248975, upload-time = "2026-08-28T10:25:10.053Z" }, + { url = "https://files.pythonhosted.org/packages/58/a1/2669ee5512e39b9d4de25faacaedf788c957f93730c5f7c63993ec4f5933/kiwisolver-1.5.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:febcce10f2bcdbb80b4ea919238a6a4ac13dbc4c7cadbe8d5d75c3682f8b5404", size = 1266301, upload-time = "2026-08-28T10:25:11.754Z" }, + { url = "https://files.pythonhosted.org/packages/28/b8/353f52f2c7f861a9e90cd2e8f90f85b3ad03060835f823e08298d094c463/kiwisolver-1.5.1-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1d852545c4d0e35a72728d072cbaa59e2fa7dd84bdf01e068d670dd0ceb58eb6", size = 1319708, upload-time = "2026-08-28T10:25:13.559Z" }, + { url = "https://files.pythonhosted.org/packages/21/0e/14b83200eadc2c1d63b76bac01c1813bf072aecf567429f303e00b70258e/kiwisolver-1.5.1-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:2e10ae1bba1899188b33557c10d73affcc12033edd18adddb57d209039976a4c", size = 971720, upload-time = "2026-08-28T10:25:14.934Z" }, + { url = "https://files.pythonhosted.org/packages/86/91/9d43d84d23b1cbff72a142d387ead1ea03db0cba8ff86ed5335addad3cc9/kiwisolver-1.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b69602970994a2ed8bbfa78c2f0394a7435226c6040489702d9f0a0ad0c07052", size = 2200119, upload-time = "2026-08-28T10:25:16.636Z" }, + { url = "https://files.pythonhosted.org/packages/ff/7c/f2bd9616f27ffb5e17cecc0baa5d0bbcee7e55aeddc0ccc871d69e2fc3ee/kiwisolver-1.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d50de98e8d807dc31822fff96f50293163a62418eb65487a21b42713d72ed0b7", size = 2295005, upload-time = "2026-08-28T10:25:18.374Z" }, + { url = "https://files.pythonhosted.org/packages/ba/17/ee671b72bf8f46a08379d4392c65582541759a542428197562f2898294ad/kiwisolver-1.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3221f78211074f561c44ca42eac0619828171bec15a2c4cf6f7747d07df76e8e", size = 1960982, upload-time = "2026-08-28T10:25:19.893Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f7/0e26b4c05bee3bdb0f048dfa305e4fe701999ea17b51e9c616ef91035bbe/kiwisolver-1.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0ba9527afc80ae3d7814ed98b6572d02bf85eaf48065678342c5f0c6dab7a8c7", size = 2464918, upload-time = "2026-08-28T10:25:21.65Z" }, + { url = "https://files.pythonhosted.org/packages/ae/62/6eb431133d30ce656ac1e5ff72fac70dd34d54c3984f4011b9ac8bf77d54/kiwisolver-1.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e12dfea7f5fc2a34a9080efbf79c4c44eb380ec5b9c6fea09407e08f0d1e941d", size = 2270967, upload-time = "2026-08-28T10:25:23.643Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9f/6f9e489c188200e6fb3193935501894811e8c97577c8ffe9033589bf3521/kiwisolver-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:1a7587dc335f2c0f5bd577fd0540bd16c66006bdb60f759a1059f025e6c4f071", size = 70744, upload-time = "2026-08-28T10:25:25.061Z" }, + { url = "https://files.pythonhosted.org/packages/c6/6d/dfc430d1d43957061599adea3f08ea982bb6f4ab601a8c974bedcf2ba850/kiwisolver-1.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:e4e4523d6f336708d732516e6cfca7796cf3d96c9474eb5aecf6165f2f1fefc3", size = 68404, upload-time = "2026-08-28T10:25:26.186Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9b/65b302742389c6f96f2956bef5decf26011309feb2fc5d79613af18adea4/kiwisolver-1.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63fb7294b768f444eb4b068965f2662f28c2fd4161e23bd60fcf3ff27b74c046", size = 123876, upload-time = "2026-08-28T10:25:27.44Z" }, + { url = "https://files.pythonhosted.org/packages/71/74/c21f339956f6f691b2ed7e31d5f3ae767304df6c460192739fc830853051/kiwisolver-1.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ebdef3eae5336568147c39a55be6a2036ffde53faa9ca2d978989ae7c2da12c", size = 66487, upload-time = "2026-08-28T10:25:28.728Z" }, + { url = "https://files.pythonhosted.org/packages/84/e5/bdb34e21523e01dceda064d63713f3bdec91388af24fba1eca7ea5e85864/kiwisolver-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1798e83840c3f627246104c4d8a9639c60fa068adf9ce92b61791781fa8a68c1", size = 64660, upload-time = "2026-08-28T10:25:30.071Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f4/dadfec469313c7f428efa7e84b4aba9732f813c13ea7131a24b7b008ef57/kiwisolver-1.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34633ecf50d16187ab8e5528b7a2530f2feb4e23f300db4672538b51cfc5cd38", size = 1477929, upload-time = "2026-08-28T10:25:31.495Z" }, + { url = "https://files.pythonhosted.org/packages/6f/35/09c58daac34e6f6ea5c6dee0094b422118e5a7c265586008a95fd135ac5f/kiwisolver-1.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d27c2123977cb9269c30a49ba45f03a4323017ef693e19db4ec9dbe1299a3002", size = 1278499, upload-time = "2026-08-28T10:25:33.375Z" }, + { url = "https://files.pythonhosted.org/packages/19/32/739765e24fbad29d13f83e546ea4abc215a78cea9d677ca09025b027724d/kiwisolver-1.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6a797a1cefc8b9c93170db580337e1fe3d011ad18b1299943231279406342048", size = 1296677, upload-time = "2026-08-28T10:25:35.059Z" }, + { url = "https://files.pythonhosted.org/packages/df/32/03304d1010e2cc45e5b3b52cef7e43fed3a2a5cd6c87a89b4a88e1d85b5d/kiwisolver-1.5.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2551cf9917af48ee7c4b29cc82320489508cf96fd26a51f6fc124de661cd44c7", size = 1346037, upload-time = "2026-08-28T10:25:36.705Z" }, + { url = "https://files.pythonhosted.org/packages/3e/57/4c49377bfd274450dd72ecaa13eaac32ea804a03363e4d1db0c5aa999ceb/kiwisolver-1.5.1-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:38f6e0deb4d0a4615efe0c4efc5990b06ae450ab50a0b321c0b078b6d238c083", size = 988248, upload-time = "2026-08-28T10:25:38.299Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c3/38df144a08b6c5d75ca4504e5cc3141bb3bfef64c04f4ef48204f42711b6/kiwisolver-1.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bfd1de989b3330420e29de39352f5c049905c9e3ee67233a50d550e3d652c148", size = 2228722, upload-time = "2026-08-28T10:25:40.038Z" }, + { url = "https://files.pythonhosted.org/packages/e7/11/3221838a89cd64d9b386353e000cd8a296069a20fbe3584507fdfd5bebae/kiwisolver-1.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1209042a623ddfda5497e4066c7b77651dde8e1d3a9dd97599dc7e97f3b9b78c", size = 2325216, upload-time = "2026-08-28T10:25:41.699Z" }, + { url = "https://files.pythonhosted.org/packages/83/d4/075c219230697bb5db910d37262b9bacf880f92b4811a02ab81ed073a253/kiwisolver-1.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:26e8268480be5061d509e29669d59103c067a26377a56491630ece11762e3858", size = 1977689, upload-time = "2026-08-28T10:25:43.559Z" }, + { url = "https://files.pythonhosted.org/packages/bb/08/1d219c3c2dd960983d0d4da623d916e9de6385df2b0bab3d1af0e9b8fccc/kiwisolver-1.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d79308fa689fac89cbcfbd4dbfc80b5f95c54c5a7fd4d194be221f9d33d026e6", size = 2491443, upload-time = "2026-08-28T10:25:45.242Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d3/024208ec1079d273f1047468d1bdffbf38bb75b7b268090fd3a0301b9d9a/kiwisolver-1.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b03af77d77e50edba2030fd5f7c352ff209314b09030a3cba7c14edf9a09a444", size = 2295200, upload-time = "2026-08-28T10:25:46.984Z" }, + { url = "https://files.pythonhosted.org/packages/6e/7c/7b210498f9f92e1cd7855f260fa69ef056881087b199ee20c208f0e4189a/kiwisolver-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:06a6917674de9e0fe3f66f5430787f59a9f2ddb64af9b714eaec547e29ef5c19", size = 70748, upload-time = "2026-08-28T10:25:48.444Z" }, + { url = "https://files.pythonhosted.org/packages/94/61/ef0daa157c8bb23672f7423e0d14c39db1dc6ef8ed47e6bc54c9c1bef3bf/kiwisolver-1.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:ad8b9671348d7c8716715652ae11f85ed0eb99e265a2df2ca490577d69860b2c", size = 68324, upload-time = "2026-08-28T10:25:49.81Z" }, + { url = "https://files.pythonhosted.org/packages/08/c1/88018321d976f53c421e379c43bc6993e70ce0c8a3ec5edc4bfe102257f6/kiwisolver-1.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b6ae6a0328f0bc035741820fdeecdcd67bf4694eee03972e843663107122f450", size = 62272, upload-time = "2026-08-28T10:25:51.02Z" }, + { url = "https://files.pythonhosted.org/packages/85/d2/712bc17ea4f1d216034928069d612defbc6c95a471c55a7203a39faecb1a/kiwisolver-1.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:886fc26012f0e8b5f69d1cfe6d711f6b11f194621539bf8e6bb1c25c5dc82724", size = 64481, upload-time = "2026-08-28T10:25:52.22Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e6/6c5380d676f43b6d918033962ea5e72360ca69e5a404154bc496b598ffdb/kiwisolver-1.5.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:aefe930d113798330e9462f7874542977869c0613cba3262e2de3a8d5dee8f3a", size = 66260, upload-time = "2026-08-28T10:25:53.387Z" }, + { url = "https://files.pythonhosted.org/packages/92/6a/7087f5822cc8bb272679641404b1966a42504dac9ee74e2b33840475a0aa/kiwisolver-1.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5ca5aebae78a0bc13c1943af4af615d4966c5b650b05d5aa83b50e427196fee", size = 123876, upload-time = "2026-08-28T10:25:54.644Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4b/9f385087ca09ee5ab9c09c6832561a7d2f7c78d3e5661d511e669f70e439/kiwisolver-1.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ed0f5e49d0ceff8b72190824d9e59c062fbbc02c231b853112c78474b3f5ec2", size = 66487, upload-time = "2026-08-28T10:25:55.899Z" }, + { url = "https://files.pythonhosted.org/packages/47/8b/40d33ffd2f378094ed462e9a9a0907e59d4de9845e65a59561272da350d4/kiwisolver-1.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:77a4c8187a5948d7f8795adb765a3c7b553d07d86d88e43038fc32fc1fb9a3f3", size = 64673, upload-time = "2026-08-28T10:25:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/ab/43/86aacc027959108b4c66eeae8b73cedb057dfa6eb3a335d05ad65197081c/kiwisolver-1.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:74ad5c3dad54a4641b4c28cd15ded70899d04459c6c7aeacafea716be97cce6d", size = 1477992, upload-time = "2026-08-28T10:25:58.479Z" }, + { url = "https://files.pythonhosted.org/packages/ed/40/b1d0369048c79733a32c8abb0f2718532e6630641368e33a81384246e844/kiwisolver-1.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e46b23a2da695c364124817bc01d970effd5483147f8d66a6a7167e3f6b851", size = 1278821, upload-time = "2026-08-28T10:26:00.121Z" }, + { url = "https://files.pythonhosted.org/packages/61/a1/fa71c1792272ff9461432715ae60ffb7e11a4d7ac3bf68961b9cab6c60cf/kiwisolver-1.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75d9b1cf8258462dbdc1eeda718c96ea7f079324c09067f6daabfcf37712b7fe", size = 1296805, upload-time = "2026-08-28T10:26:01.868Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/3f0bd94af8e06ecc47eb834b195a06f05d48711ceb2352c56d6835160f0e/kiwisolver-1.5.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fca690b00c4c48f6c2a547b0160ed511357093a4e4c9b47e0fadf3128066d89", size = 1346109, upload-time = "2026-08-28T10:26:03.59Z" }, + { url = "https://files.pythonhosted.org/packages/a4/44/3afe6ef9cf06d61220953a8963e94eca978491be1d9547cb01d82a1efa08/kiwisolver-1.5.1-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:876bbfd276473d3daffe30e8c975df4ed9429967b41a6cb362dbb5155b6f13ad", size = 988252, upload-time = "2026-08-28T10:26:05.306Z" }, + { url = "https://files.pythonhosted.org/packages/e4/30/a12bd7a7285a211e1747c3eec77b8c614dbfcc1dad942f7611a1a6921ae5/kiwisolver-1.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f942903fde7363d1d879057ec5de01310efda2597161784d752fa9953a01a71a", size = 2228846, upload-time = "2026-08-28T10:26:07.312Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/233e2958abf0dab7b18d07e52f286e02e519d7651bfbbe97af9347564109/kiwisolver-1.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c90d3022d8a94778939cda8638c6c8da8fa757b8958dad7ec868ce29c87681b8", size = 2325583, upload-time = "2026-08-28T10:26:09.093Z" }, + { url = "https://files.pythonhosted.org/packages/42/8e/7673060a27b01405b580058510adef34687069d229800239f5e44682d4d0/kiwisolver-1.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8a34616dc2521cc8dc1d7d081734da63539f021ac0450ce950908340c6e7aa2f", size = 1978221, upload-time = "2026-08-28T10:26:11.127Z" }, + { url = "https://files.pythonhosted.org/packages/3f/7b/1b882fc1a8b4a0bb8084e7d1d85004116d08c92c0705d31f2928dec607f2/kiwisolver-1.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:8bf4df63592c2a66b4f8edc5df2544998c288aa02f96ce0acd880cd1de8c8127", size = 2491819, upload-time = "2026-08-28T10:26:13.348Z" }, + { url = "https://files.pythonhosted.org/packages/39/9c/426deb49e62c5f69464b64bbeca064d3b758a7506b8913d986ef34f4619c/kiwisolver-1.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d09037ca068d784ebc4aec290ef952ca27ac15dd9c0b5801a88c6e1096b83e6b", size = 2295520, upload-time = "2026-08-28T10:26:15.042Z" }, + { url = "https://files.pythonhosted.org/packages/f5/22/deabbb3ad6d918d74b7831b2d8ae7151b09d21c87974e5ee8a456f58c94c/kiwisolver-1.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:dc23390afe9f4ef9ac3bcc72a03a56eebbde03f4c571a32cb38f859cff9a6524", size = 70758, upload-time = "2026-08-28T10:26:16.504Z" }, + { url = "https://files.pythonhosted.org/packages/b9/71/02fa5c2fd92068bb8952847e70ee6c5cb280e7febe11653d17812acc53dd/kiwisolver-1.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:186884a58486651e3c217b6acea0a53eaa9498fdd472057c46f2f0fb5c25aad5", size = 68329, upload-time = "2026-08-28T10:26:17.658Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/2d5dfad0daf17dcc18d98c48ed2332fc3f051cf599e60be6182a30dd4cf1/kiwisolver-1.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0324cd2567259b7a095f6cf18a52b0ffc6f3de9e69528ff1bc0e7a37bd43ff1a", size = 62337, upload-time = "2026-08-28T10:26:18.778Z" }, + { url = "https://files.pythonhosted.org/packages/08/c8/83e1624f15d6262b470dbcc80b09979fd4d5b2ea3ddfc6b6e3327e235726/kiwisolver-1.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:74ea337e0ec3f6f342a36a4f1b5cd94dd9affddcd28ba9aae2905af932ee8c6b", size = 64513, upload-time = "2026-08-28T10:26:19.909Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2d/827ec30eb07f528c08d8459ffb318ae91a56d793ee8acbea8b491f0ff906/kiwisolver-1.5.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ee9df1f0d77b9c6e94f4ac0fec533fbddd5ea3a327807f18d7b069ae019ded80", size = 66287, upload-time = "2026-08-28T10:26:21.078Z" }, + { url = "https://files.pythonhosted.org/packages/53/11/5c43a562529dad8def4b81e5e1877c612a7e0298105a5939b3b409d2079c/kiwisolver-1.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fc271a6f0a2126958f4090e5507b9da5848927dae331f8f763bd4aa642b3d2cd", size = 123940, upload-time = "2026-08-28T10:26:22.475Z" }, + { url = "https://files.pythonhosted.org/packages/64/db/9bd6c505c95128c258a55236bfbb3a7a3fb6023f863316b6d7d9f3c69052/kiwisolver-1.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9b3092d8992a1d69b7a59c3e39f35e1b9be327a17f68a7c35fc17329e337d6f2", size = 66493, upload-time = "2026-08-28T10:26:23.743Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f4/b3007a3ed5c9be73f81161140684cf7d9bdb9c4b632f5f484d2a1c713fb9/kiwisolver-1.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c2306e8bb53601979fcb3fa09cc65e031876d9ae01eff2fcbcd7a84ef94d5bc1", size = 64720, upload-time = "2026-08-28T10:26:24.95Z" }, + { url = "https://files.pythonhosted.org/packages/a1/13/08188f0cafa3a800403e4ff62b9aad4e7a17f9c4c7e080dc8f18c64794cf/kiwisolver-1.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:18a0cfb124546a4c2e6087c5f3029c7f44b37c85b142e0ced71f73a7599ac208", size = 1475867, upload-time = "2026-08-28T10:26:26.393Z" }, + { url = "https://files.pythonhosted.org/packages/8a/3e/053bdc3c9abdb8f2606225eda398adca25c0c91ab90add8222a69db65ee0/kiwisolver-1.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34ec467940442c9943016fb2d4c81d1ba84351eeca2f1a78f8bc87f1ba0d414c", size = 1282865, upload-time = "2026-08-28T10:26:28.118Z" }, + { url = "https://files.pythonhosted.org/packages/5e/64/a44c341b36b610588cc2f1e89b3cae072a3119aa8be578e90987cd640751/kiwisolver-1.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a83ee7107df13abe42a54a6654670eef9bb39425cf2e27f65e0007465e1286ab", size = 1300865, upload-time = "2026-08-28T10:26:30.125Z" }, + { url = "https://files.pythonhosted.org/packages/60/5e/7e7d716dca38c714478b741257a5b4a321d9932b8d851551a136dcaf3984/kiwisolver-1.5.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bebb89489b279b2f5661bbbb2abcc87bcd4a46607bb4a5c966f04f1db6b8df9a", size = 1348071, upload-time = "2026-08-28T10:26:31.829Z" }, + { url = "https://files.pythonhosted.org/packages/10/1a/2b98fdda8bf45b7be317e48ed12393d44334394d315c74b81f4a14c0e31b/kiwisolver-1.5.1-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:509735237ae0d849e8a843551d423d2500d2e0a9ac1611a145658b29c0fb9f85", size = 992191, upload-time = "2026-08-28T10:26:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/b6/55/d893f5ede0e50f9e3fcf01f6015f42ec7d9cd221e26772701fe4a98745f9/kiwisolver-1.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:824c3d763a05ea9e9003610145186b0e9848c7584a5575c79bac5a8e7cd80bad", size = 2233854, upload-time = "2026-08-28T10:26:35.282Z" }, + { url = "https://files.pythonhosted.org/packages/b1/82/f85f6279555a6ee1639fef7bfe83adb037a03e11a6fc9eaa54b8d0380339/kiwisolver-1.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1fff05e239575b1481b6ed1a782f6fad616efbf1f0b1f44e6e85c4dfe426e483", size = 2330621, upload-time = "2026-08-28T10:26:36.9Z" }, + { url = "https://files.pythonhosted.org/packages/56/31/e11aea078f66fc2fffcc179d38ca90d9da97652a241b64519169742ba46a/kiwisolver-1.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0627b9bceb9c3cdcf12b8a18655eedfed2692b038df27423383c120d0b7dc2d6", size = 1982848, upload-time = "2026-08-28T10:26:39.01Z" }, + { url = "https://files.pythonhosted.org/packages/af/ea/2956b63bf5140ca46aa2c2818e6aa03e2d5754dd2fa41db1c6b28922940c/kiwisolver-1.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8a708a47ade1fe19e8371d5da076bac0dd4b0a5a7985ad6c637f7f7e361b6baa", size = 2494850, upload-time = "2026-08-28T10:26:40.837Z" }, + { url = "https://files.pythonhosted.org/packages/11/d1/3829542258d8b3fc0898d221e7ef0e2c83eca0d348709bb8dbe54f3d4005/kiwisolver-1.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:007a5553dfc4f4e8d184f588a0200e2cd4b63a59cc8796df3c39909e679dc7a0", size = 2298067, upload-time = "2026-08-28T10:26:42.803Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0e/49522e1ab5788cbaf63a26fbd3b851f9028616828c961b8a31b35cb96df8/kiwisolver-1.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:f4167e87b397f273dc2356fcf1eaf50a6bac51e6105f45103ef7129c8efb0255", size = 72282, upload-time = "2026-08-28T10:26:44.268Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b6/22e7ca5315d363e6f81c9f37c9472e12e7b298731e77c0428e6a911a2c39/kiwisolver-1.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:5c490db2168a508088f59140dd392556a54b8bd1048fc6383c8baff13c359673", size = 69855, upload-time = "2026-08-28T10:26:45.725Z" }, + { url = "https://files.pythonhosted.org/packages/30/8c/03a9cfbe871964c8758a816eb03ac96c806da2795a9a7cd9bf9648bfb594/kiwisolver-1.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4d4ca09bf13cff792b1884f64b98ee6c2467930d632233be25c56b442d99f10e", size = 126289, upload-time = "2026-08-28T10:26:47.023Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e3/14ce3041ca79dff9c9d884ca00c7bf32374e76028a865a9ecd99b4f5a517/kiwisolver-1.5.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:44b8faef94f1857e77fa0238f3390ff1ac51d2ea20a487e2e452a59fd2b5f5ca", size = 67709, upload-time = "2026-08-28T10:26:48.268Z" }, + { url = "https://files.pythonhosted.org/packages/af/c4/45030471a66ec8ef042e9f96ffe1d522c9ab12da180186a0898966fc1385/kiwisolver-1.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ae70bc59790d2af72a3f76f24b272403e135070340281108b447cb77ea70819", size = 65909, upload-time = "2026-08-28T10:26:49.523Z" }, + { url = "https://files.pythonhosted.org/packages/42/73/17dce073a6ae259bb32cf9d686c4079d2e538868bc45967462bf33df914a/kiwisolver-1.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:43844c1a7ad6d723d5b5b4c4fc7f5bd399c40e288120d16257c7c9e8765c6e85", size = 1584907, upload-time = "2026-08-28T10:26:50.933Z" }, + { url = "https://files.pythonhosted.org/packages/8c/84/ae3c75909f507283cbfcc7e916c7e822579ef962020b97e6882b27b4478f/kiwisolver-1.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22d5e5aaad6be121f2515765e3b1c444352cb8eb4c86510801db8f2e50757316", size = 1392474, upload-time = "2026-08-28T10:26:52.638Z" }, + { url = "https://files.pythonhosted.org/packages/34/31/8bcc83caad5bce8fa4577152389848bf6bc110e51e573a2b4e7c2aa34c89/kiwisolver-1.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3fa5855898f6d3d01b72ccd48a2d65cbdee301251603fefe34e2025bddba219c", size = 1405246, upload-time = "2026-08-28T10:26:54.248Z" }, + { url = "https://files.pythonhosted.org/packages/55/72/220345537d790cf4ae54f8acfff4b5cc2468e0702a384d651cf7a771c63e/kiwisolver-1.5.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d66a64dd5dec136040ec2ae94aa026a912ee60fdd45bc28d3db30037fd809e88", size = 1456099, upload-time = "2026-08-28T10:26:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/cd/10/3725fd2398f66d18c34b4e0f81a8d03764cd4f4f089f58a527f0b4428086/kiwisolver-1.5.1-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:9e51c119992ea8820706871c30a4642ec76de20ae82f9b50b9a45517d8e9f810", size = 1073695, upload-time = "2026-08-28T10:26:57.658Z" }, + { url = "https://files.pythonhosted.org/packages/86/cb/28d6e09e66b93e4588b2e6b7d84d020ccefea09e2f4de788510a07efeab7/kiwisolver-1.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:70ed9a45c7484d2b30cdacf60d220f494a1763b9fec1ad03285c6553fa0889f2", size = 2335355, upload-time = "2026-08-28T10:26:59.202Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b3/a0f31d5e4e40af7dc97c36b8a74fdd3a36cf3c8bbd098da9a23466ff6a94/kiwisolver-1.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:98b208a7cc42c803445ef551d6753cc42a5ea13e9cab1ee66cd8b9cb70195330", size = 2426524, upload-time = "2026-08-28T10:27:01.181Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c9/728f63bd58c72cafdc79fc306abeeac7391bec03b757a48dadeb30906521/kiwisolver-1.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c6834b92dd2428e2dd85ef3d85f723d3c12f20aaf43a2ddd4f944ca25d833408", size = 2063430, upload-time = "2026-08-28T10:27:03.06Z" }, + { url = "https://files.pythonhosted.org/packages/84/df/ce188b96f92f9a2c958231da140768918cba53c9713dc887b82f85462118/kiwisolver-1.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:5d142e352eb13facc7dd047489aebdff6ba78576c239f1ea04931979caaf0567", size = 2597513, upload-time = "2026-08-28T10:27:05.072Z" }, + { url = "https://files.pythonhosted.org/packages/69/d6/76947c8203768968382e5bd74d9cc95654746703a61ea53015f2c74a2e06/kiwisolver-1.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9b1c4900736e489a812c529100de4b8fb617d4db075e931e213c57424b83d9b", size = 2394488, upload-time = "2026-08-28T10:27:07.423Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d4/14b21e4eb203c4d15425e8b6a2c625a320b4a1f2f7557eead63ffc30ffb7/kiwisolver-1.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5978c3340f16a35c30f8ab2fa7bcf559973c55f1a5ef6970e1f621acf3c4db13", size = 75404, upload-time = "2026-08-28T10:27:08.892Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f5/53157899fc7f45f76421b77b99eb1639dd0f83f26ff9d76300c96bb4a3b0/kiwisolver-1.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ca307d6c259e5c98d3cb9ade55342b47a6839762caf2536f3d7b46ee660cc82e", size = 72946, upload-time = "2026-08-28T10:27:10.944Z" }, + { url = "https://files.pythonhosted.org/packages/75/62/f786c3a27f181fa339d851a77e266d208e776b9883cabc40a5b041a31b5a/kiwisolver-1.5.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:bb7c99f0673c03017a3ee01e54a5c2617a05468b11eabe513b0080e063ed95b1", size = 62420, upload-time = "2026-08-28T10:27:12.308Z" }, + { url = "https://files.pythonhosted.org/packages/02/cd/58a91ed25fbad0facdf503297b03768efab04bdf3141e5e3b49a34be7443/kiwisolver-1.5.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:0d8924877ce22e17326a99a418c3c82037da078df3c6a260b13eca677444e6e7", size = 64577, upload-time = "2026-08-28T10:27:13.502Z" }, + { url = "https://files.pythonhosted.org/packages/f6/5c/d501ef5a0958b226eac28306d24d5e5f114be0ace50e19cabae7b6b3b197/kiwisolver-1.5.1-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:534f02c1abb31ed6dbd3515545285c330b2f12d00fdb1fdb71658b9ca5a13a6a", size = 66284, upload-time = "2026-08-28T10:27:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a6/8fbecaf4fc18c02f31f05e47a84c010a80e3ec391ed2f0bdade1d62b5954/kiwisolver-1.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:cea20da04494e662b83c872683bf4ff2345206043d036315ed0e924b652e7294", size = 124031, upload-time = "2026-08-28T10:27:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c6/bf3090d2983b4204347cbdbe952116e7c3b2abf62b4e33e50167a13e75ee/kiwisolver-1.5.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:7fd82debf43c6acd0a94359d232f6bb516ee13f269a7993736a9ac9f988bb5d9", size = 66489, upload-time = "2026-08-28T10:27:17.506Z" }, + { url = "https://files.pythonhosted.org/packages/85/de/562dddef55fdd7c291da8626d6619e72b5fc0870e6ccca0e149a5731e7f3/kiwisolver-1.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:18170a77ddfecf40ec60d0928268dc95880c881864e015a8f34094ed18b9b9ad", size = 64806, upload-time = "2026-08-28T10:27:18.673Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/ba7b9c0164ce1cf62bf2872db63b8483289cf0f3110d6f9390eb09e409ed/kiwisolver-1.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ca7f6fe0f37ca978a1e5eb7a3a68e6413f417e78e838324947ffd420202b198b", size = 1482211, upload-time = "2026-08-28T10:27:20.039Z" }, + { url = "https://files.pythonhosted.org/packages/13/dc/34da54efb4976616d45c20aae32d70e89d6e7395ed908029154d1609ef22/kiwisolver-1.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b973887ff782cfd6b67c9904ad8ca542e0bc5e4961503408b423b5a688b4d38", size = 1283739, upload-time = "2026-08-28T10:27:21.82Z" }, + { url = "https://files.pythonhosted.org/packages/5a/3a/30ffb62bee646e266e98a1b5cd276d9c75b6116fbfcb87c1190838c1b6df/kiwisolver-1.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f76fc85bd054c806960f917ec0f329e24e436f1712267d90588e4c39890caa63", size = 1301681, upload-time = "2026-08-28T10:27:23.876Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8d/13c70be22a8506880b35fdc38dca36629613bc493405c79f4037f2cd2bb9/kiwisolver-1.5.1-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:828f75af2b0080c8a972e75f649ab46af008e92c6104a57a759157200b835b75", size = 1349159, upload-time = "2026-08-28T10:27:25.899Z" }, + { url = "https://files.pythonhosted.org/packages/82/0e/993972b8ec6767f47cd69818fb3a5ff14510557d29f7d1a839be7574fa1b/kiwisolver-1.5.1-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:431dc224a1a92a5c8f582d96e505196a3b5997a7271076678da2dfde67b77e9a", size = 997613, upload-time = "2026-08-28T10:27:27.507Z" }, + { url = "https://files.pythonhosted.org/packages/36/82/ca26eddd2eda2420dfc56693449c1f821f78b485da9cbde9904c03af3f93/kiwisolver-1.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:61e9a64c7635095a6bfe483e2ff055d437c59bd45f3617a228b37277f0185d62", size = 2235109, upload-time = "2026-08-28T10:27:30.113Z" }, + { url = "https://files.pythonhosted.org/packages/c7/84/97d920881e10840b8d7c7185620298e3e4c88820b05514e3a15a258b08a6/kiwisolver-1.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:3c24cd69455e1b00ddf770c13b6e2c33e07d6dc3f2d34add0bf9277c5c6bbd46", size = 2331207, upload-time = "2026-08-28T10:27:32.795Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ce/34d74b8f25acc58800f4c09268371e8d6159cf0f1206f1e4dc7835629b48/kiwisolver-1.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:27add358abe374ebaa3b8763ef380bc99051b5a4b18d94878366a9e4f59efef0", size = 1986696, upload-time = "2026-08-28T10:27:34.628Z" }, + { url = "https://files.pythonhosted.org/packages/0e/01/f892644014612527aef7031d3306a2ffc60b3cb044f802c1561f8e5e14f3/kiwisolver-1.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:255605693a483db7bd5c79f60437f7bf658f7f520d61aa42722e32257c941951", size = 2496400, upload-time = "2026-08-28T10:27:36.818Z" }, + { url = "https://files.pythonhosted.org/packages/32/6d/d8284e66e697026536e5f418b9cfe56567bffd3c775e3ecfbae373605854/kiwisolver-1.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:7d38b0c279c3032e8c9cc013b405c6df8e1668dbf15465779aa7f15f61201812", size = 2302967, upload-time = "2026-08-28T10:27:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/46/0a/69a355e27f32ba50d5b6369949b6a1702e122f5277c89bc76d452b81c1c4/kiwisolver-1.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:958254518717542d02d0688d0d20cbf771da5e415e6f49543f92481c850a4540", size = 72283, upload-time = "2026-08-28T10:27:40.491Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d8/7a95be90c33dcdd52204d4aa6384d731443225b887283bbd8b61e7931f6c/kiwisolver-1.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:da3275833be0edbaf4830fae08bae3dc7219f40ce0c37eaa6c25825957e06612", size = 69860, upload-time = "2026-08-28T10:27:41.835Z" }, + { url = "https://files.pythonhosted.org/packages/31/f8/9bc493e7f5707788ba7f621902c68f82dc3a7ba03c78fbd337b026cef1ed/kiwisolver-1.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:470d420f98d368d6f010633a20659b544c5fdfa5329e6b70219f2ef08fd4a7ef", size = 126336, upload-time = "2026-08-28T10:27:43.379Z" }, + { url = "https://files.pythonhosted.org/packages/d1/82/3aea86b3f99712db825e9ac5631bf99571e818a8b8961ff98cebd798413e/kiwisolver-1.5.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:83f78128fa28705fa85d01c59771c72fe81c11bd0e6155edbb9f818983a7d761", size = 67698, upload-time = "2026-08-28T10:27:44.613Z" }, + { url = "https://files.pythonhosted.org/packages/84/7d/8daafc5d2e7f9c47a4f78f8865d86d2a9cf399c2a85f86c44a993594410c/kiwisolver-1.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:9506e892bcc3b409831d363c6f53e5985e1c8d1f6f6b0256d00358684ff85378", size = 65945, upload-time = "2026-08-28T10:27:45.932Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c9/51dc974d9130da70a8c47a96160123443d387ffe1b6b833d6f91d9429339/kiwisolver-1.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cea90547bfd93807e0013a004dc76552be44fad3bc1cc2b38610a9e889ed098f", size = 1588030, upload-time = "2026-08-28T10:27:47.678Z" }, + { url = "https://files.pythonhosted.org/packages/e6/86/f3e1a730e7a995149d8d3ff9e313b6d8a17b2cf1d98a8eff139dc30463fb/kiwisolver-1.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8e4d953faaded9ec7ede36824e9814082d22d4c7b1eafbfa079ecba8cd0d076", size = 1390760, upload-time = "2026-08-28T10:27:49.676Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/8ba25a2b5a0d2375e046f1b72de5179513f0be95aba6e7b094c89303929f/kiwisolver-1.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e9c01d3dd7ceba4d1d436cc021d40d592466e40b9bc7f5d83dc4e98a5c9cd8c", size = 1403279, upload-time = "2026-08-28T10:27:51.242Z" }, + { url = "https://files.pythonhosted.org/packages/18/d0/278d5cb8be812740027d5ca0a7eda0c375488a88d6dce0fa60fcc2591ad2/kiwisolver-1.5.1-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37f801b5d7cc0e5a548921308e059fd2b057bb42972b591cfa3049f95423c4ed", size = 1454429, upload-time = "2026-08-28T10:27:53.213Z" }, + { url = "https://files.pythonhosted.org/packages/94/37/bcbab41063ec284c1d200efe5087cf087798c2f8916960aa8a20dd303290/kiwisolver-1.5.1-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:e68e151428b5384f766cd25739bf77c7e4a3dc93b5ded7a12118d9fbfdf78ab6", size = 1073225, upload-time = "2026-08-28T10:27:55.089Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c5/ab79dcdf5ae28909a51210ae0a1c579e97ff997b3466414f0d04c0994583/kiwisolver-1.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:8f8fddb8e323bd6eee4e54e69a39243beab22689070f4c66b472c4cc88bb89d8", size = 2334335, upload-time = "2026-08-28T10:27:56.78Z" }, + { url = "https://files.pythonhosted.org/packages/8d/8e/71d047468a189041d9c93f3b76844b924f9793b188c44bd149fa258912da/kiwisolver-1.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:3cc210010fd2f438a3ed430b45f1b501fd13a8618bf984dc2c5ce5b69b78752e", size = 2424982, upload-time = "2026-08-28T10:27:58.639Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/1347461bbea6d0e1f0580b94ef603b18e72c2be5f667fa1653867361a00b/kiwisolver-1.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:b5664603a253efd3a75716d793d1d3a6a82723b61dc6db767b2460bbbeec4c0f", size = 2062857, upload-time = "2026-08-28T10:28:00.407Z" }, + { url = "https://files.pythonhosted.org/packages/1f/87/c7f0976c9cd0d127643351bc0c9929e0b8899d7f49d4ec238cd909e39c42/kiwisolver-1.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a7b85b2cc6ea45e5f7e8c9a30bc9fabd47cda09106cbb4b967335c3e6c43b69d", size = 2596022, upload-time = "2026-08-28T10:28:02.183Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9a/59a6f6ae6f938c15076be2c21b6cedea973d71bb1349ec84fa485fab82cf/kiwisolver-1.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ab620eb663952455271ac37f9aaad86b73c969c02f11f53cea405b38e96a4300", size = 2395634, upload-time = "2026-08-28T10:28:03.977Z" }, + { url = "https://files.pythonhosted.org/packages/e9/2d/8982c1fb7926da5bb7ed60318c3665b5c3f941447271ac982960a11b8637/kiwisolver-1.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:cb6fae641357ed2f6e533c0d3c6504a4a5703621a50c89459e46051d56b61140", size = 75379, upload-time = "2026-08-28T10:28:06.307Z" }, + { url = "https://files.pythonhosted.org/packages/07/78/ba7b6dfa1708b82b373ac056928a30c545d5c1a627df9839dcec3c6c1881/kiwisolver-1.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:b390aec180a7c054919c04898835e1c77bced23ea8383eb2c570213bf25d1a86", size = 73011, upload-time = "2026-08-28T10:28:07.578Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c4/1407df7512a5b36cc79840e01710dc575733c461b13ab866cae77eaf87f3/kiwisolver-1.5.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:482676e5bd48d70ac99d9fc78863469845421e01184fa83f1f9366dc49f7e974", size = 134002, upload-time = "2026-08-28T10:28:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/16/45/c37a21ad5c0ab581a93c55ad544721aaa1f0ae94edb29c6a678a23d013e6/kiwisolver-1.5.1-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:072bdb15a3c19a5b5dbc8f8fb1f4e1884bf4f3507eeb4cc6334401274d37a5c0", size = 194292, upload-time = "2026-08-28T10:28:11.06Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c1/69f00d627949580e43d57af0aa465df46868d7c29801c137a55374101294/kiwisolver-1.5.1-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:a5a00665d1a0e26763a7338d7e911d4598fbc1d50dd0d6b7919b7dc6c5d6569f", size = 73362, upload-time = "2026-08-28T10:28:12.449Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1d/59ba570b1774e95e97fde3a0981b2e22118a7a495f73bf74cedc538566a0/kiwisolver-1.5.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:416ba7ff9f233b7036689bb5a3783537e838ad483f63558d2a800f75afe738b1", size = 59450, upload-time = "2026-08-28T10:28:20.383Z" }, + { url = "https://files.pythonhosted.org/packages/22/98/a6849f04dc18b5400e8b98affa2cd8fd86ed583085f036e57b32e571f4fa/kiwisolver-1.5.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8af9b142ad719ae3a911ebf616bc4b78b32bbab84d6a40d3ad2f129670509957", size = 57400, upload-time = "2026-08-28T10:28:21.632Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ce/a7dc71353dd06a4cbe02222773f52d4a28c81e5a452a75797f8ed113dc99/kiwisolver-1.5.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5daa1f19e097050b9c4d9a78fcc9263cb96c9dfae08037ddc1b7c4ad1889f2a2", size = 79891, upload-time = "2026-08-28T10:28:22.936Z" }, + { url = "https://files.pythonhosted.org/packages/10/b1/d61c61a84ff85d1a36a99df2c152b59ffedb1d356c598902aba44abcdb60/kiwisolver-1.5.1-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdaeeb6c350106df6bf9d873395973e5f066a9713200b72cd64f55d0a3eafab6", size = 77605, upload-time = "2026-08-28T10:28:24.322Z" }, + { url = "https://files.pythonhosted.org/packages/d3/52/5aef56f21a460a6e43ab3cdfc7697d59d7b87deb0ec97a0f7b91aa4a521b/kiwisolver-1.5.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:17851e5dad4484be0cbccbde3b15331deae036de9aebd45eed964487802b172f", size = 98465, upload-time = "2026-08-28T10:28:25.696Z" }, +] + [[package]] name = "lxml" version = "6.1.3" @@ -643,6 +934,71 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/03/5c/91fe48856f9f8089be3096fa4dbe4b3fb5526f3bf3e852ea9497f399cb9f/lxml-6.1.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bc8dd3d9c93e70c3df974a201ac2958b6d77b465d813c51d1f15fa8e645763ae", size = 3511258, upload-time = "2026-09-02T14:46:49.046Z" }, ] +[[package]] +name = "matplotlib" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/d0/791aa183dd88491555cf7d4be0b52b0bcf6c3c2a2c22c815a2e819bf53e2/matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2", size = 9440302, upload-time = "2026-07-18T03:38:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/82bbdf683a301f4478384c8aaba6903631a2ca18294b2d7655c9a542bffb/matplotlib-3.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c", size = 9268549, upload-time = "2026-07-18T03:38:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/9b4298911303f74e6d83e64a81d996c0616405ec95046fac7f17e4258b9e/matplotlib-3.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83", size = 10024922, upload-time = "2026-07-18T03:38:08.236Z" }, + { url = "https://files.pythonhosted.org/packages/84/6f/0bc3c3d05b021db44c14bc379a7c0df7d57302aa15380c16fd4e63fd6a9b/matplotlib-3.11.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099", size = 10832170, upload-time = "2026-07-18T03:38:10.276Z" }, + { url = "https://files.pythonhosted.org/packages/db/4d/e375f39acdb2af5a9342730618608e39790ec842e6f1b392863028781459/matplotlib-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407", size = 10916701, upload-time = "2026-07-18T03:38:12.512Z" }, + { url = "https://files.pythonhosted.org/packages/bc/be/fa26ed085b41298f64a8f9b7592c671bbf1acc8b0df124c1c5de96b859f8/matplotlib-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f33a781e12b1e53b278deb2f5373c2e55ec4f10727be3440c0cfb5cda9f944f", size = 9315331, upload-time = "2026-07-18T03:38:14.949Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eb5bdf3b6e191b200db298b08bbc1638b7f3c82cdc8680f9d88bf72559ae/matplotlib-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:67e4c3cd578c65ebd81bdc09a1b6592ceafee6dfafe116dc85dfcb647b5bbb18", size = 9003475, upload-time = "2026-07-18T03:38:17.205Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/88/89/56649bbaa2fd12e20f3be03dbcc135b0c8676d88bac17977599e3eb442a0/matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f", size = 9333886, upload-time = "2026-07-18T03:38:30.477Z" }, + { url = "https://files.pythonhosted.org/packages/c1/11/4d124efbbad677b7b7552f6f85a3bd432d4232f95400cea98fcd2ae36ef3/matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a", size = 9007545, upload-time = "2026-07-18T03:38:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/4798363b7fb5644e309fe1fac30216e9146c9f70859d80d588c18caf5317/matplotlib-3.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6771b0cd7838c6a857a7209814158c0ad09bfef878db3033dd82d70ad101f191", size = 9454341, upload-time = "2026-07-18T03:38:35.001Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/6acadbe7f98df19d274bc107ac58bb439fa75df82c33dc110d71a4a8501f/matplotlib-3.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2abdee5ffa2fe11b2d19f7a5c63b785fb7c28cc46c7bc1814156341d9d1a33e1", size = 9283627, upload-time = "2026-07-18T03:38:37.061Z" }, + { url = "https://files.pythonhosted.org/packages/24/ea/65cec46fe241390ccea1b1754207ee28eb71c5ab866bd5f22fe47e538fa4/matplotlib-3.11.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0a19dcf73406d3746d25a5ed42d713604c9a3e024d129b102852b0d941cb9f3", size = 10035860, upload-time = "2026-07-18T03:38:39.663Z" }, + { url = "https://files.pythonhosted.org/packages/c7/10/63fdccccbabe002fb0960876baabc5e3f24d9c1bb4cfb25651457f74b3a0/matplotlib-3.11.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7389b77ed2ab0552f46d9a90b81b7b8e6dfcdc42adc36c37a0865799843e0e3e", size = 10843594, upload-time = "2026-07-18T03:38:42.144Z" }, + { url = "https://files.pythonhosted.org/packages/98/51/a1155945bff7b91381875022ac1522c5dfdac0d006be8e7df389b3134eae/matplotlib-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c90be0b73568da4f662afac580956a76e308437e641b4a45aa08925eeb67d95f", size = 10927962, upload-time = "2026-07-18T03:38:44.302Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3a/3d5e1f42dc761bf53401a62a83ff93389b37de9d2c093b2a3aa49ac34f1b/matplotlib-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:68408341f2312836fbbdf6b3c78047f65b2d8752f5fd221c3e72d348f5b34f8b", size = 9334074, upload-time = "2026-07-18T03:38:46.616Z" }, + { url = "https://files.pythonhosted.org/packages/e2/db/3f5ea5a5b64060ef5e1ff60a19170423e41ce21b8497a6fe15a36e0b43e3/matplotlib-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c1f44890d435c1b4ef52f701ad5828cb450ea97bcc83918fda6be74965d6cd2", size = 9007662, upload-time = "2026-07-18T03:38:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/c7ae5e0531425b69c0826b00ebbc264c85cab853f1cd6e096c9983c2cdc1/matplotlib-3.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e510088c27a89d53580a752f959146893563e63c330e161d159b0fee652af6f", size = 9503790, upload-time = "2026-07-18T03:38:51.527Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/15be162e0a2ed546939674e2e97d0e33ec2447d86d4d4e611fa295bb178c/matplotlib-3.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1524e2bdd48a93557aa47ddcfe9c225dfdd57d5a01a5c49128c20f0632980ee1", size = 9336148, upload-time = "2026-07-18T03:38:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7f/36ffe144fc4aacfe0e3ed2318f72b6755d1e73b041d619b4d393e60f5a66/matplotlib-3.11.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11664c551345553db92e61cae6cf1376f138f8c47cafdf13b64b18f3e3e9e464", size = 10049244, upload-time = "2026-07-18T03:38:55.911Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/55812d68c0a840d3a463638f48c00ab1fe338518ec49a640cb6473b444af/matplotlib-3.11.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e1f8922ba31959cf6a9dfb51be64b7f7bc582801a3957dc0c2f3afcd3537adf", size = 10860798, upload-time = "2026-07-18T03:38:58.282Z" }, + { url = "https://files.pythonhosted.org/packages/7a/64/cca444b4eb5e6c768c44fc5e1f0b5211f20ca2b282778051996e996a2bdf/matplotlib-3.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83235693abde86e5e0129998f80ee39fc7f58e6d56a88fafb28a9278833e9d5f", size = 10943282, upload-time = "2026-07-18T03:39:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/e5/0f/a49c329d394f2e9ef38506982107e8b04ecf94dd41a9d8423ff82cc737c7/matplotlib-3.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9a076f4fc5cdc43fdf510f5981418d25c2db4973418d9f22d8bb3dc8045ada78", size = 9383532, upload-time = "2026-07-18T03:39:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/e4/50/103e86afb806d8f64d04ede14e4cfc09dbfc25f512421ff85fdd6ebd59cf/matplotlib-3.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:216fbb93a74add02ddb4cb38ef5348f59ac00b3e84567eaf16598772d40e150a", size = 9059665, upload-time = "2026-07-18T03:39:04.607Z" }, + { url = "https://files.pythonhosted.org/packages/35/04/3079499fa8cb661ea66d13d6439d5a3ae6710a7afd5c7f72e08914f275f8/matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c492d4ba9448595b6fd8708c6725963f8148e25c0d8842948da5b05f0ee8d3", size = 9456022, upload-time = "2026-07-18T03:39:07.041Z" }, + { url = "https://files.pythonhosted.org/packages/53/a2/69acfe84ec1f32930e801a5782a07fc5c79c8c6599a507b806d859d5da8e/matplotlib-3.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ac104be2768ffdd8655db9e71b768cbb45f2b9aa7b450cf1595e8f65d3822319", size = 9285475, upload-time = "2026-07-18T03:39:09.562Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/31b15a2ca56d4ddd6aaa1c884c2f51cf9a61cfaf5ca6f6fbd6343d38e6df/matplotlib-3.11.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be943cb68bc6660ead58c55b3aa6366cba2ef7feb06460fbcce32360376f19f", size = 10847102, upload-time = "2026-07-18T03:39:11.532Z" }, + { url = "https://files.pythonhosted.org/packages/64/0d/a17e966e620545c1548125af0b29ac812dd17b197a18a7462ac12fa859ee/matplotlib-3.11.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5af0dcda57d471440a7b5b623e70e0a61003518443d9098f211a96ecfbbc25be", size = 11131087, upload-time = "2026-07-18T03:39:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/97/c5/5e100efdd67abb7de20befaa333612ef9bfc63417fb71398f904f25d083c/matplotlib-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3d3fd84082b1afbd9398466c81309e20045be20d48fe0fb18c43504d164cbbb2", size = 10929036, upload-time = "2026-07-18T03:39:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/d719a0a36930ecc8dfc801ff340f9dcfc4223f8ca5d39d06b4020032fff8/matplotlib-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:9601a1e90be21e4884c53b4f3dc3ee0544654946f9975258d691f1c2e2f119c6", size = 9489571, upload-time = "2026-07-18T03:39:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/facabdc2f1f6caba7e856db64dfedddca25f7608df07d96a1c8fd114fd3b/matplotlib-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:ae30c6109848ac0f9fa36c5d6270938487614c47ba31860bd5361266dabc5685", size = 9164486, upload-time = "2026-07-18T03:39:21.424Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/18da6cd01cf96354534f98c468a25380c68ce582a2c9dd0cae12b04af4f2/matplotlib-3.11.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dadfe80797174e2984aae3be0b77594a3c72d2c0a40fbd4a0de48d2728caf3ae", size = 9504876, upload-time = "2026-07-18T03:39:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/b0/f0b63555a18b79d038c81fd6126f35fc4dfce0eaff48d96103348c7cf935/matplotlib-3.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:89b193b255f4f6f7948dbcee3691f4f341ab05d9a8874a67b45ddb4182922eda", size = 9336120, upload-time = "2026-07-18T03:39:25.797Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dd/f210ec7c4a6f198d5567237048a93d0811fb5a1f1691f13320e592f95b41/matplotlib-3.11.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191163532cdefcb1571ca38a6d7e6474baccde64495783e6ba47aa07ec4b9bbb", size = 10858033, upload-time = "2026-07-18T03:39:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/d6d5324507c5fbb316db48e258c09c2807f3de03d9af47017e120070926f/matplotlib-3.11.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fdf1c818ab05d0e74002091ddaf414478a3a449ec9d51c8976d45be7e3a01e2", size = 11141827, upload-time = "2026-07-18T03:39:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/0f/68/3c22e9320bdce2c4d2f1320643ef706db7a24cb7420eea28b97a2d67f5a8/matplotlib-3.11.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b937b9dba5f5f6c1e31c47abe2186c865c0914fd18f2ce0dfc39c9adcef5951d", size = 10943061, upload-time = "2026-07-18T03:39:32.356Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4a/907ed190ee81a9df581e0ed5456134fc0f7cb55ffcfda2f9e54ca900761c/matplotlib-3.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f2912f647f3fbe1ccf085f91e213936f9101bead81a5e670565b1f1b3712f4fb", size = 9540074, upload-time = "2026-07-18T03:39:34.789Z" }, + { url = "https://files.pythonhosted.org/packages/23/d4/97c19b77e0a6e3b48581185bb65088f431cd20186076cc0f650a1757ea46/matplotlib-3.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:54d47b8ae8b579633a3902ca5b4ad6c1e132a5626d64447b2e22a66394e79987", size = 9213472, upload-time = "2026-07-18T03:39:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/ceb1d637c4db6d06141f3739e93af3321e7caaabe69b57ae48ffe3ee95b1/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741", size = 9438045, upload-time = "2026-07-18T03:39:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/72ad8b58602d3a6ef1dfc4b65ecd01634ab65a2bdf494c9fe0e966dbf081/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb", size = 9266127, upload-time = "2026-07-18T03:39:41.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/69552382fcc8e93d1f2763ef2665980a900a48b7f3a4c57ed290726d1cbc/matplotlib-3.11.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99", size = 10019439, upload-time = "2026-07-18T03:39:43.78Z" }, +] + [[package]] name = "mistune" version = "3.3.4" @@ -661,6 +1017,7 @@ dependencies = [ { name = "fastapi" }, { name = "httpx" }, { name = "jsonschema" }, + { name = "matplotlib" }, { name = "mistune" }, { name = "olefile" }, { name = "python-docx" }, @@ -682,6 +1039,7 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.116,<1.0" }, { name = "httpx", specifier = ">=0.28,<1.0" }, { name = "jsonschema", specifier = ">=4.25,<5.0" }, + { name = "matplotlib", specifier = ">=3.9,<4" }, { name = "mistune", specifier = ">=3.0,<4.0" }, { name = "olefile", specifier = ">=0.47" }, { name = "python-docx", specifier = ">=1.1,<2.0" }, @@ -695,6 +1053,164 @@ requires-dist = [ [package.metadata.requires-dev] dev = [{ name = "pytest", specifier = ">=8.4,<9.0" }] +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231, upload-time = "2026-09-06T16:27:47.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/50/8fdbb16af64895706a45f06a4068e29db732ec180f3c1375f14123359138/numpy-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9", size = 16994982, upload-time = "2026-09-06T16:24:29.244Z" }, + { url = "https://files.pythonhosted.org/packages/60/39/789131c1188c078dcb3a1692e72e1e050c68b88ffe72c9ccaac9bcd7a9cd/numpy-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c", size = 12009327, upload-time = "2026-09-06T16:24:32.491Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/a312e95696e5f601914dd8b6dd844692ba61670807417e24b68e337b5c70/numpy-2.5.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0", size = 5445405, upload-time = "2026-09-06T16:24:35.071Z" }, + { url = "https://files.pythonhosted.org/packages/30/d0/5623a1707ed4fe16e3909fe3cf5ee3da004ae677ad23d83bbf3adf1a6faf/numpy-2.5.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e", size = 6783213, upload-time = "2026-09-06T16:24:37.253Z" }, + { url = "https://files.pythonhosted.org/packages/f1/32/84146fc020ad3c25f805f70ab60da46fe3c540a21369754a7e4369754b6f/numpy-2.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58", size = 15687872, upload-time = "2026-09-06T16:24:39.751Z" }, + { url = "https://files.pythonhosted.org/packages/65/af/aa78d1a88805456e212b65461354cd943197fb9acecc4c90fd12295123a3/numpy-2.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3", size = 16717410, upload-time = "2026-09-06T16:24:42.745Z" }, + { url = "https://files.pythonhosted.org/packages/3b/24/faa79d865e69a97ba17473b23a1b74094b2259c03e820c70297293b9ea49/numpy-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff", size = 17040975, upload-time = "2026-09-06T16:24:45.961Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/8877e629445a7176297dffcaf9c485faa96a95d81728a62521ad55bd4c0f/numpy-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034", size = 18476479, upload-time = "2026-09-06T16:24:49.35Z" }, + { url = "https://files.pythonhosted.org/packages/c8/db/35e1c2d38b04cbd5b731f9d71495e055e813197669d22b612f11748d2ff9/numpy-2.5.3-cp312-cp312-win32.whl", hash = "sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4", size = 6133378, upload-time = "2026-09-06T16:24:51.915Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/accf6d4f0c80c5d9ba9735d6b1550e444180599f34dec69ca01360f717ad/numpy-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def", size = 12567828, upload-time = "2026-09-06T16:24:54.255Z" }, + { url = "https://files.pythonhosted.org/packages/22/43/1764aff32e4652526ae2f71fa8b3efd8d25c8a3d6926914454e47138ed1e/numpy-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034", size = 10485432, upload-time = "2026-09-06T16:24:57.278Z" }, + { url = "https://files.pythonhosted.org/packages/79/e5/8fb89cd46d14e35699d13bf943a5f5f441ecee8667120a1f6105ab89e349/numpy-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034", size = 16991061, upload-time = "2026-09-06T16:25:00.411Z" }, + { url = "https://files.pythonhosted.org/packages/2f/06/9dc9e48b5e5e941c8b10350c5ff2d721da42a20517d911d15544246775ff/numpy-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e", size = 12003676, upload-time = "2026-09-06T16:25:03.475Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2a/98282aa5b8f58b1157d440bb6282eed47e3632a5de53a714fbab17e659fe/numpy-2.5.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09", size = 5439695, upload-time = "2026-09-06T16:25:05.978Z" }, + { url = "https://files.pythonhosted.org/packages/a1/f9/b6533d777be9d6ffd29dc1be0867e563e6e8cc9a220ff1b716adc317f060/numpy-2.5.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958", size = 6779395, upload-time = "2026-09-06T16:25:08.599Z" }, + { url = "https://files.pythonhosted.org/packages/73/85/735720d04ec197c5dcfacdfc9922667c7f1f5f496a279b7ba4d7c74c4cc7/numpy-2.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b", size = 15681750, upload-time = "2026-09-06T16:25:11.173Z" }, + { url = "https://files.pythonhosted.org/packages/3a/1b/3b16a9bc514a440a7a0883684111dcb1ef1aee960af2ca95da8fc775f124/numpy-2.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321", size = 16708577, upload-time = "2026-09-06T16:25:14.171Z" }, + { url = "https://files.pythonhosted.org/packages/69/c4/386f397831b07328b639c96c5b62719346cf4baf07c68d927239752b1534/numpy-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231", size = 17042047, upload-time = "2026-09-06T16:25:17.582Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3e/a700ecbf36e85ae8328fd3b0e12eeddc22ed6358a64cb2bd913e0d195d65/numpy-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0", size = 18465724, upload-time = "2026-09-06T16:25:20.949Z" }, + { url = "https://files.pythonhosted.org/packages/41/ee/38e785e88a4045f6ad1d1f2808dcdfafdca48c760260c0587bf171e29fc9/numpy-2.5.3-cp313-cp313-win32.whl", hash = "sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f", size = 6129003, upload-time = "2026-09-06T16:25:23.664Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ec/100f2b1794ede74a9b3d7ec6b9736927f56713414c1dfe19ab6c383494bf/numpy-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec", size = 12560965, upload-time = "2026-09-06T16:25:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/80/b1/7dc825ca94c12acebbce4c37caa5e198695eb31424bc579679f32b1bb49d/numpy-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204", size = 10482343, upload-time = "2026-09-06T16:25:29.772Z" }, + { url = "https://files.pythonhosted.org/packages/70/78/cf416f15dc29375a229d9dfebf8db6e313f291580b39fa1a568b6052bb07/numpy-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:350ba9783ce969cf9f7ce6e6a9a58e1a6e2a19ca025b7ee448c4db727706212a", size = 16998686, upload-time = "2026-09-06T16:25:33.171Z" }, + { url = "https://files.pythonhosted.org/packages/9e/59/abcc2d8def4fd60eec7d87f92d27c13448ffd9ab14339bcc63a0d7a2fdea/numpy-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:012e66aca395d795496446e52aeeb5866312a5d4d3f27da270e5a0b43f70dc5c", size = 12013862, upload-time = "2026-09-06T16:25:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/94/75/4640d2d6e4b64a049e48425a82728a41ef4adb61332d2cba68055774878b/numpy-2.5.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:adc1ada2662f8a5f960b8a10d9986897e7499ef07e06d4cfe7197f8cce923c07", size = 5449793, upload-time = "2026-09-06T16:25:39.476Z" }, + { url = "https://files.pythonhosted.org/packages/96/cd/625b57ae33d4ca560f32cc0b47b4a5922146d9beb998ddf773900d440a73/numpy-2.5.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:54a115e5a73b8fc44f0cebef486365a1894b5c9760685d4558b72b7c3eb846e0", size = 6785176, upload-time = "2026-09-06T16:25:42.069Z" }, + { url = "https://files.pythonhosted.org/packages/9c/72/12918652e7912ef9751e8694c88820fcd1908e0618cb23f5f3caa6004b7b/numpy-2.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be5a8381859b6da607c84f4f7d6847725f1cf1853ef8a2c9e115b7d58bef47dc", size = 15703377, upload-time = "2026-09-06T16:25:45.135Z" }, + { url = "https://files.pythonhosted.org/packages/45/8f/9beacf79ca7c650688ad0baa80931adb988fe6e6e5d5903c23cc3dbd70eb/numpy-2.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0521d0f4aebb6e06189451025fa17a913287b13c03d5fe05c017333b654ea5b", size = 16711928, upload-time = "2026-09-06T16:25:48.461Z" }, + { url = "https://files.pythonhosted.org/packages/09/8d/41d0a56e1ac4c87495c897a211b1368691b7237aadabec8b3b8f3a74d48f/numpy-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9deb49575e5b0b94ed72c8a64ec4d033381adc27e9060ae842971f697ba96104", size = 17059507, upload-time = "2026-09-06T16:25:51.873Z" }, + { url = "https://files.pythonhosted.org/packages/08/1e/0dfbc5cc251d54e2af790f254d24ec38637fa97ec7d5d11de7ffed787098/numpy-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b00eefbcf0f292945c4b4dec2ae845389ef5bcdcd596e6e4328051db5b5ba694", size = 18471002, upload-time = "2026-09-06T16:25:55.233Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/dfa40f6991f8185c8c30ffd023dfcbb11888e823cfab9557b920f3bb7bed/numpy-2.5.3-cp314-cp314-win32.whl", hash = "sha256:c2381f82999704f818e2c987a865050e285ec3621262c66d40f5a96c8f899f8e", size = 6180485, upload-time = "2026-09-06T16:25:58.157Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/d2c08231e4fde7e415501fd02c715d96e98599b2d8384445933944152984/numpy-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:2c25dfa72943e4336ddb6b0ee4277b47a0c85bede0807530ec68103bf58e2c10", size = 12698179, upload-time = "2026-09-06T16:26:00.789Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e9/dcdcc9b95cf5f49815055573aee1b11cfbf5299f38a180e437ded050810f/numpy-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:15aa985ac73a8db02db7663381aa109510449d3819d37206caed27b33a65a8a6", size = 10769383, upload-time = "2026-09-06T16:26:04.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/c4/af8bc08a7ef4e1529a7c0cf24969accce316b783999802089a581ec99272/numpy-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ac7bb1c52d445bd4f8f7f97fefe6abc3a084dc4d63df50d79b17fa2b78e89297", size = 12132668, upload-time = "2026-09-06T16:26:07.138Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ae/0f15eb56d4ec5e13c1f7ff04ff407f997d1acbadb45d3e1f2e2645a8f43c/numpy-2.5.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e6ab667ba76450084eb64013762c438ea76d9d29cc676dcd6c2e9892ba37f841", size = 5568580, upload-time = "2026-09-06T16:26:09.828Z" }, + { url = "https://files.pythonhosted.org/packages/23/fb/c72a8f25d4b6e96c354e7ab45ace3b27dc11e5d6a13b6c7d0cd6b08bf112/numpy-2.5.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f7fabeb6cea87d65f3b926de33d03fb016cfdc29314c90974383b5582ae72891", size = 6882634, upload-time = "2026-09-06T16:26:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/07/a9/968c90ed2ab15060c338e8137f1215b5a60756ae07328e0a60d1c6734df4/numpy-2.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fb6f8fb9ff0b3a69f52c66ce397b0246583e9f28616231b0e32ca49259a5fa6", size = 15748923, upload-time = "2026-09-06T16:26:15.092Z" }, + { url = "https://files.pythonhosted.org/packages/59/08/9df04103947b95e3b6b1f2ed1a70521f325647a31b82da6a2aae3a485508/numpy-2.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93e1f5447e2b1e479d7bd74701e84746b86450cff1fc368b132d195e2b8f8211", size = 16746748, upload-time = "2026-09-06T16:26:18.43Z" }, + { url = "https://files.pythonhosted.org/packages/41/a0/14c8d5fe5b53a334aabb653deb391c0fef49558f491880ea300ed6785224/numpy-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c00abe94c1a69d75d827dcf1c025b25c8a45d230b3bcd77a9020883a1b047653", size = 17111561, upload-time = "2026-09-06T16:26:22.113Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a6/d7e96e42f01522e154c32489640f16dfc4f6181d165d05fc3bec8c2c4999/numpy-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:536f963710a4e63934d80ac0dc4f478804a83e9a84b6828018f25d09953ada33", size = 18513945, upload-time = "2026-09-06T16:26:25.401Z" }, + { url = "https://files.pythonhosted.org/packages/25/39/3453afb7119d0449ef11c886874120ff180e2c337760e0e2d88f70f1a945/numpy-2.5.3-cp314-cp314t-win32.whl", hash = "sha256:4c8a6d2ebce6305fd82fbefca827775437147052a976ee7c94b36a0c1b52ac6c", size = 6335421, upload-time = "2026-09-06T16:26:28.175Z" }, + { url = "https://files.pythonhosted.org/packages/99/01/22815d2b19a1a746b1d45205cffebb3fe511a18acb75fba6c88491fc9894/numpy-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9a37475425b431b4d060f23b4f52cd2f3aef6bc7c654bd760adf0040eec9d435", size = 12896420, upload-time = "2026-09-06T16:26:31.265Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ee/a7cbba67eeaff038dc29ca8b98a88396c8b0cc9c89d4924f4a27a5c9150b/numpy-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2d8240cb4c16fd831074aa2b2cf9fc54664d826341d61c372245b96a74a49a9a", size = 10857177, upload-time = "2026-09-06T16:26:34.167Z" }, + { url = "https://files.pythonhosted.org/packages/45/56/78194492883ff5eec90423fe56a3a44b154da047d88a6307f629713c584f/numpy-2.5.3-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a6391fafaba97500887132cd582abc6e19452b1ac775a47caa7b24490e152058", size = 16996531, upload-time = "2026-09-06T16:26:37.287Z" }, + { url = "https://files.pythonhosted.org/packages/11/39/dd55c0af90bbab564b09ae3b0aa60ec5c02b900fa4f1ba23440525c8b32d/numpy-2.5.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:09d5a423c71ad5feb5625844ad58050e35df43871004b52ac9c0ad44a56775be", size = 12012569, upload-time = "2026-09-06T16:26:40.707Z" }, + { url = "https://files.pythonhosted.org/packages/b6/51/04f67d32e4862b281b1cb84ceeaed3421189a84fb6fb51a391cd6d5009f7/numpy-2.5.3-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:f9579f383d1bf9df80081e72760e84960a7fd4f88cf0c9e535a8597c9bb646f5", size = 5448498, upload-time = "2026-09-06T16:26:43.435Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c9/25b4dc0dd1344ec26c7319e84fd4e9809d2b5628f4e12decd618036e5178/numpy-2.5.3-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:86bff898a431c0fb71f7610b75726e75a54d47b37edc9d537f48de63bb3c0b90", size = 6783026, upload-time = "2026-09-06T16:26:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c7/29285be1e5232a6e7ee3268a33c85843f5a8ee93350c6465cddd66ebbf76/numpy-2.5.3-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f3ed25271581281f2fccb1adcedfcde4c07362eec69189b50baf6f90e3ae159", size = 15697322, upload-time = "2026-09-06T16:26:49.415Z" }, + { url = "https://files.pythonhosted.org/packages/55/49/bbad5335fb4996a16881f853ff3e0ba582f01720e55c89b1c06b8fc42a90/numpy-2.5.3-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffdc76bfcae6b255dff75202c5e7feaf95b40246bc0a17944facc1fecf9f79ab", size = 16708995, upload-time = "2026-09-06T16:26:53.127Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e9/1df35483760b04a65ea44669f89dc64f30e5aca098b48ceb8b1310b0e0fe/numpy-2.5.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:116f96cadd935c6122e9228d676fe7ede19e741f5c8bb1c3cddbe0c51ccebea2", size = 17052508, upload-time = "2026-09-06T16:26:56.464Z" }, + { url = "https://files.pythonhosted.org/packages/b8/99/66e54da8265cc8be8a7382bf96edce17aaa2837d6f484432025932a3caa5/numpy-2.5.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:09ffa5d903faeaa5c4dd05009cf81c8bab9f2cb37c548b8d39b65b4cfa7c97f7", size = 18468224, upload-time = "2026-09-06T16:26:59.966Z" }, + { url = "https://files.pythonhosted.org/packages/01/bc/b5e90a91c115168d793dfd2ad9c69c438c2fe7a13a437e770bc5b078e732/numpy-2.5.3-cp315-cp315-win32.whl", hash = "sha256:e01c918ac3d48e18a927cf7b14a26a3e29ff2bdf2eacb976da0aecd6a43ed034", size = 6179919, upload-time = "2026-09-06T16:27:03.166Z" }, + { url = "https://files.pythonhosted.org/packages/37/ea/780748fd3985109075514ef8fc64cd25f943e40dde13a6d59141eb268fc8/numpy-2.5.3-cp315-cp315-win_amd64.whl", hash = "sha256:e931e4f499e0dc7ef29d269a8e5b35dd722e5d14be07df6240166ea7c6532fae", size = 12697656, upload-time = "2026-09-06T16:27:06.153Z" }, + { url = "https://files.pythonhosted.org/packages/b3/16/407be69a2a87c8cab64d95975a8977a426a29e138f07e276ec258f0fe4e5/numpy-2.5.3-cp315-cp315-win_arm64.whl", hash = "sha256:26e15e4aecd8617dfbaecb37d223e365d7b39411fba20454be2670a96aa74cb5", size = 10767601, upload-time = "2026-09-06T16:27:09.297Z" }, + { url = "https://files.pythonhosted.org/packages/44/bf/a97ffb01e41d50a32a9177aef942a4d0e389a3daf451d04e5f38ef6afb87/numpy-2.5.3-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:6cef4bb1706dfec49243c05d921eefb4e190d41e2528b30d8035ea1f36b4c24a", size = 17090092, upload-time = "2026-09-06T16:27:12.907Z" }, + { url = "https://files.pythonhosted.org/packages/d1/24/136c02f2c2af9a067a84d0c3aa10c99012c0476fa5066732fa4a4202557d/numpy-2.5.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:d1c89973648c85069c5046ad460f7b8a00218b29a2e42359ac8cc63e9ab94832", size = 12129429, upload-time = "2026-09-06T16:27:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6c/b47582d6597789bf946d5efbeb6b9e56fd8bcbd5efc6fbf51dbe1ea31eb3/numpy-2.5.3-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:214045a5bf00113a146ab9ee9730c44501af6723cdf1f6830932f7b5ef2e7af0", size = 5565452, upload-time = "2026-09-06T16:27:19.868Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/ef3cc6da73774202d4deae16bb321fd8298a4e0561e3539f8c4be237d916/numpy-2.5.3-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:8617bbfae4486cf99c9f899966699428d19da931d06ca94ad3da986c76e15997", size = 6876736, upload-time = "2026-09-06T16:27:22.232Z" }, + { url = "https://files.pythonhosted.org/packages/9e/24/e3813329498596cb842703dcacac1741612ed9fb9c4e6a3e0c7e2ebbc597/numpy-2.5.3-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:595d020938c84e320bcf40ad71089e108eac0d377cd018e14a8c094f39e98d85", size = 15745777, upload-time = "2026-09-06T16:27:25.181Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9e/4e7a07fd0776dc2210cdacf2010be8665194d094defc10c419d7dea794cc/numpy-2.5.3-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f24021b9f22bc6301c37b196974a92c1c18dccedb6fef3dd252e95f2d6adbe4", size = 16746949, upload-time = "2026-09-06T16:27:28.576Z" }, + { url = "https://files.pythonhosted.org/packages/91/db/01674c0e20335057813a00c2ebd546ed25bff9ed7914f9bced00f8c55d94/numpy-2.5.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:71b39d9f935b6ec0f8753e3e2afb51e3efba6f2e05b68b32a40754d24bcd4a3c", size = 17108994, upload-time = "2026-09-06T16:27:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/45/7a/584c5e71f8d378e57cac0b033891ed65c683ef90573ba4854e8c28203db0/numpy-2.5.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6b05c171afb3aa07adbd20abc00aea86fe375beb0fdb9ef780ec5b7f63bab1c0", size = 18512266, upload-time = "2026-09-06T16:27:35.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d2/4e1014173aa3c55e6a756e0e567290743a6ab33a288460374d7ef6bcd239/numpy-2.5.3-cp315-cp315t-win32.whl", hash = "sha256:f54660b0eb6b0b9f36e7fe1cdfdff472028dd0d14acd9b9b65098efbad059469", size = 6330292, upload-time = "2026-09-06T16:27:38.149Z" }, + { url = "https://files.pythonhosted.org/packages/6c/b0/ff5658a58199b7bcaad87bf260eef6713d9d42cca4e028f935b4fc5fbac6/numpy-2.5.3-cp315-cp315t-win_amd64.whl", hash = "sha256:1aad64d99730d013cfc6debafed22783b4fc5a7f4b8bc744d2d8cf7dcc880551", size = 12884918, upload-time = "2026-09-06T16:27:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/b12a2df5d1b774bd9007a6fdff9381145b6223d37f11afc9c37ab0efd9a1/numpy-2.5.3-cp315-cp315t-win_arm64.whl", hash = "sha256:befa1ae5bd6030b3f512b43ff3fa5290bbed6b84411a44244b14adf835f5b89d", size = 10850807, upload-time = "2026-09-06T16:27:43.868Z" }, +] + [[package]] name = "olefile" version = "0.47" @@ -942,6 +1458,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, ] +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + [[package]] name = "pytest" version = "8.4.2" @@ -958,6 +1483,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + [[package]] name = "python-docx" version = "1.2.0" @@ -1185,6 +1722,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", size = 586486, upload-time = "2026-06-30T07:17:51.141Z" }, ] +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + [[package]] name = "sqlite-vec" version = "0.1.9" diff --git a/frontend/scripts/phase2-benchmark-browser.cjs b/frontend/scripts/phase2-benchmark-browser.cjs new file mode 100644 index 0000000..e2824cc --- /dev/null +++ b/frontend/scripts/phase2-benchmark-browser.cjs @@ -0,0 +1,22 @@ +const {chromium}=require('playwright');const fs=require('node:fs/promises');const path=require('node:path'); +(async()=>{ + if(!process.argv.includes('--execute')&&!process.argv.includes('--reuse'))throw Error('--execute required; four real Agent cases use existing quota'); + const output=path.resolve('.local-plans/phase2-completion/browser'); + const browser=await chromium.launch({channel:'msedge',headless:true}); + const page=await browser.newPage({viewport:{width:1300,height:1000}}); + await page.goto('http://127.0.0.1:5187/#/benchmarks'); + await page.getByLabel(/^类型/).selectOption('agent'); + await page.getByLabel(/^数据集/).selectOption('agent-core-v1'); + if(!process.argv.includes('--reuse')) await page.getByRole('button',{name:'运行评测',exact:true}).click(); + const row=page.locator('tbody tr').filter({hasText:'agent-core-v1'}).first(); + await row.getByRole('button',{name:'查看报告'}).waitFor({timeout:180000}); + await row.getByRole('button',{name:'查看报告'}).click(); + await page.getByRole('heading',{name:'评测报告'}).waitFor(); + await page.screenshot({path:path.join(output,'benchmark-report.png'),fullPage:true}); + const pending=page.waitForEvent('download');await page.getByRole('button',{name:'下载完整 JSON'}).click(); + await(await pending).saveAs(path.join(output,'agent-ui-report.json')); + await row.getByRole('link',{name:'Agent Trace'}).click(); + await page.waitForSelector('.agent-page .trace-visualization',{timeout:20000}); + await page.screenshot({path:path.join(output,'benchmark-trace.png'),fullPage:true}); + await browser.close();console.log('Benchmark UI start/report/download/Trace completed'); +})().catch(e=>{console.error(e);process.exit(1)}) diff --git a/frontend/scripts/phase2-browser-check.cjs b/frontend/scripts/phase2-browser-check.cjs new file mode 100644 index 0000000..20c4871 --- /dev/null +++ b/frontend/scripts/phase2-browser-check.cjs @@ -0,0 +1,94 @@ +// Run with NODE_PATH pointing to the bundled Playwright package, or a local install. +const { chromium } = require('playwright') +const fs = require('node:fs/promises') +const path = require('node:path') +;(async () => { + const output=path.resolve(process.argv[2] || '../.local-plans/phase2-completion/browser') + await fs.mkdir(output,{recursive:true}) + const browser=await chromium.launch({channel:'msedge',headless:true}) + const results=[] + for(const theme of ['light','dark','sepia','paper-moments','ocean-blue','midnight-purple']) { + const page=await browser.newPage({viewport:{width:1200,height:1000}}) + const errors=[];page.on('pageerror',e=>errors.push(e.message)) + await page.goto(`http://127.0.0.1:5187/tests/visual/phase2.html?theme=${theme}`) + await page.waitForSelector('.editor-mermaid-preview polyline',{timeout:60000}) + await page.waitForSelector('.markdown-mermaid polyline',{timeout:60000}) + const zoom=page.locator('.markdown-mermaid').first() + await zoom.hover() + await zoom.locator('[data-diagram-action="in"]').click() + if(Number(await zoom.getAttribute('data-diagram-scale'))<=1) throw Error('zoom failed') + await zoom.locator('[data-diagram-action="reset"]').click() + await zoom.locator('[data-code-action="source"]').click() + if(!(await zoom.locator('.markdown-code-source').isVisible())) throw Error('source toggle failed') + await zoom.locator('[data-code-action="source"]').click() + await zoom.hover() + await zoom.locator('[data-diagram-action="view"]').click() + await page.screenshot({path:path.join(output,`${theme}-viewer.png`)}) + await page.keyboard.press('Escape') + await page.screenshot({path:path.join(output,`${theme}-wide.png`),fullPage:true}) + await page.setViewportSize({width:390,height:844}) + await page.screenshot({path:path.join(output,`${theme}-narrow.png`),fullPage:true}) + if(theme==='light') { + await page.setViewportSize({width:1200,height:1000}) + await page.getByRole('button',{name:'导出',exact:true}).click() + for(const format of ['html','pdf','docx']) { + await page.getByLabel('格式',{exact:true}).selectOption(format) + await page.getByRole('button',{name:'开始导出',exact:true}).click() + const row=page.locator('.export-modal > ul > li').filter({hasText:`phase2-demo.${format}`}).first() + await row.getByRole('button',{name:'下载',exact:true}).waitFor({timeout:60000}) + const download=page.waitForEvent('download') + await row.getByRole('button',{name:'下载',exact:true}).click() + await (await download).saveAs(path.join(output,`demo.${format}`)) + } + await page.screenshot({path:path.join(output,'export-jobs.png')}) + } + if(theme==='light') { + await page.getByRole('button',{name:'关闭',exact:true}).click() + await page.getByRole('button',{name:'源码',exact:true}).click() + const input=page.getByRole('textbox',{name:'Markdown 源码编辑器'}) + const original=await input.inputValue() + await input.fill(original.replace('y = x^2','y = cos(x)')) + await page.getByRole('button',{name:'写作',exact:true}).click() + await page.waitForFunction(()=>document.querySelector('.editor-mermaid-preview svg')?.textContent.includes('cos(x)')) + await page.getByRole('button',{name:'测试切换主题'}).click() + await page.waitForFunction(()=>document.documentElement.dataset.theme==='dark') + await page.waitForFunction(()=>document.querySelector('.editor-mermaid-preview svg rect')?.getAttribute('fill')!=='#ffffff') + await page.getByRole('button',{name:'源码',exact:true}).click() + await input.fill(original.replace('y = x^2','y = __import__("os")')) + await page.getByRole('button',{name:'写作',exact:true}).click() + await page.waitForFunction(()=>!document.querySelector('.editor-mermaid-preview polyline')&&!document.querySelector('.markdown-function-plot polyline')) + await page.screenshot({path:path.join(output,'invalid-expression.png'),fullPage:true}) + await page.getByRole('button',{name:'源码',exact:true}).click() + await input.fill(original) + await page.getByRole('button',{name:'写作',exact:true}).click() + await page.waitForSelector('.editor-mermaid-preview polyline') + await page.getByRole('button',{name:'导出',exact:true}).click() + await page.getByLabel('格式',{exact:true}).selectOption('pdf') + await page.getByRole('button',{name:'开始导出',exact:true}).click() + await page.locator('.export-warnings').first().waitFor({timeout:60000}) + await page.screenshot({path:path.join(output,'print-warning.png')}) + } + results.push({theme,errors,plotCount:await page.locator('polyline').count()});await page.close() + } + const cancellation=await browser.newPage() + await cancellation.goto('http://127.0.0.1:5187/tests/visual/phase2.html') + await cancellation.getByRole('button',{name:'源码',exact:true}).click() + const heavy=Array(13).fill('```function-plot\ny = '+Array(150).fill('(x+x)').join('+')+'\n```').join('\n\n') + await cancellation.getByRole('textbox',{name:'Markdown 源码编辑器'}).fill(heavy) + await cancellation.getByRole('button',{name:'导出',exact:true}).click() + await cancellation.getByLabel('格式',{exact:true}).selectOption('pdf') + await cancellation.getByRole('button',{name:'开始导出',exact:true}).click() + const cancelRow=cancellation.locator('.export-modal > ul > li').first() + await cancelRow.getByRole('button',{name:'取消',exact:true}).click() + await cancellation.waitForFunction(()=>document.querySelector('.export-modal > ul > li')?.textContent.includes('已取消'),null,{timeout:60000}) + await cancellation.screenshot({path:path.join(output,'export-cancelled.png')}) + await cancellation.close() + const matrix=await browser.newPage() + await matrix.goto('http://127.0.0.1:5187/tests/visual/mermaid-matrix.html') + await matrix.waitForFunction(()=>document.documentElement.dataset.complete==='true',null,{timeout:120000}) + const mermaid=JSON.parse(await matrix.locator('#status').textContent()) + if(mermaid.passed!==36 || mermaid.total!==36) throw Error('Mermaid matrix failed') + await fs.writeFile(path.join(output,'mermaid-matrix.json'),JSON.stringify(mermaid,null,2)) + await matrix.close() + await fs.writeFile(path.join(output,'results.json'),JSON.stringify(results,null,2));console.log(JSON.stringify(results));await browser.close() +})().catch(e=>{console.error(e);process.exit(1)}) diff --git a/frontend/src/components/common/DiagramInteractions.vue b/frontend/src/components/common/DiagramInteractions.vue index 7a04229..449630a 100644 --- a/frontend/src/components/common/DiagramInteractions.vue +++ b/frontend/src/components/common/DiagramInteractions.vue @@ -220,3 +220,8 @@ function close() { disarm(); viewer.value?.close(); svgHtml.value = ''; opener?. :is(.editor-mermaid-preview, .markdown-mermaid):is(:hover, :focus-within) > .diagram-controls { opacity: 1; pointer-events: auto; } @media (hover: none) { :is(.editor-mermaid-preview, .markdown-mermaid) > .diagram-controls { opacity: 1; pointer-events: auto; } } + + diff --git a/frontend/src/components/common/MarkdownContent.vue b/frontend/src/components/common/MarkdownContent.vue index e6e2746..489a217 100644 --- a/frontend/src/components/common/MarkdownContent.vue +++ b/frontend/src/components/common/MarkdownContent.vue @@ -24,7 +24,7 @@ const diagramTheme = computed<'light' | 'dark'>(() => (themeStore.isDark ? 'dark // 主题切换需要重渲染:Mermaid SVG 的配色在渲染时烘焙,无法靠 CSS 变量事后调整。 watch([() => props.source, diagramTheme, () => themeStore.currentThemeId, () => JSON.stringify(markdownPreferences.normalized), () => JSON.stringify([props.citationNumbers, props.citationAliases])], async ([source, theme]) => { const version = ++renderVersion - const result = await renderMarkdown(source, { theme, preferences: markdownPreferences.normalized, citationNumbers: props.citationNumbers, citationAliases: props.citationAliases }) + const result = await renderMarkdown(source, { theme, themeId: themeStore.currentThemeId, preferences: markdownPreferences.normalized, citationNumbers: props.citationNumbers, citationAliases: props.citationAliases }) if (version === renderVersion) html.value = result }, { immediate: true, flush: 'post' }) diff --git a/frontend/src/components/common/PrimarySidebar.vue b/frontend/src/components/common/PrimarySidebar.vue index b0966ad..43b2c5b 100644 --- a/frontend/src/components/common/PrimarySidebar.vue +++ b/frontend/src/components/common/PrimarySidebar.vue @@ -20,6 +20,7 @@ const navItems = computed(() => [ { name: 'plugins', icon: Connection, label: 'Plugin' }, { name: 'mcp-servers', icon: Monitor, label: 'MCP' }, { name: 'themes', icon: Brush, label: t('主题', 'Themes') }, + { name: 'benchmarks', icon: Monitor, label: 'Benchmark' }, { name: 'logs', icon: Document, label: t('日志', 'Logs') }, { name: 'settings', icon: Setting, label: t('设置', 'Settings') }, ]) diff --git a/frontend/src/features/benchmarks/BenchmarkView.vue b/frontend/src/features/benchmarks/BenchmarkView.vue new file mode 100644 index 0000000..6eb63e8 --- /dev/null +++ b/frontend/src/features/benchmarks/BenchmarkView.vue @@ -0,0 +1,65 @@ + +

Benchmark 评测

标准数据集通过真实检索引擎或 Agent Runtime 执行。Agent 会使用所选提供商额度;需要权限时请打开 Trace 处理。

+
+ + + +
+

{{ error }}

+
数据集状态操作
{{ run.datasetId }}{{ run.id }}{{ run.status }} {{ run.progress === null ? '' : `${Math.round(run.progress*100)}%` }} {{ run.errorCode }}Agent Trace
+

评测报告

+

{{ group.name }}

{{ row.label }}
{{ row.value }}
+
冻结配置与逐例证据
{{ JSON.stringify(report,null,2) }}
+ + + diff --git a/frontend/src/features/editor/EditorHeader.vue b/frontend/src/features/editor/EditorHeader.vue index 8f86f4f..c385dd8 100644 --- a/frontend/src/features/editor/EditorHeader.vue +++ b/frontend/src/features/editor/EditorHeader.vue @@ -1,6 +1,7 @@ + + diff --git a/frontend/src/features/editor/VisualMarkdownEditor.vue b/frontend/src/features/editor/VisualMarkdownEditor.vue index 90d4a28..7daa9a7 100644 --- a/frontend/src/features/editor/VisualMarkdownEditor.vue +++ b/frontend/src/features/editor/VisualMarkdownEditor.vue @@ -156,20 +156,20 @@ function foldHeadings(action: 'toggle' | 'all' | 'none') { } }) } -const diagramPreviews = new Map void }>() -function renderDiagram(source: string, apply: (value: HTMLElement) => void) { +const diagramPreviews = new Map void }>() +function renderDiagram(source: string, apply: (value: HTMLElement) => void, kind = 'mermaid') { for (const [id, entry] of diagramPreviews) { if (entry.apply === apply) diagramPreviews.delete(id) } - const element = createMermaidPreview(source, themeStore.isDark, apply) - diagramPreviews.set(element.dataset.previewId!, { source, apply }) + const element = createMermaidPreview(source, themeStore.isDark, apply, kind, themeStore.currentThemeId) + diagramPreviews.set(element.dataset.previewId!, { source, apply, kind }) return element } watch(() => themeStore.currentThemeId, () => { const current = [...diagramPreviews.entries()] diagramPreviews.clear() for (const [id, entry] of current) { - if (editorRoot.value?.querySelector(`[id="${id}"]`)) entry.apply(renderDiagram(entry.source, entry.apply)) + if (editorRoot.value?.querySelector(`[id="${id}"]`)) entry.apply(renderDiagram(entry.source, entry.apply, entry.kind)) } }, { flush: 'post' }) @@ -333,8 +333,8 @@ onMounted(async () => { ...config, languages: shikiLanguages(themeStore.resolvedCodeBlockTheme), renderLanguage: renderCodeLanguage, - renderPreview: (language, content, applyPreview) => language.trim().toLowerCase() === 'mermaid' - ? markdownPreferences.diagrams ? renderDiagram(content, applyPreview) : null + renderPreview: (language, content, applyPreview) => ['mermaid', 'function-plot'].includes(language.trim().toLowerCase()) + ? markdownPreferences.diagrams ? renderDiagram(content, applyPreview, language.trim().toLowerCase()) : null : config.renderPreview(language, content, applyPreview), extensions: [basicSetup, keymap.of([indentWithTab]), shikiEditorTheme(themeStore.resolvedCodeBlockTheme), indentUnit.of(' '.repeat(markdownPreferences.indent)), CodeEditorState.tabSize.of(markdownPreferences.indent), diff --git a/frontend/src/features/editor/mermaidPreview.ts b/frontend/src/features/editor/mermaidPreview.ts index bd0c23b..51b4c96 100644 --- a/frontend/src/features/editor/mermaidPreview.ts +++ b/frontend/src/features/editor/mermaidPreview.ts @@ -1,17 +1,18 @@ +import { renderFunctionPlot } from '@/services/functionPlotService' import { nextTick } from 'vue' import { renderMermaid } from '@/services/mermaidService' import { t } from '@/i18n' import { appendDiagramControls } from '@/utils/diagramControls' let previewId = 0 -export function createMermaidPreview(source: string, dark: boolean, applyPreview: (value: HTMLElement) => void): HTMLElement { +export function createMermaidPreview(source: string, dark: boolean, applyPreview: (value: HTMLElement) => void, kind = 'mermaid', themeId = 'light'): HTMLElement { // Each revision owns its element, so a slow render cannot replace newer content. // Milkdown sanitizes Element input to its inner HTML; retain the revision // marker and controls inside an otherwise disposable envelope. const envelope = document.createElement('div') const container = document.createElement('div') envelope.append(container) - container.className = 'editor-mermaid-preview' + container.className = 'editor-mermaid-preview' + (kind === 'function-plot' ? ' function-plot-preview' : '') container.id = `editor-mermaid-preview-${++previewId}` envelope.dataset.previewId = container.id container.setAttribute('aria-live', 'polite') @@ -27,8 +28,11 @@ export function createMermaidPreview(source: string, dark: boolean, applyPreview applyPreview(envelope.cloneNode(true) as HTMLElement) } } - void renderMermaid(source, { theme: dark ? 'dark' : 'light' }).then(result => { - if (result.warnings.length) { + void (kind === 'function-plot' ? new Promise(resolve => setTimeout(resolve, 180)) : Promise.resolve()).then<{ svg: string; warnings: string[] }>(() => { + if (kind === 'function-plot' && !document.getElementById(container.id)) throw new Error('stale preview') + return kind === 'function-plot' ? renderFunctionPlot(source, themeId) : renderMermaid(source, { theme: dark ? 'dark' : 'light' }) + }).then(result => { + if (result.warnings.length && (kind === 'mermaid' || !result.svg)) { container.classList.add('has-error') container.textContent = `${t('图表语法有误,可点击编辑修改:', 'Diagram syntax error. Choose Edit to fix:')} ${result.warnings.join('\n')}` void publish() @@ -37,6 +41,7 @@ export function createMermaidPreview(source: string, dark: boolean, applyPreview // Mermaid runs in strict mode; Milkdown sanitizes the preview before insertion. container.innerHTML = result.svg appendDiagramControls(container) + if (result.warnings.length) { const warning = document.createElement('p'); warning.textContent = result.warnings.join('\n'); warning.setAttribute('role', 'status'); container.append(warning) } void publish() }).catch(() => { container.textContent = t('图表渲染失败,请点击编辑检查源码。', 'Unable to render diagram. Choose Edit to inspect the source.') diff --git a/frontend/src/features/editor/shikiCodeMirror.spec.ts b/frontend/src/features/editor/shikiCodeMirror.spec.ts index 3100cb0..02ac3c3 100644 --- a/frontend/src/features/editor/shikiCodeMirror.spec.ts +++ b/frontend/src/features/editor/shikiCodeMirror.spec.ts @@ -69,7 +69,7 @@ it('offers fenced-code aliases and retains the LaTeX selector', () => { it('offers every bundled Shiki language and alias', () => { const languages = shikiLanguages('github-light') - expect(languages).toHaveLength(bundledLanguagesInfo.length + 1) + expect(languages).toHaveLength(bundledLanguagesInfo.length + 2) for (const info of bundledLanguagesInfo) { const language = languages.find(item => item.alias.includes(info.id))! expect(language, info.id).toBeDefined() diff --git a/frontend/src/features/editor/shikiCodeMirror.ts b/frontend/src/features/editor/shikiCodeMirror.ts index 292d7a5..83e7f9e 100644 --- a/frontend/src/features/editor/shikiCodeMirror.ts +++ b/frontend/src/features/editor/shikiCodeMirror.ts @@ -60,6 +60,7 @@ export async function shikiLanguage(language: string, theme: CodeTheme): Promise export function shikiLanguages(theme: CodeTheme): LanguageDescription[] { return [ + LanguageDescription.of({ name: 'function-plot', alias: ['Function Plot'], load: () => shikiLanguage('text', theme) }), ...bundledLanguagesInfo.map(info => LanguageDescription.of({ name: info.id, alias: [info.name, ...(info.aliases ?? [])], diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index d5655fb..b583e97 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -3,6 +3,7 @@ import { useWorkspaceStore } from '@/stores/workspace' import { t } from '@/i18n' const routes = [ + { path: '/benchmarks', name: 'benchmarks', component: () => import('@/features/benchmarks/BenchmarkView.vue'), meta: { title: 'Benchmark' } }, { path: '/logs', name: 'logs', component: () => import('@/features/logs/LogsView.vue'), meta: { title: '运行日志' } }, { path: '/media', name: 'media', component: () => import('@/features/media/MediaView.vue'), meta: { title: '音视频转写', requiresVault: true } }, { @@ -80,7 +81,10 @@ const router = createRouter({ router.beforeEach((to) => { const workspaceStore = useWorkspaceStore() - if (to.meta.requiresVault && !workspaceStore.hasVault) { + // A benchmark can run without the workspace UI being open. Its persisted Trace + // and permission tickets must remain reachable from the report page. + const existingAgentRun = to.name === 'agent' && Boolean(to.params.runId) + if (to.meta.requiresVault && !workspaceStore.hasVault && !existingAgentRun) { return { path: '/' } } if (to.path === '/' && workspaceStore.hasVault) { diff --git a/frontend/src/services/benchmarkService.ts b/frontend/src/services/benchmarkService.ts new file mode 100644 index 0000000..cdf66ad --- /dev/null +++ b/frontend/src/services/benchmarkService.ts @@ -0,0 +1,14 @@ +import { apiClient } from './apiClient' +interface RunWire { run_id: string; kind: 'rag' | 'agent'; dataset_id: string; status: string; progress: number | null; config_snapshot: Record; error_code: string | null } +export interface BenchmarkRun { id: string; kind: 'rag' | 'agent'; datasetId: string; status: string; progress: number | null; agentId?: string; errorCode: string | null } +const map = (r: RunWire): BenchmarkRun => ({ id: r.run_id, kind: r.kind, datasetId: r.dataset_id, status: r.status, progress: r.progress, agentId: r.config_snapshot.active_agent_run_id as string | undefined, errorCode: r.error_code }) +export const benchmarkService = { + async datasets(kind: 'rag' | 'agent') { + const r = await apiClient.get<{ items: { dataset_id: string; description: string; case_count: number }[] }>('/api/benchmarks/datasets', { params: { kind } }) + return r.items.map(d => ({ id: d.dataset_id, description: d.description, cases: d.case_count })) + }, + async list() { return (await apiClient.get<{ items: RunWire[] }>('/api/benchmarks/runs')).items.map(map) }, + async start(kind: 'rag' | 'agent', body: object) { return map(await apiClient.post(`/api/benchmarks/${kind}/runs`, body)) }, + cancel(id: string) { return apiClient.post(`/api/benchmarks/runs/${encodeURIComponent(id)}/cancel`) }, + report(id: string) { return apiClient.get<{ metrics: Record; cases: unknown[]; config_snapshot: Record }>(`/api/benchmarks/runs/${encodeURIComponent(id)}/report`) }, +} diff --git a/frontend/src/services/exportService.spec.ts b/frontend/src/services/exportService.spec.ts new file mode 100644 index 0000000..8121e47 --- /dev/null +++ b/frontend/src/services/exportService.spec.ts @@ -0,0 +1,18 @@ +import {describe,it,expect,vi} from 'vitest' +vi.mock('./apiClient',()=>({apiClient:{post:vi.fn(),get:vi.fn()}})) +import {apiClient} from './apiClient' +import {exportService} from './exportService' +describe('export snapshot contract',()=>{ + it('submits the unsaved Markdown snapshot and maps warnings and filename',async()=>{ + vi.mocked(apiClient.post).mockResolvedValue({job_id:'job',status:'queued',warnings:['print palette'],file:{file_name:'note.pdf'},error:null}) + const job=await exportService.create('# unsaved', 'note','pdf',{theme_id:'dark',include_title:true,page_size:'A4'},undefined,'folder/note.md') + expect(apiClient.post).toHaveBeenCalledWith('/api/exports',expect.objectContaining({source:{type:'markdown',markdown:'# unsaved',file_path:'folder/note.md'},format:'pdf'})) + expect(job.warnings).toEqual(['print palette']);expect(job.fileName).toBe('note.pdf') + }) + it('aborted preparation does not create a backend job',async()=>{ + vi.mocked(apiClient.post).mockClear() + const abort=new AbortController();abort.abort() + await expect(exportService.create('snapshot','note','html',{theme_id:'light',include_title:true,page_size:'A4'},abort.signal)).rejects.toThrow() + expect(apiClient.post).not.toHaveBeenCalled() + }) +}) diff --git a/frontend/src/services/exportService.ts b/frontend/src/services/exportService.ts new file mode 100644 index 0000000..64dc51f --- /dev/null +++ b/frontend/src/services/exportService.ts @@ -0,0 +1,68 @@ +import { apiClient } from './apiClient' +import { Marked } from 'marked' +import { renderMermaid } from './mermaidService' + +export type ExportFormat = 'html' | 'pdf' | 'docx' +interface JobWire { + job_id: string; status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled' + warnings: string[]; error: string | null + file: { file_name: string; size: number } | null +} +export interface ExportJob { id: string; status: JobWire['status']; warnings: string[]; error: string | null; fileName?: string } +const mapJob = (w: JobWire): ExportJob => ({ id: w.job_id, status: w.status, warnings: w.warnings, error: w.error, fileName: w.file?.file_name }) +export async function rasterize(svg: string, signal?: AbortSignal): Promise { + const doc = new DOMParser().parseFromString(svg, 'image/svg+xml') + const root = doc.documentElement + const box = root.getAttribute('viewBox')?.split(/[ ,]+/).map(Number) + const width = box?.[2] || 800, height = box?.[3] || 600 + if (!Number.isFinite(width + height) || width <= 0 || height <= 0) throw new Error('图表尺寸无效') + const scale = Math.min(4, Math.max(2, 1200 / width), Math.sqrt(4_000_000 / (width * height))) + root.setAttribute('width', String(Math.floor(width * scale))); root.setAttribute('height', String(Math.floor(height * scale))) + root.style.maxWidth = 'none' + const data = new XMLSerializer().serializeToString(root) + const image = new Image() + image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(data)}` + await new Promise((resolve, reject) => { + const abort = () => reject(new DOMException('Aborted', 'AbortError')) + const timer = setTimeout(() => reject(new Error('图表图片解码超时')), 15000) + const cleanup = () => { clearTimeout(timer); signal?.removeEventListener('abort', abort) } + if (signal?.aborted) { cleanup(); abort(); return } + signal?.addEventListener('abort', abort, { once: true }) + image.decode().then(resolve, reject).finally(cleanup) + }) + const canvas = document.createElement('canvas') + canvas.width = Math.floor(width * scale); canvas.height = Math.floor(height * scale) + const context = canvas.getContext('2d')! + context.fillStyle = '#ffffff'; context.fillRect(0, 0, canvas.width, canvas.height) + context.drawImage(image, 0, 0, canvas.width, canvas.height) + return canvas.toDataURL('image/png').split(',')[1]! +} +export async function hashSource(source: string) { + return [...new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(source.trim())))].map(v => v.toString(16).padStart(2, '0')).join('') +} +export const exportService = { + async create(markdown: string, title: string, format: ExportFormat, options: { theme_id: string; include_title: boolean; page_size: string }, signal?: AbortSignal, filePath?: string) { + const blocks: string[] = [] + const parser = new Marked() + parser.walkTokens(parser.lexer(markdown), token => { if (token.type === 'code' && token.lang === 'mermaid') blocks.push(token.text) }) + const assets = [] + for (const source of [...new Set(blocks)]) { + signal?.throwIfAborted() + if (assets.length >= 16) throw new Error('每次导出最多 16 个 Mermaid 图表') + const result = await renderMermaid(source, { mode: 'raster', theme: 'light' }) + if (result.warnings.length) throw new Error(`Mermaid 无法导出:${result.warnings.join('; ')}`) + assets.push({ kind: 'mermaid', source_hash: await hashSource(source), png_base64: await rasterize(result.svg, signal) }) + } + signal?.throwIfAborted() + return mapJob(await apiClient.post('/api/exports', { source: { type: 'markdown', markdown, file_path: filePath }, title, format, options, assets })) + }, + async get(id: string) { return mapJob(await apiClient.get(`/api/exports/${encodeURIComponent(id)}`)) }, + async list() { const response = await apiClient.get<{ items: JobWire[] }>('/api/exports'); return response.items.map(mapJob) }, + cancel(id: string) { return apiClient.post(`/api/exports/${encodeURIComponent(id)}/cancel`) }, + async download(job: ExportJob) { + const response = await apiClient.get(`/api/exports/${encodeURIComponent(job.id)}/file`) + const url = URL.createObjectURL(await response.blob()) + const link = document.createElement('a'); link.href = url; link.download = job.fileName ?? 'export' + link.click(); setTimeout(() => URL.revokeObjectURL(url), 1000) + }, +} diff --git a/frontend/src/services/functionPlotService.spec.ts b/frontend/src/services/functionPlotService.spec.ts new file mode 100644 index 0000000..5c5b4f4 --- /dev/null +++ b/frontend/src/services/functionPlotService.spec.ts @@ -0,0 +1,22 @@ +// @vitest-environment jsdom +import { describe,it,expect,vi,beforeEach } from 'vitest' +vi.mock('./apiClient',()=>({apiClient:{post:vi.fn()}})) +import { apiClient } from './apiClient' +import { renderFunctionPlot } from './functionPlotService' +describe('function plot preview boundary',()=>{ + beforeEach(()=>vi.clearAllMocks()) + it('shares requests only for the same source and theme, sanitizes SVG',async()=>{ + vi.mocked(apiClient.post).mockResolvedValue({result:{content:'',warnings:[]},diagnostics:[],node_count:3}) + const a=await renderFunctionPlot('y = x + 100','dark') + await renderFunctionPlot('y = x + 100','dark') + await renderFunctionPlot('y = x + 100','light') + expect(apiClient.post).toHaveBeenCalledTimes(2) + expect(a.svg).not.toMatch(/onload|script|alert/) + }) + it('does not cache network failures',async()=>{ + vi.mocked(apiClient.post).mockRejectedValueOnce(new Error('offline')).mockResolvedValueOnce({result:null,diagnostics:[{message:'bad expression',line:2}],node_count:0}) + await expect(renderFunctionPlot('bad expression')).rejects.toThrow('offline') + const result=await renderFunctionPlot('bad expression') + expect(result.svg).toBe('');expect(result.warnings[0]).toContain('2') + }) +}) diff --git a/frontend/src/services/functionPlotService.ts b/frontend/src/services/functionPlotService.ts new file mode 100644 index 0000000..2cc8700 --- /dev/null +++ b/frontend/src/services/functionPlotService.ts @@ -0,0 +1,21 @@ +import { apiClient } from './apiClient' +import DOMPurify from 'dompurify' + +interface PlotWire { + result: { content: string; width: number; height: number; warnings: string[] } | null + diagnostics: { message: string; severity: string; line?: number }[] + node_count: number +} +const cache = new Map>() +export function renderFunctionPlot(source: string, themeId = 'light') { + const key = JSON.stringify([source, themeId]) + if (cache.has(key)) return cache.get(key)! + const result = apiClient.post('/api/plots/function', { source, theme_id: themeId }, { timeoutMs: 30000 }).then(wire => ({ + svg: DOMPurify.sanitize(wire.result?.content ?? '', { USE_PROFILES: { svg: true } }), + warnings: [...wire.diagnostics.map(d => `${d.message}${d.line ? ` (行 ${d.line})` : ''}`), ...(wire.result?.warnings ?? [])], + nodeCount: wire.node_count, + })).catch(error => { cache.delete(key); throw error }) + if (cache.size >= 32) cache.delete(cache.keys().next().value!) + cache.set(key, result) + return result +} diff --git a/frontend/src/services/mermaidService.ts b/frontend/src/services/mermaidService.ts index 81dcc32..fc65f57 100644 --- a/frontend/src/services/mermaidService.ts +++ b/frontend/src/services/mermaidService.ts @@ -8,8 +8,8 @@ function loadMermaid() { import { computed } from 'vue' import { useThemeStore } from '@/stores/theme' -export function mermaidThemeVariables(dark: boolean) { - const style = typeof document === 'undefined' ? null : getComputedStyle(document.documentElement) +export function mermaidThemeVariables(dark: boolean, useDocument = true) { + const style = !useDocument || typeof document === 'undefined' ? null : getComputedStyle(document.documentElement) const color = (name: string, fallback: string) => style?.getPropertyValue(`--color-${name}`).trim() || fallback const text = color('text-primary', dark ? '#e6edf3' : '#1f2328') const border = color('border-default', dark ? '#484f58' : '#d0d7de') @@ -29,15 +29,15 @@ export function mermaidThemeVariables(dark: boolean) { } } -async function ensureInitialized(theme: 'light' | 'dark') { +async function ensureInitialized(theme: 'light' | 'dark', raster = false) { const mermaid = await loadMermaid() mermaid.initialize({ startOnLoad: false, theme: 'base', - themeVariables: mermaidThemeVariables(theme === 'dark'), + themeVariables: mermaidThemeVariables(theme === 'dark', !raster), securityLevel: 'strict', - fontFamily: 'var(--font-ui-sans)', - flowchart: { useMaxWidth: true, htmlLabels: true }, + fontFamily: raster ? 'Arial, Microsoft YaHei, sans-serif' : 'var(--font-ui-sans)', + flowchart: { useMaxWidth: true, htmlLabels: !raster }, sequence: { useMaxWidth: true }, gantt: { useMaxWidth: true }, }) @@ -65,18 +65,18 @@ export interface MermaidParseError { let renderCounter = 0 -export function renderMermaid(source: string, options: { theme?: 'light' | 'dark'; mode?: 'interactive' | 'static' } = {}): Promise { +export function renderMermaid(source: string, options: { theme?: 'light' | 'dark'; mode?: 'interactive' | 'static' | 'raster' } = {}): Promise { return serialized(() => renderMermaidNow(source, options)) } async function renderMermaidNow( source: string, - options: { theme?: 'light' | 'dark'; mode?: 'interactive' | 'static' } = {} + options: { theme?: 'light' | 'dark'; mode?: 'interactive' | 'static' | 'raster' } = {} ): Promise { const theme = options.theme ?? 'light' const id = `mermaid-${Date.now()}-${++renderCounter}` try { - const mermaid = await ensureInitialized(theme) + const mermaid = await ensureInitialized(theme, options.mode === 'raster') const result = await mermaid.render(id, source) const parser = new DOMParser() const doc = parser.parseFromString(result.svg, 'image/svg+xml') diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css index 5dee836..25a94f6 100644 --- a/frontend/src/styles/tokens.css +++ b/frontend/src/styles/tokens.css @@ -375,3 +375,41 @@ ol { --color-border-focus: #8a5b32; --color-border-disabled: #eadfc4; } + +/* Function plot tokens inherit all installed themes, including custom packages. */ +:root { + --color-plot-background: var(--color-surface-primary); + --color-plot-text: var(--color-text-primary); + --color-plot-axis: var(--color-text-secondary); + --color-plot-grid: var(--color-border-default); + --color-plot-curve-0: #0969da; + --color-plot-curve-1: #d1242f; + --color-plot-curve-2: #1a7f37; + --color-plot-curve-3: #8250df; + --color-plot-curve-4: #9a6700; + --color-plot-curve-5: #bc4c00; +} +[data-theme='dark'], [data-theme='midnight-purple'] { + --color-plot-curve-0: #79c0ff; + --color-plot-curve-1: #ff9b9b; + --color-plot-curve-2: #7ee787; + --color-plot-curve-3: #d2a8ff; + --color-plot-curve-4: #f2cc60; + --color-plot-curve-5: #ffa657; +} +.function-plot-svg > rect:first-child { fill: var(--color-plot-background); } +.function-plot-svg .plot-grid { stroke: var(--color-plot-grid); } +.function-plot-svg .plot-axis { stroke: var(--color-plot-axis); } +.function-plot-svg text { fill: var(--color-plot-text); } +.function-plot-svg .plot-curve-0 { stroke: var(--color-plot-curve-0); } +.function-plot-svg .plot-legend-0 { fill: var(--color-plot-curve-0); } +.function-plot-svg .plot-curve-1 { stroke: var(--color-plot-curve-1); } +.function-plot-svg .plot-legend-1 { fill: var(--color-plot-curve-1); } +.function-plot-svg .plot-curve-2 { stroke: var(--color-plot-curve-2); } +.function-plot-svg .plot-legend-2 { fill: var(--color-plot-curve-2); } +.function-plot-svg .plot-curve-3 { stroke: var(--color-plot-curve-3); } +.function-plot-svg .plot-legend-3 { fill: var(--color-plot-curve-3); } +.function-plot-svg .plot-curve-4 { stroke: var(--color-plot-curve-4); } +.function-plot-svg .plot-legend-4 { fill: var(--color-plot-curve-4); } +.function-plot-svg .plot-curve-5 { stroke: var(--color-plot-curve-5); } +.function-plot-svg .plot-legend-5 { fill: var(--color-plot-curve-5); } diff --git a/frontend/src/utils/markdown.ts b/frontend/src/utils/markdown.ts index 64ca89b..fc6b11b 100644 --- a/frontend/src/utils/markdown.ts +++ b/frontend/src/utils/markdown.ts @@ -1,3 +1,4 @@ +import { renderFunctionPlot } from '@/services/functionPlotService' import DOMPurify from 'dompurify' import { Marked } from 'marked' import { defaultMarkdownPreferences, type MarkdownPreferences } from '@/stores/markdownPreferences' @@ -125,7 +126,7 @@ export async function getCodeTokenizer(theme: 'github-light' | 'github-dark', re } } -export async function renderMarkdown(source: string, options?: { theme?: 'light' | 'dark'; preferences?: MarkdownPreferences; citationNumbers?: number[]; citationAliases?: Record }): Promise { +export async function renderMarkdown(source: string, options?: { themeId?: string; theme?: 'light' | 'dark'; preferences?: MarkdownPreferences; citationNumbers?: number[]; citationAliases?: Record }): Promise { const preferences = options?.preferences ?? defaultMarkdownPreferences const marked = createMarkdownParser(preferences) const citations = new Set(options?.citationNumbers ?? []) @@ -141,12 +142,12 @@ export async function renderMarkdown(source: string, options?: { theme?: 'light' const html = marked.parse(source, { async: false }) as string const documentNode = new DOMParser().parseFromString(`${html}`, 'text/html') - const mermaidBlocks: { pre: Element; source: string }[] = [] + const mermaidBlocks: { pre: Element; source: string; kind: string }[] = [] for (const code of documentNode.querySelectorAll('pre > code')) { const requestedLanguage = [...code.classList].find((name) => name.startsWith('language-'))?.slice(9) || 'text' - if (requestedLanguage === 'mermaid' && preferences.diagrams) { - mermaidBlocks.push({ pre: code.parentElement!, source: code.textContent ?? '' }) + if (['mermaid', 'function-plot'].includes(requestedLanguage) && preferences.diagrams) { + mermaidBlocks.push({ pre: code.parentElement!, source: code.textContent ?? '', kind: requestedLanguage }) continue } if (requestedLanguage.toLowerCase() === 'latex' && preferences.math) { @@ -168,19 +169,23 @@ export async function renderMarkdown(source: string, options?: { theme?: 'light' code.parentElement?.replaceWith(wrapper) } - for (const { pre, source } of mermaidBlocks) { + let plotCount = 0, plotNodes = 0 + for (const { pre, source, kind } of mermaidBlocks) { try { - const result = await renderMermaid(source, { theme: options?.theme, mode: 'static' }) + if (kind === 'function-plot' && ++plotCount > 16) throw new Error('函数图像数量超过 16') + const result = kind === 'function-plot' ? await renderFunctionPlot(source, options?.themeId) : await renderMermaid(source, { theme: options?.theme, mode: 'static' }) + if ('nodeCount' in result && (plotNodes += result.nodeCount) > 8000) throw new Error('函数图像累计复杂度超过 8000') const container = document.createElement('div') - container.className = 'markdown-mermaid' + container.className = 'markdown-mermaid' + (kind === 'function-plot' ? ' markdown-function-plot' : '') container.innerHTML = result.svg - appendCodeToolbar(container, 'mermaid', source, true) - if (!result.warnings.length) appendDiagramControls(container) + appendCodeToolbar(container, kind, source, true) + if (result.warnings.length) { const message = document.createElement('p'); message.textContent = result.warnings.join('\n'); message.setAttribute('role', 'status'); container.append(message) } + if (!result.warnings.length || (kind === 'function-plot' && result.svg)) appendDiagramControls(container) pre.replaceWith(container) - } catch { + } catch (error) { const fallback = document.createElement('pre') fallback.className = 'mermaid-error' - fallback.textContent = source + fallback.textContent = `${error instanceof Error ? error.message : '图表渲染失败'}\n${source}` pre.replaceWith(fallback) } } diff --git a/frontend/tests/visual/mermaid-matrix.html b/frontend/tests/visual/mermaid-matrix.html index ddfd2f6..a55977c 100644 --- a/frontend/tests/visual/mermaid-matrix.html +++ b/frontend/tests/visual/mermaid-matrix.html @@ -15,6 +15,19 @@ const fixtures = { }; const allThemes = [{theme_id:'light',is_dark:false},{theme_id:'dark',is_dark:true},{theme_id:'sepia',is_dark:false},...mockCommunityThemes]; const requested = new URLSearchParams(location.search).get('theme'); +if (!requested) { + const results=[]; + for (const theme of allThemes) { + const frame=document.createElement('iframe'); + frame.title=theme.theme_id; frame.style.cssText='width:100%;height:900px;border:0'; + frame.src='?theme='+encodeURIComponent(theme.theme_id); document.querySelector('#results').append(frame); + await new Promise(resolve=>frame.onload=resolve); + while(frame.contentDocument.documentElement.dataset.complete!=='true') await new Promise(resolve=>setTimeout(resolve,100)); + results.push(...JSON.parse(frame.contentDocument.querySelector('#status').textContent).results); + } + document.querySelector('#status').textContent=JSON.stringify({passed:results.filter(r=>r.passed).length,total:results.length,results}); + document.documentElement.dataset.complete='true'; +} else { const themes = requested ? allThemes.filter(t=>t.theme_id===requested) : allThemes; const style = document.createElement('style'); document.head.append(style); const results = []; @@ -35,4 +48,5 @@ for (const theme of themes) { } document.querySelector('#status').textContent=JSON.stringify({passed:results.filter(r=>r.passed).length,total:results.length,results}); document.documentElement.dataset.complete='true'; +} diff --git a/frontend/tests/visual/phase2.html b/frontend/tests/visual/phase2.html new file mode 100644 index 0000000..182359e --- /dev/null +++ b/frontend/tests/visual/phase2.html @@ -0,0 +1,20 @@ +Phase 2 interaction acceptance
+ diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 0c13b75..8cbae8b 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -17,6 +17,7 @@ function mathChunk(module: string) { export default defineConfig({ plugins: [vue()], resolve: { + dedupe: ['katex'], alias: { '@': path.resolve(__dirname, 'src'), }, @@ -42,8 +43,8 @@ export default defineConfig({ host: '127.0.0.1', port: 5173, proxy: { - '/api': 'http://127.0.0.1:8000', - '/health': 'http://127.0.0.1:8000', + '/api': process.env.NOTES_API_TARGET ?? 'http://127.0.0.1:8000', + '/health': process.env.NOTES_API_TARGET ?? 'http://127.0.0.1:8000', }, }, }) -- 2.43.0 From e667dd55ddf285454bd1c646c8845254766e8fa2 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Mon, 7 Sep 2026 02:55:06 +0800 Subject: [PATCH 02/10] Record phase two acceptance evidence and synchronize delivery status --- README.md | 2 + backend/README.md | 2 + docs/README.md | 2 + docs/architecture/第三阶段实施规划.md | 6 +- docs/architecture/第二阶段团队分工表.md | 47 +- docs/contracts/第二阶段接口契约-开发版.md | 54 +- docs/development/Benchmark开发说明.md | 13 +- .../phase2-20260907/agent-task-after.json | 234 + .../phase2-20260907/agent-task-before.json | 230 + .../phase2-20260907/agent-ui-report.json | 142 + .../evidence/phase2-20260907/artifacts.json | 46 + .../phase2-20260907/backend-tests.txt | 19 + .../browser-mermaid-matrix.json | 222 + .../phase2-20260907/browser-results.json | 32 + .../evidence/phase2-20260907/build.txt | 560 + .../phase2-20260907/context-budget256.json | 25 + .../evidence/phase2-20260907/context.json | 25 + .../phase2-20260907/frontend-tests.txt | 27 + .../evidence/phase2-20260907/integration.json | 107 + .../evidence/phase2-20260907/live.json | 196 + .../evidence/phase2-20260907/rag-quality.json | 11750 ++++++++++++++++ .../第二阶段全量验收报告-2026-09-07.md | 106 + .../第二阶段收尾实现与验收-2026-09-07.md | 87 + frontend/README.md | 3 + 24 files changed, 13892 insertions(+), 45 deletions(-) create mode 100644 docs/development/evidence/phase2-20260907/agent-task-after.json create mode 100644 docs/development/evidence/phase2-20260907/agent-task-before.json create mode 100644 docs/development/evidence/phase2-20260907/agent-ui-report.json create mode 100644 docs/development/evidence/phase2-20260907/artifacts.json create mode 100644 docs/development/evidence/phase2-20260907/backend-tests.txt create mode 100644 docs/development/evidence/phase2-20260907/browser-mermaid-matrix.json create mode 100644 docs/development/evidence/phase2-20260907/browser-results.json create mode 100644 docs/development/evidence/phase2-20260907/build.txt create mode 100644 docs/development/evidence/phase2-20260907/context-budget256.json create mode 100644 docs/development/evidence/phase2-20260907/context.json create mode 100644 docs/development/evidence/phase2-20260907/frontend-tests.txt create mode 100644 docs/development/evidence/phase2-20260907/integration.json create mode 100644 docs/development/evidence/phase2-20260907/live.json create mode 100644 docs/development/evidence/phase2-20260907/rag-quality.json create mode 100644 docs/development/第二阶段全量验收报告-2026-09-07.md create mode 100644 docs/development/第二阶段收尾实现与验收-2026-09-07.md diff --git a/README.md b/README.md index bc869ae..8dab76f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Notes Agent(暂命名) 团队开发说明 +> 第二阶段收尾(开发分支,2026-09-07):标准 Agent/RAG Benchmark 与报告页、函数图预览、三格式快照导出及真实 Provider/MCP 结果见[实现与验收记录](docs/development/第二阶段收尾实现与验收-2026-09-07.md)。当前分支尚未合并,不更改下文历史 main 基线。 + > 本文件用于团队开发期间快速配置环境、启动项目并了解当前实现状态,不是正式的项目 README。 NotesAgent 是本地优先的 AI 笔记与知识库项目。当前可运行形态为 Vue/Vite Web 前端与 FastAPI AI Core:Markdown 和附件保存在本地 Vault,SQLite 管理元数据、全文索引、向量空间、搜索历史、AI 会话、任务、Agent Trace、多模态任务及运行诊断。AI 对话已接入知识库检索,会话与消息由后端持久化并供 Web 和桌面客户端共用。 diff --git a/backend/README.md b/backend/README.md index df4bbf3..91a3e26 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,5 +1,7 @@ # NotesAgent Backend +> 第二阶段收尾:标准 Agent/RAG Benchmark 与报告页、函数图预览、三格式快照导出及真实 Provider/MCP 结果见[实现与验收记录](../docs/development/第二阶段收尾实现与验收-2026-09-07.md)。当前分支尚未合并,不更改下文历史 main 基线。 + NotesAgent Backend 是基于 Python 3.11+、FastAPI、Pydantic v2 和 SQLite 的本地 AI Core / Agent Core,使用 uv 管理 API 依赖和虚拟环境。 当前实现包含 Knowledge/Retrieval、Chat、Agent、Tool/Permission、Skill/Plugin、MCP、模型提供商、RAG Benchmark、多模态任务、本地模型调度、Token/音频用量和运行诊断。数据持久化位于后端 SQLite 与 Vault;Tauri Sidecar 生命周期、Stronghold 和操作系统级 Plugin 沙箱属于后续桌面阶段。 diff --git a/docs/README.md b/docs/README.md index a4c992e..a4ff94d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,7 @@ # NotesAgent 文档索引 +> 第二阶段收尾:标准 Agent/RAG Benchmark 与报告页、函数图预览、三格式快照导出及真实 Provider/MCP 结果见[实现与验收记录](development/第二阶段收尾实现与验收-2026-09-07.md)。当前分支尚未合并,不更改下文历史 main 基线。 + 本目录集中保存团队开发期间需要长期维护的架构、接口、实现、协作和问题复盘文档。文档按用途分类,避免设计约束、开发记录与故障复盘混放。 当前文档基线为 2026-09-06:第一阶段和第二阶段 A~F 工程范围已经合并到 `main`,当前可运行形态仍为 Vue/Vite Web 前端与 FastAPI AI Core。Tauri/Rust Host、Stronghold、原生多 Vault 文件系统、生产级 MCP 沙箱和 Sync Server 尚未接入。 diff --git a/docs/architecture/第三阶段实施规划.md b/docs/architecture/第三阶段实施规划.md index 40846ee..0e24f9b 100644 --- a/docs/architecture/第三阶段实施规划.md +++ b/docs/architecture/第三阶段实施规划.md @@ -25,9 +25,9 @@ | 同步 | 技术栈中已有目标设计;`server sync/` 当前无实现文件 | 协议、独立服务、客户端队列、冲突、设备身份、部署运维 | | 原生桌面 | 已有需求文档 | Tauri 工程和 Rust Host 均需建设,不能将 Web 页面当作桌面交付 | -本次社区准备包验证为 70 项相关后端测试通过,并在本地 API 上完成真实 ZIP 导入、启用和命令执行;它不是所有第三阶段功能的验收。开发服务器监听新解压 `.py` 会热重载,当前内存安装记录随之丢失;持久化与开发监听排除规则列为首批问题。 +本次社区准备包验证为 70 项相关后端测试通过,并在本地 API 上完成真实 ZIP 导入、启用和命令执行;它不是所有第三阶段功能的验收。早期开发监听曾使内存安装记录丢失;第二阶段已补齐持久安装库、摘要复核及重启恢复,不再将其列作未实现。第三阶段继续负责升级事务和桌面生命周期。 -第二阶段待验收事项单独保留:目标 Provider 真实账号兼容性、声纹阈值校准、带标注音频质量、逐字对齐和重叠语音。已有约 37 分 16 秒录音的 CUDA 功能闭环,无参考标注,不能报告 WER/CER、DER 达标。杨星萱负责的检索调优、Benchmark、导出和函数图像须由对应负责人确认状态,不因本规划自动判为完成或重新归责。 +第二阶段待验收事项单独保留:目标 Provider 真实账号兼容性、声纹阈值校准、带标注音频质量、逐字对齐和重叠语音。已有约 37 分 16 秒录音的 CUDA 功能闭环,无参考标注,不能报告 WER/CER、DER 达标。检索调优、Benchmark、导出和函数图像的当前交接结果见[第二阶段收尾验收](../development/第二阶段收尾实现与验收-2026-09-07.md),不因本规划扩大为第三阶段任务。 ## 3. 架构、写入所有权与目录 @@ -212,7 +212,7 @@ base_revision 不匹配返回 409 类冲突与当前 Revision;界面展示本 | --- | --- | | Web 单 Vault → 桌面多 Vault | 识别旧目录,备份元数据,保持 note_id/file_id 对应关系,校验文件摘要和数量;索引可重建,正文不能覆盖 | | Fernet → Stronghold | 按 credential_id 迁移、验证、记录版本,失败重试;迁移完成前保留旧存储,不向 UI 返回明文 | -| 内存扩展记录 → 持久化安装库 | 探测用户认可的受管理包、重新校验、不自动继承高权限;重启恢复与包损坏修复必须实测 | +| 既有持久化安装库 → 桌面受管理安装库 | 探测用户认可的受管理包、重新校验、不自动继承高权限;重启恢复与包损坏修复必须实测 | | 旧主题/Skill/Plugin → 社区版本 | ID/来源/版本/摘要关联,未知来源标本地;配置迁移保留备份,用户修改包不能静默覆盖 | | 首次绑定同步 | 本地/远端清单对账、显示新增和冲突,不以空 Vault 向另一端下发批量删除;绑定信息可撤销 | | 升级与降级 | Schema 版本门禁,升级前备份;不支持降级的数据库禁止旧客户端写入,提供恢复路径 | diff --git a/docs/architecture/第二阶段团队分工表.md b/docs/architecture/第二阶段团队分工表.md index f85bd78..e5feb72 100644 --- a/docs/architecture/第二阶段团队分工表.md +++ b/docs/architecture/第二阶段团队分工表.md @@ -1,5 +1,10 @@ # 第二阶段团队分工表 +## 2026-09-07 全范围收尾状态 + +本轮覆盖三位成员的第二阶段模块(音频质量专项除外),以[实现与验收记录](../development/第二阶段收尾实现与验收-2026-09-07.md)及下方更新后的DoD为当前口径。此前“不含杨侧验收”仅描述当日范围。现有DeepSeek/MiniMax真实链路通过,其他未配置协议保留外部验收项;音频使用Qwen3-ASR/ERes2NetV2技术路径,不声称交付原清单指定模型。 + + ## 2026-09-06 复核修复补充(不含杨侧验收) - Agent 增加断点续读、有界重连和手动恢复;连接中断不再隐藏仍在运行任务的取消入口。 @@ -776,33 +781,33 @@ Markdown - [x] Plugin Settings Contribution 后端可解析; - [x] Plugin Command 可以显示并从前端执行; - [x] Plugin Settings 可以动态生成设置项并独立提交 Secret; -- [ ] Provider Adapter 的 Streaming / Tool Calling / Error Mapping 稳定; -- [ ] 完成跨模块接口审阅和第二阶段集成。 +- [ ] 全部目标Provider真实矩阵;现有DeepSeek Streaming/Tool Calling/Error Mapping已通过,其他协议缺配置; +- [x] 完成非音频范围跨模块接口审阅和第二阶段集成;外部协议/DOCX整页视觉边界见验收记录。 ### 杨星萱 -- [ ] RAG Benchmark Dataset 可以稳定运行; -- [ ] 能输出 Hit@K、Recall@K、MRR、Latency 等指标; -- [ ] Agent Benchmark Framework 可以执行标准 Case; -- [ ] Markdown 可以导出 HTML; -- [ ] Markdown 可以导出 PDF; -- [ ] Markdown 可以导出 DOCX; -- [ ] Mermaid 在导出链路中可以保留为静态图; -- [ ] 函数图像能够由结构化表达生成; -- [ ] 函数图像能够进入预览和导出链路; -- [ ] Retrieval 调优结果有 Benchmark 数据支撑。 +- [x] RAG Benchmark Dataset 可以稳定运行; +- [x] 能输出 Hit@K、Recall@K、MRR、Latency 等指标; +- [x] Agent Benchmark Framework 可以执行标准 Case; +- [x] Markdown 可以导出 HTML; +- [x] Markdown 可以导出 PDF; +- [x] Markdown 可以导出 DOCX; +- [x] Mermaid 在导出链路中可以保留为静态图; +- [x] 函数图像能够由结构化表达生成; +- [x] 函数图像能够进入预览和导出链路; +- [x] Retrieval 调优结果有 Benchmark 数据支撑。 ### 吉海燕 -- [ ] Theme Package 可以导入; -- [ ] Theme Manifest 可以校验; -- [ ] Theme 可以启用、停用和卸载; -- [ ] Agent Trace 可以展示完整 Tool Call 顺序; -- [ ] Trace Node 可以查看参数、结果、耗时和错误; -- [ ] Markdown Mermaid Code Block 可以渲染; -- [ ] Mermaid 支持主题切换; -- [ ] Mermaid 渲染错误可以明确展示; -- [ ] Mermaid 图能够提供给 Export Service。 +- [x] Theme Package 可以导入; +- [x] Theme Manifest 可以校验; +- [x] Theme 可以启用、停用和卸载; +- [x] Agent Trace 可以展示完整 Tool Call 顺序; +- [x] Trace Node 可以查看参数、结果、耗时和错误; +- [x] Markdown Mermaid Code Block 可以渲染; +- [x] Mermaid 支持主题切换; +- [x] Mermaid 渲染错误可以明确展示; +- [x] Mermaid 图能够提供给 Export Service。 --- diff --git a/docs/contracts/第二阶段接口契约-开发版.md b/docs/contracts/第二阶段接口契约-开发版.md index 3ba5f5c..5fb4ef8 100644 --- a/docs/contracts/第二阶段接口契约-开发版.md +++ b/docs/contracts/第二阶段接口契约-开发版.md @@ -878,18 +878,19 @@ Dataset 从仓库或受控导入目录注册。API 不接受调用方提交任 ```json { - "case_id": "agent-os-review-001", - "prompt": "查找死锁内容并创建三个复习任务", - "allowed_tools": ["rag.search", "notes.read", "tasks.create"], - "expected_tools": ["rag.search", "notes.read", "tasks.create"], - "expected_conditions": { - "citation_required": true, - "tasks_created": 3 - }, - "tags": ["os", "write"] + "case_id": "agent-add-001", + "prompt": "调用 math.add 计算17加25,然后回答42。", + "allowed_tools": ["math.add"], + "expected_tools": [{"name": "math.add", "arguments": {"left": 17, "right": 25}}], + "output_contains": ["42"], + "citation_required": false, + "tasks_created": null, + "tags": ["math"] } ``` +每个Dataset最多100例且case_id唯一;Case至少声明工具、输出子串、引用或任务数量之一作为客观断言。预期工具必须属于allowed_tools。参数按声明键的值匹配,重复/额外调用计入错误,任务创建按成功Tool Result计数。 + 有写操作的 Agent Case 必须运行在隔离 Vault/数据库中,测试结束后清理 Fixture,禁止作用于用户真实知识库。 ### 9.4 创建 RAG Benchmark @@ -902,6 +903,7 @@ Dataset 从仓库或受控导入目录注册。API 不接受调用方提交任 "modes": ["fts", "vector", "hybrid"], "retrieval": { "top_k": 10, + "fusion": "rrf", "rrf_k": 60, "rerank": true, "rerank_candidates": 20, @@ -914,23 +916,28 @@ Dataset 从仓库或受控导入目录注册。API 不接受调用方提交任 配置快照必须记录 Embedding model ID/version/dimension、Reranker、索引版本、Dataset Hash 和运行环境。 -### 9.5 创建 Agent Benchmark(暂缓,未暴露接口) +### 9.5 创建 Agent Benchmark(已实现) `POST /api/benchmarks/agent/runs` ```json { "dataset_id": "agent-core-v1", - "provider_id": "mock", - "model": "mock-1", - "skill_id": null, - "max_steps": 10, - "token_budget": 20000, - "concurrency": 1, - "metadata": {} + "provider_id": "已有启用的Provider ID", + "model": "已有模型", + "max_steps": 6, + "timeout_seconds": 90, + "token_budget": 6000, + "repeat": 1, + "allow_network": false, + "offline": false } ``` +`max_steps` 1–20、`timeout_seconds` 1–300、`token_budget` 1–30000、`repeat` 1–3。顺序执行以限制额度;默认拒绝mock。仅显式offline=true可使用mock,且offline不能选真实Provider。创建返回202;无Provider为404,模式冲突为422,容量超限为429。权限由用户在真实Trace中处理,不自动批准。共用运行查询/SSE/取消/报告端点。 + +报告包含逐例agent_run_id、success/checks、调用/匹配/无效数量、steps、latency_ms、token_usage。汇总task_success_rate以计划案例总数为分母,total_cases/evaluated_cases区分未完成样本;选择/参数准确率以max(实际调用总数,预期调用总数)为分母,无调用时为null。无效调用率以实际调用数为分母。平均步骤/耗时采用计划数分母,取消/失败报告不可当完整性能测量。配置冻结dataset hash/version、Provider类型/引用、模型、预算、评分版本与权限策略。 + Benchmark Runner 通过正式 Agent Runtime 创建 Run,并从 Trace 计算结果,不能直接调用 Tool Executor 绕过权限和步骤控制。 ### 9.6 Benchmark Run @@ -1080,7 +1087,7 @@ VECTOR_INDEX_REBUILD_REQUIRED ## 10. Export Service -> 实现状态:HTML / PDF / DOCX 导出均已实现(`backend/app/export/`),`format` 支持 `html`/`pdf`/`docx` 三格式。`function-plot` 已支持静态 SVG 内嵌(HTML)与矢量图内嵌(PDF,经 `backend/app/plot/render_reportlab.py` 复用共享几何),解析或渲染失败时回退为源码占位并记录 warning;Mermaid 目前仍以占位代码块保留并记 warning。DOCX 为文本优先 v1,`function-plot` 与 Mermaid 保留源码占位并记 warning。 +> 当前实现:三格式已有编辑器快照入口、任务/取消/下载/warning。函数图为HTML SVG、PDF共享几何矢量、DOCX PNG;Mermaid通过前端准备并按源码摘要绑定PNG后进入三格式。未提供静态资源的直接API Mermaid请求仍保留源码并warning,不能假称服务器独立运行Mermaid。受支持MathText公式及Vault图片内嵌,完整限制见本节末补充。 ### 10.1 创建导出任务 @@ -1616,3 +1623,14 @@ CUDA 组件:`GET /api/local-models/runtime-components/cuda` 返回 status、st #### 回答版本的上下文快照(2026-09-07) `ChatMessage.context_captured` 为布尔值,旧记录默认 false。新 assistant 消息保存本次请求的 `workspace_context` 和 `attachments`,并设置 context_captured 为 true;此时 null 文件上下文和空附件列表都是明确快照。重新生成不覆盖原 user 消息的快照。客户端恢复旧记录时仅在 context_captured 为 false 时回退到对应父用户消息。 + + +## 2026-09-07 预览与静态资源增量契约 + +`POST /api/plots/function`:请求`{source, theme_id}`,source最多20000字符;响应`{result: {content,mime_type,width,height,warnings} | null, diagnostics: [{severity,code,message,line}], node_count}`。语法错误为200诊断、请求字段违规422。共享plot白名单解释器,不执行eval;每块16表达式、8000累计节点、并发2。主题映射当前六个Theme ID并提供CSS图表Token;未知主题回退light。 + +`ExportRequest`新增可选title(最多200字符)、assets(最多64);`source.file_path`为未保存快照中相对图片的基准位置,不能用于任意文件读。每个asset为`{kind: mermaid|math_block|math_inline|image, source_hash: 64位sha256十六进制, png_base64}`;摘要为strip后UTF-8源码(image为src)的SHA256。只接受有效PNG并重编码,每图4百万像素、总16百万像素/8MiB;重复kind/hash或超限返回422 EXPORT_ASSET_INVALID。摘要失配不替换当前节点,不接受客户端SVG/XML/URL执行。 + +未带资源的公式由受限MathText转换(512字符/20层/64资源),仅解析Vault范围内PNG/JPEG/WebP(单文件2MB),拒绝远端与越界路径。资源失败保留源码/替代文字和warning。PDF/DOCX采用浅色打印样式;HTML保留有限主题调色板,不复刻任意主题CSS。DOCX图片为静态内容,不提供可编辑公式对象。 + +RAG `retrieval.fusion`接受rrf(默认)或weighted(归一化FTS/vector各50%),参数写入config_snapshot。真实本地单查询Embedding可命中有界进程缓存,provenance.query_embedding_cache为hit/miss;比较延迟须分别报告冷暖样本。HashEmbedding仍仅为确定性单元测试,不是当前生产检索模型。 diff --git a/docs/development/Benchmark开发说明.md b/docs/development/Benchmark开发说明.md index 628b5bf..ec1231f 100644 --- a/docs/development/Benchmark开发说明.md +++ b/docs/development/Benchmark开发说明.md @@ -1,6 +1,6 @@ # Benchmark 开发说明 -> 所属模块:Knowledge / Retrieval Core(后端,负责人 yxx)。RAG Benchmark 已交付;Agent Benchmark 暂缓,待 Agent Runtime 完成后在同一契约下补齐。 +> 所属模块:Knowledge / Retrieval Core(后端,负责人 yxx)。RAG 与标准Agent Benchmark均已实现,前端入口`/benchmarks`。真实验收结果与范围见[第二阶段收尾记录](第二阶段收尾实现与验收-2026-09-07.md)。 ## 定位 @@ -18,7 +18,7 @@ Benchmark Service 用受控 Dataset 对检索引擎做可复现评测:创建 | POST | `/api/benchmarks/runs/{run_id}/cancel` | 取消运行 | | GET | `/api/benchmarks/runs/{run_id}/report` | 结构化完整报告 | -Agent Benchmark 的 `/api/benchmarks/agent/runs` 未暴露(暂缓),不在 OpenAPI 注册占位接口。 +Agent Benchmark的`POST /api/benchmarks/agent/runs`已实现并注册OpenAPI,使用正式Agent Runtime与Trace;请求/Case见[契约§9](../contracts/第二阶段接口契约-开发版.md)。默认只接受真实Provider,显式offline才允许mock,不自动批准工具权限。 ## Dataset @@ -30,7 +30,7 @@ RAG Case 结构:`case_id`、`query`、`expected_note_ids`、`expected_block_id `queued → running → completed | failed | cancelled`。 -- 创建时校验索引兼容性:索引非空、Embedding model/dim 与当前引擎一致、vector/hybrid 时向量索引非空;不满足返回 `BENCHMARK_INDEX_INCOMPATIBLE`(409),避免把环境/索引错误误判为检索质量差。 +- RAG创建时校验索引兼容性:索引非空、Embedding model/dim 与当前引擎一致、vector/hybrid 时向量索引非空;不满足返回 `BENCHMARK_INDEX_INCOMPATIBLE`(409),避免把环境/索引错误误判为检索质量差。 - 内存注册表上限 `MAX_RUNS=100`,超限只淘汰终态 run;满容量且全为活动 run 时返回 `BENCHMARK_CAPACITY_EXCEEDED`(429)。 - 失败/取消只向公开响应暴露项目错误码与安全消息,详细异常进入日志,不通过 HTTP/SSE 返回。 @@ -76,3 +76,10 @@ uv run pytest -q ``` `tests/test_benchmark.py` 覆盖数据集注册与校验、指标纯函数、端到端运行、取消、索引兼容、容量与失败样本聚合;`tests/test_retrieval.py` 覆盖 FTS 阈值与分页 total 一致性。 + + +## 2026-09-07 数据与复现补充 + +`agent-core-v1.json`为四例标准任务;`rag-phase2-v1.json`对应独立测试语料,不对应任意用户Vault,运行前必须用`phase2-quality.py`准备语料及真实索引。语料源、配置、逐例结果和冷暖缓存说明见收尾验收记录。UI支持RAG/Agent切换、Provider/模型、融合/TopK/RRF/rerank配置、取消、报告JSON与Trace入口。 + +Agent成功判定包含终态完成、预期工具及声明参数一一匹配、无额外调用、工具结果成功、输出子串、引用和任务数量;并非由另一个LLM主观打分。失败/取消报告保留total_cases与evaluated_cases,不能当成完成的质量测量。报告注册表最多100条且不跨进程恢复,重要结果须下载保存;持久Agent Trace独立保留。工具选择/参数准确率和无效调用率在无分母时返回null。 diff --git a/docs/development/evidence/phase2-20260907/agent-task-after.json b/docs/development/evidence/phase2-20260907/agent-task-after.json new file mode 100644 index 0000000..4e58d65 --- /dev/null +++ b/docs/development/evidence/phase2-20260907/agent-task-after.json @@ -0,0 +1,234 @@ +{ + "python": "3.13.9", + "platform": "Windows-11-10.0.26100-SP0", + "provider": "mock with 50 ms injected delay per model turn; no network", + "results": [ + { + "scenario": "agent_tool_runs", + "concurrency": 1, + "runs": 20, + "latency": { + "count": 20, + "median_ms": 168.93, + "p95_ms": 172.17, + "max_ms": 173.06 + }, + "completed": 20, + "ordered_events_and_replay": true, + "terminal_recovery": true, + "recovery_read_ms": 35.15, + "retained_records": 20, + "elapsed_ms": 3446.25, + "event_loop_lag": { + "count": 223, + "median_ms": 5.42, + "p95_ms": 12.94, + "max_ms": 15.02 + } + }, + { + "scenario": "agent_tool_runs", + "concurrency": 10, + "runs": 20, + "latency": { + "count": 20, + "median_ms": 203.54, + "p95_ms": 205.86, + "max_ms": 206.36 + }, + "completed": 20, + "ordered_events_and_replay": true, + "terminal_recovery": true, + "recovery_read_ms": 31.0, + "retained_records": 40, + "elapsed_ms": 479.2, + "event_loop_lag": { + "count": 31, + "median_ms": 5.12, + "p95_ms": 13.95, + "max_ms": 14.6 + } + }, + { + "scenario": "agent_tool_runs", + "concurrency": 50, + "runs": 50, + "latency": { + "count": 50, + "median_ms": 396.17, + "p95_ms": 397.13, + "max_ms": 397.52 + }, + "completed": 50, + "ordered_events_and_replay": true, + "terminal_recovery": true, + "recovery_read_ms": 86.99, + "retained_records": 90, + "elapsed_ms": 554.8, + "event_loop_lag": { + "count": 35, + "median_ms": 5.12, + "p95_ms": 11.71, + "max_ms": 46.86 + } + }, + { + "scenario": "agent_tool_runs", + "concurrency": 200, + "runs": 200, + "latency": { + "count": 200, + "median_ms": 1004.47, + "p95_ms": 1155.52, + "max_ms": 1155.62 + }, + "completed": 200, + "ordered_events_and_replay": true, + "terminal_recovery": true, + "recovery_read_ms": 313.29, + "retained_records": 200, + "elapsed_ms": 1550.94, + "event_loop_lag": { + "count": 113, + "median_ms": 3.39, + "p95_ms": 10.13, + "max_ms": 12.19 + } + }, + { + "scenario": "capacity_and_cancel", + "active_limit": 200, + "overflow_rejected": true, + "cancelled": 200, + "cancel_latency": { + "count": 200, + "median_ms": 4.26, + "p95_ms": 5.85, + "max_ms": 12.49 + }, + "elapsed_ms": 1977.57, + "event_loop_lag": { + "count": 167, + "median_ms": 1.68, + "p95_ms": 4.24, + "max_ms": 6.54 + } + }, + { + "scenario": "permission_wait", + "runs": 20, + "approved_completed": 10, + "cancelled_waiting_permission": 10, + "subscribers_released": true, + "elapsed_ms": 294.49, + "event_loop_lag": { + "count": 20, + "median_ms": 4.08, + "p95_ms": 6.57, + "max_ms": 9.92 + } + }, + { + "scenario": "failure_and_timeout_isolation", + "runs": 20, + "success": 5, + "provider_errors": 5, + "model_timeouts": 5, + "tool_timeouts": 5, + "remaining_tool_executors": 0, + "elapsed_ms": 1220.51, + "event_loop_lag": { + "count": 80, + "median_ms": 5.38, + "p95_ms": 6.33, + "max_ms": 11.29 + } + }, + { + "scenario": "task_api_crud", + "tasks": 100, + "client_concurrency": 20, + "latencies": { + "create": { + "count": 100, + "median_ms": 103.05, + "p95_ms": 143.89, + "max_ms": 167.59 + }, + "update": { + "count": 100, + "median_ms": 124.21, + "p95_ms": 139.58, + "max_ms": 146.49 + }, + "list": { + "count": 1, + "median_ms": 3.33, + "p95_ms": 3.33, + "max_ms": 3.33 + }, + "delete": { + "count": 100, + "median_ms": 118.6, + "p95_ms": 134.84, + "max_ms": 147.0 + } + }, + "default_page_count": 50, + "default_total": 100, + "pagination_complete": true, + "final_total": 0, + "elapsed_ms": 1841.45, + "event_loop_lag": { + "count": 146, + "median_ms": 1.87, + "p95_ms": 5.05, + "max_ms": 65.53 + } + }, + { + "scenario": "task_api_crud", + "tasks": 1000, + "client_concurrency": 20, + "latencies": { + "create": { + "count": 1000, + "median_ms": 109.16, + "p95_ms": 121.67, + "max_ms": 133.03 + }, + "update": { + "count": 1000, + "median_ms": 125.42, + "p95_ms": 146.2, + "max_ms": 165.48 + }, + "list": { + "count": 10, + "median_ms": 5.12, + "p95_ms": 7.31, + "max_ms": 7.31 + }, + "delete": { + "count": 1000, + "median_ms": 122.71, + "p95_ms": 145.87, + "max_ms": 171.81 + } + }, + "default_page_count": 50, + "default_total": 1000, + "pagination_complete": true, + "final_total": 0, + "elapsed_ms": 18067.11, + "event_loop_lag": { + "count": 1540, + "median_ms": 1.33, + "p95_ms": 4.46, + "max_ms": 31.93 + } + } + ], + "database_bytes": 2932736, + "complete": true +} \ No newline at end of file diff --git a/docs/development/evidence/phase2-20260907/agent-task-before.json b/docs/development/evidence/phase2-20260907/agent-task-before.json new file mode 100644 index 0000000..fe1c82f --- /dev/null +++ b/docs/development/evidence/phase2-20260907/agent-task-before.json @@ -0,0 +1,230 @@ +{ + "python": "3.13.9", + "platform": "Windows-11-10.0.26100-SP0", + "provider": "mock with 50 ms injected delay per model turn; no network", + "results": [ + { + "scenario": "agent_tool_runs", + "concurrency": 1, + "runs": 20, + "latency": { + "count": 20, + "median_ms": 167.71, + "p95_ms": 181.07, + "max_ms": 203.5 + }, + "completed": 20, + "ordered_events_and_replay": true, + "terminal_recovery": true, + "retained_records": 20, + "elapsed_ms": 3464.02, + "event_loop_lag": { + "count": 224, + "median_ms": 5.41, + "p95_ms": 13.45, + "max_ms": 33.56 + } + }, + { + "scenario": "agent_tool_runs", + "concurrency": 10, + "runs": 20, + "latency": { + "count": 20, + "median_ms": 219.61, + "p95_ms": 224.28, + "max_ms": 224.39 + }, + "completed": 20, + "ordered_events_and_replay": true, + "terminal_recovery": true, + "retained_records": 40, + "elapsed_ms": 500.46, + "event_loop_lag": { + "count": 33, + "median_ms": 4.35, + "p95_ms": 13.1, + "max_ms": 24.21 + } + }, + { + "scenario": "agent_tool_runs", + "concurrency": 50, + "runs": 50, + "latency": { + "count": 50, + "median_ms": 356.51, + "p95_ms": 358.17, + "max_ms": 358.28 + }, + "completed": 50, + "ordered_events_and_replay": true, + "terminal_recovery": true, + "retained_records": 90, + "elapsed_ms": 495.22, + "event_loop_lag": { + "count": 27, + "median_ms": 2.66, + "p95_ms": 35.07, + "max_ms": 73.78 + } + }, + { + "scenario": "agent_tool_runs", + "concurrency": 200, + "runs": 200, + "latency": { + "count": 200, + "median_ms": 944.9, + "p95_ms": 1007.22, + "max_ms": 1007.96 + }, + "completed": 200, + "ordered_events_and_replay": true, + "terminal_recovery": true, + "retained_records": 200, + "elapsed_ms": 1412.6, + "event_loop_lag": { + "count": 77, + "median_ms": 3.46, + "p95_ms": 10.98, + "max_ms": 326.14 + } + }, + { + "scenario": "capacity_and_cancel", + "active_limit": 200, + "overflow_rejected": true, + "cancelled": 200, + "cancel_latency": { + "count": 200, + "median_ms": 4.47, + "p95_ms": 6.37, + "max_ms": 14.65 + }, + "elapsed_ms": 2086.28, + "event_loop_lag": { + "count": 174, + "median_ms": 1.48, + "p95_ms": 4.69, + "max_ms": 14.19 + } + }, + { + "scenario": "permission_wait", + "runs": 20, + "approved_completed": 10, + "cancelled_waiting_permission": 10, + "subscribers_released": true, + "elapsed_ms": 307.52, + "event_loop_lag": { + "count": 20, + "median_ms": 5.28, + "p95_ms": 12.2, + "max_ms": 15.35 + } + }, + { + "scenario": "failure_and_timeout_isolation", + "runs": 20, + "success": 5, + "provider_errors": 5, + "model_timeouts": 5, + "tool_timeouts": 5, + "remaining_tool_executors": 0, + "elapsed_ms": 1236.24, + "event_loop_lag": { + "count": 80, + "median_ms": 5.44, + "p95_ms": 6.58, + "max_ms": 15.75 + } + }, + { + "scenario": "task_api_crud", + "tasks": 100, + "client_concurrency": 20, + "latencies": { + "create": { + "count": 100, + "median_ms": 107.62, + "p95_ms": 146.4, + "max_ms": 177.83 + }, + "update": { + "count": 100, + "median_ms": 119.9, + "p95_ms": 148.86, + "max_ms": 176.92 + }, + "list": { + "count": 1, + "median_ms": 3.96, + "p95_ms": 3.96, + "max_ms": 3.96 + }, + "delete": { + "count": 100, + "median_ms": 126.81, + "p95_ms": 180.34, + "max_ms": 181.32 + } + }, + "default_page_count": 50, + "default_total": 100, + "pagination_complete": true, + "final_total": 0, + "elapsed_ms": 1908.85, + "event_loop_lag": { + "count": 156, + "median_ms": 1.39, + "p95_ms": 5.2, + "max_ms": 65.37 + } + }, + { + "scenario": "task_api_crud", + "tasks": 1000, + "client_concurrency": 20, + "latencies": { + "create": { + "count": 1000, + "median_ms": 109.25, + "p95_ms": 122.91, + "max_ms": 130.79 + }, + "update": { + "count": 1000, + "median_ms": 127.29, + "p95_ms": 150.37, + "max_ms": 180.05 + }, + "list": { + "count": 10, + "median_ms": 4.6, + "p95_ms": 8.08, + "max_ms": 8.08 + }, + "delete": { + "count": 1000, + "median_ms": 124.33, + "p95_ms": 146.88, + "max_ms": 167.34 + } + }, + "default_page_count": 50, + "default_total": 1000, + "pagination_complete": true, + "final_total": 0, + "elapsed_ms": 18264.74, + "event_loop_lag": { + "count": 1555, + "median_ms": 1.31, + "p95_ms": 4.68, + "max_ms": 39.35 + } + } + ], + "database_bytes": 2928640, + "complete": true +} \ No newline at end of file diff --git a/docs/development/evidence/phase2-20260907/agent-ui-report.json b/docs/development/evidence/phase2-20260907/agent-ui-report.json new file mode 100644 index 0000000..ec269bd --- /dev/null +++ b/docs/development/evidence/phase2-20260907/agent-ui-report.json @@ -0,0 +1,142 @@ +{ + "run_id": "benchmark_e3047791a92d", + "kind": "agent", + "dataset_id": "agent-core-v1", + "dataset_hash": "sha256:82fd1b84cc2dc2feec0fc67357d64b145dcdf6c44ec2660f182d550e85b9d27f", + "status": "completed", + "config_snapshot": { + "dataset_id": "agent-core-v1", + "provider_id": "provider_65fd326edba646e3bc735c25fbf5ffe8", + "model": "deepseek-v4-flash", + "max_steps": 6, + "timeout_seconds": 90, + "token_budget": 6000, + "repeat": 1, + "allow_network": false, + "offline": false, + "dataset_hash": "sha256:82fd1b84cc2dc2feec0fc67357d64b145dcdf6c44ec2660f182d550e85b9d27f", + "dataset_version": "1.0.0", + "execution": "real_agent_runtime", + "provider_type": "openai_compatible", + "scoring_version": "1.0", + "permission_policy": "runtime_user_decision", + "active_agent_run_id": "run_1230c37a1c2c4559b69cbc4e01b4721b" + }, + "metrics": { + "total_cases": 4, + "evaluated_cases": 4, + "task_success_rate": 1, + "tool_selection_accuracy": 1, + "tool_argument_accuracy": 1, + "invalid_tool_call_rate": 0, + "average_steps": 1.75, + "average_latency_ms": 4646.515175001696, + "token_usage": 5939, + "tool_calls": 3, + "expected_calls": 3 + }, + "cases": [ + { + "case_id": "arithmetic", + "repeat": 0, + "agent_run_id": "run_f4baf45c66c34e3f94b60b90f0846a95", + "success": true, + "tool_calls": 1, + "expected_calls": 1, + "selected_calls": 1, + "accurate_calls": 1, + "invalid_calls": 0, + "steps": 2, + "latency_ms": 4902.329800010193, + "token_usage": 1585, + "checks": { + "completed": true, + "tools_selected": true, + "tool_arguments": true, + "no_extra_calls": true, + "tool_results": true, + "output": true, + "citation": true, + "tasks_created": true + }, + "error_code": null + }, + { + "case_id": "echo", + "repeat": 0, + "agent_run_id": "run_5cf8341f73fa431ab7978f9ed4fe84c7", + "success": true, + "tool_calls": 1, + "expected_calls": 1, + "selected_calls": 1, + "accurate_calls": 1, + "invalid_calls": 0, + "steps": 2, + "latency_ms": 5341.693700000178, + "token_usage": 1821, + "checks": { + "completed": true, + "tools_selected": true, + "tool_arguments": true, + "no_extra_calls": true, + "tool_results": true, + "output": true, + "citation": true, + "tasks_created": true + }, + "error_code": null + }, + { + "case_id": "no-tool", + "repeat": 0, + "agent_run_id": "run_226a20145fbc4737ae2d3e812ff8f25c", + "success": true, + "tool_calls": 0, + "expected_calls": 0, + "selected_calls": 0, + "accurate_calls": 0, + "invalid_calls": 0, + "steps": 1, + "latency_ms": 1806.5612000063993, + "token_usage": 541, + "checks": { + "completed": true, + "tools_selected": true, + "tool_arguments": true, + "no_extra_calls": true, + "tool_results": true, + "output": true, + "citation": true, + "tasks_created": true + }, + "error_code": null + }, + { + "case_id": "markdown-catalog", + "repeat": 0, + "agent_run_id": "run_1230c37a1c2c4559b69cbc4e01b4721b", + "success": true, + "tool_calls": 1, + "expected_calls": 1, + "selected_calls": 1, + "accurate_calls": 1, + "invalid_calls": 0, + "steps": 2, + "latency_ms": 6535.4759999900125, + "token_usage": 1992, + "checks": { + "completed": true, + "tools_selected": true, + "tool_arguments": true, + "no_extra_calls": true, + "tool_results": true, + "output": true, + "citation": true, + "tasks_created": true + }, + "error_code": null + } + ], + "error": null, + "error_code": null +} \ No newline at end of file diff --git a/docs/development/evidence/phase2-20260907/artifacts.json b/docs/development/evidence/phase2-20260907/artifacts.json new file mode 100644 index 0000000..6357ad2 --- /dev/null +++ b/docs/development/evidence/phase2-20260907/artifacts.json @@ -0,0 +1,46 @@ +{ + "html": { + "bytes": 42542, + "sha256": "4c5e3f5eeb0167110cbbc750c12ef0a52420975288853f9653575cb49f3b4098", + "snapshot_present": true, + "static_images": 2 + }, + "pdf": { + "bytes": 53952, + "sha256": "a2aa2095495cfa612f4b3f2e2c35104f305289468bb5a8a5de8748a899d8f598" + }, + "docx": { + "bytes": 59638, + "sha256": "ade1b2d993c8e157e245af5476a7808c891d70ae91b82ac300e12d3fa727288e", + "snapshot_present": true, + "images": [ + { + "name": "word/media/image1.png", + "width": 1280, + "height": 1008 + }, + { + "name": "word/media/image2.png", + "width": 1000, + "height": 280 + }, + { + "name": "word/media/image3.png", + "width": 135, + "height": 32 + } + ] + }, + "browser_interactions": [ + "six_themes", + "wide_narrow_viewer", + "source_toggle", + "zoom_reset", + "edit_refresh", + "theme_switch", + "invalid_expression", + "html_pdf_docx_download", + "print_warning", + "export_cancelled" + ] +} diff --git a/docs/development/evidence/phase2-20260907/backend-tests.txt b/docs/development/evidence/phase2-20260907/backend-tests.txt new file mode 100644 index 0000000..4f9decc --- /dev/null +++ b/docs/development/evidence/phase2-20260907/backend-tests.txt @@ -0,0 +1,19 @@ +........................................................................ [ 8%] +........................................................................ [ 16%] +........................................................................ [ 25%] +........................................................................ [ 33%] +........................................................................ [ 41%] +........................................................................ [ 50%] +........................................................................ [ 58%] +........................................................................ [ 66%] +........................................................................ [ 75%] +........................................................................ [ 83%] +........................................................................ [ 91%] +..................................................................... [100%] +============================== warnings summary =============================== +.venv\Lib\site-packages\fastapi\testclient.py:1 + C:\Users\KiriAky\.codex\worktrees\7950\NotesAgent\backend\.venv\Lib\site-packages\fastapi\testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead. + from starlette.testclient import TestClient as TestClient # noqa + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +861 passed, 1 warning in 48.32s diff --git a/docs/development/evidence/phase2-20260907/browser-mermaid-matrix.json b/docs/development/evidence/phase2-20260907/browser-mermaid-matrix.json new file mode 100644 index 0000000..9d66b4f --- /dev/null +++ b/docs/development/evidence/phase2-20260907/browser-mermaid-matrix.json @@ -0,0 +1,222 @@ +{ + "passed": 36, + "total": 36, + "results": [ + { + "theme": "light", + "type": "flowchart", + "passed": true, + "warnings": [] + }, + { + "theme": "light", + "type": "sequence", + "passed": true, + "warnings": [] + }, + { + "theme": "light", + "type": "class", + "passed": true, + "warnings": [] + }, + { + "theme": "light", + "type": "state", + "passed": true, + "warnings": [] + }, + { + "theme": "light", + "type": "er", + "passed": true, + "warnings": [] + }, + { + "theme": "light", + "type": "gantt", + "passed": true, + "warnings": [] + }, + { + "theme": "dark", + "type": "flowchart", + "passed": true, + "warnings": [] + }, + { + "theme": "dark", + "type": "sequence", + "passed": true, + "warnings": [] + }, + { + "theme": "dark", + "type": "class", + "passed": true, + "warnings": [] + }, + { + "theme": "dark", + "type": "state", + "passed": true, + "warnings": [] + }, + { + "theme": "dark", + "type": "er", + "passed": true, + "warnings": [] + }, + { + "theme": "dark", + "type": "gantt", + "passed": true, + "warnings": [] + }, + { + "theme": "sepia", + "type": "flowchart", + "passed": true, + "warnings": [] + }, + { + "theme": "sepia", + "type": "sequence", + "passed": true, + "warnings": [] + }, + { + "theme": "sepia", + "type": "class", + "passed": true, + "warnings": [] + }, + { + "theme": "sepia", + "type": "state", + "passed": true, + "warnings": [] + }, + { + "theme": "sepia", + "type": "er", + "passed": true, + "warnings": [] + }, + { + "theme": "sepia", + "type": "gantt", + "passed": true, + "warnings": [] + }, + { + "theme": "paper-moments", + "type": "flowchart", + "passed": true, + "warnings": [] + }, + { + "theme": "paper-moments", + "type": "sequence", + "passed": true, + "warnings": [] + }, + { + "theme": "paper-moments", + "type": "class", + "passed": true, + "warnings": [] + }, + { + "theme": "paper-moments", + "type": "state", + "passed": true, + "warnings": [] + }, + { + "theme": "paper-moments", + "type": "er", + "passed": true, + "warnings": [] + }, + { + "theme": "paper-moments", + "type": "gantt", + "passed": true, + "warnings": [] + }, + { + "theme": "ocean-blue", + "type": "flowchart", + "passed": true, + "warnings": [] + }, + { + "theme": "ocean-blue", + "type": "sequence", + "passed": true, + "warnings": [] + }, + { + "theme": "ocean-blue", + "type": "class", + "passed": true, + "warnings": [] + }, + { + "theme": "ocean-blue", + "type": "state", + "passed": true, + "warnings": [] + }, + { + "theme": "ocean-blue", + "type": "er", + "passed": true, + "warnings": [] + }, + { + "theme": "ocean-blue", + "type": "gantt", + "passed": true, + "warnings": [] + }, + { + "theme": "midnight-purple", + "type": "flowchart", + "passed": true, + "warnings": [] + }, + { + "theme": "midnight-purple", + "type": "sequence", + "passed": true, + "warnings": [] + }, + { + "theme": "midnight-purple", + "type": "class", + "passed": true, + "warnings": [] + }, + { + "theme": "midnight-purple", + "type": "state", + "passed": true, + "warnings": [] + }, + { + "theme": "midnight-purple", + "type": "er", + "passed": true, + "warnings": [] + }, + { + "theme": "midnight-purple", + "type": "gantt", + "passed": true, + "warnings": [] + } + ] +} \ No newline at end of file diff --git a/docs/development/evidence/phase2-20260907/browser-results.json b/docs/development/evidence/phase2-20260907/browser-results.json new file mode 100644 index 0000000..e8fab66 --- /dev/null +++ b/docs/development/evidence/phase2-20260907/browser-results.json @@ -0,0 +1,32 @@ +[ + { + "theme": "light", + "errors": [], + "plotCount": 4 + }, + { + "theme": "dark", + "errors": [], + "plotCount": 4 + }, + { + "theme": "sepia", + "errors": [], + "plotCount": 4 + }, + { + "theme": "paper-moments", + "errors": [], + "plotCount": 4 + }, + { + "theme": "ocean-blue", + "errors": [], + "plotCount": 4 + }, + { + "theme": "midnight-purple", + "errors": [], + "plotCount": 4 + } +] \ No newline at end of file diff --git a/docs/development/evidence/phase2-20260907/build.txt b/docs/development/evidence/phase2-20260907/build.txt new file mode 100644 index 0000000..806b118 --- /dev/null +++ b/docs/development/evidence/phase2-20260907/build.txt @@ -0,0 +1,560 @@ + +> notes-agent-frontend@0.2.0 build C:\Users\KiriAky\.codex\worktrees\7950\NotesAgent\frontend +> vue-tsc -b && vite build + +vite v6.4.3 building for production... +transforming... +✓ 3262 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.55 kB │ gzip: 0.33 kB +dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff 4.42 kB +dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 4.93 kB +dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 5.21 kB +dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 5.47 kB +dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff 5.98 kB +dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff 6.19 kB +dist/assets/KaTeX_Size1-Regular-C195tn64.woff 6.50 kB +dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 6.91 kB +dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 6.91 kB +dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf 7.59 kB +dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff 7.66 kB +dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff 7.72 kB +dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 9.64 kB +dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 10.34 kB +dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf 10.36 kB +dist/assets/KaTeX_Script-Regular-D5yQViql.woff 10.59 kB +dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 11.32 kB +dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 11.35 kB +dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf 11.51 kB +dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 12.03 kB +dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 12.22 kB +dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf 12.23 kB +dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff 12.32 kB +dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf 12.34 kB +dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf 12.37 kB +dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff 13.21 kB +dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff 13.30 kB +dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 13.57 kB +dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff 14.11 kB +dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff 14.41 kB +dist/assets/paper-moments-DOmjCJQa.theme 15.58 kB +dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff 16.03 kB +dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 16.40 kB +dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 16.44 kB +dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf 16.65 kB +dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 16.78 kB +dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 16.99 kB +dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff 18.67 kB +dist/assets/KaTeX_Math-Italic-DA0__PXp.woff 18.75 kB +dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff 19.41 kB +dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf 19.44 kB +dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf 19.57 kB +dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf 19.58 kB +dist/assets/KaTeX_Main-Italic-BMLOBm91.woff 19.68 kB +dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf 22.36 kB +dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf 24.50 kB +dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 25.32 kB +dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 26.27 kB +dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf 27.56 kB +dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 28.08 kB +dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff 29.91 kB +dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff 30.77 kB +dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf 31.20 kB +dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf 31.31 kB +dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf 32.97 kB +dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff 33.52 kB +dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf 33.58 kB +dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf 51.34 kB +dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf 53.58 kB +dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf 63.63 kB +dist/.vite/manifest.json 258.05 kB │ gzip: 20.84 kB +dist/assets/HeadingStyleSettings-DHfF7b37.css 0.57 kB │ gzip: 0.29 kB +dist/assets/SkillsView-BwLulPo9.css 0.98 kB │ gzip: 0.39 kB +dist/assets/WorkspaceChat-DhGri1NS.css 1.09 kB │ gzip: 0.48 kB +dist/assets/ExtensionInstallDialog-DRNL9bJf.css 1.14 kB │ gzip: 0.45 kB +dist/assets/LogsView-BrIX2apd.css 1.35 kB │ gzip: 0.53 kB +dist/assets/BenchmarkView-DLutUBdD.css 1.39 kB │ gzip: 0.55 kB +dist/assets/TasksView-DUgcfVCX.css 1.47 kB │ gzip: 0.58 kB +dist/assets/SearchView-DW-1gSdx.css 1.47 kB │ gzip: 0.56 kB +dist/assets/FilePicker-BO9C6lOI.css 1.63 kB │ gzip: 0.60 kB +dist/assets/MediaView-D48AE7U_.css 1.98 kB │ gzip: 0.69 kB +dist/assets/ChatPersonaDialog-DHMXahgY.css 2.03 kB │ gzip: 0.72 kB +dist/assets/MarkdownContent-CLXIe9X7.css 3.56 kB │ gzip: 0.94 kB +dist/assets/WorkspaceView-yzL09utK.css 4.35 kB │ gzip: 1.23 kB +dist/assets/McpServersView-BFLRUwRT.css 5.10 kB │ gzip: 1.25 kB +dist/assets/ChatView-BQEJrX-5.css 5.20 kB │ gzip: 1.41 kB +dist/assets/markdown-D2L0VTei.css 5.43 kB │ gzip: 1.51 kB +dist/assets/VaultEntry-DjSz9QsE.css 5.64 kB │ gzip: 1.37 kB +dist/assets/ThemesView-i5ccHc0M.css 7.00 kB │ gzip: 1.72 kB +dist/assets/PluginsView-CwBTnoBl.css 11.21 kB │ gzip: 2.11 kB +dist/assets/AgentView-DSWsxP_V.css 12.59 kB │ gzip: 2.31 kB +dist/assets/SettingsView-CNkfTZZK.css 13.85 kB │ gzip: 3.02 kB +dist/assets/math-katex-0-18-4-CEK31ho9.css 30.19 kB │ gzip: 8.06 kB +dist/assets/index-DUxb9c05.css 50.43 kB │ gzip: 9.59 kB +dist/assets/editor-milkdown-DebjlF2b.css 83.87 kB │ gzip: 16.14 kB +dist/assets/VisualMarkdownEditor-IxqBomSx.css 390.03 kB │ gzip: 105.48 kB +dist/assets/channel-CrNH6nrN.js 0.11 kB │ gzip: 0.13 kB +dist/assets/init-Gi6I4Gst.js 0.15 kB │ gzip: 0.13 kB +dist/assets/chunk-2Q5K7J3B-BZASLIXN.js 0.19 kB │ gzip: 0.16 kB +dist/assets/chunk-XXDRQBXY-ChINTzlJ.js 0.23 kB │ gzip: 0.21 kB +dist/assets/chunk-JWPE2WC7-BNAOuj8n.js 0.30 kB │ gzip: 0.21 kB +dist/assets/diff-DbItnlRl.js 0.31 kB │ gzip: 0.24 kB +dist/assets/chunk-5VM5RSS4-TQefSFWe.js 0.37 kB │ gzip: 0.27 kB +dist/assets/index-DIIxsTGV.js 0.48 kB │ gzip: 0.31 kB +dist/assets/stateDiagram-v2-MP3YSRHH-Nc43I8Ns.js 0.51 kB │ gzip: 0.35 kB +dist/assets/chunk-POPQ4Y6H-BG03mjXr.js 0.53 kB │ gzip: 0.38 kB +dist/assets/codeowners-Bp6g37R7.js 0.55 kB │ gzip: 0.32 kB +dist/assets/classDiagram-ZZMXUADV-CI5Lv9Pw.js 0.55 kB │ gzip: 0.36 kB +dist/assets/classDiagram-v2-VYDZK3BY-CI5Lv9Pw.js 0.55 kB │ gzip: 0.36 kB +dist/assets/brainfuck-C4LP7Hcl.js 0.61 kB │ gzip: 0.33 kB +dist/assets/swimlanesDiagram-VR7AAH4N-C4Ag8puk.js 0.63 kB │ gzip: 0.41 kB +dist/assets/infoDiagram-27XIBGKW-Bb4UVFAL.js 0.66 kB │ gzip: 0.45 kB +dist/assets/properties-C78fOPTZ.js 0.67 kB │ gzip: 0.35 kB +dist/assets/shellsession-BADoaaVG.js 0.71 kB │ gzip: 0.43 kB +dist/assets/tsv-B_m7g4N7.js 0.74 kB │ gzip: 0.34 kB +dist/assets/cmake-BQqOBYOt.js 0.78 kB │ gzip: 0.46 kB +dist/assets/asciiarmor-Df11BRmG.js 0.79 kB │ gzip: 0.41 kB +dist/assets/http-DBlCnlav.js 0.85 kB │ gzip: 0.41 kB +dist/assets/protobuf-ChK-085T.js 0.86 kB │ gzip: 0.52 kB +dist/assets/solr-DehyRSwq.js 0.87 kB │ gzip: 0.46 kB +dist/assets/html-derivative-DlHx6ybY.js 0.90 kB │ gzip: 0.50 kB +dist/assets/troff-wAsdV37c.js 0.96 kB │ gzip: 0.44 kB +dist/assets/git-rebase-r7XF79zn.js 0.98 kB │ gzip: 0.44 kB +dist/assets/qmldir-C8lEn-DE.js 1.00 kB │ gzip: 0.45 kB +dist/assets/FilePicker-CjNjYr-N.js 1.04 kB │ gzip: 0.61 kB +dist/assets/sizeCapture-INFHLROL-DWCqhl6c.js 1.08 kB │ gzip: 0.61 kB +dist/assets/spreadsheet-BCZA_wO0.js 1.14 kB │ gzip: 0.54 kB +dist/assets/toml-Bm5Em-hy.js 1.14 kB │ gzip: 0.54 kB +dist/assets/csv-fuZLfV_i.js 1.14 kB │ gzip: 0.37 kB +dist/assets/ordinal-Cboi1Yqb.js 1.19 kB │ gzip: 0.57 kB +dist/assets/git-commit-F4YmCXRG.js 1.23 kB │ gzip: 0.53 kB +dist/assets/xsl-CtQFsRM5.js 1.39 kB │ gzip: 0.52 kB +dist/assets/mbox-CNhZ1qSd.js 1.40 kB │ gzip: 0.66 kB +dist/assets/dotenv-Da5cRb03.js 1.42 kB │ gzip: 0.53 kB +dist/assets/sparql-rVzFXLq3.js 1.48 kB │ gzip: 0.82 kB +dist/assets/index-Vcq4gwWv.js 1.49 kB │ gzip: 0.82 kB +dist/assets/ini-BEwlwnbL.js 1.53 kB │ gzip: 0.50 kB +dist/assets/MarkdownContent.vue_vue_type_style_index_0_lang-B376YVIn.js 1.56 kB │ gzip: 0.83 kB +dist/assets/sieve-C3Gn_uJK.js 1.62 kB │ gzip: 0.77 kB +dist/assets/rpm-CTu-6PCP.js 1.62 kB │ gzip: 0.83 kB +dist/assets/markdownPreferences-DKoh9bVv.js 1.66 kB │ gzip: 0.86 kB +dist/assets/fortran-fixed-form-CkoXwp7k.js 1.67 kB │ gzip: 0.69 kB +dist/assets/factor-kuTfRLto.js 1.67 kB │ gzip: 0.61 kB +dist/assets/railroadDiagram-O6MQD6OU-DwB0kF9i.js 1.69 kB │ gzip: 0.80 kB +dist/assets/docker-BcOcwvcX.js 1.74 kB │ gzip: 0.60 kB +dist/assets/z80-Hz9HOZM7.js 1.75 kB │ gzip: 0.81 kB +dist/assets/eiffel-CnydiIhH.js 1.81 kB │ gzip: 0.92 kB +dist/assets/desktop-BmXAJ9_W.js 1.83 kB │ gzip: 0.76 kB +dist/assets/pluginCommandForm-DV9KKjoP.js 1.85 kB │ gzip: 0.95 kB +dist/assets/chunk-F27PBJKO-Ctsjd131.js 1.88 kB │ gzip: 0.83 kB +dist/assets/abnfDiagram-VCTEODGH-s3lnCumS.js 1.89 kB │ gzip: 0.94 kB +dist/assets/elm-vLlmbW-K.js 1.89 kB │ gzip: 0.82 kB +dist/assets/hxml-2-FPmUDs.js 1.89 kB │ gzip: 0.90 kB +dist/assets/mathematica-DTrFuWx2.js 1.92 kB │ gzip: 0.82 kB +dist/assets/dockerfile-BKs6k2Af.js 1.95 kB │ gzip: 0.67 kB +dist/assets/turtle-B1tBg_DP.js 1.98 kB │ gzip: 0.90 kB +dist/assets/ebnf-CDyGwa7X.js 1.99 kB │ gzip: 0.81 kB +dist/assets/smalltalk-CnHTOXQT.js 2.01 kB │ gzip: 0.85 kB +dist/assets/pegDiagram-XKGWAZYB-CUdM1qfX.js 2.04 kB │ gzip: 0.97 kB +dist/assets/dtd-DF_7sFjM.js 2.06 kB │ gzip: 0.87 kB +dist/assets/mumps-BT43cFF4.js 2.07 kB │ gzip: 0.98 kB +dist/assets/fcl-Kvtd6kyn.js 2.08 kB │ gzip: 0.95 kB +dist/assets/ebnfDiagram-PWID7BFC-B0NYa1C1.js 2.09 kB │ gzip: 0.92 kB +dist/assets/ntriples-BfvgReVJ.js 2.10 kB │ gzip: 0.75 kB +dist/assets/yacas-BJ4BC0dw.js 2.15 kB │ gzip: 1.09 kB +dist/assets/wenyan-BV7otONQ.js 2.16 kB │ gzip: 1.09 kB +dist/assets/jssm-C2t-YnRu.js 2.24 kB │ gzip: 0.62 kB +dist/assets/simple-mode-GW_nhZxv.js 2.28 kB │ gzip: 1.09 kB +dist/assets/apl-B4CMkyY2.js 2.30 kB │ gzip: 1.23 kB +dist/assets/pascal--L3eBynH.js 2.30 kB │ gzip: 1.19 kB +dist/assets/octave-Ck1zUtKM.js 2.31 kB │ gzip: 1.09 kB +dist/assets/commonlisp-DBKNyK5s.js 2.32 kB │ gzip: 1.10 kB +dist/assets/reg-C-SQnVFl.js 2.35 kB │ gzip: 0.70 kB +dist/assets/tcl-DVfN8rqt.js 2.36 kB │ gzip: 1.23 kB +dist/assets/edge-FbVlp4U3.js 2.36 kB │ gzip: 0.70 kB +dist/assets/index-CjhMCctz.js 2.46 kB │ gzip: 1.52 kB +dist/assets/webidl-ZXfAyPTL.js 2.52 kB │ gzip: 1.26 kB +dist/assets/pig-CevX1Tat.js 2.53 kB │ gzip: 1.38 kB +dist/assets/puppet-DMA9R1ak.js 2.54 kB │ gzip: 1.20 kB +dist/assets/forth-Ffai-XNe.js 2.54 kB │ gzip: 1.33 kB +dist/assets/diff-D97Zzqfu.js 2.57 kB │ gzip: 0.70 kB +dist/assets/shell-CjFT_Tl9.js 2.57 kB │ gzip: 1.21 kB +dist/assets/gleam-BspZqrRM.js 2.58 kB │ gzip: 0.82 kB +dist/assets/erb-DXfck5VN.js 2.61 kB │ gzip: 0.84 kB +dist/assets/hy-DFXneXwc.js 2.65 kB │ gzip: 1.18 kB +dist/assets/index-Bu1SCIgx.js 2.66 kB │ gzip: 1.53 kB +dist/assets/velocity-D8B20fx6.js 2.67 kB │ gzip: 1.11 kB +dist/assets/tiddlywiki-DO-Gjzrf.js 2.78 kB │ gzip: 1.07 kB +dist/assets/modelica-Dc1JOy9r.js 2.79 kB │ gzip: 1.29 kB +dist/assets/json-Cp-IABpG.js 2.82 kB │ gzip: 0.78 kB +dist/assets/openscad-C4EeE6gA.js 2.82 kB │ gzip: 1.01 kB +dist/assets/log-2UxHyX5q.js 2.85 kB │ gzip: 0.90 kB +dist/assets/oz-BzwKVEFT.js 2.90 kB │ gzip: 1.28 kB +dist/assets/cairo-KRGpt6FW.js 2.94 kB │ gzip: 0.81 kB +dist/assets/r-B6wPVr8A.js 2.94 kB │ gzip: 1.36 kB +dist/assets/berry-uYugtg8r.js 3.01 kB │ gzip: 0.81 kB +dist/assets/jsonl-DcaNXYhu.js 3.01 kB │ gzip: 0.79 kB +dist/assets/jsonc-Des-eS-w.js 3.11 kB │ gzip: 0.80 kB +dist/assets/HeadingStyleSettings-Bhiwz6_B.js 3.12 kB │ gzip: 1.48 kB +dist/assets/stex-C3f8Ysf7.js 3.12 kB │ gzip: 1.20 kB +dist/assets/logo-BtOb2qkB.js 3.13 kB │ gzip: 1.47 kB +dist/assets/po-BTJTHyun.js 3.24 kB │ gzip: 0.91 kB +dist/assets/tiki-DGYXhP31.js 3.25 kB │ gzip: 1.26 kB +dist/assets/json5-C9tS-k6U.js 3.25 kB │ gzip: 0.83 kB +dist/assets/mipsasm-CKIfxQSi.js 3.26 kB │ gzip: 1.18 kB +dist/assets/tasl-QIJgUcNo.js 3.29 kB │ gzip: 0.85 kB +dist/assets/rbs-CpoqiR4B.js 3.31 kB │ gzip: 0.73 kB +dist/assets/vhdl-lSbBsy5d.js 3.35 kB │ gzip: 1.51 kB +dist/assets/genie-D0YGMca9.js 3.36 kB │ gzip: 1.21 kB +dist/assets/rel-C3B-1QV4.js 3.37 kB │ gzip: 1.11 kB +dist/assets/vala-CsfeWuGM.js 3.37 kB │ gzip: 1.19 kB +dist/assets/lua-VAEuO923.js 3.41 kB │ gzip: 1.43 kB +dist/assets/mermaidService-EVMjUBQI.js 3.42 kB │ gzip: 1.79 kB +dist/assets/arc-DnWybcCe.js 3.43 kB │ gzip: 1.47 kB +dist/assets/splunk-BtCnVYZw.js 3.44 kB │ gzip: 1.52 kB +dist/assets/index-833-DuH6.js 3.44 kB │ gzip: 1.74 kB +dist/assets/mscgen-BA5vi2Kp.js 3.51 kB │ gzip: 0.98 kB +dist/assets/sparql-DkYu6x3z.js 3.55 kB │ gzip: 1.63 kB +dist/assets/cypher-C_CwsFkJ.js 3.56 kB │ gzip: 1.52 kB +dist/assets/fluent-C4IJs8-o.js 3.61 kB │ gzip: 0.90 kB +dist/assets/ssh-config-_ykCGR6B.js 3.62 kB │ gzip: 1.60 kB +dist/assets/jsonnet-DFQXde-d.js 3.62 kB │ gzip: 1.05 kB +dist/assets/kdl-DV7GczEv.js 3.63 kB │ gzip: 1.04 kB +dist/assets/glsl-DplSGwfg.js 3.63 kB │ gzip: 1.41 kB +dist/assets/hurl-irOxFIW8.js 3.65 kB │ gzip: 1.16 kB +dist/assets/narrat-DRg8JJMk.js 3.67 kB │ gzip: 1.11 kB +dist/assets/_baseMerge-BNBydYQA.js 3.68 kB │ gzip: 1.73 kB +dist/assets/turtle-BsS91CYL.js 3.70 kB │ gzip: 0.98 kB +dist/assets/d-pRatUO7H.js 3.72 kB │ gzip: 1.68 kB +dist/assets/coffeescript-S37ZYGWr.js 3.87 kB │ gzip: 1.69 kB +dist/assets/vb-CmGdzxic.js 3.90 kB │ gzip: 1.79 kB +dist/assets/zenscript-DVFEvuxE.js 3.91 kB │ gzip: 1.28 kB +dist/assets/ron-D8l8udqQ.js 3.91 kB │ gzip: 0.98 kB +dist/assets/swift-BzpIVaGY.js 3.96 kB │ gzip: 1.79 kB +dist/assets/VaultEntry-DmoPppw3.js 3.96 kB │ gzip: 1.95 kB +dist/assets/WorkspaceChat-CrwU7la3.js 3.97 kB │ gzip: 1.89 kB +dist/assets/asn1-EdZsLKOL.js 3.98 kB │ gzip: 1.96 kB +dist/assets/gn-n2N0HUVH.js 4.00 kB │ gzip: 1.49 kB +dist/assets/q-pXgVlZs6.js 4.03 kB │ gzip: 1.67 kB +dist/assets/ttcn-cfg-B9xdYoR4.js 4.05 kB │ gzip: 1.71 kB +dist/assets/dylan-DwRh75JA.js 4.05 kB │ gzip: 1.65 kB +dist/assets/livescript-BwQOo05w.js 4.09 kB │ gzip: 1.64 kB +dist/assets/groovy-D9Dt4D0W.js 4.14 kB │ gzip: 1.77 kB +dist/assets/pascal-D93ZcfNL.js 4.15 kB │ gzip: 1.67 kB +dist/assets/haskell-Cw1EW3IL.js 4.17 kB │ gzip: 1.89 kB +dist/assets/SearchView-B47FoyI1.js 4.26 kB │ gzip: 2.09 kB +dist/assets/diagram-Z3DM3KII-Bb5zNdmf.js 4.33 kB │ gzip: 1.91 kB +dist/assets/tcl-dwOrl1Do.js 4.43 kB │ gzip: 1.52 kB +dist/assets/asterisk-B-8jnY81.js 4.48 kB │ gzip: 1.78 kB +dist/assets/nextflow-C-mBbutL.js 4.51 kB │ gzip: 1.17 kB +dist/assets/rosmsg-BJDFO7_C.js 4.52 kB │ gzip: 1.06 kB +dist/assets/http-jrhK8wxY.js 4.55 kB │ gzip: 1.12 kB +dist/assets/gas-Bneqetm1.js 4.57 kB │ gzip: 1.28 kB +dist/assets/fortran-DYz_wnZ1.js 4.65 kB │ gzip: 2.01 kB +dist/assets/polar-C0HS_06l.js 4.67 kB │ gzip: 1.12 kB +dist/assets/defaultLocale-DX6XiGOO.js 4.69 kB │ gzip: 2.18 kB +dist/assets/sdbl-DVxCFoDh.js 4.70 kB │ gzip: 2.01 kB +dist/assets/fennel-BYunw83y.js 4.77 kB │ gzip: 1.53 kB +dist/assets/mllike-CXdrOF99.js 4.79 kB │ gzip: 1.53 kB +dist/assets/bibtex-CHM0blh-.js 4.80 kB │ gzip: 0.83 kB +dist/assets/ttcn-CfJYG6tj.js 4.80 kB │ gzip: 2.13 kB +dist/assets/SkillsView-BIOhy4r9.js 4.90 kB │ gzip: 2.06 kB +dist/assets/index-BlxF_W4d.js 4.95 kB │ gzip: 1.51 kB +dist/assets/llvm-BZoOZj88.js 5.04 kB │ gzip: 2.01 kB +dist/assets/index-E-vsI35b.js 5.10 kB │ gzip: 2.43 kB +dist/assets/index-D2e4uDXw.js 5.11 kB │ gzip: 2.55 kB +dist/assets/ecl-Cabwm37j.js 5.13 kB │ gzip: 2.31 kB +dist/assets/crystal-SjHAIU92.js 5.13 kB │ gzip: 2.07 kB +dist/assets/wgsl-Dx-B1_4e.js 5.14 kB │ gzip: 1.39 kB +dist/assets/ruby-B2Rjki9n.js 5.16 kB │ gzip: 2.15 kB +dist/assets/gdresource-TyuKm33G.js 5.29 kB │ gzip: 1.34 kB +dist/assets/qml-3beO22l8.js 5.34 kB │ gzip: 1.38 kB +dist/assets/zig-VOosw3JB.js 5.34 kB │ gzip: 1.55 kB +dist/assets/dax-CEL-wOlO.js 5.37 kB │ gzip: 2.23 kB +dist/assets/bicep-Bmn6On1c.js 5.38 kB │ gzip: 1.15 kB +dist/assets/xml-sdJ4AIDG.js 5.38 kB │ gzip: 1.21 kB +dist/assets/julia-DuME0IfC.js 5.39 kB │ gzip: 2.14 kB +dist/assets/awk-DMzUqQB5.js 5.46 kB │ gzip: 1.38 kB +dist/assets/browser-c3U3mMxj.js 5.53 kB │ gzip: 2.79 kB +dist/assets/TasksView-BnSHnCQV.js 5.65 kB │ gzip: 2.49 kB +dist/assets/linear-Dsc1LO7N.js 5.66 kB │ gzip: 2.31 kB +dist/assets/ExtensionInstallDialog-BSAwvlPk.js 5.68 kB │ gzip: 2.80 kB +dist/assets/jinja-f2NsQr07.js 5.69 kB │ gzip: 1.40 kB +dist/assets/lean-BZvkOJ9d.js 5.78 kB │ gzip: 1.92 kB +dist/assets/vbscript-BuJXcnF6.js 5.82 kB │ gzip: 2.57 kB +dist/assets/powerquery-CEu0bR-o.js 5.90 kB │ gzip: 1.52 kB +dist/assets/mirc-CjQqDB4T.js 5.92 kB │ gzip: 2.68 kB +dist/assets/shaderlab-Dg9Lc6iA.js 5.92 kB │ gzip: 2.08 kB +dist/assets/cypher-COkxafJQ.js 5.96 kB │ gzip: 1.73 kB +dist/assets/coq-C7JzOVbR.js 6.01 kB │ gzip: 1.95 kB +dist/assets/vb-Cu-pLBUe.js 6.08 kB │ gzip: 2.33 kB +dist/assets/cobol-CWcv1MsR.js 6.20 kB │ gzip: 2.96 kB +dist/assets/diagram-UQ7AKVKN-Cms5HwfU.js 6.31 kB │ gzip: 2.73 kB +dist/assets/gdshader-DkwncUOv.js 6.33 kB │ gzip: 1.73 kB +dist/assets/ara-BRHolxvo.js 6.36 kB │ gzip: 1.81 kB +dist/assets/scheme-C41bIUwD.js 6.37 kB │ gzip: 2.39 kB +dist/assets/verilog-nZwndyjY.js 6.41 kB │ gzip: 1.92 kB +dist/assets/clojure-P80f7IUj.js 6.41 kB │ gzip: 1.42 kB +dist/assets/postcss-CXtECtnM.js 6.42 kB │ gzip: 1.91 kB +dist/assets/toml-vGWfd6FD.js 6.43 kB │ gzip: 1.28 kB +dist/assets/pieDiagram-E7YTZNPT-YK6ICI5_.js 6.43 kB │ gzip: 2.70 kB +dist/assets/python-BuPzkPfP.js 6.48 kB │ gzip: 2.73 kB +dist/assets/LogsView-ZSnm_Csu.js 6.52 kB │ gzip: 3.14 kB +dist/assets/proto-C7zT0LnQ.js 6.55 kB │ gzip: 1.42 kB +dist/assets/chapel-DTp_pixX.js 6.62 kB │ gzip: 1.85 kB +dist/assets/xquery-DzFWVndE.js 6.62 kB │ gzip: 2.56 kB +dist/assets/pug-DeIclll2.js 6.67 kB │ gzip: 1.94 kB +dist/assets/r-Cf5RLm7j.js 6.74 kB │ gzip: 1.84 kB +dist/assets/talonscript-CkByrt1z.js 6.76 kB │ gzip: 1.49 kB +dist/assets/textile-CnDTJFAw.js 6.80 kB │ gzip: 2.44 kB +dist/assets/nsis-LdVXkNf5.js 6.81 kB │ gzip: 2.99 kB +dist/assets/BenchmarkView-CoNnmLzX.js 6.89 kB │ gzip: 3.19 kB +dist/assets/riscv-BM1_JUlF.js 6.91 kB │ gzip: 1.98 kB +dist/assets/soy-8wufbnw4.js 6.98 kB │ gzip: 1.66 kB +dist/assets/scheme-C98Dy4si.js 7.17 kB │ gzip: 2.05 kB +dist/assets/hlsl-D3lLCCz7.js 7.26 kB │ gzip: 2.19 kB +dist/assets/nginx-DdIZxoE0.js 7.34 kB │ gzip: 2.72 kB +dist/assets/qss-IeuSbFQv.js 7.47 kB │ gzip: 2.58 kB +dist/assets/prisma-Vru482bI.js 7.74 kB │ gzip: 1.48 kB +dist/assets/powershell-CFHJl5sT.js 7.77 kB │ gzip: 3.33 kB +dist/assets/ChatPersonaDialog-BHpRSHc3.js 7.84 kB │ gzip: 3.61 kB +dist/assets/systemd-4A_iFExJ.js 7.87 kB │ gzip: 2.55 kB +dist/assets/haxe-H-WmDvRZ.js 7.89 kB │ gzip: 2.95 kB +dist/assets/regexp-CDVJQ6XC.js 7.99 kB │ gzip: 1.42 kB +dist/assets/erlang-BNw1qcRV.js 8.10 kB │ gzip: 2.89 kB +dist/assets/verilog-C6RDOZhf.js 8.24 kB │ gzip: 3.52 kB +dist/assets/haml-D5jkg6IW.js 8.26 kB │ gzip: 1.81 kB +dist/assets/diagram-S7CK7UJ4-C0vmdMvd.js 8.44 kB │ gzip: 3.77 kB +dist/assets/vue-html-AaS7Mt5G.js 8.47 kB │ gzip: 1.68 kB +dist/assets/plsql-ChMvpjG-.js 8.51 kB │ gzip: 3.00 kB +dist/assets/isArrayLikeObject-Csl51Uc7.js 8.52 kB │ gzip: 3.19 kB +dist/assets/dart-bE4Kk8sk.js 8.62 kB │ gzip: 2.00 kB +dist/assets/kotlin-BdnUsdx6.js 8.79 kB │ gzip: 2.13 kB +dist/assets/make-CHLpvVh8.js 8.96 kB │ gzip: 1.77 kB +dist/assets/sas-DEy46yEz.js 9.06 kB │ gzip: 3.81 kB +dist/assets/smithy-cds9vsN8.js 9.13 kB │ gzip: 1.53 kB +dist/assets/sass-Cj5Yp3dK.js 9.29 kB │ gzip: 2.49 kB +dist/assets/sas-B4kiWyti.js 9.33 kB │ gzip: 4.11 kB +dist/assets/tex-D96PA37w.js 9.67 kB │ gzip: 3.06 kB +dist/assets/jison-wvAkD_A8.js 9.69 kB │ gzip: 1.85 kB +dist/assets/perl-CdXCOZ3F.js 9.75 kB │ gzip: 3.46 kB +dist/assets/cmake-D1j8_8rp.js 9.86 kB │ gzip: 3.37 kB +dist/assets/hcl-BWvSN4gD.js 10.05 kB │ gzip: 1.93 kB +dist/assets/gherkin-heZmZLOM.js 10.16 kB │ gzip: 5.09 kB +dist/assets/cynefinDiagram-5FMLGOSQ-BD2mGua6.js 10.18 kB │ gzip: 3.68 kB +dist/assets/pkl-u5AG7uiY.js 10.37 kB │ gzip: 1.38 kB +dist/assets/beancount-k_qm7-4y.js 10.37 kB │ gzip: 1.44 kB +dist/assets/nextflow-groovy-vE_lwT2v.js 10.44 kB │ gzip: 2.14 kB +dist/assets/dream-maker-BtqSS_iP.js 10.47 kB │ gzip: 2.25 kB +dist/assets/raku-DXvB9xmW.js 10.47 kB │ gzip: 2.94 kB +dist/assets/stateDiagram-D77RDMKH-B-0A9xPX.js 10.48 kB │ gzip: 3.69 kB +dist/assets/yaml-Buea-lGh.js 10.51 kB │ gzip: 2.27 kB +dist/assets/rst-bs7f0vWN.js 10.67 kB │ gzip: 2.42 kB +dist/assets/clojure-BMjYHr_A.js 10.82 kB │ gzip: 3.99 kB +dist/assets/diagram-VSXAHHWV-Df9UVgEq.js 10.90 kB │ gzip: 4.21 kB +dist/assets/elm-DbKCFpqz.js 10.97 kB │ gzip: 2.12 kB +dist/assets/just-Cwhn7H3k.js 11.23 kB │ gzip: 2.80 kB +dist/assets/dagre-GXQ25YYZ-xEIgJp7x.js 11.25 kB │ gzip: 4.25 kB +dist/assets/prolog-CbFg5uaA.js 11.36 kB │ gzip: 3.83 kB +dist/assets/terraform-BETggiCN.js 11.39 kB │ gzip: 2.51 kB +dist/assets/puppet-BMWR74SV.js 11.44 kB │ gzip: 2.11 kB +dist/assets/idl-BEugSyMb.js 11.63 kB │ gzip: 4.52 kB +dist/assets/gherkin-DyxjwDmM.js 11.95 kB │ gzip: 5.05 kB +dist/assets/wasm-MzD3tlZU.js 12.01 kB │ gzip: 2.19 kB +dist/assets/hjson-D5-asLiD.js 12.05 kB │ gzip: 1.64 kB +dist/assets/surrealql-Cjom0U5J.js 12.06 kB │ gzip: 3.30 kB +dist/assets/handlebars-BpdQsYii.js 12.15 kB │ gzip: 2.38 kB +dist/assets/apache-Pmp26Uib.js 12.46 kB │ gzip: 3.72 kB +dist/assets/index-CZ9cYO_F.js 12.75 kB │ gzip: 5.63 kB +dist/assets/bat-CickPsom.js 12.89 kB │ gzip: 3.23 kB +dist/assets/fish-BvzEVeQv.js 13.04 kB │ gzip: 1.74 kB +dist/assets/nsis-BlV79W_Q.js 13.13 kB │ gzip: 4.07 kB +dist/assets/v-BGw2Nkan.js 13.33 kB │ gzip: 2.77 kB +dist/assets/pug-DKIMFp6K.js 13.84 kB │ gzip: 2.58 kB +dist/assets/smalltalk-BOQMe2GC.js 14.13 kB │ gzip: 2.16 kB +dist/assets/clarity-Dn5IMItf.js 14.36 kB │ gzip: 2.51 kB +dist/assets/gnuplot-DdkO51Og.js 14.78 kB │ gzip: 3.27 kB +dist/assets/index-CvBQ3_sw.js 14.83 kB │ gzip: 6.00 kB +dist/assets/rust-B1yitclQ.js 15.07 kB │ gzip: 2.72 kB +dist/assets/kusto-wEQ09or8.js 15.17 kB │ gzip: 3.92 kB +dist/assets/ChatView-yeXQKTN6.js 15.41 kB │ gzip: 6.38 kB +dist/assets/nix-CwoSXNpI.js 15.51 kB │ gzip: 2.48 kB +dist/assets/lua-BaeVxFsk.js 15.54 kB │ gzip: 3.16 kB +dist/assets/actionscript-3-B3316cI-.js 15.54 kB │ gzip: 2.71 kB +dist/assets/MediaView-DDn-_XZH.js 15.57 kB │ gzip: 6.74 kB +dist/assets/chunk-SVP7TREG-C9m5TIe-.js 15.75 kB │ gzip: 4.42 kB +dist/assets/abap-BdImnpbu.js 15.85 kB │ gzip: 5.91 kB +dist/assets/matlab-D7o27uSR.js 16.09 kB │ gzip: 3.06 kB +dist/assets/luau-BnpPk5vE.js 16.14 kB │ gzip: 3.55 kB +dist/assets/diagram-VX7I27RA-ziQFycS8.js 16.15 kB │ gzip: 5.78 kB +dist/assets/cue-D82EKSYY.js 16.20 kB │ gzip: 2.06 kB +dist/assets/elixir-CkH2-t6x.js 16.32 kB │ gzip: 2.80 kB +dist/assets/index-Cs5JlGLq.js 16.35 kB │ gzip: 7.47 kB +dist/assets/solidity-DijEV5ha.js 16.41 kB │ gzip: 3.16 kB +dist/assets/odin-BBf5iR-q.js 16.51 kB │ gzip: 2.94 kB +dist/assets/ts-tags-D351s5mN.js 16.57 kB │ gzip: 2.07 kB +dist/assets/javascript-iXu5QeM3.js 17.08 kB │ gzip: 5.75 kB +dist/assets/ishikawaDiagram-5VMMS53U-DiQySQzt.js 17.65 kB │ gzip: 6.72 kB +dist/assets/move-el3G9tDJ.js 17.66 kB │ gzip: 3.06 kB +dist/assets/graphql-ChdNCCLP.js 18.00 kB │ gzip: 2.52 kB +dist/assets/liquid-C0sCDyMI.js 18.09 kB │ gzip: 3.16 kB +dist/assets/svelte-Cy7k_4gC.js 18.24 kB │ gzip: 3.14 kB +dist/assets/gdscript-DqcFQ5yU.js 19.09 kB │ gzip: 3.77 kB +dist/assets/groovy-gcz8RCvz.js 19.18 kB │ gzip: 3.60 kB +dist/assets/WorkspaceView-D--Q9mXh.js 19.88 kB │ gzip: 8.44 kB +dist/assets/glimmer-js-ByusRIyA.js 20.07 kB │ gzip: 2.95 kB +dist/assets/glimmer-ts-BfAWNZQY.js 20.07 kB │ gzip: 2.94 kB +dist/assets/powershell-BmBUJMz7.js 20.16 kB │ gzip: 4.07 kB +dist/assets/mdc-D1_yUvq7.js 20.18 kB │ gzip: 6.81 kB +dist/assets/viml-CJc9bBzg.js 20.37 kB │ gzip: 6.73 kB +dist/assets/kanban-definition-UXKFOSKX-C3erkLcV.js 20.78 kB │ gzip: 7.34 kB +dist/assets/nushell-D3jzshHO.js 20.88 kB │ gzip: 5.36 kB +dist/assets/index-D0DaXqvL.js 20.96 kB │ gzip: 9.37 kB +dist/assets/index-Cmf0-okb.js 21.23 kB │ gzip: 9.76 kB +dist/assets/wit-5i3qLPDT.js 21.47 kB │ gzip: 2.89 kB +dist/assets/twig-27uCiNez.js 21.98 kB │ gzip: 3.98 kB +dist/assets/clike-B9uivgTg.js 22.30 kB │ gzip: 7.86 kB +dist/assets/nim-BIad80T-.js 22.46 kB │ gzip: 3.16 kB +dist/assets/common-lisp-Cg-RD9OK.js 22.58 kB │ gzip: 6.06 kB +dist/assets/index-DduR1oeT.js 23.10 kB │ gzip: 10.41 kB +dist/assets/sql-CRqJ_cUM.js 23.43 kB │ gzip: 7.42 kB +dist/assets/mindmap-definition-YA3MSWOX-DSZ7Nbk9.js 23.44 kB │ gzip: 7.94 kB +dist/assets/sankeyDiagram-P5KCCOFB-Cnmoq8od.js 23.47 kB │ gzip: 8.63 kB +dist/assets/journeyDiagram-3NMN7TZE-BwGxWKov.js 23.65 kB │ gzip: 8.39 kB +dist/assets/cadence-Bv_4Rxtq.js 23.67 kB │ gzip: 3.67 kB +dist/assets/astro-HNnZUWAn.js 24.01 kB │ gzip: 7.55 kB +dist/assets/apl-CORt7UWP.js 24.04 kB │ gzip: 4.20 kB +dist/assets/templ-DhtptRzy.js 24.06 kB │ gzip: 5.40 kB +dist/assets/vhdl-CeAyd5Ju.js 24.26 kB │ gzip: 3.87 kB +dist/assets/angular-html-DA-rfuFy.js 24.29 kB │ gzip: 4.01 kB +dist/assets/purescript-CklMAg4u.js 24.69 kB │ gzip: 3.25 kB +dist/assets/McpServersView-BXDi-IBv.js 24.81 kB │ gzip: 9.19 kB +dist/assets/moonbit-CHtswR0a.js 24.94 kB │ gzip: 3.79 kB +dist/assets/vue-BqiEGhQt.js 24.96 kB │ gzip: 3.04 kB +dist/assets/typespec-CAFt9gP4.js 25.05 kB │ gzip: 2.63 kB +dist/assets/fsharp-CXgrBDvD.js 25.31 kB │ gzip: 4.13 kB +dist/assets/marko-DjSrsDqO.js 25.48 kB │ gzip: 3.59 kB +dist/assets/stylus-B533Al4x.js 25.80 kB │ gzip: 8.60 kB +dist/assets/wardleyDiagram-VM6X3IG4-bQBfHON_.js 26.18 kB │ gzip: 6.99 kB +dist/assets/c3-Dp5svz6Z.js 26.39 kB │ gzip: 3.99 kB +dist/assets/system-verilog-0hqHdDBg.js 26.56 kB │ gzip: 4.88 kB +dist/assets/PluginsView-L41qWUGa.js 26.83 kB │ gzip: 8.88 kB +dist/assets/codeql-DsOJ9woJ.js 26.88 kB │ gzip: 3.79 kB +dist/assets/css-BnMrqG3P.js 27.13 kB │ gzip: 8.47 kB +dist/assets/AgentView-vuEh8_sU.js 27.16 kB │ gzip: 9.10 kB +dist/assets/scss-D5BDwBP9.js 27.20 kB │ gzip: 4.20 kB +dist/assets/java-CylS5w8V.js 27.22 kB │ gzip: 4.26 kB +dist/assets/coffee-Ch7k5sss.js 27.42 kB │ gzip: 6.35 kB +dist/assets/razor-BjBPvh-w.js 27.51 kB │ gzip: 3.57 kB +dist/assets/index-yVH2efBJ.js 28.27 kB │ gzip: 12.76 kB +dist/assets/index-rtxKP8li.js 28.80 kB │ gzip: 11.71 kB +dist/assets/scala-CqE71os6.js 28.89 kB │ gzip: 3.94 kB +dist/assets/purify.es-5AjVNlXF.js 28.92 kB │ gzip: 11.14 kB +dist/assets/crystal-DGywbUpC.js 29.39 kB │ gzip: 4.45 kB +dist/assets/applescript-Co6uUVPk.js 29.57 kB │ gzip: 5.93 kB +dist/assets/gitGraphDiagram-WWUBYQGX-C3QLpWqM.js 30.02 kB │ gzip: 8.92 kB +dist/assets/index-l9h65gFm.js 30.82 kB │ gzip: 12.67 kB +dist/assets/erDiagram-RLTQ6QDP-gaZhO_Bd.js 31.01 kB │ gzip: 10.68 kB +dist/assets/stylus-BEDo0Tqx.js 31.07 kB │ gzip: 7.99 kB +dist/assets/julia-5Bft2YPA.js 31.08 kB │ gzip: 4.34 kB +dist/assets/requirementDiagram-BXWQKSXE-Cia-WX85.js 31.29 kB │ gzip: 9.87 kB +dist/assets/timeline-definition-24CTP7MA-DRPkaIkb.js 31.43 kB │ gzip: 10.47 kB +dist/assets/ahk-CsyLZFj1.js 31.44 kB │ gzip: 7.81 kB +dist/assets/layout-BPporBfO.js 32.04 kB │ gzip: 11.47 kB +dist/assets/VisualMarkdownEditor-DpC_b1CQ.js 32.34 kB │ gzip: 12.47 kB +dist/assets/index-BtCU2wNO.js 32.36 kB │ gzip: 13.09 kB +dist/assets/bsl-DlhNcFeZ.js 33.95 kB │ gzip: 8.36 kB +dist/assets/quadrantDiagram-AXDQQJYC-BjYsuyTc.js 34.57 kB │ gzip: 10.18 kB +dist/assets/nginx-BpAMiNFr.js 35.37 kB │ gzip: 4.43 kB +dist/assets/haxe-CfZj7gIn.js 35.95 kB │ gzip: 6.01 kB +dist/assets/org-DM6o9KBp.js 36.32 kB │ gzip: 3.70 kB +dist/assets/sql-D0XecflT.js 37.05 kB │ gzip: 10.94 kB +dist/assets/erlang-DsQrWhSR.js 37.48 kB │ gzip: 4.40 kB +dist/assets/chunk-IMKFNOWR-CMjnXVE8.js 38.62 kB │ gzip: 12.68 kB +dist/assets/bird2-Bx8U0n9b.js 38.68 kB │ gzip: 8.50 kB +dist/assets/cobol-nBiQ_Alo.js 39.13 kB │ gzip: 10.86 kB +dist/assets/asm-D_Q5rh1f.js 40.72 kB │ gzip: 8.21 kB +dist/assets/index-CRAC0HLJ.js 40.77 kB │ gzip: 16.77 kB +dist/assets/shellscript-Yzrsuije.js 41.48 kB │ gzip: 6.09 kB +dist/assets/haskell-Df6bDoY_.js 41.49 kB │ gzip: 6.44 kB +dist/assets/vennDiagram-4TSXK5OY-CS54t_H7.js 42.45 kB │ gzip: 15.85 kB +dist/assets/perl-B9cMNwum.js 43.16 kB │ gzip: 4.67 kB +dist/assets/blockDiagram-I7D4REHJ-CwXV7v-G.js 43.24 kB │ gzip: 13.96 kB +dist/assets/d-85-TOEBH.js 43.80 kB │ gzip: 8.47 kB +dist/assets/index-CiwVSVo-.js 44.38 kB │ gzip: 15.39 kB +dist/assets/xychartDiagram-S5SC5T6Z-SD9juUjS.js 44.59 kB │ gzip: 12.66 kB +dist/assets/ahk2-8Zs4aa1G.js 45.07 kB │ gzip: 9.88 kB +dist/assets/index-Dl3jeLBI.js 45.14 kB │ gzip: 19.18 kB +dist/assets/ruby-C0TQ7zu5.js 46.72 kB │ gzip: 5.83 kB +dist/assets/go-C27-OAKa.js 46.82 kB │ gzip: 5.18 kB +dist/assets/apex-DhZFqWV2.js 47.89 kB │ gzip: 6.90 kB +dist/assets/ada-bCR0ucgS.js 48.08 kB │ gzip: 6.03 kB +dist/assets/css-CLj8gQPS.js 49.04 kB │ gzip: 11.86 kB +dist/assets/chunk-TICWLB2K-Dykoa1DO.js 49.34 kB │ gzip: 15.79 kB +dist/assets/imba-DGztddWO.js 49.93 kB │ gzip: 9.46 kB +dist/assets/mermaid-CQcHuHx7.js 54.81 kB │ gzip: 4.81 kB +dist/assets/wikitext-BhOHFoWU.js 55.89 kB │ gzip: 4.76 kB +dist/assets/stata-DI20mbqo.js 56.99 kB │ gzip: 12.36 kB +dist/assets/html-pp8916En.js 57.25 kB │ gzip: 11.69 kB +dist/assets/ballerina-BFfxhgS-.js 58.69 kB │ gzip: 8.15 kB +dist/assets/markdown-Cvjx9yec.js 59.34 kB │ gzip: 5.64 kB +dist/assets/ThemesView-ROhLW1IX.js 60.69 kB │ gzip: 16.28 kB +dist/assets/ocaml-C0hk2d4L.js 62.45 kB │ gzip: 5.02 kB +dist/assets/flowDiagram-HODETNUW-B-wmlXgh.js 62.65 kB │ gzip: 20.06 kB +dist/assets/c4Diagram-7LVT6UL2-CZK_oPAh.js 65.64 kB │ gzip: 18.87 kB +dist/assets/mojo-DJz3ZmWd.js 69.52 kB │ gzip: 9.27 kB +dist/assets/ganttDiagram-EL5Y4UJY-C00FfxS-.js 69.78 kB │ gzip: 23.60 kB +dist/assets/python-B6aJPvgy.js 69.95 kB │ gzip: 9.13 kB +dist/assets/index-C7y4_zOF.js 70.84 kB │ gzip: 25.86 kB +dist/assets/c-BIGW1oBm.js 72.11 kB │ gzip: 10.51 kB +dist/assets/latex-D5pSuvFb.js 72.90 kB │ gzip: 6.78 kB +dist/assets/vyper-CDx5xZoG.js 74.65 kB │ gzip: 10.74 kB +dist/assets/preload-helper-BYUGvJBp.js 77.31 kB │ gzip: 30.71 kB +dist/assets/hack-BWmVpMyf.js 81.13 kB │ gzip: 26.38 kB +dist/assets/cose-bilkent-JH36ORCC-ucdrH9O7.js 81.80 kB │ gzip: 22.53 kB +dist/assets/index-DJmQi6DI.js 87.01 kB │ gzip: 34.53 kB +dist/assets/swift-C2oV4EkX.js 87.23 kB │ gzip: 14.84 kB +dist/assets/fortran-free-form-BxgE0vQu.js 88.97 kB │ gzip: 11.27 kB +dist/assets/csharp-DSvCPggb.js 90.19 kB │ gzip: 10.75 kB +dist/assets/racket-BqYA7rlc.js 92.39 kB │ gzip: 15.02 kB +dist/assets/less-B1dDrJ26.js 97.63 kB │ gzip: 14.70 kB +dist/assets/index-B_zoUCtT.js 97.90 kB │ gzip: 28.48 kB +dist/assets/index-CVdm6qZy.js 103.95 kB │ gzip: 34.40 kB +dist/assets/blade-2xfisSek.js 104.98 kB │ gzip: 28.20 kB +dist/assets/objective-c-DXmwc3jG.js 105.41 kB │ gzip: 23.33 kB +dist/assets/SettingsView-CA8mvhNi.js 110.00 kB │ gzip: 38.72 kB +dist/assets/php-Csjmro_R.js 113.09 kB │ gzip: 28.74 kB +dist/assets/swimlanes-42K2YHIH-DBnK_luN.js 117.20 kB │ gzip: 42.88 kB +dist/assets/sequenceDiagram-WJ2MYXX4-CT529fmH.js 117.53 kB │ gzip: 31.32 kB +dist/assets/asciidoc-CSVQ5wI8.js 136.05 kB │ gzip: 9.50 kB +dist/assets/mdx-Cmh6b_Ma.js 136.11 kB │ gzip: 23.35 kB +dist/assets/architectureDiagram-5GKGNRK7-MxGRspvX.js 152.10 kB │ gzip: 43.27 kB +dist/assets/typst-BUadGCkm.js 163.55 kB │ gzip: 10.98 kB +dist/assets/index-CX-Z_M19.js 165.35 kB │ gzip: 49.44 kB +dist/assets/objective-cpp-CLxacb5B.js 171.97 kB │ gzip: 30.62 kB +dist/assets/javascript-wDzz0qaB.js 174.83 kB │ gzip: 16.51 kB +dist/assets/tsx-COt5Ahok.js 175.54 kB │ gzip: 16.51 kB +dist/assets/jsx-g9-lgVsj.js 177.79 kB │ gzip: 16.61 kB +dist/assets/markdown-D1OO11DI.js 180.56 kB │ gzip: 56.48 kB +dist/assets/typescript-BPQ3VLAy.js 181.08 kB │ gzip: 16.04 kB +dist/assets/angular-ts-BrjP3tb8.js 183.82 kB │ gzip: 16.63 kB +dist/assets/vue-vine-BoDAl6tE.js 190.05 kB │ gzip: 17.99 kB +dist/assets/math-katex-0-18-4-CHandE1Z.js 261.18 kB │ gzip: 77.76 kB +dist/assets/editor-prosemirror-DdSLUblA.js 261.62 kB │ gzip: 81.66 kB +dist/assets/wolfram-lXgVvXCa.js 262.39 kB │ gzip: 77.14 kB +dist/assets/editor-milkdown-CfUWLk7w.js 288.62 kB │ gzip: 86.76 kB +dist/assets/index-BPFAEZBr.js 347.56 kB │ gzip: 114.62 kB +dist/assets/editor-codemirror-BFoSK6g_.js 425.42 kB │ gzip: 137.40 kB +dist/assets/cytoscape.esm-Bch-eiPH.js 443.83 kB │ gzip: 142.41 kB +dist/assets/wasm-CG6Dc4jp.js 622.34 kB │ gzip: 230.29 kB +dist/assets/mermaid.core-C2yjh7Um.js 658.47 kB │ gzip: 158.25 kB +dist/assets/cynefin-OW5HDTMX-DYRSVYbi.js 690.86 kB │ gzip: 155.12 kB +dist/assets/cpp-BMRokrvK.js 785.49 kB │ gzip: 53.20 kB +dist/assets/emacs-lisp-C_m_b--Z.js 790.01 kB │ gzip: 198.83 kB + +(!) Some chunks are larger than 500 kB after minification. Consider: +- Using dynamic import() to code-split the application +- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks +- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit. +✓ built in 18.06s diff --git a/docs/development/evidence/phase2-20260907/context-budget256.json b/docs/development/evidence/phase2-20260907/context-budget256.json new file mode 100644 index 0000000..c26e3cb --- /dev/null +++ b/docs/development/evidence/phase2-20260907/context-budget256.json @@ -0,0 +1,25 @@ +{ + "model": "deepseek-v4-flash", + "configured_test_window": 8192, + "vendor_max_context_tested": false, + "detect": { + "error_code": "CONTEXT_COMPRESSION_REQUIRED", + "network_calls": 0 + }, + "compression": { + "passed": false, + "before_estimate": 2262, + "after_estimate": 523, + "archive_unchanged": true, + "network_calls": 2, + "answer_input_tokens": 132, + "answer_output_tokens": 256 + }, + "observed_provider_cache": { + "requests": 18, + "reporting_requests": 16, + "positive_hit_requests": 12, + "positive_miss_requests": 16, + "scope": "provider reported usage across this isolated acceptance session; not deterministic cache control" + } +} \ No newline at end of file diff --git a/docs/development/evidence/phase2-20260907/context.json b/docs/development/evidence/phase2-20260907/context.json new file mode 100644 index 0000000..2099556 --- /dev/null +++ b/docs/development/evidence/phase2-20260907/context.json @@ -0,0 +1,25 @@ +{ + "model": "deepseek-v4-flash", + "configured_test_window": 8192, + "vendor_max_context_tested": false, + "detect": { + "error_code": "CONTEXT_COMPRESSION_REQUIRED", + "network_calls": 0 + }, + "compression": { + "passed": true, + "before_estimate": 2262, + "after_estimate": 516, + "archive_unchanged": true, + "network_calls": 2, + "answer_input_tokens": 129, + "answer_output_tokens": 289 + }, + "observed_provider_cache": { + "requests": 20, + "reporting_requests": 18, + "positive_hit_requests": 13, + "positive_miss_requests": 18, + "scope": "provider reported usage across this isolated acceptance session; not deterministic cache control" + } +} \ No newline at end of file diff --git a/docs/development/evidence/phase2-20260907/frontend-tests.txt b/docs/development/evidence/phase2-20260907/frontend-tests.txt new file mode 100644 index 0000000..f5e2e33 --- /dev/null +++ b/docs/development/evidence/phase2-20260907/frontend-tests.txt @@ -0,0 +1,27 @@ + +> notes-agent-frontend@0.2.0 test C:\Users\KiriAky\.codex\worktrees\7950\NotesAgent\frontend +> vitest run "--reporter=dot" + + + RUN v4.1.11 C:/Users/KiriAky/.codex/worktrees/7950/NotesAgent/frontend + +stderr | src/features/editor/shikiCodeMirror.spec.ts +Warning: KaTeX doesn't work in quirks mode. Make sure your website has a suitable doctype. + +stderr | src/features/themes/ThemesView.spec.ts +Warning: KaTeX doesn't work in quirks mode. Make sure your website has a suitable doctype. + +stderr | src/features/editor/diagramIntegration.spec.ts +Warning: KaTeX doesn't work in quirks mode. Make sure your website has a suitable doctype. +Warning: KaTeX doesn't work in quirks mode. Make sure your website has a suitable doctype. + +stderr | src/features/editor/EditorPane.spec.ts > EditorPane file switching > recreates the visual editor with the newly loaded file content +Warning: KaTeX doesn't work in quirks mode. Make sure your website has a suitable doctype. +Warning: KaTeX doesn't work in quirks mode. Make sure your website has a suitable doctype. + +····················································································································································································································································································································································································································································· + + Test Files 80 passed (80) + Tests 437 passed (437) + Start at 02:50:15 + Duration 21.99s (transform 24.03s, setup 0ms, import 75.03s, tests 27.89s, environment 143.74s) diff --git a/docs/development/evidence/phase2-20260907/integration.json b/docs/development/evidence/phase2-20260907/integration.json new file mode 100644 index 0000000..ac69b19 --- /dev/null +++ b/docs/development/evidence/phase2-20260907/integration.json @@ -0,0 +1,107 @@ +{ + "run_id": "benchmark_f98741e7649a", + "kind": "agent", + "dataset_id": "agent-integration-v1", + "dataset_hash": "sha256:af10b953037a35cad6fae678cbc118b519b209e88906f80721080560d49a508c", + "status": "completed", + "config_snapshot": { + "dataset_id": "agent-integration-v1", + "provider_id": "provider_65fd326edba646e3bc735c25fbf5ffe8", + "model": "deepseek-v4-flash", + "max_steps": 10, + "timeout_seconds": 150, + "token_budget": 10000, + "repeat": 1, + "allow_network": true, + "offline": false, + "dataset_hash": "sha256:af10b953037a35cad6fae678cbc118b519b209e88906f80721080560d49a508c", + "dataset_version": "1.0.0", + "execution": "real_agent_runtime", + "provider_type": "openai_compatible", + "scoring_version": "1.0", + "permission_policy": "runtime_user_decision", + "active_agent_run_id": "run_d55e28a22cb0433498ae59d0eabce566" + }, + "metrics": { + "total_cases": 2, + "task_success_rate": 1.0, + "tool_selection_accuracy": 1.0, + "tool_argument_accuracy": 1.0, + "invalid_tool_call_rate": 0.0, + "average_steps": 2.5, + "average_latency_ms": 10539.258899982087, + "token_usage": 16070, + "tool_calls": 6, + "expected_calls": 6 + }, + "cases": [ + { + "case_id": "retrieval-tasks", + "repeat": 0, + "agent_run_id": "run_dc5ad9f709c64b59b792bebafad77653", + "success": true, + "tool_calls": 5, + "expected_calls": 5, + "selected_calls": 5, + "accurate_calls": 5, + "invalid_calls": 0, + "steps": 3, + "latency_ms": 13835.580999962986, + "token_usage": 8651, + "checks": { + "completed": true, + "tools_selected": true, + "tool_arguments": true, + "no_extra_calls": true, + "tool_results": true, + "output": true, + "citation": true, + "tasks_created": true + }, + "error_code": null + }, + { + "case_id": "mcp-search", + "repeat": 0, + "agent_run_id": "run_d55e28a22cb0433498ae59d0eabce566", + "success": true, + "tool_calls": 1, + "expected_calls": 1, + "selected_calls": 1, + "accurate_calls": 1, + "invalid_calls": 0, + "steps": 2, + "latency_ms": 7242.936800001189, + "token_usage": 7419, + "checks": { + "completed": true, + "tools_selected": true, + "tool_arguments": true, + "no_extra_calls": true, + "tool_results": true, + "output": true, + "citation": true, + "tasks_created": true + }, + "error_code": null + } + ], + "error": null, + "error_code": null, + "permission_approvals": 3, + "mcp_present": true, + "tasks": [ + { + "task_id": "task_7380b0dd0b2c4161a20f3b6c1a19dffa", + "title": "验收-锁顺序" + }, + { + "task_id": "task_5e0584d287c442b597ca4c22f8998214", + "title": "验收-循环等待" + }, + { + "task_id": "task_22ac49c4e4214c98951b387a6f128a94", + "title": "验收-互斥条件" + } + ] +} \ No newline at end of file diff --git a/docs/development/evidence/phase2-20260907/live.json b/docs/development/evidence/phase2-20260907/live.json new file mode 100644 index 0000000..147946e --- /dev/null +++ b/docs/development/evidence/phase2-20260907/live.json @@ -0,0 +1,196 @@ +{ + "provider_id": "provider_65fd326edba646e3bc735c25fbf5ffe8", + "protocol": "openai_compatible", + "model": "deepseek-v4-flash", + "checks": { + "discovery": { + "passed": true, + "count": 3, + "elapsed_ms": 1904.77 + }, + "normal_chat": { + "passed": true, + "input_tokens": 87, + "output_tokens": 16, + "elapsed_ms": 2107.22 + }, + "stream_usage_reasoning": { + "passed": true, + "events": [ + "Done", + "TextDelta", + "ThinkingDelta", + "Usage" + ], + "usage_reported": true, + "reasoning_observed": true, + "elapsed_ms": 2118.0 + }, + "stream_cancel": { + "passed": true, + "first_event": "ThinkingDelta", + "close_ms": 12.160799989942461, + "scope": "client stream resource close; provider billing cessation not observable", + "elapsed_ms": 1842.69 + }, + "error_mapping": { + "passed": true, + "error_code": "PROVIDER_INVALID_REQUEST", + "elapsed_ms": 1590.6 + } + }, + "unconfigured_protocols": [ + "openai_responses", + "anthropic_messages", + "ollama" + ], + "not_tested": [ + "vendor_maximum_context_boundary" + ], + "max_direct_requests": 5, + "agent": { + "run_id": "benchmark_8ebbf8c6f27f", + "kind": "agent", + "dataset_id": "agent-core-v1", + "dataset_hash": "sha256:82fd1b84cc2dc2feec0fc67357d64b145dcdf6c44ec2660f182d550e85b9d27f", + "status": "completed", + "config_snapshot": { + "dataset_id": "agent-core-v1", + "provider_id": "provider_65fd326edba646e3bc735c25fbf5ffe8", + "model": "deepseek-v4-flash", + "max_steps": 6, + "timeout_seconds": 90, + "token_budget": 6000, + "repeat": 1, + "allow_network": false, + "offline": false, + "dataset_hash": "sha256:82fd1b84cc2dc2feec0fc67357d64b145dcdf6c44ec2660f182d550e85b9d27f", + "dataset_version": "1.0.0", + "execution": "real_agent_runtime", + "provider_type": "openai_compatible", + "scoring_version": "1.0", + "permission_policy": "runtime_user_decision", + "active_agent_run_id": "run_c9296aaea4c7429db97528edc72b6180" + }, + "metrics": { + "total_cases": 4, + "task_success_rate": 1.0, + "tool_selection_accuracy": 1.0, + "tool_argument_accuracy": 1.0, + "invalid_tool_call_rate": 0.0, + "average_steps": 1.75, + "average_latency_ms": 5682.329325005412, + "token_usage": 5888, + "tool_calls": 3, + "expected_calls": 3 + }, + "cases": [ + { + "case_id": "arithmetic", + "repeat": 0, + "agent_run_id": "run_480c159ffc0341f9883a09914ff003b5", + "success": true, + "tool_calls": 1, + "expected_calls": 1, + "selected_calls": 1, + "accurate_calls": 1, + "invalid_calls": 0, + "steps": 2, + "latency_ms": 4888.349500019103, + "token_usage": 1545, + "checks": { + "completed": true, + "tools_selected": true, + "tool_arguments": true, + "no_extra_calls": true, + "tool_results": true, + "output": true, + "citation": true, + "tasks_created": true + }, + "error_code": null + }, + { + "case_id": "echo", + "repeat": 0, + "agent_run_id": "run_4fdbd0508bb84bcda424d97de1b91241", + "success": true, + "tool_calls": 1, + "expected_calls": 1, + "selected_calls": 1, + "accurate_calls": 1, + "invalid_calls": 0, + "steps": 2, + "latency_ms": 4978.496600000653, + "token_usage": 1601, + "checks": { + "completed": true, + "tools_selected": true, + "tool_arguments": true, + "no_extra_calls": true, + "tool_results": true, + "output": true, + "citation": true, + "tasks_created": true + }, + "error_code": null + }, + { + "case_id": "no-tool", + "repeat": 0, + "agent_run_id": "run_5dfb4ae0a6c547b79706ad102e577bab", + "success": true, + "tool_calls": 0, + "expected_calls": 0, + "selected_calls": 0, + "accurate_calls": 0, + "invalid_calls": 0, + "steps": 1, + "latency_ms": 2549.696500005666, + "token_usage": 541, + "checks": { + "completed": true, + "tools_selected": true, + "tool_arguments": true, + "no_extra_calls": true, + "tool_results": true, + "output": true, + "citation": true, + "tasks_created": true + }, + "error_code": null + }, + { + "case_id": "markdown-catalog", + "repeat": 0, + "agent_run_id": "run_c9296aaea4c7429db97528edc72b6180", + "success": true, + "tool_calls": 1, + "expected_calls": 1, + "selected_calls": 1, + "accurate_calls": 1, + "invalid_calls": 0, + "steps": 2, + "latency_ms": 10312.774699996226, + "token_usage": 2201, + "checks": { + "completed": true, + "tools_selected": true, + "tool_arguments": true, + "no_extra_calls": true, + "tool_results": true, + "output": true, + "citation": true, + "tasks_created": true + }, + "error_code": null + } + ], + "error": null, + "error_code": null + }, + "followup_evidence": [ + "context.json", + "integration.json" + ] +} diff --git a/docs/development/evidence/phase2-20260907/rag-quality.json b/docs/development/evidence/phase2-20260907/rag-quality.json new file mode 100644 index 0000000..0053d15 --- /dev/null +++ b/docs/development/evidence/phase2-20260907/rag-quality.json @@ -0,0 +1,11750 @@ +{ + "fts": { + "run_id": "benchmark_5a6c4f239eb3", + "kind": "rag", + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "status": "completed", + "config_snapshot": { + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "dataset_version": "1.0.0", + "modes": [ + "fts" + ], + "retrieval": { + "top_k": 5, + "fusion": "rrf", + "rrf_k": 60, + "rerank": false, + "rerank_candidates": 20, + "score_threshold": 0.0 + }, + "repeat": 2, + "embedding": { + "policy": "per_case", + "details": "cases[].embedding" + }, + "local_embedding": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "version": "c721113d59a1d91b447450324f51c4b3332c924a", + "dim": 384 + }, + "reranker": { + "model_id": "lexical-v1", + "version": "1" + }, + "index_meta": { + "embedding_model": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "embedding_dim": "384", + "workspace_vectors_pending": "0" + }, + "app": { + "version": "0.1.0", + "environment": "development" + }, + "python": "3.13.9", + "metadata": { + "corpus_sha256": "be8e9ba0be7c96ce527f213e1ea26af43bf548c4a7e8c6e7592281c44f76f02d", + "split": "development; no held-out production claim" + } + }, + "metrics": { + "fts": { + "hit_at_1": 0.9583333333333334, + "hit_at_5": 1.0, + "recall_at_k": 1.0, + "mrr": 0.9666666666666667, + "citation_hit_rate": 0.75, + "p50_latency_ms": 9.638050018111244, + "p95_latency_ms": 12.203904980560765, + "total_cases": 48, + "successful_cases": 48, + "failed_cases": 0, + "failure_rate": 0.0 + } + }, + "cases": [ + { + "embedding": { + "source": "not_used" + }, + "case_id": "deadlock-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 10.198100004345179, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_ce08f371b1dcf2c5", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "deadlock-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 8.61979997716844, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_ce08f371b1dcf2c5", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "deadlock-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 10.850300022866577, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_1acd666aa1e79f96", + "note_da790c1c3b905f26", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_3f8c19cf88a03805", + "blk_abe4534c5c35b694", + "blk_d15c420ab15ba221", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "deadlock-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 10.8780000009574, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_1acd666aa1e79f96", + "note_da790c1c3b905f26", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_3f8c19cf88a03805", + "blk_abe4534c5c35b694", + "blk_d15c420ab15ba221", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "starvation-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 7.763999979943037, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "starvation-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 7.96810002066195, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "starvation-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 12.042899965308607, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_04142ad0124ae76d", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_3f8c19cf88a03805", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "starvation-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 13.909799978137016, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_04142ad0124ae76d", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_3f8c19cf88a03805", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "rrf-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 10.076700011268258, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_da790c1c3b905f26", + "note_df8b1e8216af7f9a", + "note_da790c1c3b905f26" + ], + "retrieved_block_ids": [ + "blk_a5e3dba263f588f8", + "blk_abe4534c5c35b694", + "blk_432c03c5f4212020", + "blk_064caf4b9c2e2518", + "blk_d15c420ab15ba221" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "rrf-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 8.595200022682548, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_da790c1c3b905f26", + "note_df8b1e8216af7f9a", + "note_da790c1c3b905f26" + ], + "retrieved_block_ids": [ + "blk_a5e3dba263f588f8", + "blk_abe4534c5c35b694", + "blk_432c03c5f4212020", + "blk_064caf4b9c2e2518", + "blk_d15c420ab15ba221" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "rrf-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 10.860100039280951, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8", + "blk_5eac426f6220ca29", + "blk_2788f9d5e6287c34" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "rrf-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 10.855100001208484, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8", + "blk_5eac426f6220ca29", + "blk_2788f9d5e6287c34" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "rerank-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 8.558499976061285, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_efa6b9f6210964c2", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "rerank-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 8.878499967977405, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_efa6b9f6210964c2", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "rerank-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 9.136800013948232, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_4a26a95db9b832fc", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_ce08f371b1dcf2c5", + "blk_a5e3dba263f588f8" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "rerank-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 8.018900000024587, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_4a26a95db9b832fc", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_ce08f371b1dcf2c5", + "blk_a5e3dba263f588f8" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "optimistic-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 7.770400028675795, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_cdc416a180e5099b", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_dc2b0771ea50c3a4", + "blk_b68f6945024f098d", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "optimistic-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 8.588500029873103, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_cdc416a180e5099b", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_dc2b0771ea50c3a4", + "blk_b68f6945024f098d", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "optimistic-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 11.447099968791008, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_e834cb35c46dcbfa", + "blk_5eac426f6220ca29", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "optimistic-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 12.84400001168251, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_e834cb35c46dcbfa", + "blk_5eac426f6220ca29", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "idempotency-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 7.663899974431843, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_bfd65995af95a6a6", + "note_4a26a95db9b832fc", + "note_894ec7d0760d0cd6" + ], + "retrieved_block_ids": [ + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_c4644f51b0853ef2", + "blk_ce08f371b1dcf2c5", + "blk_748b1be4cee7cb9b" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "idempotency-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 7.632600027136505, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_bfd65995af95a6a6", + "note_4a26a95db9b832fc", + "note_894ec7d0760d0cd6" + ], + "retrieved_block_ids": [ + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_c4644f51b0853ef2", + "blk_ce08f371b1dcf2c5", + "blk_748b1be4cee7cb9b" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "idempotency-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 10.404300002846867, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_48f96c75ea97d552", + "note_df8b1e8216af7f9a", + "note_894ec7d0760d0cd6", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_efa6b9f6210964c2", + "blk_064caf4b9c2e2518", + "blk_748b1be4cee7cb9b", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "idempotency-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 10.27790002990514, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_48f96c75ea97d552", + "note_df8b1e8216af7f9a", + "note_894ec7d0760d0cd6", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_efa6b9f6210964c2", + "blk_064caf4b9c2e2518", + "blk_748b1be4cee7cb9b", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "sse-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 9.087299986276776, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_3f7160f36792f3a1", + "blk_064caf4b9c2e2518", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "sse-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 8.208599989302456, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_3f7160f36792f3a1", + "blk_064caf4b9c2e2518", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "sse-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 11.035299976356328, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_b68f6945024f098d", + "blk_f6328254ea8624a1" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.2, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "sse-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 10.727200016845018, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_b68f6945024f098d", + "blk_f6328254ea8624a1" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.2, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "cancel-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 10.134500043932348, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_bfd65995af95a6a6", + "note_da790c1c3b905f26" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_c4644f51b0853ef2", + "blk_d15c420ab15ba221" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "cancel-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 9.627300023566931, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_bfd65995af95a6a6", + "note_da790c1c3b905f26" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_c4644f51b0853ef2", + "blk_d15c420ab15ba221" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "cancel-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 12.007399986032397, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d", + "note_894ec7d0760d0cd6", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_b68f6945024f098d", + "blk_3f8c19cf88a03805", + "blk_748b1be4cee7cb9b", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "cancel-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 10.957200021948665, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d", + "note_894ec7d0760d0cd6", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_b68f6945024f098d", + "blk_3f8c19cf88a03805", + "blk_748b1be4cee7cb9b", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "embedding-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 9.363000048324466, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_5eac426f6220ca29", + "blk_b68f6945024f098d", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "embedding-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 12.290599988773465, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_5eac426f6220ca29", + "blk_b68f6945024f098d", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "embedding-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 11.882799968589097, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_2788f9d5e6287c34", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "embedding-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 10.104500048328191, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_2788f9d5e6287c34", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "citation-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 8.12070001848042, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_f2a60de446d8b630", + "blk_5eac426f6220ca29", + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "citation-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 6.44500000635162, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_f2a60de446d8b630", + "blk_5eac426f6220ca29", + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "citation-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 8.619399974122643, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_f2a60de446d8b630", + "blk_064caf4b9c2e2518", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "citation-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 9.691299987025559, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_f2a60de446d8b630", + "blk_064caf4b9c2e2518", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "zip-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 7.710999983828515, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_bfd65995af95a6a6", + "note_1acd666aa1e79f96", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_ec785607e700a5c0", + "blk_ce08f371b1dcf2c5", + "blk_c6c78e0831003670", + "blk_abe4534c5c35b694", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "zip-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 7.523700012825429, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_bfd65995af95a6a6", + "note_1acd666aa1e79f96", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_ec785607e700a5c0", + "blk_ce08f371b1dcf2c5", + "blk_c6c78e0831003670", + "blk_abe4534c5c35b694", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "zip-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 9.566100023221225, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_b68f6945024f098d", + "blk_ec785607e700a5c0", + "blk_dc2b0771ea50c3a4", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "zip-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 11.646599974483252, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_b68f6945024f098d", + "blk_ec785607e700a5c0", + "blk_dc2b0771ea50c3a4", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "plot-0", + "mode": "fts", + "repeat": 0, + "latency_ms": 9.648800012655556, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32", + "note_da790c1c3b905f26", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_5bfc319608e9cee0", + "blk_d15c420ab15ba221", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "plot-0", + "mode": "fts", + "repeat": 1, + "latency_ms": 8.502600016072392, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32", + "note_da790c1c3b905f26", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_5bfc319608e9cee0", + "blk_d15c420ab15ba221", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "plot-1", + "mode": "fts", + "repeat": 0, + "latency_ms": 10.013899998739362, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_04142ad0124ae76d", + "note_1acd666aa1e79f96", + "note_da790c1c3b905f26", + "note_894ec7d0760d0cd6" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_3f8c19cf88a03805", + "blk_abe4534c5c35b694", + "blk_d15c420ab15ba221", + "blk_748b1be4cee7cb9b" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "not_used" + }, + "case_id": "plot-1", + "mode": "fts", + "repeat": 1, + "latency_ms": 9.062100027222186, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_04142ad0124ae76d", + "note_1acd666aa1e79f96", + "note_da790c1c3b905f26", + "note_894ec7d0760d0cd6" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_3f8c19cf88a03805", + "blk_abe4534c5c35b694", + "blk_d15c420ab15ba221", + "blk_748b1be4cee7cb9b" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + } + ], + "error": null, + "error_code": null + }, + "vector": { + "run_id": "benchmark_2028aee8b573", + "kind": "rag", + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "status": "completed", + "config_snapshot": { + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "dataset_version": "1.0.0", + "modes": [ + "vector" + ], + "retrieval": { + "top_k": 5, + "fusion": "rrf", + "rrf_k": 60, + "rerank": false, + "rerank_candidates": 20, + "score_threshold": 0.0 + }, + "repeat": 2, + "embedding": { + "policy": "per_case", + "details": "cases[].embedding" + }, + "local_embedding": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "version": "c721113d59a1d91b447450324f51c4b3332c924a", + "dim": 384 + }, + "reranker": { + "model_id": "lexical-v1", + "version": "1" + }, + "index_meta": { + "embedding_model": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "embedding_dim": "384", + "workspace_vectors_pending": "0" + }, + "app": { + "version": "0.1.0", + "environment": "development" + }, + "python": "3.13.9", + "metadata": { + "corpus_sha256": "be8e9ba0be7c96ce527f213e1ea26af43bf548c4a7e8c6e7592281c44f76f02d", + "split": "development; no held-out production claim" + } + }, + "metrics": { + "vector": { + "hit_at_1": 1.0, + "hit_at_5": 1.0, + "recall_at_k": 1.0, + "mrr": 1.0, + "citation_hit_rate": 0.5833333333333334, + "p50_latency_ms": 4571.744949993445, + "p95_latency_ms": 10284.191175037995, + "total_cases": 48, + "successful_cases": 48, + "failed_cases": 0, + "failure_rate": 0.0 + } + }, + "cases": [ + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 9157.754899992142, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_efa6b9f6210964c2", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 16.02660003118217, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_efa6b9f6210964c2", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 9208.04609998595, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_ff8dd1be067854a7", + "blk_3f8c19cf88a03805", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 12.92400003876537, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_ff8dd1be067854a7", + "blk_3f8c19cf88a03805", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 9391.045500000473, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_894ec7d0760d0cd6" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_ce08f371b1dcf2c5", + "blk_3f8c19cf88a03805", + "blk_748b1be4cee7cb9b" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 14.709699957165867, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_894ec7d0760d0cd6" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_ce08f371b1dcf2c5", + "blk_3f8c19cf88a03805", + "blk_748b1be4cee7cb9b" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 9808.362899988424, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_4a26a95db9b832fc" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_ce08f371b1dcf2c5" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 15.165499993599951, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_4a26a95db9b832fc" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_ce08f371b1dcf2c5" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 9104.640499979723, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_a5e3dba263f588f8", + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 15.831600001547486, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_a5e3dba263f588f8", + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 9176.87389999628, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_2788f9d5e6287c34", + "blk_a5e3dba263f588f8", + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 14.966599992476404, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_2788f9d5e6287c34", + "blk_a5e3dba263f588f8", + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 9133.037099963985, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_dc2b0771ea50c3a4", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 16.24160003848374, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_dc2b0771ea50c3a4", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 9185.094400017988, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_a5e3dba263f588f8" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 15.247399976942688, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_a5e3dba263f588f8" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 9292.875599989202, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_a0b8289f7f334952", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_f2a60de446d8b630", + "blk_dc2b0771ea50c3a4", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 14.421100029721856, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_a0b8289f7f334952", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_f2a60de446d8b630", + "blk_dc2b0771ea50c3a4", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 9225.853099953383, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 14.767799992114305, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 10237.51240002457, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_5cf7aef15e17bd32", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_84afb04006190ead", + "blk_f6328254ea8624a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 16.680400003679097, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_5cf7aef15e17bd32", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_84afb04006190ead", + "blk_f6328254ea8624a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 13356.72889999114, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_3f7160f36792f3a1", + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 38.84940000716597, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_3f7160f36792f3a1", + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 21347.127699991688, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 17.397299990989268, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 10309.325900045224, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_5bfc319608e9cee0", + "blk_84afb04006190ead", + "blk_f6328254ea8624a1", + "blk_7b697512f3d11a1d", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 15.522899979259819, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_5bfc319608e9cee0", + "blk_84afb04006190ead", + "blk_f6328254ea8624a1", + "blk_7b697512f3d11a1d", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 10054.351200000383, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 15.64709999365732, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 9987.820700043812, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_04142ad0124ae76d", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_3f8c19cf88a03805", + "blk_7b697512f3d11a1d", + "blk_5bfc319608e9cee0" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 15.249199990648776, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_04142ad0124ae76d", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_3f8c19cf88a03805", + "blk_7b697512f3d11a1d", + "blk_5bfc319608e9cee0" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 9889.74470004905, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_84afb04006190ead", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 12.628600001335144, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_84afb04006190ead", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 9258.062000037171, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_2788f9d5e6287c34", + "blk_abe4534c5c35b694", + "blk_064caf4b9c2e2518", + "blk_c4644f51b0853ef2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 13.0928999860771, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_2788f9d5e6287c34", + "blk_abe4534c5c35b694", + "blk_064caf4b9c2e2518", + "blk_c4644f51b0853ef2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 9243.302999995649, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_04142ad0124ae76d", + "note_cdc416a180e5099b", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_f2a60de446d8b630", + "blk_5eac426f6220ca29", + "blk_3f8c19cf88a03805", + "blk_3f7160f36792f3a1", + "blk_c4644f51b0853ef2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 15.68569999653846, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_04142ad0124ae76d", + "note_cdc416a180e5099b", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_f2a60de446d8b630", + "blk_5eac426f6220ca29", + "blk_3f8c19cf88a03805", + "blk_3f7160f36792f3a1", + "blk_c4644f51b0853ef2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 9782.767299970146, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_f2a60de446d8b630", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 14.568900049198419, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_f2a60de446d8b630", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 9433.200299972668, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_ec785607e700a5c0", + "blk_ce08f371b1dcf2c5", + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_c4644f51b0853ef2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 14.684900001157075, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_ec785607e700a5c0", + "blk_ce08f371b1dcf2c5", + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_c4644f51b0853ef2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 9682.491299987305, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_5cf7aef15e17bd32", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_f6328254ea8624a1", + "blk_ec785607e700a5c0", + "blk_3f8c19cf88a03805", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 14.825299964286387, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_5cf7aef15e17bd32", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_f6328254ea8624a1", + "blk_ec785607e700a5c0", + "blk_3f8c19cf88a03805", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-0", + "mode": "vector", + "repeat": 0, + "latency_ms": 9925.786300038453, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_6e13fbe17c9f7a30", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_c6c78e0831003670", + "blk_c4644f51b0853ef2", + "blk_2788f9d5e6287c34", + "blk_efa6b9f6210964c2", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-0", + "mode": "vector", + "repeat": 1, + "latency_ms": 12.584000010974705, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_6e13fbe17c9f7a30", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_c6c78e0831003670", + "blk_c4644f51b0853ef2", + "blk_2788f9d5e6287c34", + "blk_efa6b9f6210964c2", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "miss", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-1", + "mode": "vector", + "repeat": 0, + "latency_ms": 9515.448999998625, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_04142ad0124ae76d", + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_c6c78e0831003670", + "blk_3f8c19cf88a03805", + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-1", + "mode": "vector", + "repeat": 1, + "latency_ms": 15.061200014315546, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_04142ad0124ae76d", + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_c6c78e0831003670", + "blk_3f8c19cf88a03805", + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + } + ], + "error": null, + "error_code": null + }, + "hybrid-weighted": { + "run_id": "benchmark_51ef998a3242", + "kind": "rag", + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "status": "completed", + "config_snapshot": { + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "dataset_version": "1.0.0", + "modes": [ + "hybrid" + ], + "retrieval": { + "top_k": 5, + "fusion": "weighted", + "rrf_k": 60, + "rerank": false, + "rerank_candidates": 20, + "score_threshold": 0.0 + }, + "repeat": 2, + "embedding": { + "policy": "per_case", + "details": "cases[].embedding" + }, + "local_embedding": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "version": "c721113d59a1d91b447450324f51c4b3332c924a", + "dim": 384 + }, + "reranker": { + "model_id": "lexical-v1", + "version": "1" + }, + "index_meta": { + "embedding_model": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "embedding_dim": "384", + "workspace_vectors_pending": "0" + }, + "app": { + "version": "0.1.0", + "environment": "development" + }, + "python": "3.13.9", + "metadata": { + "corpus_sha256": "be8e9ba0be7c96ce527f213e1ea26af43bf548c4a7e8c6e7592281c44f76f02d", + "split": "development; no held-out production claim" + } + }, + "metrics": { + "hybrid": { + "hit_at_1": 0.9583333333333334, + "hit_at_5": 1.0, + "recall_at_k": 1.0, + "mrr": 0.96875, + "citation_hit_rate": 0.6666666666666666, + "p50_latency_ms": 15.436549991136417, + "p95_latency_ms": 19.694495006115176, + "total_cases": 48, + "successful_cases": 48, + "failed_cases": 0, + "failure_rate": 0.0 + } + }, + "cases": [ + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.839500014204532, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_cdc416a180e5099b", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_b68f6945024f098d", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 16.484799969475716, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_cdc416a180e5099b", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_b68f6945024f098d", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 18.049800011795014, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_da790c1c3b905f26", + "note_894ec7d0760d0cd6", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_3f8c19cf88a03805", + "blk_d15c420ab15ba221", + "blk_ff8dd1be067854a7", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 18.06110003963113, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_da790c1c3b905f26", + "note_894ec7d0760d0cd6", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_3f8c19cf88a03805", + "blk_d15c420ab15ba221", + "blk_ff8dd1be067854a7", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 19.66829999582842, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_894ec7d0760d0cd6" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_ce08f371b1dcf2c5", + "blk_3f8c19cf88a03805", + "blk_748b1be4cee7cb9b" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.93320002919063, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_894ec7d0760d0cd6" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_ce08f371b1dcf2c5", + "blk_3f8c19cf88a03805", + "blk_748b1be4cee7cb9b" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 19.85340000828728, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 16.443900007288903, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 13.740300026256591, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_df8b1e8216af7f9a", + "note_da790c1c3b905f26", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_a5e3dba263f588f8", + "blk_abe4534c5c35b694", + "blk_064caf4b9c2e2518", + "blk_432c03c5f4212020", + "blk_c4644f51b0853ef2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 12.951200013048947, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_df8b1e8216af7f9a", + "note_da790c1c3b905f26", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_a5e3dba263f588f8", + "blk_abe4534c5c35b694", + "blk_064caf4b9c2e2518", + "blk_432c03c5f4212020", + "blk_c4644f51b0853ef2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 13.957499992102385, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8", + "blk_2788f9d5e6287c34", + "blk_064caf4b9c2e2518" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.939500018954277, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8", + "blk_2788f9d5e6287c34", + "blk_064caf4b9c2e2518" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.237800020258874, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552", + "note_6e13fbe17c9f7a30", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_efa6b9f6210964c2", + "blk_dc2b0771ea50c3a4", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 21.614700031932443, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552", + "note_6e13fbe17c9f7a30", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_efa6b9f6210964c2", + "blk_dc2b0771ea50c3a4", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.422699983697385, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_4a26a95db9b832fc" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_84afb04006190ead", + "blk_ce08f371b1dcf2c5" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 13.633299968205392, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_4a26a95db9b832fc" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_84afb04006190ead", + "blk_ce08f371b1dcf2c5" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.735500026494265, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_dc2b0771ea50c3a4", + "blk_5eac426f6220ca29", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 18.51730002090335, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_dc2b0771ea50c3a4", + "blk_5eac426f6220ca29", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.222500009462237, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_b68f6945024f098d", + "blk_f6328254ea8624a1", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 19.408599997404963, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_b68f6945024f098d", + "blk_f6328254ea8624a1", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.764299965463579, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_5cf7aef15e17bd32", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_84afb04006190ead", + "blk_f6328254ea8624a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 13.781399989966303, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_5cf7aef15e17bd32", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_84afb04006190ead", + "blk_f6328254ea8624a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.382900019176304, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_f6328254ea8624a1", + "blk_84afb04006190ead" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 13.9435000019148, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_f6328254ea8624a1", + "blk_84afb04006190ead" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 13.594799966085702, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 16.04160002898425, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 13.6027000262402, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_d15c420ab15ba221", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.25, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 16.95280004059896, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_d15c420ab15ba221", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.25, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 13.98340001469478, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.146700035780668, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.434599957894534, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_04142ad0124ae76d", + "note_cdc416a180e5099b", + "note_894ec7d0760d0cd6" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_3f8c19cf88a03805", + "blk_b68f6945024f098d", + "blk_748b1be4cee7cb9b" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.960199991241097, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_04142ad0124ae76d", + "note_cdc416a180e5099b", + "note_894ec7d0760d0cd6" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_3f8c19cf88a03805", + "blk_b68f6945024f098d", + "blk_748b1be4cee7cb9b" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 19.708600011654198, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.590099967084825, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.215800016652793, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_2788f9d5e6287c34", + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 18.72470002854243, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_2788f9d5e6287c34", + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.515900042373687, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_04142ad0124ae76d", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_f2a60de446d8b630", + "blk_5eac426f6220ca29", + "blk_3f8c19cf88a03805", + "blk_dc2b0771ea50c3a4", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.49019996309653, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_04142ad0124ae76d", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_f2a60de446d8b630", + "blk_5eac426f6220ca29", + "blk_3f8c19cf88a03805", + "blk_dc2b0771ea50c3a4", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.52560001052916, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_064caf4b9c2e2518", + "blk_f2a60de446d8b630", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.35369998216629, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_064caf4b9c2e2518", + "blk_f2a60de446d8b630", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 19.509100005961955, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_ec785607e700a5c0", + "blk_ce08f371b1dcf2c5", + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_c6c78e0831003670" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 13.297000026796013, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_ec785607e700a5c0", + "blk_ce08f371b1dcf2c5", + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_c6c78e0831003670" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.641700010746717, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_b68f6945024f098d", + "blk_ec785607e700a5c0", + "blk_3f8c19cf88a03805", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.604999974835664, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_b68f6945024f098d", + "blk_ec785607e700a5c0", + "blk_3f8c19cf88a03805", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.29320004535839, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_6e13fbe17c9f7a30", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_c6c78e0831003670", + "blk_c4644f51b0853ef2", + "blk_2788f9d5e6287c34", + "blk_efa6b9f6210964c2", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.002300031483173, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_6e13fbe17c9f7a30", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_c6c78e0831003670", + "blk_c4644f51b0853ef2", + "blk_2788f9d5e6287c34", + "blk_efa6b9f6210964c2", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.9097000034526, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_04142ad0124ae76d", + "note_bfd65995af95a6a6", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_3f8c19cf88a03805", + "blk_c6c78e0831003670", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.798099979292601, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_04142ad0124ae76d", + "note_bfd65995af95a6a6", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_3f8c19cf88a03805", + "blk_c6c78e0831003670", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + } + ], + "error": null, + "error_code": null + }, + "hybrid-rrf": { + "run_id": "benchmark_d83eea0d766a", + "kind": "rag", + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "status": "completed", + "config_snapshot": { + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "dataset_version": "1.0.0", + "modes": [ + "hybrid" + ], + "retrieval": { + "top_k": 5, + "fusion": "rrf", + "rrf_k": 60, + "rerank": false, + "rerank_candidates": 20, + "score_threshold": 0.0 + }, + "repeat": 2, + "embedding": { + "policy": "per_case", + "details": "cases[].embedding" + }, + "local_embedding": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "version": "c721113d59a1d91b447450324f51c4b3332c924a", + "dim": 384 + }, + "reranker": { + "model_id": "lexical-v1", + "version": "1" + }, + "index_meta": { + "embedding_model": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "embedding_dim": "384", + "workspace_vectors_pending": "0" + }, + "app": { + "version": "0.1.0", + "environment": "development" + }, + "python": "3.13.9", + "metadata": { + "corpus_sha256": "be8e9ba0be7c96ce527f213e1ea26af43bf548c4a7e8c6e7592281c44f76f02d", + "split": "development; no held-out production claim" + } + }, + "metrics": { + "hybrid": { + "hit_at_1": 0.9166666666666666, + "hit_at_5": 1.0, + "recall_at_k": 1.0, + "mrr": 0.9583333333333334, + "citation_hit_rate": 0.6666666666666666, + "p50_latency_ms": 15.443600015714765, + "p95_latency_ms": 20.275999984005466, + "total_cases": 48, + "successful_cases": 48, + "failed_cases": 0, + "failure_rate": 0.0 + } + }, + "cases": [ + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.709700015373528, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.912199974991381, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.348199998494238, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_3f8c19cf88a03805", + "blk_ff8dd1be067854a7", + "blk_d15c420ab15ba221", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.392899989616126, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_3f8c19cf88a03805", + "blk_ff8dd1be067854a7", + "blk_d15c420ab15ba221", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.231500019785017, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_ce08f371b1dcf2c5", + "blk_3f8c19cf88a03805", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 17.13029999518767, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_ce08f371b1dcf2c5", + "blk_3f8c19cf88a03805", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.900000009220093, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 13.991499959956855, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.77439996274188, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_a5e3dba263f588f8", + "blk_abe4534c5c35b694", + "blk_064caf4b9c2e2518", + "blk_c4644f51b0853ef2", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 13.726399978622794, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_a5e3dba263f588f8", + "blk_abe4534c5c35b694", + "blk_064caf4b9c2e2518", + "blk_c4644f51b0853ef2", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 19.99000000068918, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8", + "blk_2788f9d5e6287c34", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 16.68890001019463, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8", + "blk_2788f9d5e6287c34", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.249400002881885, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_dc2b0771ea50c3a4", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.447800000198185, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_dc2b0771ea50c3a4", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.351500012911856, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_84afb04006190ead", + "blk_a5e3dba263f588f8" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 20.42999997502193, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_84afb04006190ead", + "blk_a5e3dba263f588f8" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 17.57780002662912, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_dc2b0771ea50c3a4", + "blk_5eac426f6220ca29", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.827800041530281, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_dc2b0771ea50c3a4", + "blk_5eac426f6220ca29", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.083200007211417, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_5cf7aef15e17bd32", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_f6328254ea8624a1", + "blk_5eac426f6220ca29", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 13.954599970020354, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_5cf7aef15e17bd32", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_f6328254ea8624a1", + "blk_5eac426f6220ca29", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.237999974284321, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_84afb04006190ead", + "blk_c4644f51b0853ef2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 18.67269998183474, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_84afb04006190ead", + "blk_c4644f51b0853ef2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.285999979823828, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_064caf4b9c2e2518", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 16.366399999242276, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_064caf4b9c2e2518", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 13.17900000140071, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.84720000391826, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.049299992620945, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2", + "blk_b68f6945024f098d", + "blk_5bfc319608e9cee0" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.535700018517673, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2", + "blk_b68f6945024f098d", + "blk_5bfc319608e9cee0" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 17.063899955246598, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_da790c1c3b905f26", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_d15c420ab15ba221", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.65899997856468, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_da790c1c3b905f26", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_d15c420ab15ba221", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 22.916799993254244, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_04142ad0124ae76d", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_3f8c19cf88a03805", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d", + "blk_5bfc319608e9cee0" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 17.554599966388196, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_04142ad0124ae76d", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_3f8c19cf88a03805", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d", + "blk_5bfc319608e9cee0" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 18.017099995631725, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_b68f6945024f098d", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.910099959000945, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_b68f6945024f098d", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.750500035006553, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_2788f9d5e6287c34", + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 12.897000007797033, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_2788f9d5e6287c34", + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 20.705999981146306, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_04142ad0124ae76d", + "note_6e13fbe17c9f7a30", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_f2a60de446d8b630", + "blk_5eac426f6220ca29", + "blk_3f8c19cf88a03805", + "blk_dc2b0771ea50c3a4", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.643499976955354, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_04142ad0124ae76d", + "note_6e13fbe17c9f7a30", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_f2a60de446d8b630", + "blk_5eac426f6220ca29", + "blk_3f8c19cf88a03805", + "blk_dc2b0771ea50c3a4", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 18.65019998513162, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_064caf4b9c2e2518", + "blk_f2a60de446d8b630", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.863000018522143, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_064caf4b9c2e2518", + "blk_f2a60de446d8b630", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.355799976736307, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ec785607e700a5c0", + "blk_ce08f371b1dcf2c5", + "blk_c6c78e0831003670", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 13.86780000757426, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ec785607e700a5c0", + "blk_ce08f371b1dcf2c5", + "blk_c6c78e0831003670", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 18.96780001698062, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_ec785607e700a5c0", + "blk_b68f6945024f098d", + "blk_3f8c19cf88a03805", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.684800047893077, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_ec785607e700a5c0", + "blk_b68f6945024f098d", + "blk_3f8c19cf88a03805", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.664000016637146, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_1acd666aa1e79f96", + "note_5cf7aef15e17bd32", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_abe4534c5c35b694", + "blk_5bfc319608e9cee0", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 13.755299965851009, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_1acd666aa1e79f96", + "note_5cf7aef15e17bd32", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_abe4534c5c35b694", + "blk_5bfc319608e9cee0", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 17.891299969051033, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_cdc416a180e5099b", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_b68f6945024f098d", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.99529996747151, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_cdc416a180e5099b", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_b68f6945024f098d", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + } + ], + "error": null, + "error_code": null + }, + "hybrid-rerank": { + "run_id": "benchmark_25fcd4245d85", + "kind": "rag", + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "status": "completed", + "config_snapshot": { + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "dataset_version": "1.0.0", + "modes": [ + "hybrid" + ], + "retrieval": { + "top_k": 5, + "fusion": "rrf", + "rrf_k": 60, + "rerank": true, + "rerank_candidates": 20, + "score_threshold": 0.0 + }, + "repeat": 2, + "embedding": { + "policy": "per_case", + "details": "cases[].embedding" + }, + "local_embedding": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "version": "c721113d59a1d91b447450324f51c4b3332c924a", + "dim": 384 + }, + "reranker": { + "model_id": "lexical-v1", + "version": "1" + }, + "index_meta": { + "embedding_model": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "embedding_dim": "384", + "workspace_vectors_pending": "0" + }, + "app": { + "version": "0.1.0", + "environment": "development" + }, + "python": "3.13.9", + "metadata": { + "corpus_sha256": "be8e9ba0be7c96ce527f213e1ea26af43bf548c4a7e8c6e7592281c44f76f02d", + "split": "development; no held-out production claim" + } + }, + "metrics": { + "hybrid": { + "hit_at_1": 0.9166666666666666, + "hit_at_5": 1.0, + "recall_at_k": 1.0, + "mrr": 0.9583333333333334, + "citation_hit_rate": 0.875, + "p50_latency_ms": 15.696999995270744, + "p95_latency_ms": 22.259169968310744, + "total_cases": 48, + "successful_cases": 48, + "failed_cases": 0, + "failure_rate": 0.0 + } + }, + "cases": [ + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 12.92270002886653, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.154200034681708, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.112900000531226, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_da790c1c3b905f26", + "note_5cf7aef15e17bd32", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_3f8c19cf88a03805", + "blk_d15c420ab15ba221", + "blk_f6328254ea8624a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 16.520500008482486, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_da790c1c3b905f26", + "note_5cf7aef15e17bd32", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_3f8c19cf88a03805", + "blk_d15c420ab15ba221", + "blk_f6328254ea8624a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.599000024143606, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_ce08f371b1dcf2c5", + "blk_3f8c19cf88a03805", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.040899976156652, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_ce08f371b1dcf2c5", + "blk_3f8c19cf88a03805", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.93889995617792, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.77030000044033, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.204399951267987, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_a5e3dba263f588f8", + "blk_064caf4b9c2e2518", + "blk_c4644f51b0853ef2", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.712999982293695, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_a5e3dba263f588f8", + "blk_064caf4b9c2e2518", + "blk_c4644f51b0853ef2", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.316499979235232, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8", + "blk_5eac426f6220ca29", + "blk_2788f9d5e6287c34" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.064599992707372, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8", + "blk_5eac426f6220ca29", + "blk_2788f9d5e6287c34" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.097900002729148, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_dc2b0771ea50c3a4", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.000400027725846, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_dc2b0771ea50c3a4", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.597899968270212, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_4a26a95db9b832fc" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_84afb04006190ead", + "blk_ce08f371b1dcf2c5" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 22.513300005812198, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_4a26a95db9b832fc" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_84afb04006190ead", + "blk_ce08f371b1dcf2c5" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 17.64449995243922, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_dc2b0771ea50c3a4", + "blk_5eac426f6220ca29", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.694100013002753, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_dc2b0771ea50c3a4", + "blk_5eac426f6220ca29", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 23.406099993735552, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_5eac426f6220ca29", + "blk_b68f6945024f098d", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.817500010598451, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_5eac426f6220ca29", + "blk_b68f6945024f098d", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 13.998600014019758, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_4a26a95db9b832fc", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_3f7160f36792f3a1", + "blk_84afb04006190ead", + "blk_ce08f371b1dcf2c5", + "blk_c4644f51b0853ef2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.076500025112182, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_4a26a95db9b832fc", + "note_bfd65995af95a6a6" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_3f7160f36792f3a1", + "blk_84afb04006190ead", + "blk_ce08f371b1dcf2c5", + "blk_c4644f51b0853ef2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 21.506499964743853, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552", + "note_6e13fbe17c9f7a30", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_064caf4b9c2e2518", + "blk_efa6b9f6210964c2", + "blk_dc2b0771ea50c3a4", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.397799958009273, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552", + "note_6e13fbe17c9f7a30", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_064caf4b9c2e2518", + "blk_efa6b9f6210964c2", + "blk_dc2b0771ea50c3a4", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.891199989710003, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.141600044444203, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.845199999399483, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552", + "note_da790c1c3b905f26", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_7b697512f3d11a1d", + "blk_d15c420ab15ba221", + "blk_b68f6945024f098d" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 17.27549999486655, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552", + "note_da790c1c3b905f26", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_7b697512f3d11a1d", + "blk_d15c420ab15ba221", + "blk_b68f6945024f098d" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.004699980840087, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_da790c1c3b905f26", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_d15c420ab15ba221", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.850500039756298, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_da790c1c3b905f26", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_d15c420ab15ba221", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 20.054300024639815, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_04142ad0124ae76d", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_3f8c19cf88a03805", + "blk_b68f6945024f098d", + "blk_f6328254ea8624a1", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 17.28749996982515, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_04142ad0124ae76d", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_3f8c19cf88a03805", + "blk_b68f6945024f098d", + "blk_f6328254ea8624a1", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 13.435999979265034, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_4a26a95db9b832fc" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_b68f6945024f098d", + "blk_84afb04006190ead", + "blk_ce08f371b1dcf2c5" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 17.89179997285828, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_4a26a95db9b832fc" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_b68f6945024f098d", + "blk_84afb04006190ead", + "blk_ce08f371b1dcf2c5" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.40419999603182, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_2788f9d5e6287c34", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.948099968023598, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_2788f9d5e6287c34", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.623699990101159, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_f2a60de446d8b630", + "blk_dc2b0771ea50c3a4", + "blk_abe4534c5c35b694", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 17.604900000151247, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_f2a60de446d8b630", + "blk_dc2b0771ea50c3a4", + "blk_abe4534c5c35b694", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.027599980589002, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_064caf4b9c2e2518", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4", + "blk_f2a60de446d8b630" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 21.879699954297394, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_064caf4b9c2e2518", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4", + "blk_f2a60de446d8b630" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 18.141299951821566, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_ec785607e700a5c0", + "blk_c6c78e0831003670", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 22.463499975856394, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_ec785607e700a5c0", + "blk_c6c78e0831003670", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 19.48630000697449, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_4a26a95db9b832fc", + "note_48f96c75ea97d552", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_b68f6945024f098d", + "blk_ec785607e700a5c0", + "blk_efa6b9f6210964c2", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 21.033200027886778, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_4a26a95db9b832fc", + "note_48f96c75ea97d552", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_b68f6945024f098d", + "blk_ec785607e700a5c0", + "blk_efa6b9f6210964c2", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.377300034742802, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_1acd666aa1e79f96", + "note_5cf7aef15e17bd32", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_abe4534c5c35b694", + "blk_5bfc319608e9cee0", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 17.488300043623894, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_1acd666aa1e79f96", + "note_5cf7aef15e17bd32", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_abe4534c5c35b694", + "blk_5bfc319608e9cee0", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 17.353400005958974, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_f6328254ea8624a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.95079998858273, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_f6328254ea8624a1", + "blk_abe4534c5c35b694" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + } + ], + "error": null, + "error_code": null + }, + "rrf-k20": { + "run_id": "benchmark_df80ad9f9c98", + "kind": "rag", + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "status": "completed", + "config_snapshot": { + "dataset_id": "rag-phase2-v1", + "dataset_hash": "sha256:c269104de2ea4b11ec6b5b10c7f953e21255c111385a430675340e1e7f74defd", + "dataset_version": "1.0.0", + "modes": [ + "hybrid" + ], + "retrieval": { + "top_k": 5, + "fusion": "rrf", + "rrf_k": 20, + "rerank": false, + "rerank_candidates": 20, + "score_threshold": 0.0 + }, + "repeat": 2, + "embedding": { + "policy": "per_case", + "details": "cases[].embedding" + }, + "local_embedding": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "version": "c721113d59a1d91b447450324f51c4b3332c924a", + "dim": 384 + }, + "reranker": { + "model_id": "lexical-v1", + "version": "1" + }, + "index_meta": { + "embedding_model": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "embedding_dim": "384", + "workspace_vectors_pending": "0" + }, + "app": { + "version": "0.1.0", + "environment": "development" + }, + "python": "3.13.9", + "metadata": { + "corpus_sha256": "be8e9ba0be7c96ce527f213e1ea26af43bf548c4a7e8c6e7592281c44f76f02d", + "split": "development; no held-out production claim" + } + }, + "metrics": { + "hybrid": { + "hit_at_1": 0.9166666666666666, + "hit_at_5": 1.0, + "recall_at_k": 1.0, + "mrr": 0.9583333333333334, + "citation_hit_rate": 0.6666666666666666, + "p50_latency_ms": 15.681950026191771, + "p95_latency_ms": 27.66975500271655, + "total_cases": 48, + "successful_cases": 48, + "failed_cases": 0, + "failure_rate": 0.0 + } + }, + "cases": [ + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.36730002751574, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.059400011319667, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_ff8dd1be067854a7", + "blk_748b1be4cee7cb9b", + "blk_d15c420ab15ba221", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.237799962051213, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_3f8c19cf88a03805", + "blk_ff8dd1be067854a7", + "blk_d15c420ab15ba221", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "deadlock-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 16.19379996554926, + "retrieved_note_ids": [ + "note_894ec7d0760d0cd6", + "note_04142ad0124ae76d", + "note_894ec7d0760d0cd6", + "note_da790c1c3b905f26", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_748b1be4cee7cb9b", + "blk_3f8c19cf88a03805", + "blk_ff8dd1be067854a7", + "blk_d15c420ab15ba221", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 17.44149997830391, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_ce08f371b1dcf2c5", + "blk_3f8c19cf88a03805", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.13749996572733, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_da790c1c3b905f26", + "note_4a26a95db9b832fc", + "note_04142ad0124ae76d", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_432c03c5f4212020", + "blk_ce08f371b1dcf2c5", + "blk_3f8c19cf88a03805", + "blk_abe4534c5c35b694" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.245099959429353, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "starvation-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 17.372199974488467, + "retrieved_note_ids": [ + "note_da790c1c3b905f26", + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_d15c420ab15ba221", + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 12.807799968868494, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_a5e3dba263f588f8", + "blk_abe4534c5c35b694", + "blk_064caf4b9c2e2518", + "blk_c4644f51b0853ef2", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.922100002877414, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_1acd666aa1e79f96", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_a5e3dba263f588f8", + "blk_abe4534c5c35b694", + "blk_064caf4b9c2e2518", + "blk_c4644f51b0853ef2", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.473099996801466, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8", + "blk_2788f9d5e6287c34", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rrf-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 13.339199998881668, + "retrieved_note_ids": [ + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_1acd666aa1e79f96", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_abe4534c5c35b694", + "blk_dc2b0771ea50c3a4", + "blk_a5e3dba263f588f8", + "blk_2788f9d5e6287c34", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 13.935800001490861, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_dc2b0771ea50c3a4", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 13.545800000429153, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_6e13fbe17c9f7a30", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_dc2b0771ea50c3a4", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.212799993809313, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_84afb04006190ead", + "blk_a5e3dba263f588f8" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "rerank-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 17.347100016195327, + "retrieved_note_ids": [ + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96" + ], + "retrieved_block_ids": [ + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29", + "blk_84afb04006190ead", + "blk_a5e3dba263f588f8" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 13.853699958417565, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_dc2b0771ea50c3a4", + "blk_5eac426f6220ca29", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 17.0090000028722, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_6e13fbe17c9f7a30", + "note_a0b8289f7f334952", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_dc2b0771ea50c3a4", + "blk_5eac426f6220ca29", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.03689999319613, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_5cf7aef15e17bd32", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_f6328254ea8624a1", + "blk_5eac426f6220ca29", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "optimistic-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 29.231699998490512, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_4a26a95db9b832fc", + "note_5cf7aef15e17bd32", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_ce08f371b1dcf2c5", + "blk_f6328254ea8624a1", + "blk_5eac426f6220ca29", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 29.355599952396005, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_84afb04006190ead", + "blk_c4644f51b0853ef2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 23.195700021460652, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_3f7160f36792f3a1", + "blk_b68f6945024f098d", + "blk_84afb04006190ead", + "blk_c4644f51b0853ef2", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 24.769000010564923, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_064caf4b9c2e2518", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "idempotency-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 30.61259997775778, + "retrieved_note_ids": [ + "note_cdc416a180e5099b", + "note_df8b1e8216af7f9a", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_b68f6945024f098d", + "blk_064caf4b9c2e2518", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 21.075199998449534, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 18.505499989259988, + "retrieved_note_ids": [ + "note_5cf7aef15e17bd32", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d" + ], + "retrieved_block_ids": [ + "blk_f6328254ea8624a1", + "blk_84afb04006190ead", + "blk_064caf4b9c2e2518", + "blk_3f7160f36792f3a1", + "blk_3f8c19cf88a03805" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 18.727799993939698, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2", + "blk_b68f6945024f098d", + "blk_5bfc319608e9cee0" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "sse-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 16.664499999023974, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_7b697512f3d11a1d", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2", + "blk_b68f6945024f098d", + "blk_5bfc319608e9cee0" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.499999979510903, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_da790c1c3b905f26", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_d15c420ab15ba221", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.647500007413328, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_48f96c75ea97d552", + "note_5cf7aef15e17bd32", + "note_da790c1c3b905f26", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_7b697512f3d11a1d", + "blk_efa6b9f6210964c2", + "blk_f6328254ea8624a1", + "blk_d15c420ab15ba221", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 18.47989996895194, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_04142ad0124ae76d", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_3f8c19cf88a03805", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d", + "blk_5bfc319608e9cee0" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "cancel-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.081400004215539, + "retrieved_note_ids": [ + "note_48f96c75ea97d552", + "note_04142ad0124ae76d", + "note_5cf7aef15e17bd32", + "note_cdc416a180e5099b", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_efa6b9f6210964c2", + "blk_3f8c19cf88a03805", + "blk_f6328254ea8624a1", + "blk_b68f6945024f098d", + "blk_5bfc319608e9cee0" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 19.227900018449873, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_b68f6945024f098d", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.251900011207908, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_df8b1e8216af7f9a", + "note_cdc416a180e5099b", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_064caf4b9c2e2518", + "blk_84afb04006190ead", + "blk_b68f6945024f098d", + "blk_3f7160f36792f3a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 14.950399985536933, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_2788f9d5e6287c34", + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "embedding-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.197800017427653, + "retrieved_note_ids": [ + "note_6e13fbe17c9f7a30", + "note_6e13fbe17c9f7a30", + "note_df8b1e8216af7f9a", + "note_1acd666aa1e79f96", + "note_a0b8289f7f334952" + ], + "retrieved_block_ids": [ + "blk_dc2b0771ea50c3a4", + "blk_2788f9d5e6287c34", + "blk_064caf4b9c2e2518", + "blk_abe4534c5c35b694", + "blk_5eac426f6220ca29" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.769600053317845, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_04142ad0124ae76d", + "note_6e13fbe17c9f7a30", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_f2a60de446d8b630", + "blk_5eac426f6220ca29", + "blk_3f8c19cf88a03805", + "blk_dc2b0771ea50c3a4", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 12.636800005566329, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_a0b8289f7f334952", + "note_04142ad0124ae76d", + "note_6e13fbe17c9f7a30", + "note_cdc416a180e5099b" + ], + "retrieved_block_ids": [ + "blk_f2a60de446d8b630", + "blk_5eac426f6220ca29", + "blk_3f8c19cf88a03805", + "blk_dc2b0771ea50c3a4", + "blk_b68f6945024f098d" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 16.909999947529286, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_064caf4b9c2e2518", + "blk_f2a60de446d8b630", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "citation-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 17.375700001139194, + "retrieved_note_ids": [ + "note_a0b8289f7f334952", + "note_df8b1e8216af7f9a", + "note_a0b8289f7f334952", + "note_cdc416a180e5099b", + "note_6e13fbe17c9f7a30" + ], + "retrieved_block_ids": [ + "blk_5eac426f6220ca29", + "blk_064caf4b9c2e2518", + "blk_f2a60de446d8b630", + "blk_b68f6945024f098d", + "blk_dc2b0771ea50c3a4" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 23.383799998555332, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ec785607e700a5c0", + "blk_ce08f371b1dcf2c5", + "blk_c6c78e0831003670", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 16.41749998088926, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_bfd65995af95a6a6", + "note_5cf7aef15e17bd32", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ec785607e700a5c0", + "blk_ce08f371b1dcf2c5", + "blk_c6c78e0831003670", + "blk_f6328254ea8624a1", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.095699985977262, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_ec785607e700a5c0", + "blk_b68f6945024f098d", + "blk_3f8c19cf88a03805", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "zip-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.247999992221594, + "retrieved_note_ids": [ + "note_4a26a95db9b832fc", + "note_4a26a95db9b832fc", + "note_cdc416a180e5099b", + "note_04142ad0124ae76d", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_ce08f371b1dcf2c5", + "blk_ec785607e700a5c0", + "blk_b68f6945024f098d", + "blk_3f8c19cf88a03805", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-0", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 15.200500027276576, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_1acd666aa1e79f96", + "note_5cf7aef15e17bd32", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_abe4534c5c35b694", + "blk_5bfc319608e9cee0", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-0", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 14.814800000749528, + "retrieved_note_ids": [ + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_1acd666aa1e79f96", + "note_5cf7aef15e17bd32", + "note_5cf7aef15e17bd32" + ], + "retrieved_block_ids": [ + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_abe4534c5c35b694", + "blk_5bfc319608e9cee0", + "blk_f6328254ea8624a1" + ], + "hit_at_1": true, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 1.0, + "citation_hit": true, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-1", + "mode": "hybrid", + "repeat": 0, + "latency_ms": 13.608000008389354, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_cdc416a180e5099b", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_b68f6945024f098d", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + }, + { + "embedding": { + "source": "local", + "route_version": 0, + "requested_route": null, + "query_embedding_cache": "hit", + "attempted_space": { + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384 + }, + "model_id": "hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a", + "dimensions": 384, + "fallback_reason": null + }, + "case_id": "plot-1", + "mode": "hybrid", + "repeat": 1, + "latency_ms": 15.594299999065697, + "retrieved_note_ids": [ + "note_04142ad0124ae76d", + "note_bfd65995af95a6a6", + "note_bfd65995af95a6a6", + "note_cdc416a180e5099b", + "note_48f96c75ea97d552" + ], + "retrieved_block_ids": [ + "blk_3f8c19cf88a03805", + "blk_c4644f51b0853ef2", + "blk_c6c78e0831003670", + "blk_b68f6945024f098d", + "blk_efa6b9f6210964c2" + ], + "hit_at_1": false, + "hit_at_5": true, + "recall": 1.0, + "reciprocal_rank": 0.5, + "citation_hit": false, + "citation_applicable": true, + "error": null, + "error_code": null + } + ], + "error": null, + "error_code": null + } +} \ No newline at end of file diff --git a/docs/development/第二阶段全量验收报告-2026-09-07.md b/docs/development/第二阶段全量验收报告-2026-09-07.md new file mode 100644 index 0000000..0dbf287 --- /dev/null +++ b/docs/development/第二阶段全量验收报告-2026-09-07.md @@ -0,0 +1,106 @@ +# 第二阶段全量验收报告 + +> 以下保留修复前基线审计。其后已完成的收尾实现、真实运行结果和剩余外部边界以[第二阶段收尾实现与验收](第二阶段收尾实现与验收-2026-09-07.md)为准,不能把本报告中的“未实现”继续当作当前分支状态。 + +> 日期:2026-09-07。基线:`main` / `95095197df8b607e55144748c9be0c7f6daf00df`,与本轮拉取的 `gitea/main` 一致。范围包含三位成员负责的全部第二阶段模块,不沿用历史“不含杨侧验收”的排除范围。 + +## 结论与口径 + +**第二阶段主要基础能力已落地,但尚未达到完整 Definition of Done。** 自动化测试通过不代表 Agent Benchmark、函数图像前端、Mermaid 静态导出、真实厂商协议和音频质量已经验收。 + +依据为[第二阶段团队分工表](../architecture/第二阶段团队分工表.md)、[第二阶段接口契约](../contracts/第二阶段接口契约-开发版.md)、当前实现、自动化测试、隔离压测及浏览器检查。历史文档中未经同步的勾选框不直接作为实时完成率。 + +本轮不修改业务逻辑、不调用收费模型或外部 MCP、不重跑长录音。测试使用临时数据库/Vault;内置语料检查读取 Git HEAD,避开用户未提交笔记。主工作区原有两份笔记修改保持不变。 + +## 本轮验证结果 + +| 检查 | 结果 | 证据与边界 | +| --- | --- | --- | +| 后端全量 | 838 项通过 | Python 3.12,临时 APP_DATA_DIR / APP_DB_PATH / APP_VAULT_PATH;1 条既有 Starlette/httpx 弃用提示 | +| 前端全量 | 78 个测试文件、433 项通过 | Vitest;部分测试存在 KaTeX quirks-mode 提示 | +| 类型与生产构建 | 通过 | `npm run build`,vue-tsc + Vite;仍有超过 500 kB 的惰性分块提示 | +| Mermaid 真实渲染 | 36 / 36 结构检查通过 | 6 图型 × 6 主题,无 Mermaid mock;不是所有图形字形、缩放和主题外观的完整人工验收 | +| Agent 并发 | 1 / 10 / 50 / 200 并发场景通过 | 共 290 次完成;mock 每轮注入 50 ms,验证事件顺序、回放及终态恢复,不代表真实模型性能 | +| 容量与取消 | 200 个活跃运行上限及取消通过 | 溢出拒绝;取消 P95 9.84 ms;权限等待 10 个批准、10 个取消,订阅释放 | +| 故障隔离 | 通过 | 成功、Provider 错误、模型超时、工具超时各 5 次;剩余工具执行器为 0 | +| Task API | 100 / 1000 条 CRUD、20 并发通过 | 默认分页 50,分页完整,删除后 total=0;1000 条场景创建/更新/删除 P95 为 262.36 / 275.78 / 286.18 ms | +| sqlite-vec 合成对照 | Top-20 一致 | 4000 块 × 384 维,5 次样本;Python JSON 扫描中位 1376.98 ms,sqlite-vec 12.92 ms;只证明该合成场景效率,不代表检索质量 | +| 内置 RAG 数据集完整性 | 5 / 5 案例关联有效 | 对照 HEAD 中 16 篇笔记、3787 块;预期 note_id / block_id 均存在,不等同于质量指标达标 | + +首次后端测试因本地 `.venv` 缺少 `python-docx` 等新合并依赖而在收集阶段失败。执行 `uv sync --frozen` 后补齐 6 个锁定包并重新全量通过;未改动依赖声明或锁文件。这是本机环境未同步,不作为代码回归计数。 + +性能测试与构建曾并行执行,上述延迟只用于发现功能及容量问题,不作为吞吐 SLA 或性能倍率承诺。200 并发 Agent 的事件循环最大延迟为 334.08 ms,值得单独串行复测。 + +## 第二阶段任务矩阵 + +| 任务 | 当前状态 | 实现/验证依据 | 尚缺内容 | +| --- | --- | --- | --- | +| 音频转写 | 替代技术路径已实现,质量待验收 | `app/local_models/worker.py`、阶段 F 记录;当前为 Qwen3-ASR | 原清单 faster-whisper 未交付;需明确接受技术替代并同步清单 | +| 说话人分段 | 部分实现 | ERes2NetV2 片段聚类,历史 CPU/CUDA 与长录音闭环 | 非 pyannote 完整分离;逐字强制对齐、同段多人/重叠分离未实现;CER/WER/DER、阈值缺标注验收 | +| MCP Bridge / Registry | 工程实现与离线回归通过 | `extensions`、MCP Host/Registry 测试、Agent 工具链 | 目标外部服务器专项并非本轮重验;生产 OS 沙箱属于第三阶段 | +| Plugin Command | 已实现 | Palette、工作区右键与工具栏、命令校验及 effect 回归 | 后续复杂 UI 贡献属于扩展阶段 | +| Plugin Settings / Secret | 已实现 | schema 动态表单、独立凭据引用及权限校验测试 | 真实第三方插件组合仍按包验收 | +| Skill / Plugin 本地持久注册 | 已实现 | `extensions/installed.py`、恢复、摘要复核、ZIP 清理回归 | 不应继续列为“尚无持久化”;在线升级事务与社区审核属于第三阶段 | +| Provider 适配 | 工程实现,外部专项待验收 | OpenAI Compatible/Responses、Anthropic、Ollama 适配及协议回归 | 逐厂商的发现、工具往返、流、取消、Usage、错误映射、上下文限制等真实矩阵未全部验收 | +| RAG Benchmark | 后端基础已实现 | `benchmarks`、指标与生命周期测试、5 案例数据集 | 代表性评测集与调优前后对照不足;无前端报告页 | +| Agent Benchmark | 未实现 | 无 `/api/benchmarks/agent/runs`,`Benchmark开发说明` 明确暂缓 | 标准 Case/Dataset、任务成功率、工具选择/参数准确率、无效调用率、步骤/耗时/Token 报告 | +| Retrieval 调优 | 性能改进已实现,质量验收不完整 | 模型/维度隔离 sqlite-vec、增量索引、过滤及检索回归 | 相同数据集/模型/参数下的质量前后对照;当前 reranker 仍为 LexicalReranker,不是已上线 BGE Cross-Encoder | +| HTML / PDF / DOCX 导出 | 后端基础已实现,界面联调未完成 | `export`、三种产物内容/结构回归 | 前端无 exportService、调用入口及任务下载流程;仍不能直接完成工作区总 Demo | +| 复杂内容导出 | 部分实现 | 警告框、嵌套表格、主题配色已覆盖 | Mermaid 为源码占位;PDF/DOCX 公式为文本,图片嵌入/资源解析仍有限;DOCX 函数图像占位 | +| 函数图像 | 后端解析/静态渲染已实现 | `plot`、SVG/PDF 共享几何与预算测试 | 前端没有 function-plot 渲染、编辑刷新、缩放和预览入口;仅 Mermaid 被前端识别为图表 | +| Theme Manifest / 导入 / 管理 | 已实现 | 文件/URL/ZIP、兼容性、安全校验、隔离预览、启停卸载及前端测试 | 网络 Marketplace 非第二阶段要求;导出固定配色不等于完整主题 CSS 复刻 | +| Agent Trace | 已实现 | Tree/Timeline、参数/结果/耗时/错误、筛选、引用跳转、恢复与取消测试 | 真实 Provider 的特殊事件仍需专项验证 | +| Mermaid 编辑器与预览 | 已实现 | 真实 36 组矩阵、编辑器回归、源码/预览及大图组件 | 静态 SVG/Image 向 Export Service 的传递尚未打通 | +| 跨模块共同 Demo | 部分完成 | 录音→笔记→检索、Agent/工具/Trace 已有历史闭环 | 函数图像前端、三格式导出 UI、Mermaid 静态导出等阻止完整演示闭环 | + +## 当前可推进的收尾项 + +### 优先完成第二阶段明确交付项 + +1. **Agent Benchmark**:复用已有 Agent Runtime/Trace 实现标准 Case 和报告,不以 mock 压测脚本替代任务成功率验收。 +2. **函数图像前端接入**:接入编辑器、只读 Markdown/聊天、源码/预览、编辑刷新、缩放及主题。复用后端语法和资源预算,不新增不一致语法。 +3. **导出前端闭环**:工作区格式/选项入口、后台任务状态、取消、下载、warning 呈现,支持未保存内容快照。 +4. **Mermaid 静态导出**:安全传递 SVG/Image,补齐三格式产物检查;DOCX 函数图像和公式/图片保真度随后逐项补齐。 +5. **检索质量验收**:扩充当前仅 5 案例的 Dataset,冻结语料、模型、维度、参数与回退策略,保存 FTS/vector/hybrid/RRF/rerank 的对照报告。 + +### 有外部依赖的验收 + +- 真实 Provider:准备各协议对应账号/模型矩阵与用量预算,逐项记录结果;连接测试不能代替工具、取消、错误与上下文专项。 +- 音频质量:准备人工参考文本、说话人及时间标注,明确 CER/WER/DER 和业务阈值。替代技术路径是否满足原始 DoD 应单独确认。 + +### 本阶段可继续优化 + +- 单独复测 200 并发 Agent 的事件循环峰值,定位批量持久化/事件分发热点。 +- 构建仍含两份 KaTeX 运行时与较大 Mermaid/语法资源块;按兼容性和首屏加载数据优化,不通过调高阈值隐藏问题。 +- Mermaid 矩阵循环复用全局主题,结束后页面保留最后一个主题;36/36 只能证明结构检查。视觉验收应使用 `?theme=` 分主题页面或隔离容器并保存截图。 +- 清理历史文档状态冲突:团队清单未勾选已有功能;第三阶段规划仍有“安装记录仅内存、待持久化”的旧描述;契约含旧 HashEmbedding 基线,应统一标明历史与当前实现。 + +## 与第三阶段的边界 + +Tauri/Rust Host、独立 Sync Server、联网主题/Skill/Plugin/MCP 等社区、签名分发、生产沙箱及跨设备冲突处理,仍按[第三阶段规划](../architecture/第三阶段实施规划.md)推进,不计入本轮第二阶段代码缺陷。第二阶段要求的是本地扩展、包格式与稳定接口;不能将“社区格式已实现”写成“联网社区已交付”。 + +## 复测与本地证据 + +```powershell +cd backend +uv sync --frozen +# 先设置 APP_DATA_DIR / APP_DB_PATH / APP_VAULT_PATH 为独立临时目录 +uv run pytest tests -q -p no:cacheprovider + +# 此脚本自行隔离运行数据,使用 mock,不调用远端 Provider +.venv/Scripts/python.exe scripts/agent-task-stress.py --output ../.local-plans/phase2-audit-20260907/agent-task.json +.venv/Scripts/python.exe scripts/vector-index-benchmark.py + +cd ../frontend +npm test -- --reporter=dot +npm run build +npm run dev -- --host 127.0.0.1 --port 5187 --strictPort +``` + +浏览器入口:`/tests/visual/mermaid-matrix.html`。本轮开发服务器验证完成后关闭,不替换用户正在使用的服务。 + +本地详细证据:`.local-plans/phase2-audit-20260907/agent-task.json`、`dataset-integrity.json`。这些是本机生成的审计产物,不作为真实模型质量或生产性能承诺;本报告记录可复查的汇总结果。 + +## 用户追加验收要求:所有主题支持函数图像 + +第二阶段收尾任务必须更新所有现有主题对 function-plot 的支持,包括 light、dark、sepia、paper-moments、ocean-blue、midnight-purple,以及实际主题注册表中的其他主题。统一函数图像设计变量及导出配色映射,覆盖坐标轴、刻度、网格、多曲线、图例/标签、背景、错误提示、焦点/悬停和缩放控件。检查工作区编辑器、只读预览、AI 对话、大图窗口及适用的静态导出。不得仅在默认浅色下通过;以逐主题实际渲染和截图验证对比度、文字完整性、窄屏布局及主题切换。PDF/DOCX 如仍采用明确的打印配色策略,应保留可读性和提示,不声称复刻整个主题。 diff --git a/docs/development/第二阶段收尾实现与验收-2026-09-07.md b/docs/development/第二阶段收尾实现与验收-2026-09-07.md new file mode 100644 index 0000000..ee92bde --- /dev/null +++ b/docs/development/第二阶段收尾实现与验收-2026-09-07.md @@ -0,0 +1,87 @@ +# 第二阶段收尾实现与验收 + +日期:2026-09-07。实现提交 `89df10b`,开发分支 `feat/phase2-completion`,基于最新拉取的 `gitea/main` / `95095197df8b607e55144748c9be0c7f6daf00df`。本分支尚未合并、推送或发布。本记录覆盖所有成员的第二阶段工程范围,替代早期“不含杨侧验收”的当前状态口径;[此前全量审计](第二阶段全量验收报告-2026-09-07.md)保留为修复前基线。 + +## 结论 + +原审计中可在现有环境推进的主要缺项已实现:标准 Agent Benchmark 与报告页、函数图像编辑器和共用 Markdown 预览、三格式未保存快照导出、Mermaid 静态图片、DOCX 图像与受支持公式、真实 RAG 质量对照,以及现有 Provider/MCP 的受限集成验证。 + +不将本次结果写成“所有厂商、所有文档及音频质量全部通过”。未配置的 Responses/Anthropic/Ollama 真实服务、厂商最大上下文边界、DOCX Office/LibreOffice 整页视觉复核仍列为验收边界;音频质量按本轮范围排除。Tauri/Rust、联网社区、Sync Server、操作系统沙箱仍属第三阶段。 + +## DoD 矩阵 + +| 项目 | 本轮状态 | 实现与证据 | +| --- | --- | --- | +| Agent Benchmark | 已实现、真实运行通过 | 标准版本化 Case、正式 Runtime、真实 Trace、逐例检查及成功/选择/参数/无效调用/步骤/耗时/Token 指标;4/4 标准案例与2/2集成案例通过 | +| RAG Benchmark 与报告页 | 已实现、真实本地模型对照完成 | 12篇自建中文工程笔记,关键词/语义改写各12题;6配置×24题×2轮,288个样本,零运行失败;冻结语料与逐例 provenance | +| 函数图像 | 已实现、页面复核通过 | 写作/源码切换、编辑刷新、诊断、只读/AI共用组件、缩放/重置/大图、六主题宽窄屏;共享后端白名单语法与几何 | +| 导出 UI | 已实现、浏览器下载通过 | HTML/PDF/DOCX,点击时快照、标题/纸张选项、后台状态、取消、下载和 warning;包含未保存 PHASE2-SNAPSHOT | +| 静态内容导出 | 已实现、产物检查通过 | Mermaid 浏览器净化后栅格化,按源码摘要绑定PNG;HTML/PDF/DOCX真实嵌图;DOCX函数图、MathText公式及Vault内PNG/JPEG/WebP;无法表示的资源显式warning | +| 主题与 Mermaid | 已复核 | light/dark/sepia/paper-moments/ocean-blue/midnight-purple;36组Mermaid用隔离iframe检查,无全局主题串扰 | +| Provider | 已配置协议通过 | DeepSeek OpenAI Compatible:发现、普通、流式、推理事件、Tool往返、Usage、取消与错误映射;额外上下文压缩及缓存字段观测 | +| MCP | 现有服务通过 | MiniMax Coding Plan stdio注册与Agent真实web_search调用,工具权限仍由Runtime约束 | +| Plugin/Skill、Trace、Task、工作区 | 回归通过 | 全量回归;隔离压力测试、权限等待/取消、恢复与分页;本地持久安装已实现 | +| 音频 | 本轮排除质量专项 | 当前Qwen3-ASR + ERes2NetV2;没有将faster-whisper/pyannote写成已交付,也未声称CER/WER/DER达标 | + +## RAG 结果与调优决策 + +冻结模型 `hotchpotch/bekko-embedding-v1-a8m@c721113d59a1d91b447450324f51c4b3332c924a`,384维,真实已安装本地模型;LexicalReranker `lexical-v1/1`。Top K=5、RRF K=60、候选20,参数变体K=20。向量来源逐例为local,无HashEmbedding测试注入或远端失败回退。完整记录见[rag-quality.json](evidence/phase2-20260907/rag-quality.json)。 + +| 配置 | Hit@1 | Hit@5 / Recall@5 | MRR | Citation Hit Rate | P50 / P95 ms | +| --- | ---: | ---: | ---: | ---: | ---: | +| FTS | .9583 | 1 / 1 | .9667 | .7500 | 9.64 / 12.20 | +| Vector(冷暖混合) | 1 | 1 / 1 | 1 | .5833 | 4571.74 / 10284.19 | +| Hybrid 加权50/50 | .9583 | 1 / 1 | .9688 | .6667 | 15.44 / 19.69 | +| Hybrid RRF | .9167 | 1 / 1 | .9583 | .6667 | 15.44 / 20.28 | +| RRF + Lexical rerank | .9167 | 1 / 1 | .9583 | .8750 | 15.70 / 22.26 | +| RRF K=20 | .9167 | 1 / 1 | .9583 | .6667 | 15.68 / 27.67 | + +Citation Hit Rate判断首条返回块是否命中预期引用块,不是“任意返回块命中”。因此高笔记命中率不等于准确引用。数据属于开发集,不是生产分布或留出测试集;没有事后补造质量阈值来宣布生产达标。 + +RRF K=20没有收益,保留默认60;加权融合提供可选对照,不改变默认策略。Lexical rerank改善本集引用命中,却未提高笔记Top1,不能宣布通用质量提升。 + +瓶颈是重复查询反复启动本地Embedding推理。新增128条/10分钟进程内缓存,仅存真实本地单文本向量,键包含模型目录/revision、冻结运行配置与查询摘要;返回副本,未安装模型不走缓存,不缓存远端响应。Vector的24个miss P50=9474.32ms,24个hit P50=15.11ms。后续Hybrid配置大多复用暖缓存,其时延不能与冷Vector直接比较。改前无缓存Vector P50约9368.46ms;冷推理没有提速。保留本地进程长期驻留优化作为后续独立设计,不扩大本轮范围。 + +## 真实 Provider、Agent、MCP + +[标准报告](evidence/phase2-20260907/live.json):现有DeepSeek `deepseek-v4-flash`,4/4任务成功,工具选择/参数准确率均1,无效调用率0,平均1.75步、5682.33ms,5888 Token。发现3个模型;流包含TextDelta/ThinkingDelta/Usage/Done;无效模型映射PROVIDER_INVALID_REQUEST。取消关闭客户端流约12.16ms,不等于能观测到服务商停止计费。 + +另通过实际Benchmark页面启动、下载与Trace跳转复测,4/4成功、5939 Token、平均4646.52ms,见[UI运行报告](evidence/phase2-20260907/agent-ui-report.json)。修复了未打开工作区时无法从报告进入已有Trace的路由拦截;创建普通新Agent运行仍遵守原工作区入口约束。 + +[集成报告](evidence/phase2-20260907/integration.json):检索→读笔记→3个任务创建,以及MCP web_search,共2/2通过,工具6/6匹配,16070 Token。写入仅针对隔离数据库的3个任务,按这次运行的权限票据逐项批准。报告保留真实Agent Run ID,可在保留隔离数据库时恢复Trace;Benchmark报告本身是有界内存任务,应下载保存,重启不会恢复评测注册表。 + +[上下文复测](evidence/phase2-20260907/context.json):不修改已保存Provider配置,在请求级测试配置中设8192窗口;detect在网络前返回CONTEXT_COMPRESSION_REQUIRED,compress调用真实模型生成摘要再回答,估算从2262降至516,原会话不变,最终答案保留Vault事实。首次256输出Token预算耗尽,见[失败记录](evidence/phase2-20260907/context-budget256.json);改用1024上限后最终回答使用289输出Token并通过。四次受限调用包含两轮摘要及回答。该测试验证应用的预算与压缩路径,不证明厂商最大窗口。 + +验收库中18个该Provider请求上报缓存计数,13个有正命中、18个有正未命中;缺失字段保持未知,不记作0。缓存是否命中由服务商控制,不能强制保证。没有新增账号、密钥或服务,没有打印凭据或请求认证信息。 + +外部剩余项:当前未配置OpenAI Responses、Anthropic Messages、Ollama真实服务,协议单测通过仍不足以替代真实服务矩阵;厂商极限上下文需要单独预算与目标窗口。本轮没有发出超大输入探测。 + +## 性能与回归 + +- 后端全量与前端测试/构建结果以本报告末尾最终验证记录为准。 +- [串行压力基线](evidence/phase2-20260907/agent-task-before.json)与[修复后](evidence/phase2-20260907/agent-task-after.json):200并发均完成,事件顺序、回放和终态恢复正确。恢复读取约313ms移出事件循环;该场景loop lag max从326.14降到12.19ms。运行P95从1007.22到1155.52ms,没有声称模型或总吞吐提速。 +- 200活跃运行容量拒绝与取消通过,取消P95=5.85ms;权限等待10批准/10取消、订阅释放,故障隔离后无残余工具执行器。 +- Task API 100/1000条、20并发CRUD及50条分页完整;1000条创建/更新/删除P95约121.67/146.20/145.87ms。此脚本显式mock,不能代替真实Agent Benchmark。 +- KaTeX通过Vite dedupe合为一个运行时chunk;Mermaid/语法等大惰性资源仍有构建提示,未调高阈值隐藏问题。 + +## 产物与视觉复核边界 + +浏览器真实加载项目组件与API,六主题各保存宽屏、390px窄屏和大图截图,检查缩放、源码切换、实时编辑、主题切换、无效表达式及打印warning。使用受限复杂图文档实际从UI取消后台PDF任务并看到已取消终态。[页面结果](evidence/phase2-20260907/browser-results.json)、[Mermaid矩阵](evidence/phase2-20260907/browser-mermaid-matrix.json)。窄屏函数图保留640px坐标区域并可横向滚动,避免刻度被整体缩得过小。 + +PDF使用嵌入的可用CJK字体,已通过Poppler检查实际页面;未安装可用字体时保留CID回退并明确warning。可用APP_EXPORT_FONT指定兼容TTF/TTC。三格式包含未保存快照、函数图、中文Mermaid及公式;DOCX的OOXML、图片关系和媒体内容通过结构检查,参见[产物清单](evidence/phase2-20260907/artifacts.json)。捆绑文档渲染器因缺少LibreOffice `soffice.exe` 无法完成DOCX整页渲染,不能声称Word分页已视觉通过。没有改用用户安装的Office/LibreOffice绕过工具约束。 + +MathText是受限TeX子集,不等同于KaTeX全语法;不支持的公式、远端/越界图片明确保留源码或替代文字及warning。服务器不执行客户端SVG/XML、任意TeX命令或外部资源下载。PNG每图4百万像素、总16百万像素/8MiB、最多64附件;重复引用及本地自动补全资源也受文档级累计预算约束;前端最多准备16幅Mermaid。函数图每块16表达式、预览总8000节点,导出每文档16图/8000节点,预览并发2。取消在线程渲染边界生效,不能强杀正在运行的Python绘图线程。 + +## 复现 + +1. `uv sync --frozen` 与 `pnpm install --frozen-lockfile`;把APP_DATA_DIR/APP_DB_PATH/APP_VAULT_PATH指向全新验收目录,不能指向用户知识库。 +2. 仅在用户授权下复用已有Provider凭据及模型配置;APP_MODEL_PYTHON指向已安装模型解释器。语料源为`backend/data/benchmarks/corpus/phase2-v1.json`;运行`backend/scripts/phase2-quality.py --output `,生成独立Vault和Dataset、重建真实索引。`--reuse-index`会验证块ID一致,不能跳过缺失索引。 +3. 标准真实测试:`backend/scripts/phase2-live.py --provider <已有ID> --output --execute`;上下文`phase2-context.py`同参数。写任务/MCP测试用`phase2-integration.py --execute`,先查脚本中隔离端口与现有工具ID;不能复制到生产Vault直接执行。 +4. API监听8017,前端以NOTES_API_TARGET=http://127.0.0.1:8017在5187启动。设置NODE_PATH到含Playwright的运行库后执行`node frontend/scripts/phase2-browser-check.cjs <输出目录>`,访问`/tests/visual/phase2.html`及隔离Mermaid矩阵。 +5. `backend/.venv/Scripts/python -m pytest backend/tests -q -p no:cacheprovider`;前端目录`pnpm test -- --reporter=dot`、`pnpm build`。压力脚本`backend/scripts/agent-task-stress.py --output `自建隔离数据。 + +浏览器产物及完整测试日志保留于本机`.local-plans/phase2-completion/`;可复查的配置、指标与逐例证据已提交文档目录,不依赖该忽略目录才能读到结果。主工作区两份原有未提交笔记未修改。 + +## 最终验证记录 + +后端861测试通过(Python 3.13.9,48.32s),含HTTP/OpenAPI、预览累计复杂度、重复资源预算、导出、评分、取消与缓存回归;仅1条既有Starlette/httpx弃用提示。前端80文件437测试通过,vue-tsc及Vite生产构建通过;KaTeX仅一个261.18kB运行时chunk(gzip77.76kB),大惰性分块警告仍保留。 diff --git a/frontend/README.md b/frontend/README.md index ddbed52..35fa9b0 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,5 +1,7 @@ # NotesAgent Frontend +> 第二阶段收尾:标准 Agent/RAG Benchmark 与报告页、函数图预览、三格式快照导出及真实 Provider/MCP 结果见[实现与验收记录](../docs/development/第二阶段收尾实现与验收-2026-09-07.md)。当前分支尚未合并,不更改下文历史 main 基线。 + NotesAgent Frontend 是基于 Vue 3、TypeScript、Vite、Pinia、Vue Router、Milkdown 和 CodeMirror 6 的 Web 联调前端。当前页面调用 FastAPI 真实接口,不使用业务 Mock 作为运行时回退;测试文件中的 mock 只用于隔离单元和组件测试。 ## 初始化与运行 @@ -19,6 +21,7 @@ pnpm dev | `/search` | 全文、向量和混合检索;从后端读取并清空搜索历史 | | `/chat` | 流式 AI 对话、知识库上下文与 Citation | | `/agent/runs/:runId?` | 创建 Agent 运行,查看可恢复 Trace 与 Tool/Permission 事件 | +| `/benchmarks` | RAG/Agent 数据集、参数、真实运行、报告下载与 Trace 入口 | | `/media` | 上传音频、创建/取消/重试转写、修订结果并生成知识库笔记 | | `/tasks` | 管理用户、笔记和 Agent 产生的任务 | | `/extensions/skills` | Skill 安装、启停与配置 | -- 2.43.0 From 0e3f2a7325f6556ba7b89f02712971d5e78c2ef6 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Mon, 7 Sep 2026 12:45:09 +0800 Subject: [PATCH 03/10] Add function plot feature demonstration note --- .../vault/功能演示/function-plot功能演示.md | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 backend/data/vault/功能演示/function-plot功能演示.md diff --git a/backend/data/vault/功能演示/function-plot功能演示.md b/backend/data/vault/功能演示/function-plot功能演示.md new file mode 100644 index 0000000..aa7654e --- /dev/null +++ b/backend/data/vault/功能演示/function-plot功能演示.md @@ -0,0 +1,138 @@ +# function-plot 功能演示 + +这份笔记展示函数图像的写法、编辑刷新、坐标设置和错误反馈。在 NotesAgent 中打开后,切换到「写作」查看图像;「源码」模式可查看和修改下面的代码块。 + +## 1. 从一条抛物线开始 + +`domain` 设置横轴范围,`range` 设置纵轴显示范围。`xlabel` 和 `ylabel` 设置坐标轴标签。 + +```function-plot +domain: -4, 4 +range: -2, 18 +xlabel: 横坐标 x +ylabel: 函数值 y +grid: true +y = x^2 +``` + +试着将 `y = x^2` 改为 `y = (x-1)^2 + 2`,观察顶点从 `(0, 0)` 移到 `(1, 2)`。修改后切回写作模式即可查看结果。 + +## 2. 多函数同图 + +一个代码块内每行写一个函数,曲线按顺序分配颜色,并显示对应图例。三角函数的输入单位是弧度。 + +```function-plot +domain: -6.2832, 6.2832 +range: -2.2, 2.2 +xlabel: x / 弧度 +ylabel: y +y = sin(x) +y = cos(x) +y = 2sin(x) +``` + +将第三条函数改为 `y = sin(2x)`,比较振幅变化和周期变化。 + +## 3. 隐式乘法与交点 + +支持 `2x`、`2(x+1)`、`(x+1)(x-1)` 等写法。乘号也可以显式写成 `*`,幂可以使用 `^`。 + +```function-plot +domain: -3, 4 +range: -5, 12 +xlabel: x +ylabel: y +y = (x+1)(x-1) +y = 2x + 1 +``` + +两条曲线的交点满足 `x^2 - 1 = 2x + 1`,横坐标约为 `-0.732` 和 `2.732`。 + +## 4. 指数、对数与参考直线 + +支持常量 `e`、`pi`,以及 `exp`、`ln`、`log10` 等函数。这里把横轴限定在正数范围,保证对数有定义。 + +```function-plot +domain: 0.1, 3 +range: -3, 8 +xlabel: x +ylabel: y +y = exp(x) +y = ln(x) +y = x +``` + +超出纵轴显示范围的曲线会被裁切。把 `range` 改为 `-3, 22`,可以查看更完整的指数曲线。 + +## 5. 绝对值与平方根 + +```function-plot +domain: -4, 4 +range: -0.5, 4.5 +xlabel: x +ylabel: y +y = abs(x) +y = sqrt(abs(x)) +``` + +这里用 `sqrt(abs(x))`,所以负半轴也有定义;它与 `sqrt(x)` 的定义域不同。 + +## 6. 间断点与显示范围 + +```function-plot +domain: -5, 5 +range: -5, 5 +xlabel: x +ylabel: y +y = 1/x +``` + +`x = 0` 处无定义,图像应分成左右两支,而不是跨过间断点连线。可缩放或打开大图查看原点附近;曲线是有限采样的可视化,不代替数学定义。 + +## 7. 关闭网格 + +```function-plot +domain: -6, 6 +range: -0.2, 1.2 +xlabel: x +ylabel: y +grid: false +y = exp(-x^2/2) +``` + +将 `grid: false` 改为 `grid: true`,比较有无网格的效果。 + +## 8. 错误反馈演示(故意写错) + +下面的 `sinn` 不是支持的函数名,预期显示错误诊断,不生成曲线。这是本节的演示内容。把它改为 `sin` 即可恢复图像;若希望导出一份没有错误警告的文档,请先修正这一行。 + +```function-plot +domain: -3.14, 3.14 +y = sinn(x) +``` + +## 交互与导出检查 + +- 将鼠标移到图表区域,试用缩小、放大、重置和大图查看;只读预览也可切换源码。 +- 在窄窗口中横向滚动函数图,检查坐标刻度和右侧图例。 +- 依次切换 `light`、`dark`、`sepia`、`paper-moments`、`ocean-blue`、`midnight-purple`;社区主题需先安装并启用。检查背景、网格、文字和曲线的对比度。 +- 修改第一节函数后不保存,点击编辑器顶部「导出」,分别选择 HTML、PDF、DOCX,验证文件采用点击时的编辑内容。 +- HTML 保留支持的主题配色;PDF、DOCX 使用浅色打印样式。HTML 的函数图为 SVG,PDF 为矢量图,DOCX 为静态图片。 + +## 写法速查 + +| 项目 | 示例 | +| --- | --- | +| 代码块语言 | `function-plot` | +| 函数表达式 | `y = x^2 + 2x + 1` | +| 横轴范围 | `domain: -5, 5` | +| 纵轴范围 | `range: -2, 10`,省略时自动估计 | +| 坐标标签 | `xlabel: 时间`、`ylabel: 数值` | +| 网格开关 | `grid: true` / `grid: false` | +| 数学常量 | `pi`、`e` | +| 常用函数 | `sin`、`cos`、`tan`、`sqrt`、`abs`、`exp`、`ln`、`log10` | +| 注释 | 单独一行以 `#` 开头 | + +范围端点使用数值,例如 `domain: -3.1416, 3.1416`;表达式中可以使用 `pi`。当前只绘制以 `x` 为自变量的二维函数,不支持任意脚本、参数曲线或三维曲面。 + +每个图块最多 16 条表达式;每份导出文档最多 16 个函数图、累计 8000 个表达式节点。本文件包含 7 个正常示例和 1 个有意保留的错误示例。 -- 2.43.0 From 47c53b6f38cf5ed8c3b6a03eb3ad19b2b47abef2 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Mon, 7 Sep 2026 13:26:14 +0800 Subject: [PATCH 04/10] fix: address phase two review and theme benchmark page --- backend/app/benchmarks/agent.py | 30 +++- backend/app/export/exporters/docx.py | 8 +- backend/tests/test_phase2_completion.py | 33 ++++ .../phase2-review-20260907/theme-results.json | 164 ++++++++++++++++++ .../第二阶段审阅修复与主题适配-2026-09-07.md | 24 +++ frontend/scripts/benchmark-theme-check.cjs | 34 ++++ frontend/scripts/phase2-benchmark-browser.cjs | 2 +- .../features/benchmarks/BenchmarkView.spec.ts | 29 ++++ .../src/features/benchmarks/BenchmarkView.vue | 98 ++++++++--- frontend/src/features/editor/ExportDialog.vue | 2 +- frontend/src/services/exportService.spec.ts | 48 ++++- frontend/src/services/exportService.ts | 14 +- 12 files changed, 451 insertions(+), 35 deletions(-) create mode 100644 docs/development/evidence/phase2-review-20260907/theme-results.json create mode 100644 docs/development/第二阶段审阅修复与主题适配-2026-09-07.md create mode 100644 frontend/scripts/benchmark-theme-check.cjs create mode 100644 frontend/src/features/benchmarks/BenchmarkView.spec.ts diff --git a/backend/app/benchmarks/agent.py b/backend/app/benchmarks/agent.py index 12cec3f..6701e19 100644 --- a/backend/app/benchmarks/agent.py +++ b/backend/app/benchmarks/agent.py @@ -11,15 +11,27 @@ INVALID = {'TOOL_NOT_FOUND', 'TOOL_NOT_ALLOWED', 'TOOL_ARGUMENT_INVALID', 'TOOL_ def score(case, run, events, latency, repeat): calls = [e.data for e in events if e.event.value == 'ToolCall'] - unmatched = list(calls) - selected = accurate = 0 - for expected in case.expected_tools: - candidates = [c for c in unmatched if c.get('name') == expected.name] - if not candidates: - continue - exact = next((c for c in candidates if all(k in c.get('arguments', {}) and c['arguments'][k] == v for k,v in expected.arguments.items())), None) - chosen = exact or candidates[0] - unmatched.remove(chosen); selected += 1; accurate += int(exact is not None) + # Maximum bipartite matching: broad parameter subsets must not consume the + # only call satisfying a more specific expectation. Each call is used once. + matched = {} + def assign(expected_index, visited): + expected = case.expected_tools[expected_index] + for call_index, call in enumerate(calls): + if call_index in visited or call.get('name') != expected.name: + continue + arguments = call.get('arguments', {}) + if not all(key in arguments and arguments[key] == value for key, value in expected.arguments.items()): + continue + visited.add(call_index) + if call_index not in matched or assign(matched[call_index], visited): + matched[call_index] = expected_index + return True + return False + accurate = sum(assign(index, set()) for index in range(len(case.expected_tools))) + from collections import Counter + actual_names = Counter(call.get('name') for call in calls) + expected_names = Counter(tool.name for tool in case.expected_tools) + selected = sum(min(count, actual_names[name]) for name, count in expected_names.items()) results = run.tool_results checks = { 'completed': run.status.value == 'completed', diff --git a/backend/app/export/exporters/docx.py b/backend/app/export/exporters/docx.py index 12cefcb..54bdc79 100644 --- a/backend/app/export/exporters/docx.py +++ b/backend/app/export/exporters/docx.py @@ -115,7 +115,13 @@ class DocxExporter: from PIL import Image png = node.attributes['static_png'] with Image.open(BytesIO(png)) as image: - width = min(5.8, image.width / (180 if node.type == 'math_block' else 96)) + section = self._doc.sections[-1] + available_width = (section.page_width - section.left_margin - section.right_margin) / 914400 + # Leave room for Word's containing paragraph line/spacing. + available_height = (section.page_height - section.top_margin - section.bottom_margin) / 914400 - 0.25 + width = min(5.8, available_width, + image.width / (180 if node.type == 'math_block' else 96), + available_height * image.width / image.height) self._doc.add_picture(BytesIO(png), width=Inches(width)) return handler = getattr(self, f"_block_{node.type}", None) diff --git a/backend/tests/test_phase2_completion.py b/backend/tests/test_phase2_completion.py index 0a1b8ad..6001667 100644 --- a/backend/tests/test_phase2_completion.py +++ b/backend/tests/test_phase2_completion.py @@ -164,3 +164,36 @@ def test_repeated_static_assets_share_document_resource_budget(): warnings = enrich_document(document) assert sum(bool(node.attributes.get('static_png')) for node in document.children) == 64 assert any('预算' in warning for warning in warnings) + + +@pytest.mark.parametrize('order', [(1, 2), (2, 1)]) +def test_agent_parameter_matching_is_independent_of_call_order(order): + from types import SimpleNamespace as NS + from app.benchmarks.agent import score + from app.contracts import AgentDatasetCase + case = AgentDatasetCase(case_id='overlap', prompt='test', allowed_tools=['math.add'], + expected_tools=[{'name':'math.add','arguments':{}}, {'name':'math.add','arguments':{'left':1}}]) + events = [NS(event=NS(value='ToolCall'), data={'name':'math.add','arguments':{'left':value}}) for value in order] + run = NS(status=NS(value='completed'),tool_results=[],output='',citations=[],run_id='test',current_step=1,token_usage=0,error_code=None) + result = score(case, run, events, 1, 0) + assert result.success and result.accurate_calls == result.selected_calls == 2 + # Two expectations cannot reuse one matching call. + result = score(case, run, events[:1], 1, 0) + assert not result.success and result.accurate_calls == 1 + + +@pytest.mark.parametrize('page_size', ['A4', 'Letter']) +@pytest.mark.parametrize('dimensions', [(200, 2000), (2000, 200)]) +def test_docx_static_images_fit_both_page_dimensions(page_size, dimensions): + from app.export.markdown import parse_document + from app.export.exporters.docx import DocxExporter + from app.contracts import ExportOptions + from docx import Document + png = BytesIO(); Image.new('RGB', dimensions, 'white').save(png, 'PNG') + document = parse_document('```mermaid\nflowchart TD\n A-->B\n```') + document.children[0].attributes['static_png'] = png.getvalue() + result = DocxExporter().render(document, ExportOptions(page_size=page_size)) + word = Document(BytesIO(result.content)); section = word.sections[0]; shape = word.inline_shapes[0] + assert shape.width <= section.page_width - section.left_margin - section.right_margin + assert shape.height < section.page_height - section.top_margin - section.bottom_margin + assert shape.width / shape.height == pytest.approx(dimensions[0] / dimensions[1], rel=1e-5) diff --git a/docs/development/evidence/phase2-review-20260907/theme-results.json b/docs/development/evidence/phase2-review-20260907/theme-results.json new file mode 100644 index 0000000..5fc2cca --- /dev/null +++ b/docs/development/evidence/phase2-review-20260907/theme-results.json @@ -0,0 +1,164 @@ +[ + { + "theme": "light", + "errors": [], + "overflow": false, + "colors": { + "panel": { + "background": "rgb(255, 255, 255)", + "color": "rgb(31, 35, 40)", + "border": "rgb(228, 231, 235)" + }, + "input": { + "background": "rgb(255, 255, 255)", + "color": "rgb(31, 35, 40)", + "border": "rgb(102, 113, 241)" + }, + "button": { + "background": "rgb(91, 103, 241)", + "color": "rgb(255, 255, 255)", + "border": "rgba(0, 0, 0, 0)" + }, + "metric": { + "background": "rgb(247, 248, 250)", + "color": "rgb(31, 35, 40)", + "border": "rgb(228, 231, 235)" + } + } + }, + { + "theme": "dark", + "errors": [], + "overflow": false, + "colors": { + "panel": { + "background": "rgb(22, 27, 34)", + "color": "rgb(230, 237, 243)", + "border": "rgb(48, 54, 61)" + }, + "input": { + "background": "rgb(21, 26, 33)", + "color": "rgb(230, 237, 243)", + "border": "rgb(116, 129, 231)" + }, + "button": { + "background": "rgb(125, 139, 255)", + "color": "rgb(13, 17, 23)", + "border": "rgba(0, 0, 0, 0)" + }, + "metric": { + "background": "rgb(22, 27, 34)", + "color": "rgb(230, 237, 243)", + "border": "rgb(48, 54, 61)" + } + } + }, + { + "theme": "sepia", + "errors": [], + "overflow": false, + "colors": { + "panel": { + "background": "rgb(255, 248, 232)", + "color": "rgb(64, 55, 43)", + "border": "rgb(221, 207, 173)" + }, + "input": { + "background": "rgb(255, 247, 231)", + "color": "rgb(64, 55, 43)", + "border": "rgb(148, 105, 65)" + }, + "button": { + "background": "rgb(138, 91, 50)", + "color": "rgb(255, 255, 255)", + "border": "rgba(0, 0, 0, 0)" + }, + "metric": { + "background": "rgb(244, 232, 202)", + "color": "rgb(64, 55, 43)", + "border": "rgb(221, 207, 173)" + } + } + }, + { + "theme": "paper-moments", + "errors": [], + "overflow": false, + "colors": { + "panel": { + "background": "rgb(255, 253, 245)", + "color": "rgb(73, 63, 53)", + "border": "rgb(181, 166, 147)" + }, + "input": { + "background": "rgb(254, 251, 243)", + "color": "rgb(73, 63, 53)", + "border": "rgb(147, 105, 88)" + }, + "button": { + "background": "rgb(134, 82, 66)", + "color": "rgb(255, 253, 245)", + "border": "rgba(0, 0, 0, 0)" + }, + "metric": { + "background": "rgb(243, 238, 227)", + "color": "rgb(73, 63, 53)", + "border": "rgb(181, 166, 147)" + } + } + }, + { + "theme": "ocean-blue", + "errors": [], + "overflow": false, + "colors": { + "panel": { + "background": "rgb(255, 255, 255)", + "color": "rgb(30, 41, 59)", + "border": "rgb(226, 232, 240)" + }, + "input": { + "background": "rgb(255, 255, 255)", + "color": "rgb(30, 41, 59)", + "border": "rgb(27, 133, 189)" + }, + "button": { + "background": "rgb(0, 118, 181)", + "color": "rgb(255, 255, 255)", + "border": "rgba(0, 0, 0, 0)" + }, + "metric": { + "background": "rgb(248, 250, 252)", + "color": "rgb(30, 41, 59)", + "border": "rgb(226, 232, 240)" + } + } + }, + { + "theme": "midnight-purple", + "errors": [], + "overflow": false, + "colors": { + "panel": { + "background": "rgb(36, 40, 59)", + "color": "rgb(192, 202, 245)", + "border": "rgb(59, 63, 92)" + }, + "input": { + "background": "rgb(34, 38, 55)", + "color": "rgb(192, 202, 245)", + "border": "rgb(139, 75, 197)" + }, + "button": { + "background": "rgb(156, 77, 220)", + "color": "rgb(26, 27, 38)", + "border": "rgba(0, 0, 0, 0)" + }, + "metric": { + "background": "rgb(36, 40, 59)", + "color": "rgb(192, 202, 245)", + "border": "rgb(59, 63, 92)" + } + } + } +] \ No newline at end of file diff --git a/docs/development/第二阶段审阅修复与主题适配-2026-09-07.md b/docs/development/第二阶段审阅修复与主题适配-2026-09-07.md new file mode 100644 index 0000000..d3ef173 --- /dev/null +++ b/docs/development/第二阶段审阅修复与主题适配-2026-09-07.md @@ -0,0 +1,24 @@ +# 第二阶段审阅修复与主题适配 + +日期:2026-09-07。工作目录:`G:/OSProject/NotesAgent`,分支:`feat/phase2-completion`。 + +## 修复内容 + +- DOCX 静态图同时受页面可用宽度和高度约束,保留比例,避免长图超出页面;覆盖 A4、Letter 的横图与长图。 +- Agent 参数评分改为最大一对一匹配,避免宽泛参数预期抢占唯一满足具体参数预期的调用;同一调用不可重复计分。 +- 导出提交期间取消会在收到任务 ID 后取消后台任务,并读取实际状态。已经完成、失败或取消请求失败时如实显示结果。 +- Mermaid 围栏识别统一支持大小写和附加信息,包括 `Mermaid`、`mermaid title="Flow"`。 +- Benchmark 页接入共享页面、面板、表单、按钮、状态和主题变量,补充空状态、中文状态、指标格式及窄屏布局;同步更新执行轨迹浏览器检查的定位文字。 + +## 验证 + +- 后端:867 passed;1 条既有 Starlette/httpx 弃用警告。 +- 前端:81 个测试文件、445 项测试通过。 +- 前端生产构建通过;仍有较大分包提示。 +- 六主题:light、dark、sepia、paper-moments、ocean-blue、midnight-purple。分别生成空状态、报告、窄屏记录、窄屏报告截图,共 24 张;页面脚本错误与窄屏页面横向溢出均为零。 +- 主题检查脚本:`frontend/scripts/benchmark-theme-check.cjs`。需先在 5189 端口启动 Vite,并提供 Playwright 与 Edge。接口全部使用拦截测试数据,未调用模型,也不代表真实评测质量。 +- 主题检查结果:`evidence/phase2-review-20260907/theme-results.json`;完整本机截图与测试日志在 `.local-plans/phase2-review/`。 + +DOCX 本次验证包括文档内部图片尺寸和比例;未进行 Word/LibreOffice 排版截图验证。此前真实模型验收记录仍以《第二阶段收尾实现与验收-2026-09-07》为准。 + +用户已有的三份笔记修改未纳入此次修复提交。 diff --git a/frontend/scripts/benchmark-theme-check.cjs b/frontend/scripts/benchmark-theme-check.cjs new file mode 100644 index 0000000..3739f4b --- /dev/null +++ b/frontend/scripts/benchmark-theme-check.cjs @@ -0,0 +1,34 @@ +// Visual fixtures only: all API traffic is intercepted; no provider calls or user data. +const {chromium}=require('playwright');const fs=require('node:fs/promises');const path=require('node:path'); +(async()=>{ + const output=path.resolve(process.argv[2]||'.local-plans/phase2-review/themes');await fs.mkdir(output,{recursive:true}); + const browser=await chromium.launch({channel:'msedge',headless:true});const results=[]; + for(const theme of ['light','dark','sepia','paper-moments','ocean-blue','midnight-purple']){ + const page=await browser.newPage({viewport:{width:1280,height:1400}});let populated=false;const errors=[];page.on('pageerror',e=>errors.push(e.message)); + const run=(id,status,progress)=>({run_id:id,kind:'rag',dataset_id:'rag-demo-v1',status,progress,error_code:status==='failed'?'BENCHMARK_RUN_FAILED':null,config_snapshot:{}}); + await page.route('**/api/**',async route=>{ + const request=route.request();const url=new URL(request.url());let body={items:[]}; + if(url.pathname==='/api/benchmarks/datasets')body={items:[{dataset_id:'rag-demo-v1',description:'视觉测试数据集',case_count:24}]}; + else if(url.pathname==='/api/providers')body={items:[]}; + else if(url.pathname==='/api/benchmarks/rag/runs'&&request.method()==='POST'){populated=true;body=run('visual-completed','completed',1)} + else if(url.pathname==='/api/benchmarks/runs')body={items:populated?[run('visual-completed','completed',1),run('visual-running','running',.5),run('visual-failed','failed',.25)]:[]}; + else if(url.pathname.endsWith('/report'))body={metrics:{fts:{total_cases:24,hit_at_1:.875,hit_at_5:1,mrr:.9235,citation_hit_rate:.75,p50_latency_ms:12.43,p95_latency_ms:22.16,failed_cases:0}},cases:[],config_snapshot:{fixture:true}}; + else if(url.pathname==='/status')body={status:'ready'}; + await route.fulfill({json:body}); + }); + await page.goto('http://127.0.0.1:5189/#/benchmarks');await page.getByText('还没有评测记录',{exact:true}).waitFor(); + await page.evaluate(async id=>{const{useThemeStore}=await import('/src/stores/theme.ts');const store=useThemeStore();if(!['light','dark','sepia'].includes(id))await store.installCommunityTheme(id);if(!store.applyTheme(id,{persist:false}))throw Error('theme failed')},theme); + await page.screenshot({path:path.join(output,`${theme}-empty.png`)}); + await page.getByRole('button',{name:'运行评测',exact:true}).click(); + await page.getByRole('button',{name:'查看报告',exact:true}).first().waitFor();await page.getByRole('button',{name:'查看报告',exact:true}).first().click(); + await page.getByText('87.5%',{exact:true}).waitFor(); + await page.locator('#benchmark-topk').focus(); + await page.screenshot({path:path.join(output,`${theme}-report.png`)}); + const colors=await page.evaluate(()=>{const read=selector=>{const s=getComputedStyle(document.querySelector(selector));return {background:s.backgroundColor,color:s.color,border:s.borderColor}};return {panel:read('.benchmark-config'),input:read('#benchmark-topk'),button:read('.benchmark-config .button-primary'),metric:read('.metric-card')}}); + await page.setViewportSize({width:390,height:1100});await page.screenshot({path:path.join(output,`${theme}-narrow.png`)}); + const overflow=await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth); + await page.locator('.benchmark-report').scrollIntoViewIfNeeded();await page.screenshot({path:path.join(output,`${theme}-narrow-report.png`)}); + if(errors.length||overflow)throw Error(JSON.stringify({theme,errors,overflow}));results.push({theme,errors,overflow,colors});await page.close(); + } + await fs.writeFile(path.join(output,'results.json'),JSON.stringify(results,null,2));await browser.close();console.log(JSON.stringify(results)); +})().catch(e=>{console.error(e);process.exit(1)}); diff --git a/frontend/scripts/phase2-benchmark-browser.cjs b/frontend/scripts/phase2-benchmark-browser.cjs index e2824cc..05aca9e 100644 --- a/frontend/scripts/phase2-benchmark-browser.cjs +++ b/frontend/scripts/phase2-benchmark-browser.cjs @@ -15,7 +15,7 @@ const {chromium}=require('playwright');const fs=require('node:fs/promises');cons await page.screenshot({path:path.join(output,'benchmark-report.png'),fullPage:true}); const pending=page.waitForEvent('download');await page.getByRole('button',{name:'下载完整 JSON'}).click(); await(await pending).saveAs(path.join(output,'agent-ui-report.json')); - await row.getByRole('link',{name:'Agent Trace'}).click(); + await row.getByRole('link',{name:'执行轨迹'}).click(); await page.waitForSelector('.agent-page .trace-visualization',{timeout:20000}); await page.screenshot({path:path.join(output,'benchmark-trace.png'),fullPage:true}); await browser.close();console.log('Benchmark UI start/report/download/Trace completed'); diff --git a/frontend/src/features/benchmarks/BenchmarkView.spec.ts b/frontend/src/features/benchmarks/BenchmarkView.spec.ts new file mode 100644 index 0000000..417ed28 --- /dev/null +++ b/frontend/src/features/benchmarks/BenchmarkView.spec.ts @@ -0,0 +1,29 @@ +// @vitest-environment happy-dom +import {mount,flushPromises} from '@vue/test-utils' +import {afterEach,beforeEach,expect,it,vi} from 'vitest' +import BenchmarkView from './BenchmarkView.vue' +const service=vi.hoisted(()=>({datasets:vi.fn(),list:vi.fn(),start:vi.fn(),cancel:vi.fn(),report:vi.fn()})) +vi.mock('@/services/benchmarkService',()=>({benchmarkService:service})) +vi.mock('@/services/providerService',()=>({listProviders:vi.fn().mockResolvedValue([])})) +beforeEach(()=>{service.datasets.mockResolvedValue([{id:'rag-demo',cases:2}]);service.list.mockResolvedValue([])}) +afterEach(()=>vi.clearAllMocks()) +it('shows a useful empty state and submits the selected retrieval configuration',async()=>{ + const wrapper=mount(BenchmarkView,{global:{stubs:{RouterLink:true}}});await flushPromises() + expect(wrapper.text()).toContain('还没有评测记录') + await wrapper.get('#benchmark-fusion').setValue('weighted') + expect(wrapper.get('#benchmark-rrfk').attributes('disabled')).toBeDefined() + await wrapper.get('form').trigger('submit');await flushPromises() + expect(service.start).toHaveBeenCalledWith('rag',expect.objectContaining({dataset_id:'rag-demo',retrieval:expect.objectContaining({fusion:'weighted'})})) + wrapper.unmount() +}) +it('renders report percentages, unavailable metrics and localized terminal states',async()=>{ + service.list.mockResolvedValue([{id:'r',datasetId:'agent-demo',status:'completed',progress:1,errorCode:null}]) + service.report.mockResolvedValue({metrics:{task_success_rate:.75,tool_argument_accuracy:null,token_usage:120},cases:[],config_snapshot:{}}) + const wrapper=mount(BenchmarkView,{global:{stubs:{RouterLink:true}}});await flushPromises() + expect(wrapper.text()).toContain('已完成') + await wrapper.findAll('button').find(button=>button.text()==='查看报告')!.trigger('click');await flushPromises() + expect(wrapper.get('.benchmark-report').text()).toContain('75%') + expect(wrapper.get('.benchmark-report').text()).toContain('不适用') + expect(wrapper.get('.benchmark-report').text()).toContain('agent-demo') + wrapper.unmount() +}) diff --git a/frontend/src/features/benchmarks/BenchmarkView.vue b/frontend/src/features/benchmarks/BenchmarkView.vue index 6eb63e8..435488f 100644 --- a/frontend/src/features/benchmarks/BenchmarkView.vue +++ b/frontend/src/features/benchmarks/BenchmarkView.vue @@ -6,6 +6,10 @@ const kind = ref<'rag' | 'agent'>('rag'), dataset = ref(''), error = ref(''), bu const datasets = ref>>([]), runs = ref([]) const providers = ref>>([]), provider = ref(''), model = ref('') const report = ref> | null>(null) +const reportName = ref(''), loading = ref(true) +const statusLabels: Record = { queued:'排队中', running:'运行中', completed:'已完成', failed:'失败', cancelled:'已取消' } +const activeCount = computed(() => runs.value.filter(run => ['queued','running'].includes(run.status)).length) +const ratioKeys = new Set(['task_success_rate','tool_selection_accuracy','tool_argument_accuracy','invalid_tool_call_rate','hit_at_1','hit_at_5','recall_at_k','citation_hit_rate','failure_rate']) const topK = ref(5), rrfK = ref(60), rerank = ref(false) const fusion = ref<'rrf' | 'weighted'>('rrf') const metricLabels: Record = { @@ -22,13 +26,13 @@ const metricGroups = computed(() => { return Object.entries(groups).filter(([, value]) => value && typeof value === 'object').map(([name, value]) => ({ name, rows: Object.entries(value as Record).map(([key, number]) => ({ label: metricLabels[key] ?? key, - value: number === null ? '不适用' : typeof number === 'number' ? Number(number.toFixed(4)).toLocaleString() : String(number), + value: number === null ? '不适用' : typeof number === 'number' ? ratioKeys.has(key) ? `${Number((number*100).toFixed(2))}%` : Number(number.toFixed(key.includes('latency') ? 2 : 4)).toLocaleString() : String(number), })), })) }) let timer: ReturnType | undefined, disposed = false async function loadDatasets() { try { datasets.value = await service.datasets(kind.value); dataset.value = datasets.value[0]?.id ?? '' } catch(e) { error.value = String(e) } } -async function refresh() { try { runs.value = await service.list() } catch(e) { error.value = String(e) } if (!disposed) timer = setTimeout(refresh, 1500) } +async function refresh() { try { runs.value = await service.list() } catch(e) { error.value = String(e) } finally { loading.value = false } if (!disposed) timer = setTimeout(refresh, 1500) } watch(kind, loadDatasets) watch(provider, id => { model.value = providers.value.find(p => p.provider_id === id)?.default_model ?? '' }) async function start() { @@ -36,30 +40,84 @@ async function start() { try { await service.start(kind.value, kind.value === 'agent' ? { dataset_id: dataset.value, provider_id: provider.value, model: model.value, max_steps: 6, timeout_seconds: 90, token_budget: 6000 } : { dataset_id: dataset.value, modes: ['fts','vector','hybrid'], retrieval: { top_k: topK.value, fusion: fusion.value, rrf_k: rrfK.value, rerank: rerank.value } }) } catch(e) { error.value = String(e) } finally { busy.value = false } } -async function action(run: BenchmarkRun, cancel = false) { try { if (cancel) await service.cancel(run.id); else report.value = await service.report(run.id) } catch(e) { error.value = String(e) } } +async function action(run: BenchmarkRun, cancel = false) { try { if (cancel) await service.cancel(run.id); else { report.value = await service.report(run.id); reportName.value = run.datasetId } } catch(e) { error.value = String(e) } } function download() { const url = URL.createObjectURL(new Blob([JSON.stringify(report.value,null,2)], { type:'application/json' })); const a=document.createElement('a'); a.href=url; a.download='benchmark-report.json'; a.click(); setTimeout(()=>URL.revokeObjectURL(url),1000) } onMounted(async () => { void refresh(); void loadDatasets(); try { providers.value=(await listProviders()).filter(p=>p.enabled); provider.value=providers.value[0]?.provider_id ?? '' } catch(e) { error.value=String(e) } }) onBeforeUnmount(() => { disposed=true; clearTimeout(timer) }) diff --git a/frontend/src/features/editor/ExportDialog.vue b/frontend/src/features/editor/ExportDialog.vue index ba97302..883876a 100644 --- a/frontend/src/features/editor/ExportDialog.vue +++ b/frontend/src/features/editor/ExportDialog.vue @@ -21,7 +21,7 @@ async function start() { try { const job = await exportService.create(snapshot, name, format.value, { theme_id: theme.currentThemeId, include_title: title.value, page_size: page.value }, controller.signal, editor.currentFilePath ?? undefined) if (!disposed) jobs.value.unshift(job) - } catch (e) { error.value = controller.signal.aborted ? '已取消图表准备' : String(e) } + } catch (e) { error.value = e instanceof DOMException && e.name === 'AbortError' ? '已取消导出' : String(e) } finally { preparing.value = false } } async function action(job: ExportJob, download = false) { diff --git a/frontend/src/services/exportService.spec.ts b/frontend/src/services/exportService.spec.ts index 8121e47..84bb387 100644 --- a/frontend/src/services/exportService.spec.ts +++ b/frontend/src/services/exportService.spec.ts @@ -1,5 +1,9 @@ -import {describe,it,expect,vi} from 'vitest' +// @vitest-environment jsdom +import {webcrypto} from 'node:crypto' +import {describe,it,expect,vi,afterEach} from 'vitest' vi.mock('./apiClient',()=>({apiClient:{post:vi.fn(),get:vi.fn()}})) +vi.mock('./mermaidService',()=>({renderMermaid:vi.fn()})) +import {renderMermaid} from './mermaidService' import {apiClient} from './apiClient' import {exportService} from './exportService' describe('export snapshot contract',()=>{ @@ -16,3 +20,45 @@ describe('export snapshot contract',()=>{ expect(apiClient.post).not.toHaveBeenCalled() }) }) + +const reviewOptions={theme_id:'light',include_title:true,page_size:'A4'} +const queued={job_id:'review-job',status:'queued',warnings:[],file:null,error:null} +afterEach(()=>{vi.restoreAllMocks();vi.unstubAllGlobals();vi.clearAllMocks()}) +it('cancels a created server job after an in-flight submission is aborted',async()=>{ + let finish!:(value:unknown)=>void + vi.mocked(apiClient.post).mockImplementationOnce(()=>new Promise(resolve=>{finish=resolve}) as never).mockResolvedValue({status:'completed'}) + vi.mocked(apiClient.get).mockResolvedValue({...queued,status:'cancelled'}) + const controller=new AbortController() + const pending=exportService.create('# snapshot','review','html',reviewOptions,controller.signal) + controller.abort();finish(queued) + await expect(pending).rejects.toMatchObject({name:'AbortError'}) + expect(apiClient.post).toHaveBeenLastCalledWith('/api/exports/review-job/cancel') +}) +it('does not report cancellation when the server job already completed',async()=>{ + let finish!:(value:unknown)=>void + vi.mocked(apiClient.post).mockImplementationOnce(()=>new Promise(resolve=>{finish=resolve}) as never).mockResolvedValue({status:'completed'}) + vi.mocked(apiClient.get).mockResolvedValue({...queued,status:'completed'}) + const controller=new AbortController() + const pending=exportService.create('# snapshot','review','html',reviewOptions,controller.signal) + controller.abort();finish(queued) + await expect(pending).rejects.toThrow('导出已完成,无法取消') +}) +it('surfaces a server cancellation failure instead of claiming it was cancelled',async()=>{ + let finish!:(value:unknown)=>void + vi.mocked(apiClient.post).mockImplementationOnce(()=>new Promise(resolve=>{finish=resolve}) as never).mockRejectedValue(new Error('network failure')) + const controller=new AbortController() + const pending=exportService.create('# snapshot','review','html',reviewOptions,controller.signal) + controller.abort();finish(queued) + await expect(pending).rejects.toThrow('network failure') +}) +it.each(['mermaid','Mermaid','mermaid title="Flow"'])('prepares a static asset for %s',async language=>{ + vi.stubGlobal('crypto',webcrypto) + vi.stubGlobal('Image',class {src='';decode(){return Promise.resolve()}}) + vi.spyOn(HTMLCanvasElement.prototype,'getContext').mockReturnValue({fillStyle:'',fillRect:vi.fn(),drawImage:vi.fn()} as never) + vi.spyOn(HTMLCanvasElement.prototype,'toDataURL').mockReturnValue('data:image/png;base64,YWJj') + vi.mocked(renderMermaid).mockResolvedValue({svg:'',warnings:[]} as never) + vi.mocked(apiClient.post).mockResolvedValue(queued) + await exportService.create('```'+language+'\nflowchart LR\n A-->B\n```','review','html',reviewOptions) + expect(renderMermaid).toHaveBeenCalledWith('flowchart LR\n A-->B',{mode:'raster',theme:'light'}) + expect(apiClient.post).toHaveBeenCalledWith('/api/exports',expect.objectContaining({assets:[expect.objectContaining({kind:'mermaid',png_base64:'YWJj',source_hash:expect.stringMatching(/^[a-f0-9]{64}$/)})]})) +}) diff --git a/frontend/src/services/exportService.ts b/frontend/src/services/exportService.ts index 64dc51f..7d34705 100644 --- a/frontend/src/services/exportService.ts +++ b/frontend/src/services/exportService.ts @@ -44,7 +44,7 @@ export const exportService = { async create(markdown: string, title: string, format: ExportFormat, options: { theme_id: string; include_title: boolean; page_size: string }, signal?: AbortSignal, filePath?: string) { const blocks: string[] = [] const parser = new Marked() - parser.walkTokens(parser.lexer(markdown), token => { if (token.type === 'code' && token.lang === 'mermaid') blocks.push(token.text) }) + parser.walkTokens(parser.lexer(markdown), token => { if (token.type === 'code' && token.lang?.trim().split(/\s+/)[0]?.toLowerCase() === 'mermaid') blocks.push(token.text) }) const assets = [] for (const source of [...new Set(blocks)]) { signal?.throwIfAborted() @@ -54,7 +54,17 @@ export const exportService = { assets.push({ kind: 'mermaid', source_hash: await hashSource(source), png_base64: await rasterize(result.svg, signal) }) } signal?.throwIfAborted() - return mapJob(await apiClient.post('/api/exports', { source: { type: 'markdown', markdown, file_path: filePath }, title, format, options, assets })) + // Keep the response handle when cancellation arrives during submission: + // aborting HTTP alone could leave an undiscoverable running server job. + const job = mapJob(await apiClient.post('/api/exports', { source: { type: 'markdown', markdown, file_path: filePath }, title, format, options, assets })) + if (signal?.aborted) { + await apiClient.post(`/api/exports/${encodeURIComponent(job.id)}/cancel`) + const current = await apiClient.get(`/api/exports/${encodeURIComponent(job.id)}`) + if (current.status === 'completed') throw new Error('导出已完成,无法取消;请在任务列表中下载。') + if (current.status === 'failed') throw new Error(current.error || '导出任务已失败,请查看任务列表。') + signal.throwIfAborted() + } + return job }, async get(id: string) { return mapJob(await apiClient.get(`/api/exports/${encodeURIComponent(id)}`)) }, async list() { const response = await apiClient.get<{ items: JobWire[] }>('/api/exports'); return response.items.map(mapJob) }, -- 2.43.0 From 9f097ea629fcc1abf34205360000481aec82f3b6 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Mon, 7 Sep 2026 14:04:03 +0800 Subject: [PATCH 05/10] fix: preserve exports on close and support themed PDF without export quotas --- backend/app/contracts.py | 22 +++- backend/app/export/assets.py | 41 +++---- backend/app/export/exporters/_common.py | 5 +- backend/app/export/exporters/pdf.py | 79 ++++++------- backend/app/export/service.py | 14 +-- backend/app/export/themes.py | 10 ++ backend/app/plot/parser.py | 24 ++-- backend/app/plot/render.py | 4 +- backend/app/plot/render_reportlab.py | 35 +++--- backend/tests/test_export.py | 12 +- backend/tests/test_pdf_theme_resources.py | 96 ++++++++++++++++ docs/contracts/第二阶段接口契约-开发版.md | 8 +- docs/development/Export开发说明.md | 11 ++ .../PDF主题与资源限制修复验收-2026-09-07.md | 22 ++++ .../evidence/pdf-theme-20260907/results.json | 104 ++++++++++++++++++ .../src/features/editor/ExportDialog.spec.ts | 22 ++++ frontend/src/features/editor/ExportDialog.vue | 9 +- frontend/src/services/exportService.spec.ts | 24 +++- frontend/src/services/exportService.ts | 43 ++++++-- frontend/src/services/mermaidService.ts | 23 +++- 20 files changed, 485 insertions(+), 123 deletions(-) create mode 100644 backend/tests/test_pdf_theme_resources.py create mode 100644 docs/development/PDF主题与资源限制修复验收-2026-09-07.md create mode 100644 docs/development/evidence/pdf-theme-20260907/results.json create mode 100644 frontend/src/features/editor/ExportDialog.spec.ts diff --git a/backend/app/contracts.py b/backend/app/contracts.py index dc3fd30..07e9cc4 100644 --- a/backend/app/contracts.py +++ b/backend/app/contracts.py @@ -1426,7 +1426,18 @@ class ExportSource(Contract): return self +class ExportPalette(Contract): + page: str = Field(pattern=r'^#[0-9a-fA-F]{6}$') + surface: str = Field(pattern=r'^#[0-9a-fA-F]{6}$') + text: str = Field(pattern=r'^#[0-9a-fA-F]{6}$') + muted: str = Field(pattern=r'^#[0-9a-fA-F]{6}$') + code: str = Field(pattern=r'^#[0-9a-fA-F]{6}$') + border: str = Field(pattern=r'^#[0-9a-fA-F]{6}$') + accent: str = Field(pattern=r'^#[0-9a-fA-F]{6}$') + + class ExportOptions(Contract): + palette: ExportPalette | None = None theme_id: str = "light" include_title: bool = True include_metadata: bool = False @@ -1437,16 +1448,23 @@ class ExportOptions(Contract): class ExportAsset(Contract): kind: Literal['mermaid', 'math_block', 'math_inline', 'image'] source_hash: str = Field(pattern=r'^[a-f0-9]{64}$') - png_base64: str = Field(max_length=2800000) + png_base64: str class ExportRequest(Contract): - assets: list[ExportAsset] = Field(default_factory=list, max_length=64) + assets: list[ExportAsset] = Field(default_factory=list) title: str = Field(default="", max_length=200) source: ExportSource format: ExportFormat options: ExportOptions = Field(default_factory=ExportOptions) + @model_validator(mode="after") + def _asset_limits(self) -> "ExportRequest": + if self.format != ExportFormat.pdf: + if len(self.assets) > 64 or any(len(asset.png_base64) > 2800000 for asset in self.assets): + raise ValueError("export asset count or size limit exceeded") + return self + class ExportProgress(Contract): phase: str diff --git a/backend/app/export/assets.py b/backend/app/export/assets.py index 5415345..9aca754 100644 --- a/backend/app/export/assets.py +++ b/backend/app/export/assets.py @@ -1,4 +1,4 @@ -"""Bounded raster-only resource boundary. No URLs, XML or filesystem paths accepted.""" +"""Raster-only resources; PDF bypasses export quotas but retains path/format validation.""" import base64 import hashlib import threading @@ -8,12 +8,14 @@ from app.errors import ApiError _math_lock = threading.Lock() -def enrich_document(document, file_path=None): - """Embed local vault images and bounded MathText. Unsupported TeX stays explicit.""" +def enrich_document(document, file_path=None, unlimited=False, options=None): + """Embed Vault images and MathText, with format-specific quotas and palette.""" from app.config import get_settings from urllib.parse import unquote, urlsplit vault = get_settings().vault_path.resolve() base = (vault / (file_path or '')).parent if file_path else vault + from app.export.themes import pdf_palette + palette = pdf_palette(options, []) if unlimited and options else None warnings = [] count = total = pixels = 0 def visit(node): @@ -21,14 +23,14 @@ def enrich_document(document, file_path=None): if node.type in {'image','math_block','math_inline'} or node.attributes.get('static_png'): count += 1 try: - if count > 64: raise ValueError('resource count') + if not unlimited and count > 64: raise ValueError('resource count') if node.attributes.get('static_png'): raw = node.attributes['static_png'] elif node.type == 'image': src = str(node.attributes.get('src','')) if urlsplit(src).scheme or src.startswith('//'): raise ValueError('remote image') path = (base / unquote(src)).resolve() - if not path.is_relative_to(vault) or path.suffix.lower() not in {'.png','.jpg','.jpeg','.webp'} or path.stat().st_size > 2_000_000: + if not path.is_relative_to(vault) or path.suffix.lower() not in {'.png','.jpg','.jpeg','.webp'} or (not unlimited and path.stat().st_size > 2_000_000): raise ValueError('image path or budget') raw = path.read_bytes() else: @@ -36,23 +38,24 @@ def enrich_document(document, file_path=None): depth = 0 for char in source: depth += (char == '{') - (char == '}') - if depth > 20: raise ValueError('math depth') - if len(source) > 512 or depth != 0: raise ValueError('math budget') + if not unlimited and depth > 20: raise ValueError('math depth') + if (not unlimited and len(source) > 512) or depth != 0: raise ValueError('math budget') from matplotlib.mathtext import math_to_image - with _math_lock: + from matplotlib import rc_context + with _math_lock, rc_context({'savefig.transparent': bool(palette)}): out = BytesIO() - math_to_image('$'+source+'$', out, dpi=180, format='png', color='black') + math_to_image('$'+source+'$', out, dpi=180, format='png', color=palette['text'] if palette else 'black') raw = out.getvalue() with Image.open(BytesIO(raw)) as image: pixels += image.width * image.height - if pixels > 16_000_000: raise ValueError('document pixels') - if image.width * image.height > 4_000_000: raise ValueError('image dimensions') + if not unlimited and pixels > 16_000_000: raise ValueError('document pixels') + if not unlimited and image.width * image.height > 4_000_000: raise ValueError('image dimensions') out = BytesIO() - # Flatten alpha on white for portable print/Word output. - rgba=image.convert('RGBA'); background=Image.new('RGBA',rgba.size,'white') + # Composite transparency over the PDF theme or the print/Word white surface. + rgba=image.convert('RGBA'); background=Image.new('RGBA',rgba.size,palette['surface'] if palette else 'white') background.alpha_composite(rgba); background.convert('RGB').save(out,'PNG') png=out.getvalue();total += len(png) - if total > 8_000_000: raise ValueError('resource bytes') + if not unlimited and total > 8_000_000: raise ValueError('resource bytes') node.attributes['static_png']=png except Exception: node.attributes.pop('static_png', None) @@ -66,26 +69,26 @@ def enrich_document(document, file_path=None): def source_hash(source): return hashlib.sha256(source.strip().encode()).hexdigest() -def validate_assets(assets): +def validate_assets(assets, unlimited=False): result = {} total = pixels = 0 for asset in assets: try: raw = base64.b64decode(asset.png_base64, validate=True) total += len(raw) - if total > 8 * 1024 * 1024: + if not unlimited and total > 8 * 1024 * 1024: raise ValueError('asset budget') with Image.open(BytesIO(raw)) as image: pixels += image.width * image.height - if pixels > 16_000_000: raise ValueError('document pixel budget') - if image.format != 'PNG' or image.width * image.height > 4_000_000: + if not unlimited and pixels > 16_000_000: raise ValueError('document pixel budget') + if image.format != 'PNG' or (not unlimited and image.width * image.height > 4_000_000): raise ValueError('image budget') image.load() out = BytesIO() rgba = image.convert('RGBA') background = Image.new('RGBA', rgba.size, 'white') background.alpha_composite(rgba) - background.convert('RGB').save(out, 'PNG') + (rgba if unlimited else background.convert('RGB')).save(out, 'PNG') key = (asset.kind, asset.source_hash) if key in result: raise ValueError('duplicate asset') diff --git a/backend/app/export/exporters/_common.py b/backend/app/export/exporters/_common.py index cbf41ed..f2b1cf0 100644 --- a/backend/app/export/exporters/_common.py +++ b/backend/app/export/exporters/_common.py @@ -1,7 +1,6 @@ """导出器共享工具:URL 协议校验、函数图像预算与占位 warning 文案。 -html / pdf / docx 三个导出器共用同一套安全规则与函数图像资源预算,避免各写一份 -导致行为漂移。 +导出器共享 URL 规则;HTML / DOCX 使用文档资源预算,PDF 不使用这些预算。 """ from __future__ import annotations @@ -27,7 +26,7 @@ MAX_TOTAL_PLOT_NODES = 8000 class FunctionPlotBudget: """函数图像文档级资源预算:数量上限 + 累计 AST 节点上限。 - HTML 与 PDF 导出器在渲染每个 function-plot 图块前先问预算,超限即回退源码占位, + HTML 与 DOCX 导出器在渲染每个 function-plot 图块前先问预算,超限即回退源码占位, 不解析不采样,避免多图块组合复杂度耗尽内存/CPU。 """ diff --git a/backend/app/export/exporters/pdf.py b/backend/app/export/exporters/pdf.py index 2ba7949..b771eef 100644 --- a/backend/app/export/exporters/pdf.py +++ b/backend/app/export/exporters/pdf.py @@ -20,7 +20,7 @@ from reportlab.pdfbase.cidfonts import UnicodeCIDFont from reportlab.platypus import ( Paragraph, Indenter, - Preformatted, + XPreformatted, SimpleDocTemplate, Spacer, Table, @@ -29,12 +29,11 @@ from reportlab.platypus import ( from reportlab.platypus.flowables import HRFlowable from app.contracts import ExportOptions -from app.export.themes import CALLOUTS, print_theme_warning +from app.export.themes import CALLOUTS, pdf_palette from app.export.document import Document, DocumentNode, ExportResult from app.export.exporters._common import ( MERMAID_WARNING, RAW_HTML_WARNING, - FunctionPlotBudget, format_meta_value, format_plot_diagnostic, safe_url, @@ -54,10 +53,11 @@ _HEADING_SIZES = {1: 20, 2: 16, 3: 14, 4: 12, 5: 11, 6: 10.5} _QUOTE_COLOR = "#57606a" -def _make_styles() -> dict[str, ParagraphStyle]: +def _make_styles(palette) -> dict[str, ParagraphStyle]: body = ParagraphStyle( "pdf-body", fontName=_FONT, + textColor=palette["text"], fontSize=10.5, leading=16, spaceAfter=6, @@ -67,7 +67,7 @@ def _make_styles() -> dict[str, ParagraphStyle]: "pdf-quote", parent=body, leftIndent=14, - textColor="#57606a", + textColor=palette["muted"], spaceBefore=4, spaceAfter=6, ) @@ -78,8 +78,8 @@ def _make_styles() -> dict[str, ParagraphStyle]: leading=12, leftIndent=6, rightIndent=6, - backColor="#f6f8fa", - borderColor="#d0d7de", + backColor=palette["code"], + borderColor=palette["border"], borderWidth=0.5, borderPadding=6, spaceBefore=4, @@ -88,9 +88,9 @@ def _make_styles() -> dict[str, ParagraphStyle]: math = ParagraphStyle("pdf-math", parent=body, alignment=TA_CENTER, spaceBefore=6) cell = ParagraphStyle("pdf-cell", parent=body, fontSize=10, leading=14, spaceAfter=0) cell_head = ParagraphStyle( - "pdf-cell-head", parent=cell, textColor="#1f2328", fontSize=10 + "pdf-cell-head", parent=cell, textColor=palette["text"], fontSize=10 ) - meta = ParagraphStyle("pdf-meta", parent=body, fontSize=8.5, leading=13, textColor="#57606a") + meta = ParagraphStyle("pdf-meta", parent=body, fontSize=8.5, leading=13, textColor=palette["muted"]) styles: dict[str, ParagraphStyle] = { "body": body, "title": title, @@ -109,6 +109,7 @@ def _make_styles() -> dict[str, ParagraphStyle]: leading=size * 1.4, spaceBefore=14 if level <= 2 else 10, spaceAfter=6, + keepWithNext=True, ) return styles @@ -118,17 +119,17 @@ class PdfExporter: def render(self, document: Document, options: ExportOptions) -> ExportResult: """同步渲染;CPU 密集,调用方应放入线程执行,避免阻塞事件循环。""" - self._styles = _make_styles() warnings: list[str] = [] - print_theme_warning(options, warnings, "PDF") + self._palette = pdf_palette(options, warnings) + self._styles = _make_styles(self._palette) if _FONT == "STSong-Light": warnings.append("PDF 使用 CID 字体,阅读器需提供中文字体;可配置 APP_EXPORT_FONT 嵌入 TrueType 字体") page = _PAGE_SIZES.get((options.page_size or "A4").lower(), A4) self._options = options - self._plot_budget = FunctionPlotBudget() self._plot_renderer = FunctionPlotStaticRenderer() # 内容区宽度(左右各 20mm 边距),供函数图像缩放适配页面 - self._plot_width = page[0] - 40 * mm + self._plot_width = page[0] - 40 * mm - 12 + self._plot_height = page[1] - 36 * mm - 12 buf = BytesIO() doc = SimpleDocTemplate( buf, @@ -144,7 +145,14 @@ class PdfExporter: self._render_header(document, options, story) self._render_children(document.children, story, warnings) - doc.build(story) + def paint_page(canvas, template): + canvas.saveState() + canvas.setFillColor(self._palette['page']) + canvas.rect(0, 0, page[0], page[1], fill=1, stroke=0) + canvas.setFillColor(self._palette['surface']) + canvas.roundRect(12*mm, 10*mm, page[0]-24*mm, page[1]-20*mm, 5*mm, fill=1, stroke=0) + canvas.restoreState() + doc.build(story, onFirstPage=paint_page, onLaterPages=paint_page) return ExportResult(content=buf.getvalue(), mime_type=_MIME, warnings=warnings) async def export(self, document: Document, options: ExportOptions) -> ExportResult: @@ -172,7 +180,7 @@ class PdfExporter: if node.attributes.get('static_png'): from reportlab.platypus import Image image = Image(BytesIO(node.attributes['static_png'])) - scale = min(1, self._plot_width / image.imageWidth, 600 / image.imageHeight) + scale = min(1, self._plot_width / image.imageWidth, self._plot_height / image.imageHeight) image.drawWidth = image.imageWidth * scale image.drawHeight = image.imageHeight * scale story.append(image) @@ -194,9 +202,13 @@ class PdfExporter: def _block_callout(self, node, story, warnings): kind = node.attributes['kind'] icon, color = CALLOUTS[kind] + from reportlab.lib.colors import HexColor + background = HexColor(self._palette['code']) + if .2126*background.red + .7152*background.green + .0722*background.blue < .5: + color = {'#0969da':'#a5d6ff','#7041a0':'#d2a8ff','#176f41':'#7ee787','#805400':'#f2cc60','#b42318':'#ffa198','#57606a':self._palette['muted']}[color] title = self._render_inline(node.children[0].children,warnings) style = ParagraphStyle('callout-'+kind,parent=self._styles['body'],textColor=color, - backColor='#f6f8fa',borderColor=color,borderWidth=1,borderPadding=6,spaceBefore=8,spaceAfter=8) + backColor=self._palette['code'],borderColor=color,borderWidth=1,borderPadding=6,spaceBefore=8,spaceAfter=8) story.append(Paragraph(_html.escape(icon)+' '+title,style)) self._render_children(node.children[1:],story,warnings) @@ -209,7 +221,7 @@ class PdfExporter: Paragraph(self._render_inline(child.children, warnings), self._styles["quote"]) ) elif child.type == "list": - self._block_list(child, story, warnings, indent=14, color=_QUOTE_COLOR) + self._block_list(child, story, warnings, indent=14, color=self._palette['muted']) else: self._render_block(child, story, warnings) @@ -301,7 +313,7 @@ class PdfExporter: data.append(cells) table = Table(data, repeatRows=head_row_count) commands = [ - ("GRID", (0, 0), (-1, -1), 0.5, "#d0d7de"), + ("GRID", (0, 0), (-1, -1), 0.5, self._palette["border"]), ("VALIGN", (0, 0), (-1, -1), "TOP"), ("LEFTPADDING", (0, 0), (-1, -1), 6), ("RIGHTPADDING", (0, 0), (-1, -1), 6), @@ -309,53 +321,42 @@ class PdfExporter: ("BOTTOMPADDING", (0, 0), (-1, -1), 4), ] if head_row_count: - commands.append(("BACKGROUND", (0, 0), (-1, head_row_count - 1), "#f6f8fa")) + commands.append(("BACKGROUND", (0, 0), (-1, head_row_count - 1), self._palette["code"])) table.setStyle(TableStyle(commands)) story.append(table) def _block_code_block(self, node: DocumentNode, story: list, warnings: list[str]) -> None: - story.append(Preformatted(node.text, self._styles["code"])) + story.append(XPreformatted(_html.escape(node.text), self._styles["code"])) def _block_thematic_break(self, node: DocumentNode, story: list, warnings: list[str]) -> None: story.append(Spacer(1, 4)) - story.append(HRFlowable(width="100%", color="#d0d7de", thickness=0.5)) + story.append(HRFlowable(width="100%", color=self._palette["border"], thickness=0.5)) story.append(Spacer(1, 6)) def _block_mermaid(self, node: DocumentNode, story: list, warnings: list[str]) -> None: warnings.append(MERMAID_WARNING) - story.append(Preformatted(node.text, self._styles["code"])) + story.append(XPreformatted(_html.escape(node.text), self._styles["code"])) def _block_function_plot(self, node: DocumentNode, story: list, warnings: list[str]) -> None: - # 文档级数量上限:超出部分直接回退占位,不解析不采样,防止海量图像耗尽资源 - over = self._plot_budget.check_count() - if over is not None: - warnings.append(over) - story.append(Preformatted(node.text, self._styles["code"])) - return # 解析与渲染共同纳入局部异常回退:单个图像失败只回退占位 + warning, # 绝不阻断整篇导出(含复杂表达式触发的 RecursionError 等异常)。 try: request = StaticRenderRequest( kind="function_plot", source=node.text, theme=self._options.theme_id ) - parsed = self._plot_renderer.parse(request) + from app.plot.parser import parse_source + parsed = parse_source(request.source, unlimited=True) for diag in parsed.diagnostics: warnings.append(format_plot_diagnostic(diag)) if parsed.plot is None: - story.append(Preformatted(node.text, self._styles["code"])) - return - # 文档级累计复杂度预算:超出后回退占位,不再采样求值 - over = self._plot_budget.check_nodes(parsed.plot.node_count) - if over is not None: - warnings.append(over) - story.append(Preformatted(node.text, self._styles["code"])) + story.append(XPreformatted(_html.escape(node.text), self._styles["code"])) return # Drawing 本身即 Flowable,缩放后追加到 story,与 HTML 视觉一致 - drawing = render_drawing(parsed.plot, width=self._plot_width) + drawing = render_drawing(parsed.plot, width=self._plot_width, palette=self._palette, unlimited=True, max_height=self._plot_height) story.append(drawing) except Exception as exc: warnings.append(f"函数图像:解析或渲染失败,已回退占位({exc})") - story.append(Preformatted(node.text, self._styles["code"])) + story.append(XPreformatted(_html.escape(node.text), self._styles["code"])) def _block_math_block(self, node: DocumentNode, story: list, warnings: list[str]) -> None: story.append(Paragraph(f"$${_html.escape(node.text)}$$", self._styles["math"])) @@ -393,7 +394,7 @@ class PdfExporter: if safe_href is None: warnings.append(f"链接协议不安全,已降级为纯文本:{href!r}") return inner - return f'{inner}' + return f'{inner}' if t == "image": src = str(node.attributes.get("src") or "") alt = str(node.attributes.get("alt") or "") diff --git a/backend/app/export/service.py b/backend/app/export/service.py index dfe7b31..f2fffe7 100644 --- a/backend/app/export/service.py +++ b/backend/app/export/service.py @@ -146,7 +146,7 @@ def _evict_terminal() -> bool: return True -async def _resolve_source(source: ExportSource) -> tuple[str, str, dict | None]: +async def _resolve_source(source: ExportSource, unlimited: bool = False) -> tuple[str, str, dict | None]: """把导出源解析为 (markdown, title, metadata);metadata 仅 note 源提供。""" if source.type == ExportSourceType.note: note = await note_service.get_note(source.note_id) @@ -157,7 +157,7 @@ async def _resolve_source(source: ExportSource) -> tuple[str, str, dict | None]: "note not found", {"note_id": source.note_id}, ) - if len(note.markdown) > MAX_MARKDOWN_CHARS: + if not unlimited and len(note.markdown) > MAX_MARKDOWN_CHARS: raise ApiError( 400, "EXPORT_OPTIONS_INVALID", @@ -175,7 +175,7 @@ async def _resolve_source(source: ExportSource) -> tuple[str, str, dict | None]: markdown = source.markdown or "" if not markdown.strip(): raise ApiError(400, "EXPORT_OPTIONS_INVALID", "markdown source must not be empty") - if len(markdown) > MAX_MARKDOWN_CHARS: + if not unlimited and len(markdown) > MAX_MARKDOWN_CHARS: raise ApiError( 400, "EXPORT_OPTIONS_INVALID", @@ -187,10 +187,10 @@ async def _resolve_source(source: ExportSource) -> tuple[str, str, dict | None]: async def create_export(request: ExportRequest) -> ExportJob: """创建导出任务,立即返回 queued 的 ExportJob,由后台 Task 渲染。""" - markdown, title, metadata = await _resolve_source(request.source) + markdown, title, metadata = await _resolve_source(request.source, request.format == ExportFormat.pdf) title = request.title or title from app.export.assets import validate_assets - assets = await asyncio.to_thread(validate_assets, request.assets) + assets = await asyncio.to_thread(validate_assets, request.assets, request.format == ExportFormat.pdf) if not _evict_terminal(): raise ApiError( @@ -283,12 +283,12 @@ async def _execute( document.attributes["metadata"] = metadata from app.export.assets import enrich_document - resource_warnings = await asyncio.to_thread(enrich_document, document, (metadata or {}).get('file_path')) + resource_warnings = await asyncio.to_thread(enrich_document, document, (metadata or {}).get('file_path'), format == ExportFormat.pdf, options) result = await asyncio.to_thread(_render_document, document, options, format) result.warnings[:0] = resource_warnings if cancel_event.is_set(): raise ExportCancelled() - if len(result.content) > MAX_EXPORT_BYTES: + if format != ExportFormat.pdf and len(result.content) > MAX_EXPORT_BYTES: raise ExportTooLarge() ext = _extension_for(format) diff --git a/backend/app/export/themes.py b/backend/app/export/themes.py index bf833cc..42dde44 100644 --- a/backend/app/export/themes.py +++ b/backend/app/export/themes.py @@ -32,3 +32,13 @@ ALIASES = {'summary':'abstract','tldr':'abstract','hint':'tip', 'check':'success','done':'success','help':'question','faq':'question', 'caution':'warning','attention':'warning','fail':'failure','missing':'failure', 'error':'danger','cite':'quote'} + + +def pdf_palette(options, warnings): + if options.palette is not None: + return options.palette.model_dump() + theme_id = options.theme_id + if theme_id not in PALETTES: + warnings.append(f'PDF 不支持主题 {theme_id},已使用 light 导出配色') + theme_id = 'light' + return dict(zip(('page','surface','text','muted','code','border','accent'), PALETTES[theme_id])) diff --git a/backend/app/plot/parser.py b/backend/app/plot/parser.py index 4dab231..f9dcf8c 100644 --- a/backend/app/plot/parser.py +++ b/backend/app/plot/parser.py @@ -143,7 +143,7 @@ def _preprocess(expr: str) -> str: return _insert_implicit_multiplication(expr.replace("^", "**")) -def _check_node(node: ast.AST, depth: int = 0, counter: list[int] | None = None) -> None: +def _check_node(node: ast.AST, depth: int = 0, counter: list[int] | None = None, unlimited: bool = False) -> None: """白名单校验:任何越界节点都抛 FUNCTION_PLOT_EXPRESSION_UNSAFE。 同时限制 AST 深度与节点总数,避免超长/超深表达式在递归校验或求值时触发 @@ -151,10 +151,10 @@ def _check_node(node: ast.AST, depth: int = 0, counter: list[int] | None = None) """ if counter is None: counter = [0] - if depth > _MAX_AST_DEPTH: + if not unlimited and depth > _MAX_AST_DEPTH: _unsafe(f"表达式嵌套过深(超过 {_MAX_AST_DEPTH} 层)") counter[0] += 1 - if counter[0] > _MAX_AST_NODES: + if not unlimited and counter[0] > _MAX_AST_NODES: _unsafe(f"表达式过于复杂(节点数超过 {_MAX_AST_NODES})") if isinstance(node, ast.Constant): if isinstance(node.value, bool) or not isinstance(node.value, (int, float)): @@ -167,13 +167,13 @@ def _check_node(node: ast.AST, depth: int = 0, counter: list[int] | None = None) if isinstance(node, ast.BinOp): if not isinstance(node.op, _ALLOWED_BINOPS): _unsafe(f"不支持的运算符 {type(node.op).__name__}") - _check_node(node.left, depth + 1, counter) - _check_node(node.right, depth + 1, counter) + _check_node(node.left, depth + 1, counter, unlimited) + _check_node(node.right, depth + 1, counter, unlimited) return if isinstance(node, ast.UnaryOp): if not isinstance(node.op, _ALLOWED_UNARY): _unsafe(f"不支持的运算符 {type(node.op).__name__}") - _check_node(node.operand, depth + 1, counter) + _check_node(node.operand, depth + 1, counter, unlimited) return if isinstance(node, ast.Call): if not isinstance(node.func, ast.Name) or node.func.id not in _FUNCTIONS: @@ -184,12 +184,12 @@ def _check_node(node: ast.AST, depth: int = 0, counter: list[int] | None = None) if len(node.args) != 1: _unsafe(f"{node.func.id} 需要 1 个参数,实际 {len(node.args)} 个") for arg in node.args: - _check_node(arg, depth + 1, counter) + _check_node(arg, depth + 1, counter, unlimited) return _unsafe(f"不支持的语法 {type(node).__name__}") -def parse_expression(expr: str) -> ast.Expression: +def parse_expression(expr: str, unlimited: bool = False) -> ast.Expression: """把数学表达式解析为已通过白名单校验的 AST(可直接交给 evaluate)。""" preprocessed = _preprocess(expr) try: @@ -211,7 +211,7 @@ def parse_expression(expr: str) -> ast.Expression: message="表达式嵌套过深,无法解析", ) ) from exc - _check_node(tree.body) + _check_node(tree.body, unlimited=unlimited) return tree @@ -279,7 +279,7 @@ def _parse_directive(line: str) -> tuple[str, str] | None: return key, value.strip() -def parse_source(source: str) -> FunctionPlotParseResult: +def parse_source(source: str, unlimited: bool = False) -> FunctionPlotParseResult: """把 function-plot fenced block 源码解析为 FunctionPlot + 诊断。""" diagnostics: list[PlotDiagnostic] = [] expressions: list[FunctionPlotExpression] = [] @@ -371,7 +371,7 @@ def parse_source(source: str) -> FunctionPlotParseResult: continue try: - tree = parse_expression(expr_text) + tree = parse_expression(expr_text, unlimited=unlimited) except PlotParseError as exc: exc.diagnostic.line = lineno diagnostics.append(exc.diagnostic) @@ -380,7 +380,7 @@ def parse_source(source: str) -> FunctionPlotParseResult: total_nodes += _count_nodes(tree.body) expressions.append(FunctionPlotExpression(expression=expr_text)) # 表达式数量超限:整块回退并提前终止,避免对海量表达式做采样求值 - if len(expressions) > _MAX_EXPRESSIONS: + if not unlimited and len(expressions) > _MAX_EXPRESSIONS: diagnostics.append( PlotDiagnostic( severity="error", diff --git a/backend/app/plot/render.py b/backend/app/plot/render.py index 40f494c..1685ecb 100644 --- a/backend/app/plot/render.py +++ b/backend/app/plot/render.py @@ -339,7 +339,7 @@ def _sample_segments( return clipped -def compute_geometry(plot: FunctionPlot) -> PlotGeometry: +def compute_geometry(plot: FunctionPlot, unlimited: bool = False) -> PlotGeometry: """解析并计算几何,供 SVG 与 reportlab 后端复用。""" warnings: list[str] = [] xmin, xmax = plot.domain @@ -351,7 +351,7 @@ def compute_geometry(plot: FunctionPlot) -> PlotGeometry: fns: list[tuple[object, object]] = [] for expr in plot.expressions: try: - tree = parse_expression(expr.expression) + tree = parse_expression(expr.expression, unlimited=unlimited) except PlotParseError as exc: warnings.append(f"表达式无法渲染,已跳过:{expr.expression}({exc.diagnostic.message})") continue diff --git a/backend/app/plot/render_reportlab.py b/backend/app/plot/render_reportlab.py index 576dc9f..e6b0f7a 100644 --- a/backend/app/plot/render_reportlab.py +++ b/backend/app/plot/render_reportlab.py @@ -26,9 +26,12 @@ _TICK_FONT_SIZE = 10 _LABEL_FONT_SIZE = 12 -def _build_drawing(geo: PlotGeometry) -> Drawing: +def _build_drawing(geo: PlotGeometry, palette=None) -> Drawing: """由共享几何构建矢量 Drawing(坐标翻转后仍沿用 SVG 的像素布局)。""" drawing = Drawing(geo.width, geo.height) + grid_color = HexColor(palette['border']) if palette else _GRID_COLOR + axis_color = HexColor(palette['muted']) if palette else _AXIS_COLOR + label_color = HexColor(palette['text']) if palette else _LABEL_COLOR # SVG y-down → reportlab y-up:翻转像素 y def sx(x: float) -> float: @@ -41,19 +44,19 @@ def _build_drawing(geo: PlotGeometry) -> Drawing: if geo.grid: for x in geo.xticks: drawing.add( - Line(sx(x), sy(geo.ymin), sx(x), sy(geo.ymax), strokeColor=_GRID_COLOR, strokeWidth=0.5) + Line(sx(x), sy(geo.ymin), sx(x), sy(geo.ymax), strokeColor=grid_color, strokeWidth=0.5) ) for y in geo.yticks: drawing.add( - Line(sx(geo.xmin), sy(y), sx(geo.xmax), sy(y), strokeColor=_GRID_COLOR, strokeWidth=0.5) + Line(sx(geo.xmin), sy(y), sx(geo.xmax), sy(y), strokeColor=grid_color, strokeWidth=0.5) ) # 坐标轴(过原点画在原点,否则贴边,与 SVG 一致) drawing.add( - Line(sx(geo.xmin), sy(geo.x_axis_y), sx(geo.xmax), sy(geo.x_axis_y), strokeColor=_AXIS_COLOR, strokeWidth=0.7) + Line(sx(geo.xmin), sy(geo.x_axis_y), sx(geo.xmax), sy(geo.x_axis_y), strokeColor=axis_color, strokeWidth=0.7) ) drawing.add( - Line(sx(geo.y_axis_x), sy(geo.ymin), sx(geo.y_axis_x), sy(geo.ymax), strokeColor=_AXIS_COLOR, strokeWidth=0.7) + Line(sx(geo.y_axis_x), sy(geo.ymin), sx(geo.y_axis_x), sy(geo.ymax), strokeColor=axis_color, strokeWidth=0.7) ) # 刻度数字(x 轴下方、y 轴左侧) @@ -61,14 +64,14 @@ def _build_drawing(geo: PlotGeometry) -> Drawing: drawing.add( String( sx(x), sy(geo.x_axis_y) - 14, _fmt_num(x), - fontName=_FONT, fontSize=_TICK_FONT_SIZE, fillColor=_AXIS_COLOR, textAnchor="middle", + fontName=_FONT, fontSize=_TICK_FONT_SIZE, fillColor=axis_color, textAnchor="middle", ) ) for y in geo.yticks: drawing.add( String( sx(geo.y_axis_x) - 6, sy(y) - 3, _fmt_num(y), - fontName=_FONT, fontSize=_TICK_FONT_SIZE, fillColor=_AXIS_COLOR, textAnchor="end", + fontName=_FONT, fontSize=_TICK_FONT_SIZE, fillColor=axis_color, textAnchor="end", ) ) @@ -83,7 +86,7 @@ def _build_drawing(geo: PlotGeometry) -> Drawing: drawing.add( String( geo.width / 2, 10, geo.xlabel, - fontName=_FONT, fontSize=_LABEL_FONT_SIZE, fillColor=_LABEL_COLOR, textAnchor="middle", + fontName=_FONT, fontSize=_LABEL_FONT_SIZE, fillColor=label_color, textAnchor="middle", ) ) if geo.ylabel: @@ -95,7 +98,7 @@ def _build_drawing(geo: PlotGeometry) -> Drawing: label.add( String( 0, 0, geo.ylabel, - fontName=_FONT, fontSize=_LABEL_FONT_SIZE, fillColor=_LABEL_COLOR, textAnchor="middle", + fontName=_FONT, fontSize=_LABEL_FONT_SIZE, fillColor=label_color, textAnchor="middle", ) ) label.translate(16, geo.height / 2) @@ -105,19 +108,25 @@ def _build_drawing(geo: PlotGeometry) -> Drawing: return drawing -def render_drawing(plot: FunctionPlot, width: float | None = None) -> Drawing: +def render_drawing(plot: FunctionPlot, width: float | None = None, palette=None, unlimited=False, max_height=None) -> Drawing: """把已解析的 FunctionPlot 渲染为 reportlab Drawing(可直接追加到 platypus story)。 ``width`` 为目标输出宽度(点),用于把 640px 的几何缩放到页面内容宽;省略则按 原始尺寸输出。缩放只影响 PDF 渲染,不改动共享几何。 """ - geo = compute_geometry(plot) - drawing = _build_drawing(geo) + geo = compute_geometry(plot, unlimited=unlimited) + if palette: + from reportlab.lib.colors import HexColor as color + bg = color(palette['surface']) + if .2126*bg.red + .7152*bg.green + .0722*bg.blue < .5: + colors = ['#79c0ff','#ff9b9b','#7ee787','#d2a8ff','#f2cc60','#ffa657'] + geo.colors = [value if plot.expressions[i].color else colors[i % len(colors)] for i,value in enumerate(geo.colors)] + drawing = _build_drawing(geo, palette) legend_height = ((len(plot.expressions)+1)//2)*24 drawing.height += legend_height for index, expression in enumerate(plot.expressions): drawing.add(String(24+(index%2)*310,geo.height+legend_height-18-(index//2)*24, expression.label or 'y = '+expression.expression,fontName=_FONT,fontSize=12,fillColor=HexColor(geo.colors[index]))) if width is not None and width > 0: - drawing.renderScale = min(1.0, width / geo.width) + drawing.renderScale = min(1.0, width / geo.width, max_height / drawing.height if max_height else 1.0) return drawing diff --git a/backend/tests/test_export.py b/backend/tests/test_export.py index affd158..f4d8995 100644 --- a/backend/tests/test_export.py +++ b/backend/tests/test_export.py @@ -334,17 +334,16 @@ def test_pdf_exporter_function_plot_fallback_on_error() -> None: assert any("函数图像" in w for w in result.warnings) -def test_pdf_exporter_limits_function_plot_count() -> None: +def test_pdf_exporter_has_no_function_plot_count_quota() -> None: from app.export.exporters.pdf import PdfExporter blocks = "\n\n".join("```function-plot\ny = x\n```" for _ in range(20)) result = asyncio.run(PdfExporter().export(parse_document(blocks), ExportOptions())) assert result.content[:4] == b"%PDF" - # 超出数量上限的图块回退占位并记 warning - assert any("数量超过上限" in w for w in result.warnings) + assert not any("函数图像" in w for w in result.warnings) -def test_pdf_exporter_limits_total_plot_nodes(monkeypatch) -> None: +def test_pdf_exporter_has_no_total_plot_node_quota(monkeypatch) -> None: import app.export.exporters._common as common_mod from app.export.exporters.pdf import PdfExporter @@ -352,7 +351,7 @@ def test_pdf_exporter_limits_total_plot_nodes(monkeypatch) -> None: md = "```function-plot\ny = x\n```\n\n```function-plot\ny = x + x + x + x\n```" result = asyncio.run(PdfExporter().export(parse_document(md), ExportOptions())) assert result.content[:4] == b"%PDF" - assert any("累计复杂度" in w for w in result.warnings) + assert not any("函数图像" in w for w in result.warnings) def test_docx_exporter_embeds_plot_and_warns_missing_mermaid() -> None: @@ -831,7 +830,8 @@ def test_callout_formats(name): xml = z.read("word/document.xml").decode() assert all(word in xml for word in ["Title", "Body", "item", "second", "w:shd"]) result = PdfExporter().render(doc, ExportOptions(theme_id="sepia")) - assert result.content.startswith(b"%PDF") and len(result.warnings) == 1 + assert result.content.startswith(b"%PDF") + assert not any("浅色打印" in warning for warning in result.warnings) @pytest.mark.parametrize("fold", ["", "+", "-"]) diff --git a/backend/tests/test_pdf_theme_resources.py b/backend/tests/test_pdf_theme_resources.py new file mode 100644 index 0000000..d4e0469 --- /dev/null +++ b/backend/tests/test_pdf_theme_resources.py @@ -0,0 +1,96 @@ +"""PDF theme and resource policy regressions; no real providers or user files.""" +import asyncio +import base64 +from io import BytesIO +import pytest +from PIL import Image +from pydantic import ValidationError +from app.contracts import ExportAsset, ExportOptions, ExportRequest +from app.export.assets import validate_assets, enrich_document, source_hash +from app.export.exporters.pdf import PdfExporter +from app.export.markdown import parse_document +from app.export.themes import PALETTES +from app.export import service + + +def png_asset(size=(40,30), source='graph LR; A-->B'): + out=BytesIO(); Image.new('RGBA',size,(0,0,0,0)).save(out,'PNG') + return ExportAsset(kind='mermaid',source_hash=source_hash(source),png_base64=base64.b64encode(out.getvalue()).decode()) + + +@pytest.mark.parametrize('theme',list(PALETTES)) +def test_pdf_theme_colors_are_written_on_every_page(theme): + import re, zlib + palette=PALETTES[theme] + doc=parse_document(('## Section\n\nText body\n\n> Quoted text\n\n```python\nprint(1)\n```\n\n')*30) + result=PdfExporter().render(doc,ExportOptions(theme_id=theme)) + streams=[] + for match in re.finditer(rb'stream\r?\n(.*?)endstream',result.content,re.S): + try: streams.append(zlib.decompress(base64.a85decode(match[1].strip().removesuffix(b'~>')))) + except Exception: pass + from reportlab.lib.rl_accel import fp_str + command=(fp_str(*[int(palette[0][i:i+2],16)/255 for i in (1,3,5)])+' rg').encode() + pages=[s for s in streams if b'BT' in s and b'/F' in s] + assert len(pages)>1 + assert all(command in s for s in pages) + assert not any('浅色打印' in w for w in result.warnings) + + +def test_pdf_accepts_asset_contract_beyond_previous_count_and_size(): + assets=[png_asset(source=str(i)) for i in range(65)] + assets[0]=assets[0].model_copy(update={'png_base64':'A'*2800004}) + values=dict(source={'type':'markdown','markdown':'content'},assets=assets) + ExportRequest(format='pdf',**values) + with pytest.raises(ValidationError): ExportRequest(format='html',**values) + with pytest.raises(ValidationError): ExportRequest(format='docx',**values) + + +def test_pdf_large_png_still_requires_valid_format(): + asset=png_asset((2100,2000)) + assert validate_assets([asset],unlimited=True) + with pytest.raises(Exception): validate_assets([asset]) + with pytest.raises(Exception): validate_assets([asset.model_copy(update={'png_base64':'invalid'})],unlimited=True) + + +def test_pdf_embeds_more_than_64_resources_with_theme_background(): + doc=parse_document(('```mermaid\ngraph LR; A-->B\n```\n\n')*65) + from app.export.assets import attach_assets + attach_assets(doc,validate_assets([png_asset()],unlimited=True)) + assert not enrich_document(doc,unlimited=True,options=ExportOptions(theme_id='dark')) + assert all('static_png' in node.attributes for node in doc.children) + with Image.open(BytesIO(doc.children[-1].attributes['static_png'])) as image: + assert image.getpixel((0,0)) == (13,17,23) + assert PdfExporter().render(doc,ExportOptions(theme_id='dark')).content.startswith(b'%PDF') + + +def test_pdf_pipeline_ignores_source_and_output_quotas(monkeypatch): + monkeypatch.setattr(service,'MAX_MARKDOWN_CHARS',8) + monkeypatch.setattr(service,'MAX_EXPORT_BYTES',8) + async def run(): + job=await service.create_export(ExportRequest(format='pdf',source={'type':'markdown','markdown':'Beyond the previous quota.'})) + done=await service.wait_for_export(job.job_id) + assert done.status.value=='completed' + assert service.get_export_file(job.job_id).stat().st_size>8 + with pytest.raises(Exception): + await service.create_export(ExportRequest(format='html',source={'type':'markdown','markdown':'Beyond the previous quota.'})) + asyncio.run(run()) + + +def test_pdf_accepts_more_than_16_curves_and_keeps_expression_safety(): + doc=parse_document('```function-plot\n'+'\n'.join(f'y=x+{i}' for i in range(17))+'\n```') + result=PdfExporter().render(doc,ExportOptions(theme_id='dark')) + assert not any('函数图像' in w for w in result.warnings) + unsafe=PdfExporter().render(parse_document('```function-plot\ny=__import__("os")\n```'),ExportOptions()) + assert any('函数图像' in w for w in unsafe.warnings) + + +def test_pdf_custom_palette_and_math_color(): + palette=dict(zip(('page','surface','text','muted','code','border','accent'),PALETTES['midnight-purple'])) + options=ExportOptions(theme_id='my-theme',palette=palette) + doc=parse_document('Formula $x^2$') + assert not enrich_document(doc,unlimited=True,options=options) + math=next(n for n in doc.children[0].children if n.type=='math_inline') + with Image.open(BytesIO(math.attributes['static_png'])) as image: + assert image.getpixel((0,0))==(25,19,34) + assert not any('主题' in w for w in PdfExporter().render(doc,options).warnings) + with pytest.raises(ValidationError): ExportOptions(palette={**palette,'text':'url(file:///private)'}) diff --git a/docs/contracts/第二阶段接口契约-开发版.md b/docs/contracts/第二阶段接口契约-开发版.md index 5fb4ef8..1832dc0 100644 --- a/docs/contracts/第二阶段接口契约-开发版.md +++ b/docs/contracts/第二阶段接口契约-开发版.md @@ -1629,8 +1629,12 @@ CUDA 组件:`GET /api/local-models/runtime-components/cuda` 返回 status、st `POST /api/plots/function`:请求`{source, theme_id}`,source最多20000字符;响应`{result: {content,mime_type,width,height,warnings} | null, diagnostics: [{severity,code,message,line}], node_count}`。语法错误为200诊断、请求字段违规422。共享plot白名单解释器,不执行eval;每块16表达式、8000累计节点、并发2。主题映射当前六个Theme ID并提供CSS图表Token;未知主题回退light。 -`ExportRequest`新增可选title(最多200字符)、assets(最多64);`source.file_path`为未保存快照中相对图片的基准位置,不能用于任意文件读。每个asset为`{kind: mermaid|math_block|math_inline|image, source_hash: 64位sha256十六进制, png_base64}`;摘要为strip后UTF-8源码(image为src)的SHA256。只接受有效PNG并重编码,每图4百万像素、总16百万像素/8MiB;重复kind/hash或超限返回422 EXPORT_ASSET_INVALID。摘要失配不替换当前节点,不接受客户端SVG/XML/URL执行。 +`ExportRequest`新增可选title(最多200字符)、assets(HTML/DOCX 最多64,PDF 不设数量上限);`source.file_path`为未保存快照中相对图片的基准位置,不能用于任意文件读。每个asset为`{kind: mermaid|math_block|math_inline|image, source_hash: 64位sha256十六进制, png_base64}`;摘要为strip后UTF-8源码(image为src)的SHA256。只接受有效PNG并重编码;HTML/DOCX 每图4百万像素、总16百万像素/8MiB,PDF 不使用这些预算。重复kind/hash或无效PNG返回422 EXPORT_ASSET_INVALID。摘要失配不替换当前节点,不接受客户端SVG/XML/URL执行。 -未带资源的公式由受限MathText转换(512字符/20层/64资源),仅解析Vault范围内PNG/JPEG/WebP(单文件2MB),拒绝远端与越界路径。资源失败保留源码/替代文字和warning。PDF/DOCX采用浅色打印样式;HTML保留有限主题调色板,不复刻任意主题CSS。DOCX图片为静态内容,不提供可编辑公式对象。 +未带资源的公式由 MathText 转换,仅解析 Vault 范围内 PNG/JPEG/WebP,拒绝远端与越界路径。HTML/DOCX 保留 512 字符/20 层/64 资源、单文件 2MB 的预算;PDF 不使用这些预算,也不限制导出源长度、产物字节数、函数图数量、表达式数量及累计复杂度。表达式白名单、有效图片校验、数值采样的收敛控制和队列并发调度仍保留。资源无法表示时保留源码/替代文字和 warning。 + +PDF 使用当前主题配色。`ExportOptions.palette` 可选,包含 page/surface/text/muted/code/border/accent 七个必填 `#RRGGBB` 值,由客户端在点击导出时冻结,用于自定义主题。未传 palette 时按 theme_id 解析六套内置配色,未知 ID 回退 light 并警告。PDF 页背景、正文、代码、表格、引用、链接、公式、Mermaid 和函数图均主题化;不会执行主题 CSS。DOCX 仍采用浅色打印样式;HTML 保留有限主题调色板。DOCX 图片为静态内容,不提供可编辑公式对象。 + +关闭导出窗口仅停止 UI 轮询,已发起的导出继续。主动取消在准备阶段停止提交;创建请求期间取消会等待任务 ID,调用后台取消接口并读取实际状态。 RAG `retrieval.fusion`接受rrf(默认)或weighted(归一化FTS/vector各50%),参数写入config_snapshot。真实本地单查询Embedding可命中有界进程缓存,provenance.query_embedding_cache为hit/miss;比较延迟须分别报告冷暖样本。HashEmbedding仍仅为确定性单元测试,不是当前生产检索模型。 diff --git a/docs/development/Export开发说明.md b/docs/development/Export开发说明.md index f9116d7..91d81cd 100644 --- a/docs/development/Export开发说明.md +++ b/docs/development/Export开发说明.md @@ -156,3 +156,14 @@ PDF、DOCX 保持浅色打印样式;选择其他主题时返回明确 warning 警告框识别与工作区一致:标记与标题之间可不留空格,类型允许数字、下划线和连字符;自定义类型回退 note 配色并保留自定义标题,省略标题时使用类型名称首字母大写。 警告框、普通引用、列表及交叉嵌套中的 Markdown 表格均启用容器内部解析,HTML 输出 table、PDF 输出 Table、DOCX 输出原生表格。测试逐一检查单元格内容和产物结构。每个 HTML 警告框独立初始化颜色变量,避免 NOTE 等类型继承外层 WARNING 的颜色;已在五套内置导出主题中检查嵌套配色及表格显示。 + + +### PDF 主题与资源策略更新(2026-09-07) + +当前 PDF 行为以此节为准,覆盖上文早期 PR 的浅色打印和资源预算说明。 + +PDF 使用导出按钮点击时的主题配色快照,支持六种内置/社区主题与自定义主题的七项颜色。页背景、正文、引用、代码块、表格、链接、语义提示块、Mermaid、公式与函数图均参与主题适配;公式使用透明底再合成主题表面色,深色曲线使用较亮的默认色。标题随下一块分页,代码保留背景与边框。PDF 不执行 CSS 装饰或主题脚本。 + +PDF 取消导出源/产物大小限制、Mermaid 数量和像素预算、请求资源数量和字节预算、Vault 图片大小/累计预算、MathText 长度/深度预算、函数图数量/表达式数量及 AST 预算。HTML/DOCX、在线预览仍使用原有限制;有效图片、Vault 路径、表达式语法校验和数值求解退出条件仍生效。无限制指移除应用导出配额,实际文件规模仍受浏览器、解析器和可用内存约束。 + +关闭窗口仅结束 UI 生命周期,导出流程继续;主动取消仍取消提交中的后台任务。回归测试见 `ExportDialog.spec.ts`、`exportService.spec.ts`、`test_pdf_theme_resources.py`。 diff --git a/docs/development/PDF主题与资源限制修复验收-2026-09-07.md b/docs/development/PDF主题与资源限制修复验收-2026-09-07.md new file mode 100644 index 0000000..dd627ea --- /dev/null +++ b/docs/development/PDF主题与资源限制修复验收-2026-09-07.md @@ -0,0 +1,22 @@ +# PDF 主题与资源限制修复验收 + +工作目录:`G:/OSProject/NotesAgent`;分支:`feat/phase2-completion`。 + +## 完成内容 + +1. 修复关闭窗口误取消提交中的导出任务,保留主动取消。 +2. PDF 移除前后端导出数量、大小、图片像素及累计资源配额,详见接口契约。 +3. PDF 读取点击导出时的主题颜色快照,覆盖六套主题和自定义调色板,移除强制浅色打印提示。 +4. 公式透明底、Mermaid 主题变量、矢量函数图、代码背景与边框、表格、提示块和页面背景都使用对应主题颜色。 + +## 验证 + +- 全量后端:879 项通过,1 条既有 Starlette/httpx 弃用警告。 +- 全量前端:82 文件、448 项通过。 +- 分页/代码背景最终调整后:相关后端 108 项通过。 +- 前端生产构建通过,仍有既有大分包提示。 +- 超旧限制回归:17 个 Mermaid、65 个文档资源、单图超过 400 万像素、超过旧源/产物阈值、17 条函数表达式与20个函数图。 +- 真实前端 Mermaid 栅格化 + 后端导出流水线生成 light/dark/sepia/paper-moments/ocean-blue/midnight-purple 六份 PDF;Poppler 渲染并检查全部12页,未出现白底公式、不可读深色文字、图表裁切或孤立章节标题。 +- 验证数据与截图:本机 `.local-plans/pdf-theme/`;全部接口拦截为测试数据,未访问真实模型或用户笔记。 + +本次未改动用户已有的三份笔记修改。主题适配使用颜色与语义样式,不将任意主题 CSS/脚本直接用于 PDF。 diff --git a/docs/development/evidence/pdf-theme-20260907/results.json b/docs/development/evidence/pdf-theme-20260907/results.json new file mode 100644 index 0000000..ee2e72d --- /dev/null +++ b/docs/development/evidence/pdf-theme-20260907/results.json @@ -0,0 +1,104 @@ +[ + { + "theme": "light", + "pages": 2, + "bytes": 67245, + "sha256": "5b6efed909df971f455253958ceadce47a7bd198b52ca1a1c755d9d25c51dc05", + "palette": { + "page": "#ffffff", + "surface": "#ffffff", + "text": "#1f2328", + "muted": "#656d76", + "code": "#f7f8fa", + "border": "#e4e7eb", + "accent": "#5b67f1" + }, + "warnings": [], + "visual_review": "passed" + }, + { + "theme": "dark", + "pages": 2, + "bytes": 66788, + "sha256": "b09da4dc97246b7444f17d2b5756dea6dda004981cbcbd360bc2de3d26bd00cf", + "palette": { + "page": "#0d1117", + "surface": "#161b22", + "text": "#e6edf3", + "muted": "#8b949e", + "code": "#161b22", + "border": "#30363d", + "accent": "#7d8bff" + }, + "warnings": [], + "visual_review": "passed" + }, + { + "theme": "sepia", + "pages": 2, + "bytes": 66836, + "sha256": "abc329f9691ba46d8ddd25e4b3621b63fc4cc8baba8823800c47d68feb910e0c", + "palette": { + "page": "#fbf3df", + "surface": "#fff8e8", + "text": "#40372b", + "muted": "#746653", + "code": "#f4e8ca", + "border": "#ddcfad", + "accent": "#8a5b32" + }, + "warnings": [], + "visual_review": "passed" + }, + { + "theme": "paper-moments", + "pages": 2, + "bytes": 66808, + "sha256": "e1929fbec0486b59abcc808fab557f4103ec102ad70e6fb9f0b26f84414c0fe4", + "palette": { + "page": "#faf7ee", + "surface": "#fffdf5", + "text": "#493f35", + "muted": "#6e6053", + "code": "#f3eee3", + "border": "#b5a693", + "accent": "#875343" + }, + "warnings": [], + "visual_review": "passed" + }, + { + "theme": "ocean-blue", + "pages": 2, + "bytes": 67260, + "sha256": "1e567daeb2ba8bcb5c13af465f174865a3662ade444c58cd09af66ae99233346", + "palette": { + "page": "#ffffff", + "surface": "#ffffff", + "text": "#1e293b", + "muted": "#64748b", + "code": "#f8fafc", + "border": "#e2e8f0", + "accent": "#0077b6" + }, + "warnings": [], + "visual_review": "passed" + }, + { + "theme": "midnight-purple", + "pages": 2, + "bytes": 66788, + "sha256": "fd5efcedec0d7f4892fb987e0e4ba9052aa88b7361159cbb2ca3b7e00dde495c", + "palette": { + "page": "#1a1b26", + "surface": "#24283b", + "text": "#c0caf5", + "muted": "#9aa5ce", + "code": "#24283b", + "border": "#3b3f5c", + "accent": "#9d4edd" + }, + "warnings": [], + "visual_review": "passed" + } +] \ No newline at end of file diff --git a/frontend/src/features/editor/ExportDialog.spec.ts b/frontend/src/features/editor/ExportDialog.spec.ts new file mode 100644 index 0000000..385257c --- /dev/null +++ b/frontend/src/features/editor/ExportDialog.spec.ts @@ -0,0 +1,22 @@ +// @vitest-environment jsdom +import {mount,flushPromises} from '@vue/test-utils' +import {it,expect,vi} from 'vitest' +import ExportDialog from './ExportDialog.vue' +import {apiClient} from '@/services/apiClient' +vi.mock('@/stores/editor',()=>({useEditorStore:()=>({content:'# snapshot',currentFilePath:'note.md'})})) +vi.mock('@/stores/theme',()=>({useThemeStore:()=>({currentThemeId:'light'})})) +vi.mock('@/services/mermaidService',()=>({renderMermaid:vi.fn()})) +vi.mock('@/services/apiClient',()=>({apiClient:{post:vi.fn(),get:vi.fn()}})) +it('closing the dialog after submitting preserves the background export',async()=>{ + let finish!:(value:unknown)=>void + vi.mocked(apiClient.get).mockImplementation(async(path:string)=>path==='/api/exports'?{items:[]}:{job_id:'closing-job',status:'cancelled',warnings:[],error:null,file:null}) + vi.mocked(apiClient.post).mockImplementationOnce(()=>new Promise(resolve=>{finish=resolve}) as never).mockResolvedValue({status:'completed'}) + const wrapper=mount(ExportDialog,{global:{stubs:{AppDialog:{template:'
'}}}}) + await flushPromises() + await wrapper.findAll('button').find(b=>b.text()==='开始导出')!.trigger('click') + expect(apiClient.post).toHaveBeenCalledWith('/api/exports',expect.anything()) + wrapper.unmount() + finish({job_id:'closing-job',status:'queued',warnings:[],error:null,file:null}) + await flushPromises() + expect(apiClient.post).not.toHaveBeenCalledWith('/api/exports/closing-job/cancel') +}) diff --git a/frontend/src/features/editor/ExportDialog.vue b/frontend/src/features/editor/ExportDialog.vue index 883876a..4065d46 100644 --- a/frontend/src/features/editor/ExportDialog.vue +++ b/frontend/src/features/editor/ExportDialog.vue @@ -3,7 +3,7 @@ import { ref, onMounted, onBeforeUnmount } from 'vue' import AppDialog from '@/components/common/AppDialog.vue' import { useEditorStore } from '@/stores/editor' import { useThemeStore } from '@/stores/theme' -import { exportService, type ExportFormat, type ExportJob } from '@/services/exportService' +import { exportService, captureExportPalette, type ExportFormat, type ExportJob } from '@/services/exportService' const emit = defineEmits<{ close: [] }>() const editor = useEditorStore(), theme = useThemeStore() const format = ref('html'), page = ref('A4'), title = ref(true) @@ -19,7 +19,7 @@ async function start() { preparing.value = true; error.value = ''; controller = new AbortController() const snapshot = editor.content, name = editor.currentFilePath?.split('/').pop()?.replace(/\.md$/i, '') ?? '笔记' try { - const job = await exportService.create(snapshot, name, format.value, { theme_id: theme.currentThemeId, include_title: title.value, page_size: page.value }, controller.signal, editor.currentFilePath ?? undefined) + const job = await exportService.create(snapshot, name, format.value, { theme_id: theme.currentThemeId, include_title: title.value, page_size: page.value, palette: captureExportPalette() }, controller.signal, editor.currentFilePath ?? undefined) if (!disposed) jobs.value.unshift(job) } catch (e) { error.value = e instanceof DOMException && e.name === 'AbortError' ? '已取消导出' : String(e) } finally { preparing.value = false } @@ -28,7 +28,7 @@ async function action(job: ExportJob, download = false) { try { if (download) await exportService.download(job); else await exportService.cancel(job.id) } catch (e) { error.value = String(e) } } onMounted(refresh) -onBeforeUnmount(() => { disposed = true; clearTimeout(timer); controller?.abort() }) +onBeforeUnmount(() => { disposed = true; clearTimeout(timer) })