| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- import json
- from core_domain import ServiceHealth
- from core_shared import error_detail
- from fastapi import APIRouter, Depends, HTTPException, Query
- from fastapi.responses import StreamingResponse
- from sqlalchemy import text
- from sqlalchemy.orm import Session
- from app.application.services import AgentApplicationService, build_agent_application_service
- from app.bootstrap.settings import AgentServiceSettings
- from app.db.session import get_db
- from app.schemas.agent import (
- AgentConfigCreateRequest,
- AgentConfigListRequest,
- AgentConfigResponse,
- AgentCreateRequest,
- AgentDeleteRequest,
- AgentDetailRequest,
- AgentListRequest,
- AgentResponse,
- AgentRunCreateRequest,
- AgentRunDetailRequest,
- AgentRunExecutePostRequest,
- AgentRunExecuteRequest,
- AgentRunExecuteResponse,
- AgentRunListRequest,
- AgentRunResponse,
- AgentRunStatusPostRequest,
- AgentRunStatusUpdateRequest,
- AgentStatusUpdateRequest,
- AgentStatusPostRequest,
- AgentToolInvocationListRequest,
- AgentUpdateRequest,
- AgentToolInvocationResponse,
- AgentWorkerExecuteNextRequest,
- AgentWorkerExecuteNextResponse,
- DeleteData,
- )
- router = APIRouter()
- def json_dump(payload: dict[str, object]) -> str:
- return json.dumps(payload, ensure_ascii=False, default=str)
- def get_agent_service_settings() -> AgentServiceSettings:
- return AgentServiceSettings()
- def get_agent_application_service(
- db: Session = Depends(get_db),
- settings: AgentServiceSettings = Depends(get_agent_service_settings)) -> AgentApplicationService:
- return build_agent_application_service(db=db, settings=settings)
- @router.get("/health", response_model=ServiceHealth)
- def health_check(db: Session = Depends(get_db)) -> ServiceHealth:
- db.execute(text("SELECT 1"))
- return ServiceHealth(service="agent-service", status="ok", database="ok")
- @router.post("", response_model=AgentResponse)
- def create_agent(
- payload: AgentCreateRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentResponse:
- entity = service.create_agent(payload)
- return AgentResponse.from_entity(entity)
- @router.get("", response_model=list[AgentResponse])
- def list_agents(
- service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentResponse]:
- return [AgentResponse.from_entity(item) for item in service.list_agents()]
- @router.post("/list", response_model=list[AgentResponse])
- def list_agents_post(
- payload: AgentListRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentResponse]:
- return [AgentResponse.from_entity(item) for item in service.list_agents()]
- @router.post("/detail", response_model=AgentResponse)
- def detail_agent(
- payload: AgentDetailRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentResponse:
- entity = service.get_agent(agent_id=payload.agent_id)
- if entity is None:
- raise HTTPException(status_code=404, detail=error_detail("error.agent.not_found", id=payload.agent_id))
- return AgentResponse.from_entity(entity)
- @router.post("/update", response_model=AgentResponse)
- def update_agent(
- payload: AgentUpdateRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentResponse:
- entity = service.update_agent(payload)
- if entity is None:
- raise HTTPException(status_code=404, detail=error_detail("error.agent.not_found", id=payload.agent_id))
- return AgentResponse.from_entity(entity)
- @router.patch("/{agent_id}/status", response_model=AgentResponse)
- def update_agent_status(
- agent_id: str,
- payload: AgentStatusUpdateRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentResponse:
- entity = service.update_agent_status(agent_id=agent_id, payload=payload)
- if entity is None:
- raise HTTPException(status_code=404, detail=error_detail("error.agent.not_found", id=agent_id))
- return AgentResponse.from_entity(entity)
- @router.post("/status", response_model=AgentResponse)
- def update_agent_status_post(
- payload: AgentStatusPostRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentResponse:
- entity = service.update_agent_status(
- agent_id=payload.agent_id,
- payload=AgentStatusUpdateRequest(status=payload.status))
- if entity is None:
- raise HTTPException(status_code=404, detail=error_detail("error.agent.not_found", id=payload.agent_id))
- return AgentResponse.from_entity(entity)
- @router.post("/delete", response_model=DeleteData)
- def delete_agent_post(
- payload: AgentDeleteRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> DeleteData:
- return DeleteData(
- deleted=service.delete_agent(agent_id=payload.agent_id),
- agent_id=payload.agent_id)
- @router.post("/configs/create", response_model=AgentConfigResponse)
- def create_agent_config(
- payload: AgentConfigCreateRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentConfigResponse:
- try:
- entity = service.create_agent_config(payload)
- except ValueError as exc:
- raise HTTPException(status_code=422, detail=error_detail("error.validation", message=str(exc))) from exc
- return AgentConfigResponse.from_entity(entity)
- @router.post("/configs/list", response_model=list[AgentConfigResponse])
- def list_agent_configs(
- payload: AgentConfigListRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentConfigResponse]:
- return [
- AgentConfigResponse.from_entity(item)
- for item in service.list_agent_configs(agent_id=payload.agent_id)
- ]
- @router.post("/runs", response_model=AgentRunResponse)
- def create_agent_run(
- payload: AgentRunCreateRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunResponse:
- try:
- entity = service.create_agent_run(payload)
- except ValueError as exc:
- raise HTTPException(status_code=422, detail=error_detail("error.validation", message=str(exc))) from exc
- return AgentRunResponse.from_entity(entity)
- @router.get("/runs", response_model=list[AgentRunResponse])
- def list_agent_runs(
- agent_id: str | None = Query(default=None),
- session_id: str | None = Query(default=None),
- service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentRunResponse]:
- return [
- AgentRunResponse.from_entity(item)
- for item in service.list_agent_runs(
- agent_id=agent_id,
- session_id=session_id)
- ]
- @router.post("/runs/list", response_model=list[AgentRunResponse])
- def list_agent_runs_post(
- payload: AgentRunListRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentRunResponse]:
- return [
- AgentRunResponse.from_entity(item)
- for item in service.list_agent_runs(
- agent_id=payload.agent_id,
- session_id=payload.session_id)
- ]
- @router.post("/runs/detail", response_model=AgentRunResponse)
- def get_agent_run(
- payload: AgentRunDetailRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunResponse:
- entity = service.get_agent_run(payload)
- if entity is None:
- raise HTTPException(status_code=404, detail=error_detail("error.agent_run.not_found", id=payload.agent_run_id))
- return AgentRunResponse.from_entity(entity)
- @router.get(
- "/runs/{agent_run_id}/tool-invocations",
- response_model=list[AgentToolInvocationResponse])
- def list_agent_tool_invocations(
- agent_run_id: str,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentToolInvocationResponse]:
- return [
- AgentToolInvocationResponse.from_entity(item)
- for item in service.list_agent_tool_invocations(
- agent_run_id=agent_run_id)
- ]
- @router.post(
- "/runs/tool-invocations/list",
- response_model=list[AgentToolInvocationResponse])
- def list_agent_tool_invocations_post(
- payload: AgentToolInvocationListRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentToolInvocationResponse]:
- return [
- AgentToolInvocationResponse.from_entity(item)
- for item in service.list_agent_tool_invocations(
- agent_run_id=payload.agent_run_id)
- ]
- @router.post("/runs/{agent_run_id}/status", response_model=AgentRunResponse)
- def update_agent_run_status(
- agent_run_id: str,
- payload: AgentRunStatusUpdateRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunResponse:
- entity = service.update_agent_run_status(agent_run_id=agent_run_id, payload=payload)
- if entity is None:
- raise HTTPException(status_code=404, detail=error_detail("error.agent_run.not_found", id=agent_run_id))
- return AgentRunResponse.from_entity(entity)
- @router.post("/runs/status", response_model=AgentRunResponse)
- def update_agent_run_status_post(
- payload: AgentRunStatusPostRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunResponse:
- entity = service.update_agent_run_status(
- agent_run_id=payload.agent_run_id,
- payload=AgentRunStatusUpdateRequest(
- status=payload.status,
- worker_key=payload.worker_key,
- output_text=payload.output_text,
- output_json=payload.output_json,
- error_code=payload.error_code,
- error_message=payload.error_message))
- if entity is None:
- raise HTTPException(status_code=404, detail=error_detail("error.agent_run.not_found", id=payload.agent_run_id))
- return AgentRunResponse.from_entity(entity)
- @router.post("/runs/{agent_run_id}/execute", response_model=AgentRunExecuteResponse)
- def execute_agent_run(
- agent_run_id: str,
- payload: AgentRunExecuteRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunExecuteResponse:
- entity = service.execute_agent_run(agent_run_id=agent_run_id, payload=payload)
- if entity is None:
- raise HTTPException(status_code=404, detail=error_detail("error.agent_run.not_found", id=agent_run_id))
- output_json = entity.output_json or {}
- model_value = output_json.get("model")
- dry_run_value = output_json.get("dry_run")
- return AgentRunExecuteResponse(
- run=AgentRunResponse.from_entity(entity),
- model=model_value if isinstance(model_value, str) else None,
- dry_run=dry_run_value if isinstance(dry_run_value, bool) else False)
- @router.post("/runs/{agent_run_id}/execute-stream")
- def execute_agent_run_stream(
- agent_run_id: str,
- payload: AgentRunExecuteRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> StreamingResponse:
- if service.get_agent_run(AgentRunDetailRequest(agent_run_id=agent_run_id)) is None:
- raise HTTPException(status_code=404, detail=error_detail("error.agent_run.not_found", id=agent_run_id))
- def events():
- for item in service.execute_agent_run_stream(agent_run_id=agent_run_id, payload=payload):
- event = item.get("event")
- event_name = event if isinstance(event, str) else "message"
- data = {key: value for key, value in item.items() if key != "event"}
- yield f"event: {event_name}\ndata: {json_dump(data)}\n\n"
- return StreamingResponse(
- events(),
- media_type="text/event-stream",
- headers=_sse_headers())
- def _sse_headers() -> dict[str, str]:
- return {
- "Cache-Control": "no-cache",
- "X-Accel-Buffering": "no",
- }
- @router.post("/runs/execute", response_model=AgentRunExecuteResponse)
- def execute_agent_run_post(
- payload: AgentRunExecutePostRequest,
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunExecuteResponse:
- entity = service.execute_agent_run(
- agent_run_id=payload.agent_run_id,
- payload=AgentRunExecuteRequest(
- worker_key=payload.worker_key,
- dry_run=payload.dry_run))
- if entity is None:
- raise HTTPException(status_code=404, detail=error_detail("error.agent_run.not_found", id=payload.agent_run_id))
- output_json = entity.output_json or {}
- model_value = output_json.get("model")
- dry_run_value = output_json.get("dry_run")
- return AgentRunExecuteResponse(
- run=AgentRunResponse.from_entity(entity),
- model=model_value if isinstance(model_value, str) else None,
- dry_run=dry_run_value if isinstance(dry_run_value, bool) else False)
- @router.post("/workers/execute-next", response_model=AgentWorkerExecuteNextResponse)
- def execute_next_worker_task(
- payload: AgentWorkerExecuteNextRequest,
- settings: AgentServiceSettings = Depends(get_agent_service_settings),
- service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentWorkerExecuteNextResponse:
- result = service.execute_next_claimed_agent_run(
- worker_key=payload.worker_key,
- lease_seconds=payload.lease_seconds or settings.worker_lease_seconds,
- dry_run=payload.dry_run if payload.dry_run is not None else settings.worker_dry_run)
- if result is None:
- raise HTTPException(status_code=404, detail=error_detail("error.agent_run.not_found_queued"))
- entity, released_lease_count = result
- output_json = entity.output_json or {}
- model_value = output_json.get("model")
- dry_run_value = output_json.get("dry_run")
- return AgentWorkerExecuteNextResponse(
- run=AgentRunResponse.from_entity(entity),
- model=model_value if isinstance(model_value, str) else None,
- dry_run=dry_run_value if isinstance(dry_run_value, bool) else False,
- released_lease_count=released_lease_count)
|