feat(beaver): 完成Task Team功能v1实现,重构后端架构支持统一内核
新增内部Task系统,包括验证、反馈门控机制,实现自动质量验证 (通过率>=0.75)和用户反馈闭环(satisfied/revise/abandon)。 实现Agent Team v1协调器,支持sequence/parallel/dag执行策略, sub-agent复用主AgentLoop,每个run使用独立memory snapshot。 建立Skill学习pipeline,包含draft/审核/发布/回滚完整生命周期, 通过Task验证通过且用户满意才生成学习候选。 重构目录结构,移除third_party依赖,建立统一engine内核, 所有agent共享运行时基础组件。 更新ContextBuilder清理provider消息字段,增强SkillContext版本管理, 集成TaskExecutionPlanner和TaskSkillResolver实现技能解析机制。
This commit is contained in:
167
app-instance/backend/beaver/tasks/service.py
Normal file
167
app-instance/backend/beaver/tasks/service.py
Normal file
@ -0,0 +1,167 @@
|
||||
"""Internal service for automatic Task mode."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from .models import TaskEvent, TaskRecord, ValidationResult
|
||||
from .store import TaskStore
|
||||
|
||||
|
||||
class TaskService:
|
||||
def __init__(self, root: str | Path) -> None:
|
||||
self.store = TaskStore(root)
|
||||
|
||||
def create_task(
|
||||
self,
|
||||
*,
|
||||
session_id: str,
|
||||
description: str,
|
||||
creator: str = "main-agent",
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> TaskRecord:
|
||||
now = self._now()
|
||||
task = TaskRecord(
|
||||
task_id=uuid4().hex,
|
||||
session_id=session_id,
|
||||
description=description,
|
||||
goal=description,
|
||||
constraints=[],
|
||||
priority=0,
|
||||
status="open",
|
||||
creator=creator,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
metadata=dict(metadata or {}),
|
||||
)
|
||||
self.store.upsert_task(task)
|
||||
self._event(task, "created", payload={"description": description})
|
||||
return task
|
||||
|
||||
def get_task(self, task_id: str) -> TaskRecord | None:
|
||||
return self.store.get_task(task_id)
|
||||
|
||||
def get_task_by_run_id(self, run_id: str) -> TaskRecord | None:
|
||||
return self.store.get_task_by_run_id(run_id)
|
||||
|
||||
def get_latest_open_task(self, session_id: str) -> TaskRecord | None:
|
||||
return self.store.get_latest_open_task(session_id)
|
||||
|
||||
def start_run(self, task_id: str, *, user_message: str, attempt_index: int) -> TaskRecord:
|
||||
task = self._require(task_id)
|
||||
task.status = "running"
|
||||
task.updated_at = self._now()
|
||||
task.metadata["latest_user_message"] = user_message
|
||||
task.metadata["latest_attempt_index"] = attempt_index
|
||||
self.store.upsert_task(task)
|
||||
self._event(task, "run_started", payload={"user_message": user_message, "attempt_index": attempt_index})
|
||||
return task
|
||||
|
||||
def append_run(self, task_id: str, run_id: str, *, skill_names: list[str] | None = None) -> TaskRecord:
|
||||
task = self._require(task_id)
|
||||
if run_id not in task.run_ids:
|
||||
task.run_ids.append(run_id)
|
||||
for name in skill_names or []:
|
||||
if name not in task.skill_names:
|
||||
task.skill_names.append(name)
|
||||
task.updated_at = self._now()
|
||||
self.store.upsert_task(task)
|
||||
self._event(task, "run_completed", run_id=run_id, payload={"skill_names": skill_names or []})
|
||||
return task
|
||||
|
||||
def record_validation(self, task_id: str, run_id: str, validation: ValidationResult) -> TaskRecord:
|
||||
task = self._require(task_id)
|
||||
task.status = "awaiting_feedback"
|
||||
task.updated_at = self._now()
|
||||
task.validation_result = validation.to_dict()
|
||||
self.store.upsert_task(task)
|
||||
self._event(task, "validated", run_id=run_id, payload=validation.to_dict())
|
||||
return task
|
||||
|
||||
def add_feedback(
|
||||
self,
|
||||
task_id: str,
|
||||
*,
|
||||
feedback_type: str,
|
||||
comment: str | None = None,
|
||||
run_id: str | None = None,
|
||||
) -> TaskRecord:
|
||||
task = self._require(task_id)
|
||||
now = self._now()
|
||||
matching_feedback = any(
|
||||
item.get("run_id") == run_id and item.get("feedback_type") == feedback_type
|
||||
for item in task.feedback
|
||||
)
|
||||
conflicting_feedback = next(
|
||||
(
|
||||
item
|
||||
for item in task.feedback
|
||||
if item.get("run_id") == run_id and item.get("feedback_type") != feedback_type
|
||||
),
|
||||
None,
|
||||
)
|
||||
if conflicting_feedback is not None:
|
||||
raise ValueError(
|
||||
f"Feedback for run_id={run_id!r} was already recorded as "
|
||||
f"{conflicting_feedback.get('feedback_type')!r}"
|
||||
)
|
||||
if task.status in {"closed", "abandoned"} and not matching_feedback:
|
||||
raise ValueError(f"Task {task.task_id} is already finalized as {task.status!r}")
|
||||
if matching_feedback:
|
||||
return task
|
||||
|
||||
entry = {
|
||||
"feedback_type": feedback_type,
|
||||
"comment": comment or "",
|
||||
"run_id": run_id,
|
||||
"created_at": now,
|
||||
}
|
||||
task.feedback.append(entry)
|
||||
if feedback_type == "revise":
|
||||
task.status = "needs_revision"
|
||||
elif feedback_type == "abandon":
|
||||
task.status = "abandoned"
|
||||
task.closed_at = now
|
||||
task.close_reason = comment or "abandoned"
|
||||
elif feedback_type == "satisfied":
|
||||
task.status = "closed"
|
||||
task.closed_at = now
|
||||
task.close_reason = "satisfied"
|
||||
task.satisfaction = 1.0
|
||||
task.updated_at = now
|
||||
self.store.upsert_task(task)
|
||||
self._event(task, f"feedback_{feedback_type}", run_id=run_id, payload=entry)
|
||||
return task
|
||||
|
||||
def _require(self, task_id: str) -> TaskRecord:
|
||||
task = self.store.get_task(task_id)
|
||||
if task is None:
|
||||
raise ValueError(f"Unknown task_id: {task_id}")
|
||||
return task
|
||||
|
||||
def _event(
|
||||
self,
|
||||
task: TaskRecord,
|
||||
event_type: str,
|
||||
*,
|
||||
run_id: str | None = None,
|
||||
payload: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
self.store.append_event(
|
||||
TaskEvent(
|
||||
event_id=uuid4().hex,
|
||||
task_id=task.task_id,
|
||||
session_id=task.session_id,
|
||||
run_id=run_id,
|
||||
event_type=event_type,
|
||||
created_at=self._now(),
|
||||
payload=dict(payload or {}),
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
Reference in New Issue
Block a user