## 新增功能 - 文件管理模块:分类树 + 文件上传/下载/删除 - 文件上传支持拖拽(Dragger)+ 手动上传(customRequest 模式) ## 页面布局统一(参照评测执行页) - 仪表盘/评测对象/评测场景/评测报告 全部改为全高 flex 布局 - 统一内联页头样式(h2 + 竖线分隔 + 描述) - 表格撑满高度、overflow 处理 - 每页添加刷新按钮 ## Bug 修复 - 分类树操作按钮 hover 不可见(CSS 规则缺失) - 文件上传失败(multipart boundary 缺失) - LLM API 响应 content blocks 数组格式支持(_extract_content_from_api_response) - response_time_max_ms 被静默忽略(隐式规则传空 params) - 空 messages 导致 IndexError 崩溃 - poll_reply 异常中止整个 run(缺 try/catch) - engine finally 未关闭 session - 3 个页面 UTC 时间戳解析偏差 8 小时 ## 后端 - EvalEngine: poll_reply 异常保护、空 dialog 保护、session 关闭 - LLM API 响应解析支持 content-block-array 格式 - 隐式 response_time 规则正确传递 max_ms 参数 ## 前端 - api.ts: 移除手动 Content-Type(让浏览器自动添加 boundary) - Files.tsx: customRequest 替代 beforeUpload、布局优化 - index.css: 分类树 hover 规则 - Targets/Scenarios/Home/Reports: 全高布局改造 - 3 个页面时间戳改用 formatDateTime()(修复 UTC 偏差) Co-Authored-By: Claude <noreply@anthropic.com>
72 lines
2.3 KiB
Python
72 lines
2.3 KiB
Python
"""Mock EvalChannel implementation for unit tests.
|
|
|
|
Simulates send/poll_reply with configurable latency, failure modes, and
|
|
cancellation hooks. Does not touch the network.
|
|
"""
|
|
|
|
import asyncio
|
|
from typing import Any, Optional
|
|
|
|
from agenteval.channels.base import ChannelHealth, EvalChannel, Reply, SendResult
|
|
|
|
|
|
class MockChannel(EvalChannel):
|
|
"""Configurable in-memory channel for engine tests."""
|
|
|
|
def __init__(
|
|
self,
|
|
reply_delay: float = 0.0,
|
|
send_ok: bool = True,
|
|
missing_reply: bool = False,
|
|
reply_text: Optional[str] = None,
|
|
raise_on_send: Optional[Exception] = None,
|
|
raise_on_poll: Optional[Exception] = None,
|
|
):
|
|
self.reply_delay = reply_delay
|
|
self.send_ok = send_ok
|
|
self.missing_reply = missing_reply
|
|
self.reply_text = reply_text
|
|
self.raise_on_send = raise_on_send
|
|
self.raise_on_poll = raise_on_poll
|
|
|
|
self.sent: list[str] = []
|
|
self.send_calls = 0
|
|
self.poll_calls = 0
|
|
self._msg_counter = 0
|
|
|
|
async def health_check(self) -> ChannelHealth:
|
|
return ChannelHealth(ok=True, message="mock")
|
|
|
|
async def send(self, content: str, **kwargs: Any) -> SendResult:
|
|
self.send_calls += 1
|
|
self.sent.append(content)
|
|
if self.raise_on_send:
|
|
raise self.raise_on_send
|
|
if not self.send_ok:
|
|
return SendResult(ok=False, error="mock send failure")
|
|
self._msg_counter += 1
|
|
return SendResult(ok=True, question_msg_id=f"q-{self._msg_counter}")
|
|
|
|
async def poll_reply(
|
|
self,
|
|
question_msg_id: str,
|
|
timeout: float = 30.0,
|
|
poll_interval: float = 1.0,
|
|
) -> Optional[Reply]:
|
|
self.poll_calls += 1
|
|
if self.raise_on_poll:
|
|
raise self.raise_on_poll
|
|
if self.missing_reply:
|
|
# Simulate a slow target: sleep past the timeout so the engine
|
|
# observes a poll timeout.
|
|
await asyncio.sleep(timeout + 0.05)
|
|
return None
|
|
if self.reply_delay:
|
|
await asyncio.sleep(self.reply_delay)
|
|
text = self.reply_text if self.reply_text is not None else f"echo: {question_msg_id}"
|
|
return Reply(
|
|
question_msg_id=question_msg_id,
|
|
content=text,
|
|
raw_message={"msgBody": {"content": text}},
|
|
)
|