diff --git a/backend/app/benchmarks/agent.py b/backend/app/benchmarks/agent.py index 6701e19..d493ac0 100644 --- a/backend/app/benchmarks/agent.py +++ b/backend/app/benchmarks/agent.py @@ -1,4 +1,4 @@ -"""Standard task evaluation over AgentRuntime, never a scripted substitute runner.""" +"""通过真实 AgentRuntime 执行标准任务评测,不使用脚本化替代运行器。""" import asyncio from time import perf_counter from uuid import uuid4 @@ -10,9 +10,10 @@ 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'] - # 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] @@ -49,10 +50,11 @@ def score(case, run, events, latency, repeat): 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, @@ -63,6 +65,7 @@ def aggregate(cases, planned_total=None): 'token_usage': sum(c.token_usage for c in cases), 'tool_calls': calls, 'expected_calls': expected} async def create_run(request: AgentBenchmarkRequest): + """冻结数据集与运行配置,并把评测交给后台真实 Agent Runtime。""" from app.container import container from app.providers.registry import ProviderNotFoundError try: @@ -91,6 +94,7 @@ async def create_run(request: AgentBenchmarkRequest): return run async def execute(run_id, request, dataset, runtime): + """顺序执行样本,传播取消信号,并持续发布可订阅的运行事件。""" flag = service._cancel_flags[run_id] results = []; active = None def emit(kind, data): @@ -112,7 +116,7 @@ async def execute(run_id, request, dataset, runtime): 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. + # 样本仍在运行时就暴露真实 Trace 与权限入口,便于界面处理待决授权。 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()) diff --git a/backend/app/export/assets.py b/backend/app/export/assets.py index 8791398..2026272 100644 --- a/backend/app/export/assets.py +++ b/backend/app/export/assets.py @@ -1,4 +1,4 @@ -"""Raster-only resources; PDF bypasses export quotas but retains path/format validation.""" +"""处理栅格资源;PDF 不受导出配额限制,但仍执行路径和格式校验。""" import base64 import hashlib import threading @@ -9,7 +9,7 @@ from app.errors import ApiError _math_lock = threading.Lock() def enrich_document(document, file_path=None, unlimited=False, options=None, preserve_alpha=False): - """Embed Vault images and MathText, with format-specific quotas and palette.""" + """内嵌 Vault 图片和 MathText,并按导出格式应用配额与主题配色。""" from app.config import get_settings from urllib.parse import unquote, urlsplit vault = get_settings().vault_path.resolve() @@ -51,7 +51,7 @@ def enrich_document(document, file_path=None, unlimited=False, options=None, pre 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() - # Composite transparency over the PDF theme or the print/Word white surface. + # 透明像素按 PDF 主题表面色合成;打印 HTML 与 Word 使用白色底色。 rgba=image.convert('RGBA'); background=Image.new('RGBA',rgba.size,palette['surface'] if palette else 'white') background.alpha_composite(rgba); (rgba if preserve_alpha else background.convert('RGB')).save(out,'PNG') png=out.getvalue();total += len(png) @@ -70,6 +70,7 @@ def source_hash(source): return hashlib.sha256(source.strip().encode()).hexdigest() def validate_assets(assets, unlimited=False): + """校验前端静态资源并解码为 PNG;PDF 仅解除容量限制,不放宽格式要求。""" result = {} total = pixels = 0 for asset in assets: @@ -98,6 +99,7 @@ def validate_assets(assets, unlimited=False): 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)) @@ -109,7 +111,7 @@ def attach_assets(document, assets): visit(child) def plot_png(plot): - """DOCX consumes the same clipped geometry as SVG/PDF, rendered at 2x.""" + """按 SVG/PDF 共用的裁剪几何,以二倍分辨率生成 DOCX 图像。""" from app.plot.render import compute_geometry, _sx, _sy, _fmt_num from PIL import ImageDraw, ImageFont geo = compute_geometry(plot) @@ -135,7 +137,7 @@ def plot_png(plot): 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. + # 纵轴标题横排在左上边距,避免 CJK 文本在 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) diff --git a/backend/app/export/browser_pdf.py b/backend/app/export/browser_pdf.py index 36022a0..8c00fae 100644 --- a/backend/app/export/browser_pdf.py +++ b/backend/app/export/browser_pdf.py @@ -1,8 +1,7 @@ -"""Print the app's self-contained theme snapshot with a real browser engine. +"""使用真实浏览器引擎打印应用生成的自包含主题快照。 -A child process isolates Playwright's Windows event loop from Uvicorn and keeps -browser lifecycle scoped to one export. Snapshot scripts/network/file loads are -blocked; fonts and images must already be embedded by the client. +子进程隔离 Playwright 在 Windows 上的事件循环与 Uvicorn,并把浏览器生命周期限制在 +单次导出内。快照禁止脚本、网络和文件加载,字体与图片必须由客户端提前内嵌。 """ from pathlib import Path import os @@ -14,6 +13,7 @@ from app.export.document import ExportResult def browser_executable(): + """优先使用显式配置,再查找系统已安装的 Chromium 系浏览器。""" configured = os.environ.get('APP_PDF_BROWSER') if configured: return configured @@ -28,6 +28,7 @@ def browser_executable(): def render_snapshot(snapshot: str, page_size: str) -> ExportResult: + """在隔离子进程中打印快照,避免阻塞或污染服务进程的事件循环。""" with tempfile.TemporaryDirectory(prefix='notes-pdf-') as directory: source = Path(directory) / 'snapshot.html' output = Path(directory) / 'document.pdf' @@ -42,6 +43,7 @@ def render_snapshot(snapshot: str, page_size: str) -> ExportResult: def print_snapshot(source: Path, output: Path, page_size: str): + """在离线、禁用 JavaScript 的上下文中将自包含 HTML 打印为 PDF。""" from playwright.sync_api import sync_playwright with sync_playwright() as runtime: browser = runtime.chromium.launch(executable_path=browser_executable(), headless=True) diff --git a/backend/app/export/exporters/docx.py b/backend/app/export/exporters/docx.py index 54bdc79..b045c78 100644 --- a/backend/app/export/exporters/docx.py +++ b/backend/app/export/exporters/docx.py @@ -117,7 +117,7 @@ class DocxExporter: with Image.open(BytesIO(png)) as image: 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. + # 为 Word 外层段落的行高和间距预留空间,避免图片跨出页面。 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), diff --git a/backend/app/export/exporters/pdf.py b/backend/app/export/exporters/pdf.py index b771eef..42af3f2 100644 --- a/backend/app/export/exporters/pdf.py +++ b/backend/app/export/exporters/pdf.py @@ -285,7 +285,7 @@ class PdfExporter: parts.append(self._render_inline(child.children, warnings)) elif hasattr(self, f"_block_{child.type}"): flush() - # Keep block content inside the list frame, including tables and callouts. + # 表格、警告框等块级内容也要保持在列表缩进框内。 story.append(Indenter(left=indent)) self._render_block(child, story, warnings) story.append(Indenter(left=-indent)) diff --git a/backend/app/export/fonts.py b/backend/app/export/fonts.py index df2440f..d74243e 100644 --- a/backend/app/export/fonts.py +++ b/backend/app/export/fonts.py @@ -1,4 +1,4 @@ -"""Embed an available CJK TrueType font; retain the portable CID fallback.""" +"""嵌入可用的 CJK TrueType 字体,找不到时保留可移植的 CID 字体回退。""" import os from pathlib import Path from reportlab.pdfbase import pdfmetrics @@ -6,6 +6,7 @@ from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase.cidfonts import UnicodeCIDFont def register_font(): + """按显式配置、系统字体、Linux 字体的顺序注册 PDF 中文字体。""" candidates = [os.getenv('APP_EXPORT_FONT',''), str(Path(os.getenv('WINDIR','C:/Windows'))/'Fonts/simsun.ttc'), '/usr/share/fonts/truetype/arphic/uming.ttc'] diff --git a/backend/app/export/markdown.py b/backend/app/export/markdown.py index 7f83986..521fbf9 100644 --- a/backend/app/export/markdown.py +++ b/backend/app/export/markdown.py @@ -179,6 +179,7 @@ class _AstMapper: children=self.map_inline(token.get("children", [])), ) if kind == "inline_html": + # 保留行内 HTML 的来源标记,仅供 PDF 资源扫描识别 img;最终 HTML 仍由前端净化。 return DocumentNode(type="text", node_id=self.next_id(), text=token.get("raw", ""), attributes={"raw_html": True}) if kind == "codespan": return DocumentNode(type="codespan", node_id=self.next_id(), text=token.get("raw", "")) diff --git a/backend/app/export/service.py b/backend/app/export/service.py index 70c351a..68bd4bc 100644 --- a/backend/app/export/service.py +++ b/backend/app/export/service.py @@ -406,7 +406,7 @@ async def wait_for_export(job_id: str) -> ExportJob | None: async def preview_resources(request: ExportRequest): - """Prepare Vault images and vector plots for the shared browser renderer.""" + """为浏览器渲染器准备通过 Vault 校验的图片和静态函数图。""" import base64 from app.export.assets import enrich_document from app.plot.parser import parse_source @@ -418,6 +418,8 @@ async def preview_resources(request: ExportRequest): document = parse_document(markdown) images, plots = [], [] class HtmlImages(HTMLParser): + # 原始 HTML 只提取 img.src;路径、扩展名和图片格式仍交给 enrich_document 校验。 + # 行内代码和代码块在 AST 中不是 HTML 节点,因此不会误当作图片资源。 def handle_starttag(self, tag, attrs): if tag == 'img': src = dict(attrs).get('src') diff --git a/backend/app/local_models/runtime.py b/backend/app/local_models/runtime.py index 63766d1..3c941ca 100644 --- a/backend/app/local_models/runtime.py +++ b/backend/app/local_models/runtime.py @@ -241,9 +241,8 @@ 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. +# 对确定性的单文本本地向量做有界内存复用。键包含模型目录、不可变版本和冻结运行配置; +# 远程 API 响应以及模型不可用时的回退结果都不进入缓存。 _embedding_cache = OrderedDict() _EMBEDDING_CACHE_TTL = 600 diff --git a/backend/app/plot_routes.py b/backend/app/plot_routes.py index 0163820..303118b 100644 --- a/backend/app/plot_routes.py +++ b/backend/app/plot_routes.py @@ -1,4 +1,4 @@ -"""Interactive previews use the same bounded parser and geometry as exports.""" +"""交互预览复用导出使用的有界解析器和几何计算。""" import asyncio from fastapi import APIRouter from pydantic import BaseModel, Field @@ -19,6 +19,7 @@ class PlotResponse(BaseModel): node_count: int = 0 def preview(request): + """同步解析并渲染函数图,供受并发限制的异步路由在线程中调用。""" parsed = parse_source(request.source) if parsed.plot is None: return PlotResponse(diagnostics=parsed.diagnostics) @@ -30,5 +31,6 @@ def preview(request): @router.post('/function', response_model=PlotResponse) async def render_function(request: PlotRequest): + # 绘图属于 CPU 密集任务,限制并发并移入线程,避免阻塞事件循环。 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 bf743a5..82f1008 100644 --- a/backend/app/retrieval/engine.py +++ b/backend/app/retrieval/engine.py @@ -115,6 +115,7 @@ class RetrievalEngine: candidate_scores = vec_scores else: # hybrid:RRF 融合 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) diff --git a/backend/scripts/phase2-context.py b/backend/scripts/phase2-context.py index d76ff4b..5f142f8 100644 --- a/backend/scripts/phase2-context.py +++ b/backend/scripts/phase2-context.py @@ -1,4 +1,4 @@ -"""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])) diff --git a/backend/scripts/phase2-integration.py b/backend/scripts/phase2-integration.py index eac3e85..e6374ff 100644 --- a/backend/scripts/phase2-integration.py +++ b/backend/scripts/phase2-integration.py @@ -1,7 +1,7 @@ -"""Explicit isolated Demo: retrieval → read → three tasks, then real read-only MCP. +"""显式隔离演示:检索、读取、创建三个任务,再调用真实只读 MCP。 -Approves only this run's tasks.write tickets. Requires the quality fixture Vault. -Calls public API contracts; never writes completion state into SQLite. +只批准本次运行产生的 tasks.write 权限票据,需要质量验收用 Vault。 +脚本仅调用公共 API 契约,不把伪造的完成状态写入 SQLite。 """ import argparse, json, time from pathlib import Path diff --git a/backend/scripts/phase2-live.py b/backend/scripts/phase2-live.py index 7ffde97..c89d9b2 100644 --- a/backend/scripts/phase2-live.py +++ b/backend/scripts/phase2-live.py @@ -1,7 +1,7 @@ -"""Bounded real protocol/Agent checks using existing configuration; no secret output. +"""使用现有配置执行有界的真实协议与 Agent 检查,不输出凭据。 -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. +必须显式传入 --execute;最多发起 5 次直接模型请求和一组 4 样本 Agent 评测, +每个样本最多 6 步、6000 Token。不创建配置,也不执行外部写入。 """ import argparse, asyncio, json, sys from pathlib import Path diff --git a/backend/scripts/phase2-quality.py b/backend/scripts/phase2-quality.py index 2464344..c8abd4f 100644 --- a/backend/scripts/phase2-quality.py +++ b/backend/scripts/phase2-quality.py @@ -1,7 +1,7 @@ -"""Reproducible real-model quality run in an explicitly isolated APP_DATA_DIR. +"""在显式隔离的 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 diff --git a/backend/tests/test_pdf_browser.py b/backend/tests/test_pdf_browser.py index 531a8fb..c2054c7 100644 --- a/backend/tests/test_pdf_browser.py +++ b/backend/tests/test_pdf_browser.py @@ -33,8 +33,7 @@ def test_preview_resources_keeps_vault_boundary_and_plot_quota_removed(): @pytest.mark.skipif(browser_executable() is None,reason='No installed Chromium browser') def test_browser_prints_css_without_executing_document_scripts(tmp_path): - # The script would erase all text if executed. Embedded CSS and fonts must - # survive the browser path while network and file resources stay blocked. + # 若脚本被执行会清空正文;测试同时确认 CSS/字体可用且网络、文件资源保持禁用。 html='
'
result=render_snapshot(html,'A4')
assert result.content.startswith(b'%PDF')
diff --git a/frontend/src/components/common/DiagramInteractions.vue b/frontend/src/components/common/DiagramInteractions.vue
index 449630a..87be769 100644
--- a/frontend/src/components/common/DiagramInteractions.vue
+++ b/frontend/src/components/common/DiagramInteractions.vue
@@ -222,6 +222,6 @@ function close() { disarm(); viewer.value?.close(); svgHtml.value = ''; opener?.
diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts
index b583e97..c63469c 100644
--- a/frontend/src/router/index.ts
+++ b/frontend/src/router/index.ts
@@ -81,8 +81,7 @@ const router = createRouter({
router.beforeEach((to) => {
const workspaceStore = useWorkspaceStore()
- // A benchmark can run without the workspace UI being open. Its persisted Trace
- // and permission tickets must remain reachable from the report page.
+ // Benchmark 不依赖工作区界面;报告中的持久化 Trace 和待决权限入口必须仍可访问。
const existingAgentRun = to.name === 'agent' && Boolean(to.params.runId)
if (to.meta.requiresVault && !workspaceStore.hasVault && !existingAgentRun) {
return { path: '/' }
diff --git a/frontend/src/services/benchmarkService.ts b/frontend/src/services/benchmarkService.ts
index cdf66ad..56d2151 100644
--- a/frontend/src/services/benchmarkService.ts
+++ b/frontend/src/services/benchmarkService.ts
@@ -2,6 +2,7 @@ import { apiClient } from './apiClient'
interface RunWire { run_id: string; kind: 'rag' | 'agent'; dataset_id: string; status: string; progress: number | null; config_snapshot: Record