feat: V1 - DAG编排/告警系统/Agent循环/知识库RAG/Webhook + 审核管理富上下文修复
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
"""Review service: HITL approval/rejection flow."""
|
||||
"""Review service: HITL approval/rejection flow with full context."""
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
from app.models.review import Review
|
||||
from app.models.task import Task
|
||||
from app.schemas.review import ReviewCreate, ReviewDecision
|
||||
from app.models.project import Project
|
||||
from app.models.worker import AIWorker
|
||||
from app.models.cost import CostLog
|
||||
from app.schemas.review import ReviewCreate, ReviewDecision, ReviewDetailResponse
|
||||
|
||||
|
||||
class ReviewService:
|
||||
|
||||
# ---- Basic CRUD ----
|
||||
|
||||
@staticmethod
|
||||
def list_reviews(
|
||||
db: Session,
|
||||
@@ -51,6 +58,100 @@ class ReviewService:
|
||||
db.refresh(review)
|
||||
return review
|
||||
|
||||
# ---- Detail with full context ----
|
||||
|
||||
@staticmethod
|
||||
def list_reviews_detail(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
status: Optional[str] = None,
|
||||
project_id: Optional[int] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> List[ReviewDetailResponse]:
|
||||
"""List reviews with joined task/project/worker/cost context."""
|
||||
reviews = ReviewService.list_reviews(
|
||||
db, tenant_id, status, project_id, skip, limit
|
||||
)
|
||||
return [ReviewService._enrich(db, r) for r in reviews]
|
||||
|
||||
@staticmethod
|
||||
def get_review_detail(
|
||||
db: Session, tenant_id: int, review_id: int
|
||||
) -> Optional[ReviewDetailResponse]:
|
||||
"""Get a single review with full context."""
|
||||
review = ReviewService.get_review(db, tenant_id, review_id)
|
||||
if not review:
|
||||
return None
|
||||
return ReviewService._enrich(db, review)
|
||||
|
||||
@staticmethod
|
||||
def _enrich(db: Session, review: Review) -> ReviewDetailResponse:
|
||||
"""Join review with task, project, worker, and aggregate cost."""
|
||||
task = (
|
||||
db.query(Task)
|
||||
.filter(Task.id == review.task_id, Task.tenant_id == review.tenant_id)
|
||||
.first()
|
||||
)
|
||||
project = (
|
||||
db.query(Project)
|
||||
.filter(Project.id == review.project_id, Project.tenant_id == review.tenant_id)
|
||||
.first()
|
||||
)
|
||||
worker = None
|
||||
if task and task.worker_id:
|
||||
worker = (
|
||||
db.query(AIWorker)
|
||||
.filter(AIWorker.id == task.worker_id, AIWorker.tenant_id == review.tenant_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
# Aggregate cost data for this task
|
||||
cost_row = (
|
||||
db.query(
|
||||
func.sum(CostLog.cost_cents).label("total_cost"),
|
||||
func.sum(CostLog.total_tokens).label("total_tokens"),
|
||||
func.sum(CostLog.duration_ms).label("total_duration"),
|
||||
)
|
||||
.filter(CostLog.task_id == review.task_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
return ReviewDetailResponse(
|
||||
id=review.id,
|
||||
task_id=review.task_id,
|
||||
project_id=review.project_id,
|
||||
review_type=review.review_type,
|
||||
status=review.status,
|
||||
reviewer_id=review.reviewer_id,
|
||||
reviewer_role=review.reviewer_role,
|
||||
review_content=review.review_content,
|
||||
reviewer_comment=review.reviewer_comment,
|
||||
auto_check_result=review.auto_check_result,
|
||||
submitted_at=review.submitted_at,
|
||||
reviewed_at=review.reviewed_at,
|
||||
created_at=review.created_at,
|
||||
task_title=task.title if task else "",
|
||||
task_description=task.description if task else "",
|
||||
task_input_data=task.input_data if task else "",
|
||||
task_output_data=task.output_data if task else "",
|
||||
task_priority=task.priority if task else "medium",
|
||||
task_type=task.task_type if task else "single_call",
|
||||
task_status=task.status if task else "",
|
||||
project_name=project.name if project else "",
|
||||
project_description=project.description if project else "",
|
||||
worker_id=worker.id if worker else None,
|
||||
worker_name=worker.name if worker else None,
|
||||
worker_model_name=worker.model_name if worker else None,
|
||||
worker_provider=worker.provider if worker else None,
|
||||
worker_system_prompt=worker.system_prompt if worker else None,
|
||||
cost_cents=cost_row.total_cost or 0 if cost_row else 0,
|
||||
token_usage=cost_row.total_tokens or 0 if cost_row else 0,
|
||||
duration_ms=cost_row.total_duration or 0 if cost_row else 0,
|
||||
)
|
||||
|
||||
# ---- Decision ----
|
||||
|
||||
@staticmethod
|
||||
def make_decision(
|
||||
db: Session,
|
||||
@@ -72,7 +173,6 @@ class ReviewService:
|
||||
review.reviewer_comment = req.comment
|
||||
review.reviewed_at = datetime.utcnow()
|
||||
|
||||
# Update task status
|
||||
task = (
|
||||
db.query(Task)
|
||||
.filter(Task.id == review.task_id, Task.tenant_id == tenant_id)
|
||||
|
||||
Reference in New Issue
Block a user