diff --git a/app/admin/api.py b/app/admin/api.py index d3d8e71..63470ca 100644 --- a/app/admin/api.py +++ b/app/admin/api.py @@ -201,17 +201,16 @@ async def create_agent( 3. Reinitializes agent if already in cache 4. Masks sensitive data in response - Args: - - agent: Agent configuration - - db: Database session + **Request Body:** + * `agent` - Agent configuration - Returns: - - AgentResponse: Updated agent configuration with additional processed data + **Returns:** + * `AgentResponse` - Updated agent configuration with additional processed data - Raises: - - HTTPException: - - 400: Invalid agent ID format - - 500: Database error + **Raises:** + * `HTTPException`: + - 400: Invalid agent ID format + - 500: Database error """ latest_agent, agent_data = await _process_agent(agent, subject) return AgentResponse.from_agent(latest_agent, agent_data) @@ -226,11 +225,8 @@ async def create_agent( async def get_agents(db: AsyncSession = Depends(get_db)) -> list[AgentResponse]: """Get all agents with their quota information. - Args: - db: Database session - - Returns: - list[AgentResponse]: List of agents with their quota information and additional processed data + **Returns:** + * `list[AgentResponse]` - List of agents with their quota information and additional processed data """ # Query all agents first agents = (await db.exec(select(Agent))).all() @@ -255,19 +251,21 @@ async def get_agents(db: AsyncSession = Depends(get_db)) -> list[AgentResponse]: dependencies=[Depends(verify_jwt)], operation_id="get_agent", ) -async def get_agent(agent_id: str, db: AsyncSession = Depends(get_db)) -> AgentResponse: +async def get_agent( + agent_id: str = Path(..., description="ID of the agent to retrieve"), + db: AsyncSession = Depends(get_db), +) -> AgentResponse: """Get a single agent by ID. - Args: - agent_id: ID of the agent to retrieve - db: Database session + **Path Parameters:** + * `agent_id` - ID of the agent to retrieve - Returns: - AgentResponse: Agent configuration with additional processed data + **Returns:** + * `AgentResponse` - Agent configuration with additional processed data - Raises: - HTTPException: - - 404: Agent not found + **Raises:** + * `HTTPException`: + - 404: Agent not found """ agent = (await db.exec(select(Agent).where(Agent.id == agent_id))).first() if not agent: @@ -309,23 +307,23 @@ class MemCleanRequest(BaseModel): ) async def clean_memory( request: MemCleanRequest = Body( - MemCleanRequest, description="Agent memory cleanup requestd" + MemCleanRequest, description="Agent memory cleanup request" ), db: AsyncSession = Depends(get_db), ) -> str: """Clear an agent memory. - Args: - request (MemCleanRequest): The execution request containing agent ID, message, and thread ID + **Request Body:** + * `request` - The execution request containing agent ID, message, and thread ID - Returns: - str: Formatted response lines from agent memory cleanup + **Returns:** + * `str` - Formatted response lines from agent memory cleanup - Raises: - HTTPException: - - 400: If input parameters are invalid (empty agent_id, thread_id, or message text) - - 404: If agent not found - - 500: For other server-side errors + **Raises:** + * `HTTPException`: + - 400: If input parameters are invalid (empty agent_id, thread_id, or message text) + - 404: If agent not found + - 500: For other server-side errors """ # Validate input parameters if not request.agent_id or not request.agent_id.strip(): @@ -366,19 +364,19 @@ async def clean_memory( dependencies=[Depends(verify_jwt)], ) async def export_agent( - agent_id: str, + agent_id: str = Path(..., description="ID of the agent to export"), ) -> str: """Export agent configuration as YAML. - Args: - agent_id: ID of the agent to export + **Path Parameters:** + * `agent_id` - ID of the agent to export - Returns: - str: YAML configuration of the agent + **Returns:** + * `str` - YAML configuration of the agent - Raises: - HTTPException: - - 404: Agent not found + **Raises:** + * `HTTPException`: + - 404: Agent not found """ try: agent = await Agent.get(agent_id) @@ -414,20 +412,20 @@ async def import_agent( """Import agent configuration from YAML file. Only updates existing agents, will not create new ones. - Args: - agent_id: ID of the agent to update - file: YAML file containing agent configuration - subject: JWT subject for authorization - db: Database session + **Path Parameters:** + * `agent_id` - ID of the agent to update - Returns: - str: Success message + **Request Body:** + * `file` - YAML file containing agent configuration + + **Returns:** + * `str` - Success message - Raises: - HTTPException: - - 400: Invalid YAML or agent configuration - - 404: Agent not found - - 500: Server error + **Raises:** + * `HTTPException`: + - 400: Invalid YAML or agent configuration + - 404: Agent not found + - 500: Server error """ try: # First check if agent exists diff --git a/app/admin/schema.py b/app/admin/schema.py index ee6bed7..bc735b3 100644 --- a/app/admin/schema.py +++ b/app/admin/schema.py @@ -25,9 +25,9 @@ async def get_agent_schema() -> JSONResponse: """Get the JSON schema for Agent model with all $ref references resolved. - Returns: - JSONResponse: The complete JSON schema for the Agent model with application/json content type - """ + **Returns:** + * `JSONResponse` - The complete JSON schema for the Agent model with application/json content type +""" base_uri = f"file://{AGENT_SCHEMA_PATH}" with open(AGENT_SCHEMA_PATH) as f: schema = jsonref.load(f, base_uri=base_uri, proxies=False, lazy_load=False) @@ -49,22 +49,20 @@ async def get_agent_schema() -> JSONResponse: ) async def get_skill_schema( skill: str = PathParam( - ..., - description="Skill name (alphanumeric and dash only)", - regex="^[a-zA-Z0-9-]+$", + ..., description="Skill name", regex="^[a-zA-Z0-9_-]+$" ), ) -> JSONResponse: - """Get the JSON schema for a skill. + """Get the JSON schema for a specific skill. - Args: - skill: The name of the skill to get the schema for (alphanumeric and dash only) + **Path Parameters:** + * `skill` - Skill name - Returns: - JSONResponse: The JSON schema for the skill with application/json content type + **Returns:** + * `JSONResponse` - The complete JSON schema for the skill with application/json content type - Raises: - HTTPException: If the skill is not found or name is invalid - """ + **Raises:** + * `HTTPException` - If the skill is not found or name is invalid +""" base_path = PROJECT_ROOT / "skills" schema_path = base_path / skill / "schema.json" normalized_path = schema_path.resolve() diff --git a/app/core/api.py b/app/core/api.py index fd798c1..5b67604 100644 --- a/app/core/api.py +++ b/app/core/api.py @@ -25,17 +25,17 @@ async def execute( ) -> list[ChatMessage]: """Execute an agent with the given input and return response lines. - Args: - message (ChatMessage): The chat message containing agent_id, chat_id and message content + **Request Body:** + * `message` - The chat message containing agent_id, chat_id and message content - Returns: - list[str]: Formatted response lines from agent execution + **Returns:** + * `list[ChatMessage]` - Formatted response lines from agent execution - Raises: - HTTPException: - - 400: If input parameters are invalid - - 404: If agent not found - - 500: For other server-side errors + **Raises:** + * `HTTPException`: + - 400: If input parameters are invalid + - 404: If agent not found + - 500: For other server-side errors """ message.created_at = None # Validate input parameters diff --git a/app/entrypoints/web.py b/app/entrypoints/web.py index 7ade05a..5229098 100644 --- a/app/entrypoints/web.py +++ b/app/entrypoints/web.py @@ -3,6 +3,7 @@ import json import logging import secrets +import textwrap from typing import List from epyxid import XID @@ -189,11 +190,14 @@ async def debug_chat( 3. Executes the agent with the query 4. Updates quota usage - **Parameters:** + **Path Parameters:** * `aid` - Agent ID + + **Query Parameters:** * `q` - User's input query * `debug` - Enable debug mode (show whole skill response) * `thread` - Thread ID for conversation tracking + * `chat_id` - Chat ID for conversation tracking **Returns:** * `str` - Formatted chat response @@ -291,8 +295,10 @@ async def get_chat_history( * `public` - Public chat history in X and TG groups * `owner` - Owner chat history (coming soon) - **Parameters:** + **Path Parameters:** * `aid` - Agent ID + + **Query Parameters:** * `chat_id` - Chat ID to get history for **Returns:** @@ -340,8 +346,10 @@ async def retry_chat_deprecated( If the last message is from the agent, return it directly. If the last message is from a user, generate a new agent response. - **Parameters:** + **Path Parameters:** * `aid` - Agent ID + + **Query Parameters:** * `chat_id` - Chat ID to retry **Returns:** @@ -411,8 +419,10 @@ async def retry_chat( If the last message is from the agent, return it directly. If the last message is from a user, generate a new agent response. - **Parameters:** + **Path Parameters:** * `aid` - Agent ID + + **Query Parameters:** * `chat_id` - Chat ID to retry **Returns:** @@ -488,12 +498,14 @@ async def create_chat_deprecated( > **Note:** This is for internal/private use and may have additional features or fewer > restrictions compared to the public endpoint. - **Parameters:** + **Path Parameters:** * `aid` - Agent ID + + **Request Body:** * `request` - Chat message request object **Returns:** - * `List[ChatMessage]` - List of chat messages including both user input and agent response + * `ChatMessage` - Agent's response message **Raises:** * `404` - Agent not found @@ -524,19 +536,27 @@ async def create_chat_deprecated( attachments=request.attachments, ) - try: - # Execute agent - response_messages = await execute_agent(user_message) + # Execute agent + response_messages = await execute_agent(user_message) - # Update quota - await quota.add_message() + # Create or active chat + chat = await Chat.get(request.chat_id) + if chat: + await chat.add_round() + else: + chat = Chat( + id=request.chat_id, + agent_id=aid, + user_id=request.user_id, + summary=textwrap.shorten(request.message, width=20, placeholder="..."), + rounds=1, + ) + await chat.create() - return response_messages[-1] + # Update quota + await quota.add_message() - except Exception as e: - if isinstance(e, HTTPException): - raise - raise HTTPException(status_code=500, detail=str(e)) + return response_messages[-1] @chat_router.post( @@ -564,8 +584,13 @@ async def create_chat( > **Note:** This is the public-facing endpoint with appropriate rate limiting > and security measures. - **Parameters:** + **Path Parameters:** * `aid` - Agent ID + + **Query Parameters:** + * `owner_mode` - Enable owner mode + + **Request Body:** * `request` - Chat message request object **Returns:** @@ -606,26 +631,35 @@ async def create_chat( attachments=request.attachments, ) - try: - # Execute agent - response_messages = await execute_agent(user_message) + # Execute agent + response_messages = await execute_agent(user_message) - # Update quota - await quota.add_message() + # Create or active chat + chat = await Chat.get(request.chat_id) + if chat: + await chat.add_round() + else: + chat = Chat( + id=request.chat_id, + agent_id=aid, + user_id=request.user_id, + summary=textwrap.shorten(request.message, width=20, placeholder="..."), + rounds=1, + ) + await chat.create() - return response_messages + # Update quota + await quota.add_message() - except Exception as e: - if isinstance(e, HTTPException): - raise - raise HTTPException(status_code=500, detail=str(e)) + return response_messages @chat_router_readonly.get( "/agents/{aid}/chats", response_model=List[Chat], - summary="Get chat list by agent and user", + summary="User Chat List", tags=["Chat"], + operation_id="get_agent_chats", ) async def get_agent_chats( aid: str = Path(..., description="Agent ID"), @@ -633,8 +667,10 @@ async def get_agent_chats( ): """Get chat list for a specific agent and user. - **Parameters:** + **Path Parameters:** * `aid` - Agent ID + + **Query Parameters:** * `user_id` - User ID **Returns:** @@ -667,8 +703,9 @@ class ChatSummaryUpdate(BaseModel): @chat_router.put( "/agents/{aid}/chats/{chat_id}", response_model=Chat, - summary="Update chat summary", + summary="Update Chat Summary", tags=["Chat"], + operation_id="update_chat_summary", ) async def update_chat_summary( update_data: ChatSummaryUpdate, @@ -677,9 +714,11 @@ async def update_chat_summary( ): """Update the summary of a specific chat. - **Parameters:** + **Path Parameters:** * `aid` - Agent ID * `chat_id` - Chat ID + + **Request Body:** * `update_data` - Summary update data (in request body) **Returns:** @@ -710,8 +749,9 @@ async def update_chat_summary( @chat_router.delete( "/agents/{aid}/chats/{chat_id}", status_code=status.HTTP_204_NO_CONTENT, - summary="Delete a chat", + summary="Delete a Chat", tags=["Chat"], + operation_id="delete_chat", ) async def delete_chat( aid: str = Path(..., description="Agent ID"), @@ -719,7 +759,7 @@ async def delete_chat( ): """Delete a specific chat. - **Parameters:** + **Path Parameters:** * `aid` - Agent ID * `chat_id` - Chat ID diff --git a/app/services/twitter/oauth2.py b/app/services/twitter/oauth2.py index 8e321e5..be93354 100644 --- a/app/services/twitter/oauth2.py +++ b/app/services/twitter/oauth2.py @@ -105,12 +105,12 @@ class TwitterAuthResponse(BaseModel): async def get_twitter_auth_url(agent_id: str, redirect_uri: str) -> TwitterAuthResponse: """Get Twitter OAuth2 authorization URL. - Args: - agent_id: ID of the agent to authenticate - redirect_uri: DApp URI to redirect to after authorization from agentkit to DApp + **Query Parameters:** + * `agent_id` - ID of the agent to authenticate + * `redirect_uri` - DApp URI to redirect to after authorization from agentkit to DApp - Returns: - Object containing agent_id and authorization URL + **Returns:** + * Object containing agent_id and authorization URL """ url = oauth2_user_handler.get_authorization_url(agent_id, redirect_uri) return TwitterAuthResponse(agent_id=agent_id, url=url) @@ -119,11 +119,11 @@ async def get_twitter_auth_url(agent_id: str, redirect_uri: str) -> TwitterAuthR def get_authorization_url(agent_id: str, redirect_uri: str) -> str: """Get Twitter OAuth2 authorization URL. - Args: - agent_id: ID of the agent to authenticate - redirect_uri: DApp URI to redirect to after authorization from agentkit to DApp + **Query Parameters:** + * `agent_id` - ID of the agent to authenticate + * `redirect_uri` - DApp URI to redirect to after authorization from agentkit to DApp - Returns: - Authorization URL with agent_id as state parameter + **Returns:** + * Authorization URL with agent_id as state parameter """ return oauth2_user_handler.get_authorization_url(agent_id, redirect_uri) diff --git a/app/services/twitter/oauth2_callback.py b/app/services/twitter/oauth2_callback.py index 5b5de9e..41935ca 100644 --- a/app/services/twitter/oauth2_callback.py +++ b/app/services/twitter/oauth2_callback.py @@ -43,16 +43,14 @@ async def twitter_oauth_callback( It exchanges the authorization code for access and refresh tokens, then stores them in the database. - Args: - state: URL-encoded state containing agent_id and redirect_uri - code: Authorization code from Twitter - - Returns: - JSONResponse or RedirectResponse depending on redirect_uri - - Raises: - HTTPException: If state/code is missing or token exchange fails - """ + **Query Parameters:** + * `state` - URL-encoded state containing agent_id and redirect_uri + * `code` - Authorization code from Twitter + * `error` - Error message from Twitter (optional) + + **Returns:** + * JSONResponse or RedirectResponse depending on redirect_uri +""" if not state: raise HTTPException(status_code=400, detail="Missing state parameter") diff --git a/models/chat.py b/models/chat.py index e6c487e..09d9dee 100644 --- a/models/chat.py +++ b/models/chat.py @@ -2,7 +2,6 @@ from enum import Enum from typing import List, NotRequired, Optional, TypedDict -from fastapi import HTTPException from pydantic import BaseModel, Field from sqlalchemy import Column, DateTime, Index, String, func from sqlalchemy.dialects.postgresql import JSONB @@ -237,10 +236,7 @@ class Chat(SQLModel, table=True): @classmethod async def get(cls, id: str) -> "Chat": async with get_session() as db: - chat = await db.get(cls, id) - if not chat: - raise HTTPException(status_code=404, detail="Chat not found") - return chat + return await db.get(cls, id) async def create(self): async with get_session() as db: @@ -250,7 +246,7 @@ async def create(self): async def delete(self): async with get_session() as db: - db.delete(self) + await db.delete(self) await db.commit() async def add_round(self): diff --git a/utils/logging.py b/utils/logging.py index 37986a9..fb9a895 100644 --- a/utils/logging.py +++ b/utils/logging.py @@ -52,6 +52,7 @@ def setup_logging(env: str, debug: bool = False): ) logging.getLogger("openai._base_client").setLevel(logging.INFO) logging.getLogger("httpcore.http11").setLevel(logging.INFO) + logging.getLogger("sqlalchemy.engine").setLevel(logging.DEBUG) else: # For non-local environments, use JSON format handler = logging.StreamHandler()