feat(engine): 优化智能体循环中的助手消息处理逻辑

- 在没有工具调用时才添加助手消息到上下文
- 确保工具调用响应正确添加到消息上下文中
- 修复了消息构建的条件逻辑

fix(cron): 改进定时任务调度的时间解析功能

- 添加正则表达式导入用于时间显示解析
- 实现从显示文本中提取毫秒间隔的功能
- 增强整数转换的安全性,避免类型错误
- 优化定时任务配置的解析逻辑

feat(outlook): 增强Outlook集成的功能和稳定性

- 将默认超时时间从10秒增加到180秒
- 为状态检查函数添加可选的验证参数
- 串行执行邮件概览获取操作而非并行
- 改进连接状态验证逻辑

feat(channel): 添加设备名称作为会话标识的选项

- 为终端WebSocket适配器添加新的配置选项
- 实现基于设备名称生成会话对等ID的功能
- 记录原始对等ID和设备名称的元数据
- 支持从设备名称创建会话对等ID

feat(skills): 完善技能学习评估系统和进度跟踪

- 在应用启动时自动调度待评估的技能草稿
- 为技能评估工作创建独立的循环工厂
- 实现异步技能评估任务的取消和清理机制
- 添加技能评估进度报告和状态跟踪功能
- 扩展会话列表API以包含更多详细信息
- 防止对不存在的会话进行操作
- 优化技能草稿提交和评估的业务逻辑

perf(skills): 提升技能评估的并发性能

- 实现并行技能案例评估以提高效率
- 添加最大并行案例数的环境变量控制
- 实现实时评估进度更新和回调机制
- 优化评估过程中的资源管理和同步

refactor(services): 创建隔离的智能体循环实例

- 添加创建独立智能体循环的工厂方法
- 确保新循环继承运行时服务配置
- 支持技能评估等需要隔离环境的场景
```
This commit is contained in:
2026-06-15 14:48:16 +08:00
parent 8aeb97a5fc
commit 4b0bf65ace
53 changed files with 4328 additions and 292 deletions

View File

@ -2,7 +2,7 @@
from __future__ import annotations
from typing import Any
from typing import Any, Callable
from beaver.engine.providers import ProviderBundle
from beaver.memory.skills import SkillDraftEvalReport, SkillDraftSafetyReport, SkillLearningCandidate, SkillLearningStore
@ -174,12 +174,20 @@ class SkillLearningPipelineService:
safety = self.get_safety_report(skill_name, draft_id)
if safety is not None and (not safety.passed or safety.risk_level == "critical"):
raise ValueError("Draft cannot enter review because safety check failed")
return self.review_service.submit_for_review(
review = self.review_service.submit_for_review(
skill_name,
draft_id,
reviewer_request=notes,
requested_by=requested_by,
)
self._mark_candidate_by_draft(
skill_name,
draft_id,
"review_pending",
"review_submitted",
last_error=None,
)
return review
def approve(
self,
@ -258,9 +266,13 @@ class SkillLearningPipelineService:
draft = self.get_draft(skill_name, draft_id)
report = self.safety_checker.check(draft)
self.learning_store.write_safety_report(report)
status = "safety_failed" if not report.passed or report.risk_level == "critical" else "draft_ready"
status = (
"safety_failed"
if not report.passed or report.risk_level == "critical"
else self._candidate_status_for_draft(draft)
)
current = self._candidate_by_draft(skill_name, draft_id)
if current is not None and current.status == "eval_failed" and status == "draft_ready":
if current is not None and current.status == "eval_failed" and status != "safety_failed":
status = "eval_failed"
self._mark_candidate_by_draft(
skill_name,
@ -287,6 +299,7 @@ class SkillLearningPipelineService:
*,
provider_bundle: ProviderBundle | None,
replay_runner: ReplayRunner | None = None,
progress_callback: Callable[[dict[str, Any]], None] | None = None,
) -> SkillDraftEvalReport:
draft = self.get_draft(skill_name, draft_id)
candidate = self.get_candidate(candidate_id)
@ -296,13 +309,14 @@ class SkillLearningPipelineService:
draft=draft,
provider_bundle=provider_bundle,
replay_runner=replay_runner,
progress_callback=progress_callback,
)
self.learning_store.write_eval_report(report)
if report.status == "skipped_provider_unavailable":
status = "draft_ready"
status = self._candidate_status_for_draft(draft)
error = "eval skipped: provider unavailable"
elif report.passed:
status = "draft_ready"
status = self._candidate_status_for_draft(draft)
error = None
else:
status = "eval_failed"
@ -316,11 +330,43 @@ class SkillLearningPipelineService:
status,
event_type="eval_completed",
eval_report_id=report.report_id,
eval_progress={
"phase": "completed",
"completed_arms": len(report.cases) * 2 if report.mode == "replay" else 0,
"total_arms": len(report.cases) * 2 if report.mode == "replay" else 0,
"completed_cases": len(report.cases),
"total_cases": len(report.cases),
},
last_error=error,
payload=report.to_dict(),
)
return report
def mark_eval_progress(self, candidate_id: str, progress: dict[str, Any]) -> SkillLearningCandidate:
return self._require_updated(
self.learning_store.update_learning_candidate(
candidate_id,
eval_progress=dict(progress),
),
candidate_id,
)
def mark_eval_failed(self, candidate_id: str, error: str) -> SkillLearningCandidate:
candidate = self.get_candidate(candidate_id)
progress = dict(candidate.eval_progress)
progress["phase"] = "failed"
return self._require_updated(
self.learning_store.transition_learning_candidate(
candidate_id,
"eval_failed",
eval_progress=progress,
event_type="eval_failed",
last_error=error,
payload={"error": error},
),
candidate_id,
)
def _validate_publish_gates(self, draft: SkillDraft, *, confirm_high_risk: bool) -> None:
reviews = self.reviews_for_draft(draft.skill_name, draft.draft_id)
if not any(review.status in {SkillReviewState.IN_REVIEW.value, SkillReviewState.APPROVED.value} for review in reviews):
@ -372,6 +418,14 @@ class SkillLearningPipelineService:
return candidate
return None
@staticmethod
def _candidate_status_for_draft(draft: SkillDraft) -> str:
if draft.status == SkillReviewState.APPROVED.value:
return "approved"
if draft.status == SkillReviewState.IN_REVIEW.value:
return "review_pending"
return "draft_ready"
@staticmethod
def _require_updated(candidate: SkillLearningCandidate | None, candidate_id: str) -> SkillLearningCandidate:
if candidate is None: