Agent skill

chat-api-orchestration

Implement single-endpoint chat API with deterministic request orchestration. This skill should be used when users need to create /api/{user_id}/chat endpoint, implement request validation, resolve conversation IDs, call agent runners, or design chat API contracts with structured responses.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/chat-api-orchestration

SKILL.md

Chat API Orchestration

Guide for implementing a single-endpoint chat API with deterministic request flow.

What This Skill Does

  • Design /api/{user_id}/chat endpoint contract
  • Implement deterministic 5-step request flow
  • Validate incoming chat requests
  • Resolve or create conversation_id
  • Orchestrate agent runner calls
  • Return structured responses

What This Skill Does NOT Do

  • Implement the agent/LLM logic itself
  • Handle conversation persistence (see conversation-state-management skill)
  • Manage authentication tokens (assumes auth exists)
  • Deploy or scale the API

Before Implementation

Gather context to ensure successful implementation:

Source Gather
Codebase Existing routers, auth dependencies, error handling patterns
Conversation User's agent runner interface, specific response fields needed
Skill References API contract, request flow, error handling patterns
User Guidelines Project conventions, existing schema patterns

Core Architecture

Single Endpoint Design

POST /api/{user_id}/chat
     │
     ▼
┌─────────────────────────────────────────┐
│         DETERMINISTIC FLOW              │
│                                         │
│  1. Validate Request                    │
│  2. Authenticate User                   │
│  3. Resolve Conversation                │
│  4. Execute Agent                       │
│  5. Return Response                     │
│                                         │
└─────────────────────────────────────────┘

Why Single Endpoint?

Benefit Description
Simplicity One URL to remember, one contract to maintain
Stateless Each request is self-contained
Idempotent design Can safely retry failed requests
Clear ownership user_id in path ensures proper scoping

The 5-Step Deterministic Flow

Every request follows this exact sequence. No shortcuts, no variations.

┌─────────────────────────────────────────────────────────────┐
│ Step 1: VALIDATE REQUEST                                    │
│   • Check required fields (message)                         │
│   • Validate field types and constraints                    │
│   • Return 400 if invalid                                   │
└─────────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 2: AUTHENTICATE USER                                   │
│   • Verify JWT from Authorization header                    │
│   • Extract user_id from token                              │
│   • Verify path user_id matches token user_id               │
│   • Return 401/403 if unauthorized                          │
└─────────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 3: RESOLVE CONVERSATION                                │
│   • If conversation_id provided: load existing              │
│   • If not provided: create new conversation                │
│   • Verify conversation ownership                           │
│   • Return 404 if conversation not found                    │
└─────────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 4: EXECUTE AGENT                                       │
│   • Build context from conversation history                 │
│   • Add user message to context                             │
│   • Call agent runner with context                          │
│   • Persist user message + agent response                   │
│   • Return 503 if agent fails                               │
└─────────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 5: RETURN RESPONSE                                     │
│   • Format structured response                              │
│   • Include conversation_id for continuation                │
│   • Return 200 with ChatResponse                            │
└─────────────────────────────────────────────────────────────┘

Implementation Workflow

Step 1: Define Request/Response Schemas

See references/api-contract.md for complete schemas.

python
# schemas/chat.py
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime


class ChatRequest(BaseModel):
    """Incoming chat request."""
    message: str = Field(..., min_length=1, max_length=10000)
    conversation_id: Optional[str] = Field(
        None,
        description="Existing conversation ID, or omit to create new"
    )
    model: Optional[str] = Field("gpt-4", description="Model to use")


class ChatResponse(BaseModel):
    """Chat API response."""
    conversation_id: str
    message: str
    role: str = "assistant"
    created_at: datetime
    usage: Optional[dict] = None

Step 2: Create the Router

See references/request-flow.md for detailed implementation.

python
# routers/chat.py
from fastapi import APIRouter, Depends, HTTPException, status

router = APIRouter(prefix="/api", tags=["chat"])


@router.post("/{user_id}/chat", response_model=ChatResponse)
async def chat(
    user_id: str,
    request: ChatRequest,
    current_user: AuthenticatedUser = Depends(get_current_user),
    session: Session = Depends(get_session),
    agent: AgentRunner = Depends(get_agent_runner)
):
    """
    Single chat endpoint with deterministic flow.

    1. Validate (handled by Pydantic)
    2. Authenticate (handled by dependency)
    3. Resolve conversation
    4. Execute agent
    5. Return response
    """
    # Step 2: Verify user_id matches authenticated user
    if user_id != current_user.user_id:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Cannot access another user's chat"
        )

    # Step 3: Resolve conversation
    conversation_id = await resolve_conversation(
        session=session,
        user_id=user_id,
        conversation_id=request.conversation_id
    )

    # Step 4: Execute agent
    response = await execute_agent(
        session=session,
        agent=agent,
        conversation_id=conversation_id,
        user_id=user_id,
        message=request.message,
        model=request.model
    )

    # Step 5: Return response
    return response

Step 3: Implement Conversation Resolution

python
# services/conversation.py
async def resolve_conversation(
    session: Session,
    user_id: str,
    conversation_id: Optional[str]
) -> str:
    """
    Resolve or create conversation.

    - If conversation_id provided: verify exists and owned by user
    - If not provided: create new conversation

    Returns: conversation_id (existing or new)
    """
    if conversation_id:
        # Load existing
        conv = get_conversation(session, conversation_id, user_id)
        if not conv:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Conversation not found"
            )
        return conv.id
    else:
        # Create new
        conv = create_conversation(session, user_id=user_id)
        return conv.id

Step 4: Implement Agent Execution

python
# services/agent.py
async def execute_agent(
    session: Session,
    agent: AgentRunner,
    conversation_id: str,
    user_id: str,
    message: str,
    model: str
) -> ChatResponse:
    """
    Execute agent with conversation context.

    1. Load conversation history
    2. Add user message
    3. Call agent
    4. Persist response
    5. Return structured response
    """
    # Load context
    history = get_messages(session, conversation_id, user_id)
    context = [{"role": m.role, "content": m.content} for m in history]

    # Add user message
    add_message(session, conversation_id, user_id, "user", message)
    context.append({"role": "user", "content": message})

    try:
        # Call agent
        result = await agent.run(messages=context, model=model)

        # Persist response
        add_message(session, conversation_id, user_id, "assistant", result.content)

        return ChatResponse(
            conversation_id=conversation_id,
            message=result.content,
            role="assistant",
            created_at=datetime.now(timezone.utc),
            usage=result.usage
        )
    except AgentError as e:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=f"Agent error: {str(e)}"
        )

Error Handling

See references/error-handling.md for complete patterns.

Step Error Status Response
1. Validate Invalid request 400 {"detail": "Field X is required"}
2. Auth Missing token 401 {"detail": "Authentication required"}
2. Auth Wrong user 403 {"detail": "Cannot access another user's chat"}
3. Resolve Conv not found 404 {"detail": "Conversation not found"}
4. Execute Agent fails 503 {"detail": "Agent error: ..."}
* Unexpected 500 {"detail": "Internal server error"}

Idempotency Considerations

For safe retries, consider adding request deduplication:

python
class ChatRequest(BaseModel):
    message: str
    conversation_id: Optional[str] = None
    request_id: Optional[str] = Field(
        None,
        description="Client-generated UUID for idempotency"
    )

If request_id was seen recently, return cached response instead of re-executing.


Output Checklist

Before delivering implementation:

  • ChatRequest schema with message, conversation_id, model
  • ChatResponse schema with conversation_id, message, role, created_at
  • POST /{user_id}/chat endpoint
  • Path user_id validated against token user_id
  • Conversation resolution (create or load)
  • Agent execution with context
  • Message persistence (user + assistant)
  • Error handling for all 5 steps
  • Consistent error response format

Reference Files

File When to Read
references/api-contract.md Complete request/response schemas and examples
references/request-flow.md Detailed implementation of each step
references/error-handling.md Error codes, formats, and recovery patterns

Didn't find tool you were looking for?

Be as detailed as possible for better results