diff --git a/agents-api/agents_api/routers/agents/__init__.py b/agents-api/agents_api/routers/agents/__init__.py index fa07d0740..af9233c56 100644 --- a/agents-api/agents_api/routers/agents/__init__.py +++ b/agents-api/agents_api/routers/agents/__init__.py @@ -1 +1,3 @@ -from .routers import router # noqa: F401 +from fastapi import APIRouter + +router = APIRouter() diff --git a/agents-api/agents_api/routers/agents/create_agent.py b/agents-api/agents_api/routers/agents/create_agent.py new file mode 100644 index 000000000..f0f81a6c9 --- /dev/null +++ b/agents-api/agents_api/routers/agents/create_agent.py @@ -0,0 +1,26 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from pydantic import UUID4 +from typing import Annotated + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.agent.create_agent import create_agent_query +from agents_api.autogen.openapi_model import CreateAgentRequest, ResourceCreatedResponse +from agents_api.common.utils.datetime import utcnow + +router = APIRouter() + +@router.post("/agents", status_code=HTTP_201_CREATED, tags=["agents"]) +async def create_agent( + request: CreateAgentRequest, + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], +) -> ResourceCreatedResponse: + agent_id = create_agent_query( + developer_id=x_developer_id, + name=request.name, + about=request.about, + instructions=request.instructions, + model=request.model, + default_settings=request.default_settings, + metadata=request.metadata, + ) + return ResourceCreatedResponse(id=agent_id, created_at=utcnow()) diff --git a/agents-api/agents_api/routers/agents/delete_agent.py b/agents-api/agents_api/routers/agents/delete_agent.py new file mode 100644 index 000000000..bfe7da8e2 --- /dev/null +++ b/agents-api/agents_api/routers/agents/delete_agent.py @@ -0,0 +1,23 @@ +from fastapi import APIRouter, Depends, HTTPException +from pydantic import UUID4 +from starlette.status import HTTP_202_ACCEPTED + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.agent.delete_agent import delete_agent_query +from agents_api.common.exceptions.agents import AgentNotFoundError +from agents_api.common.utils.datetime import utcnow +from agents_api.autogen.openapi_model import ResourceDeletedResponse + +router = APIRouter() + +@router.delete("/agents/{agent_id}", status_code=HTTP_202_ACCEPTED, tags=["agents"]) +async def delete_agent( + agent_id: UUID4, x_developer_id: Annotated[UUID4, Depends(get_developer_id)] +) -> ResourceDeletedResponse: + try: + delete_agent_query(x_developer_id, agent_id) + except AgentNotFoundError as e: + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, detail=str(e) + ) + return ResourceDeletedResponse(id=agent_id, deleted_at=utcnow()) diff --git a/agents-api/agents_api/routers/agents/get_agent_details.py b/agents-api/agents_api/routers/agents/get_agent_details.py new file mode 100644 index 000000000..adaf9bbd1 --- /dev/null +++ b/agents-api/agents_api/routers/agents/get_agent_details.py @@ -0,0 +1,26 @@ +from fastapi import APIRouter, Depends, HTTPException +from pydantic import UUID4 +from starlette.status import HTTP_404_NOT_FOUND + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.agent.get_agent import get_agent_query +from agents_api.common.exceptions.agents import AgentNotFoundError +from agents_api.autogen.openapi_model import Agent + +router = APIRouter() + +@router.get("/agents/{agent_id}", tags=["agents"]) +async def get_agent_details( + agent_id: UUID4, + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], +) -> Agent: + try: + agent = get_agent_query(developer_id=x_developer_id, agent_id=agent_id) + if not agent: + raise AgentNotFoundError(f"Agent with ID {agent_id} not found") + return Agent(**agent) + except AgentNotFoundError as e: + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail=str(e) + ) diff --git a/agents-api/agents_api/routers/agents/list_agents.py b/agents-api/agents_api/routers/agents/list_agents.py new file mode 100644 index 000000000..a21398d86 --- /dev/null +++ b/agents-api/agents_api/routers/agents/list_agents.py @@ -0,0 +1,24 @@ +from fastapi import APIRouter, Depends +from pydantic import UUID4 +from typing import List + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.agent.list_agents import list_agents_query +from agents_api.autogen.openapi_model import Agent + +router = APIRouter() + +@router.get("/agents", tags=["agents"]) +async def list_agents( + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], + limit: int = 100, + offset: int = 0, + metadata_filter: str = "{}", +) -> List[Agent]: + agents = list_agents_query( + developer_id=x_developer_id, + limit=limit, + offset=offset, + metadata_filter=metadata_filter, + ) + return [Agent(**agent) for agent in agents] diff --git a/agents-api/agents_api/routers/agents/patch_agent.py b/agents-api/agents_api/routers/agents/patch_agent.py new file mode 100644 index 000000000..3fe0d1946 --- /dev/null +++ b/agents-api/agents_api/routers/agents/patch_agent.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter, Depends, HTTPException +from pydantic import UUID4 +from starlette.status import HTTP_404_NOT_FOUND, HTTP_200_OK + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.agent.patch_agent import patch_agent_query +from agents_api.common.exceptions.agents import AgentNotFoundError +from agents_api.autogen.openapi_model import PatchAgentRequest, ResourceUpdatedResponse + +router = APIRouter() + +@router.patch("/agents/{agent_id}", response_model=ResourceUpdatedResponse, status_code=HTTP_200_OK, tags=["agents"]) +async def patch_agent( + agent_id: UUID4, + request: PatchAgentRequest, + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], +) -> ResourceUpdatedResponse: + try: + updated_agent = patch_agent_query( + agent_id=agent_id, + developer_id=x_developer_id, + default_settings=request.default_settings, + name=request.name, + about=request.about, + model=request.model, + metadata=request.metadata, + instructions=request.instructions, + ) + return ResourceUpdatedResponse(**updated_agent) + except AgentNotFoundError as e: + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail=str(e) + ) diff --git a/agents-api/agents_api/routers/agents/routers.py b/agents-api/agents_api/routers/agents/routers.py deleted file mode 100644 index ba973656f..000000000 --- a/agents-api/agents_api/routers/agents/routers.py +++ /dev/null @@ -1,588 +0,0 @@ -import json -from json import JSONDecodeError -from typing import Annotated -from uuid import uuid4, UUID - -from agents_api.autogen.openapi_model import ContentItem -from agents_api.model_registry import validate_configuration -from fastapi import APIRouter, HTTPException, status, Depends -import pandas as pd -from pycozo.client import QueryException -from pydantic import UUID4, BaseModel -from starlette.status import HTTP_201_CREATED, HTTP_202_ACCEPTED - -from agents_api.clients.embed import embed -from agents_api.common.utils.datetime import utcnow -from agents_api.common.exceptions.agents import ( - AgentNotFoundError, - AgentToolNotFoundError, - AgentDocNotFoundError, -) -from agents_api.models.agent.create_agent import create_agent_query -from agents_api.models.agent.list_agents import list_agents_query -from agents_api.models.agent.delete_agent import delete_agent_query -from agents_api.models.agent.update_agent import update_agent_query -from agents_api.models.agent.patch_agent import patch_agent_query -from agents_api.models.agent.get_agent import get_agent_query -from agents_api.models.agent.create_tools import create_tools_query -from agents_api.models.agent.update_tool import update_tool_by_id_query -from agents_api.models.agent.patch_tool import patch_tool_by_id_query - -from agents_api.models.docs.create_docs import ( - create_docs_query, -) -from agents_api.models.docs.list_docs import ( - list_docs_snippets_by_owner_query, - ensure_owner_exists_query, -) -from agents_api.models.docs.delete_docs import ( - delete_docs_by_id_query, -) -from agents_api.models.docs.get_docs import ( - get_docs_snippets_by_id_query, -) -from agents_api.models.tools.create_tools import create_function_query -from agents_api.models.tools.list_tools import list_functions_by_agent_query -from agents_api.models.tools.get_tools import get_function_by_id_query -from agents_api.models.tools.delete_tools import delete_function_by_id_query -from agents_api.dependencies.developer_id import get_developer_id -from agents_api.autogen.openapi_model import ( - Agent, - CreateAgentRequest, - UpdateAgentRequest, - ResourceCreatedResponse, - ResourceDeletedResponse, - ResourceUpdatedResponse, - AgentDefaultSettings, - CreateDoc, - Doc, - CreateToolRequest, - Tool, - FunctionDef, - UpdateToolRequest, - PatchToolRequest, - PatchAgentRequest, -) -from ...clients.temporal import run_embed_docs_task - - -class AgentList(BaseModel): - items: list[Agent] - - -class DocsList(BaseModel): - items: list[Doc] - - -class ToolList(BaseModel): - items: list[Tool] - - -router = APIRouter() -snippet_embed_instruction = "Encode this passage for retrieval: " -function_embed_instruction = "Transform this tool description for retrieval: " - - -@router.delete("/agents/{agent_id}", status_code=HTTP_202_ACCEPTED, tags=["agents"]) -async def delete_agent( - agent_id: UUID4, x_developer_id: Annotated[UUID4, Depends(get_developer_id)] -) -> ResourceDeletedResponse: - # TODO: maybe add better 404 handling, than catching QueryException - try: - delete_agent_query(x_developer_id, agent_id) - except QueryException as e: - if e.code == "transact::assertion_failure": - raise AgentNotFoundError(x_developer_id, agent_id) - - raise - - return ResourceDeletedResponse(id=agent_id, deleted_at=utcnow()) - - -@router.put("/agents/{agent_id}", tags=["agents"]) -async def update_agent( - agent_id: UUID4, - request: UpdateAgentRequest, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> ResourceUpdatedResponse: - if isinstance(request.instructions, str): - request.instructions = [request.instructions] - - model = request.model or "julep-ai/samantha-1-turbo" - - validate_configuration(model) - try: - resp = update_agent_query( - agent_id=agent_id, - developer_id=x_developer_id, - default_settings=( - request.default_settings or AgentDefaultSettings() - ).model_dump(), - name=request.name, - about=request.about, - model=model, - metadata=request.metadata, - instructions=request.instructions or [], - ) - - updated_agent_id = resp["agent_id"][0] - res = ResourceUpdatedResponse( - id=updated_agent_id, - updated_at=resp["updated_at"][0], - ) - - return res - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Agent not found", - ) - except QueryException as e: - if e.code in ("transact::assertion_failure", "eval::assert_some_failure"): - raise AgentNotFoundError(x_developer_id, agent_id) - - raise - - -@router.patch("/agents/{agent_id}", tags=["agents"]) -async def patch_agent( - agent_id: UUID4, - request: PatchAgentRequest, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> ResourceUpdatedResponse: - if isinstance(request.instructions, str): - request.instructions = [request.instructions] - - try: - resp = patch_agent_query( - agent_id=agent_id, - developer_id=x_developer_id, - default_settings=( - request.default_settings or AgentDefaultSettings() - ).model_dump(), - name=request.name, - about=request.about, - model=request.model or "julep-ai/samantha-1-turbo", - metadata=request.metadata, - instructions=request.instructions, - ) - - updated_agent_id = resp["agent_id"][0] - res = ResourceUpdatedResponse( - id=updated_agent_id, - updated_at=resp["updated_at"][0], - ) - - return res - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Agent not found", - ) - except QueryException as e: - if e.code == "transact::assertion_failure": - raise AgentNotFoundError(x_developer_id, agent_id) - - raise - - -@router.get("/agents/{agent_id}", tags=["agents"]) -async def get_agent_details( - agent_id: UUID4, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> Agent: - try: - resp = [ - row.to_dict() - for _, row in get_agent_query( - developer_id=x_developer_id, - agent_id=agent_id, - ).iterrows() - ][0] - - return Agent(**resp) - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Agent not found", - ) - except QueryException as e: - if e.code == "transact::assertion_failure": - raise AgentNotFoundError(x_developer_id, agent_id) - - raise - - -@router.post("/agents", status_code=HTTP_201_CREATED, tags=["agents"]) -async def create_agent( - request: CreateAgentRequest, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> ResourceCreatedResponse: - if isinstance(request.instructions, str): - request.instructions = [request.instructions] - - validate_configuration(request.model) - resp = create_agent_query( - agent_id=uuid4(), - developer_id=x_developer_id, - name=request.name, - about=request.about, - instructions=request.instructions or [], - model=request.model, - default_settings=( - request.default_settings or AgentDefaultSettings() - ).model_dump(), - metadata=request.metadata or {}, - ) - new_agent_id = UUID(resp["agent_id"][0], version=4) - docs = request.docs or [] - job_ids = [uuid4()] * len(docs) - for job_id, doc in zip(job_ids, docs): - content = [ - (c.model_dump() if isinstance(c, ContentItem) else c) - for c in ([doc.content] if isinstance(doc.content, str) else doc.content) - ] - docs_resp = create_docs_query( - owner_type="agent", - owner_id=new_agent_id, - id=uuid4(), - title=doc.title, - content=content, - metadata=doc.metadata or {}, - ) - - doc_id = docs_resp["doc_id"][0] - - await run_embed_docs_task( - doc_id=doc_id, title=doc.title, content=content, job_id=job_id - ) - - if request.tools: - functions = [t.function for t in request.tools] - # embeddings = await embed( - # [ - # function_embed_instruction - # + f"{function.name}, {function.description}, " - # + "required_params:" - # + function.parameters.model_dump_json() - # for function in functions - # ] - # ) - create_tools_query( - new_agent_id, - functions, - [[0.0] * 768] * len(functions), - ) - - return ResourceCreatedResponse( - id=new_agent_id, - created_at=resp["created_at"][0], - jobs=set(job_ids), - ) - - -@router.get("/agents", tags=["agents"]) -async def list_agents( - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], - limit: int = 100, - offset: int = 0, - metadata_filter: str = "{}", -) -> AgentList: - try: - metadata_filter = json.loads(metadata_filter) - except JSONDecodeError: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="metadata_filter is not a valid JSON", - ) - - return AgentList( - items=[ - Agent(**row.to_dict()) - for _, row in list_agents_query( - developer_id=x_developer_id, - limit=limit, - offset=offset, - metadata_filter=metadata_filter, - ).iterrows() - ] - ) - - -@router.post("/agents/{agent_id}/docs", tags=["agents"]) -async def create_docs(agent_id: UUID4, request: CreateDoc) -> ResourceCreatedResponse: - doc_id = uuid4() - content = [ - (c.model_dump() if isinstance(c, ContentItem) else c) - for c in ( - [request.content] if isinstance(request.content, str) else request.content - ) - ] - - resp: pd.DataFrame = create_docs_query( - owner_type="agent", - owner_id=agent_id, - id=doc_id, - title=request.title, - content=content, - metadata=request.metadata or {}, - ) - - job_id = uuid4() - doc_id = resp["doc_id"][0] - res = ResourceCreatedResponse( - id=doc_id, - created_at=resp["created_at"][0], - jobs={job_id}, - ) - - await run_embed_docs_task( - doc_id=doc_id, title=request.title, content=content, job_id=job_id - ) - - return res - - -@router.get("/agents/{agent_id}/docs", tags=["agents"]) -async def list_docs( - agent_id: UUID4, limit: int = 100, offset: int = 0, metadata_filter: str = "{}" -) -> DocsList: - try: - metadata_filter = json.loads(metadata_filter) - except JSONDecodeError: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="metadata_filter is not a valid JSON", - ) - - resp = list_docs_snippets_by_owner_query( - owner_type="agent", - owner_id=agent_id, - metadata_filter=metadata_filter, - ) - - return DocsList( - items=[ - Doc( - created_at=row["created_at"], - id=row["doc_id"], - title=row["title"], - content=row["snippet"], - metadata=row.get("metadata"), - ) - for _, row in resp.iterrows() - ] - ) - - -@router.delete("/agents/{agent_id}/docs/{doc_id}", tags=["agents"]) -async def delete_docs(agent_id: UUID4, doc_id: UUID4) -> ResourceDeletedResponse: - resp = get_docs_snippets_by_id_query( - owner_type="agent", - doc_id=doc_id, - ) - - if not resp.size: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Docs not found", - ) - - try: - delete_docs_by_id_query( - owner_type="agent", - owner_id=agent_id, - doc_id=doc_id, - ) - - except QueryException as e: - if e.code == "transact::assertion_failure": - raise AgentDocNotFoundError(agent_id, doc_id) - - raise - - return ResourceDeletedResponse(id=doc_id, deleted_at=utcnow()) - - -@router.post("/agents/{agent_id}/tools", tags=["agents"]) -async def create_tool( - agent_id: UUID4, request: CreateToolRequest -) -> ResourceCreatedResponse: - resp = create_function_query( - agent_id=agent_id, - id=uuid4(), - function=request.function, - ) - - tool_id = resp["tool_id"][0] - res = ResourceCreatedResponse( - id=tool_id, - created_at=resp["created_at"][0], - ) - - # embeddings = await embed( - # [ - # function_embed_instruction - # + request.function.description - # + "\nParameters: " - # + json.dumps(request.function.parameters.model_dump()) - # ] - # ) - - # embed_functions_query( - # agent_id=agent_id, - # tool_ids=[tool_id], - # embeddings=embeddings, - # ) - - return res - - -@router.get("/agents/{agent_id}/tools", tags=["agents"]) -async def list_tools(agent_id: UUID4, limit: int = 100, offset: int = 0) -> ToolList: - if not len(list(ensure_owner_exists_query("agent", agent_id).iterrows())): - raise AgentNotFoundError("", agent_id) - - resp = list_functions_by_agent_query( - agent_id=agent_id, - limit=limit, - offset=offset, - ) - - return ToolList( - items=[ - Tool( - type="function", - function=FunctionDef( - description=row.get("description"), - name=row["name"], - parameters=row["parameters"], - ), - id=row["tool_id"], - ) - for _, row in resp.iterrows() - ] - ) - - -@router.delete("/agents/{agent_id}/tools/{tool_id}", tags=["agents"]) -async def delete_tool(agent_id: UUID4, tool_id: UUID4) -> ResourceDeletedResponse: - resp = get_function_by_id_query( - agent_id=agent_id, - tool_id=tool_id, - ) - - if not resp.size: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Tool not found", - ) - - try: - delete_function_by_id_query( - agent_id=agent_id, - tool_id=tool_id, - ) - - except QueryException as e: - if e.code == "transact::assertion_failure": - raise AgentToolNotFoundError(agent_id, tool_id) - - raise - - return ResourceDeletedResponse(id=tool_id, deleted_at=utcnow()) - - -@router.put("/agents/{agent_id}/tools/{tool_id}", tags=["agents"]) -async def update_tool( - agent_id: UUID4, tool_id: UUID4, request: UpdateToolRequest -) -> ResourceUpdatedResponse: - embeddings = await embed( - [ - function_embed_instruction - + (request.function.description or "") - + "\nParameters: " - + json.dumps(request.function.parameters.model_dump()) - ], - join_inputs=True, - ) - - try: - resp = [ - row.to_dict() - for _, row in update_tool_by_id_query( - agent_id=agent_id, - tool_id=tool_id, - function=request.function, - embedding=embeddings[0] if embeddings else [], - ).iterrows() - ][0] - - return ResourceUpdatedResponse( - id=resp["tool_id"], - updated_at=resp["updated_at"], - ) - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Agent or tool not found", - ) - except QueryException as e: - if e.code == "transact::assertion_failure": - raise AgentToolNotFoundError(agent_id, tool_id) - - raise - - -@router.patch("/agents/{agent_id}/tools/{tool_id}", tags=["agents"]) -async def patch_tool( - agent_id: UUID4, tool_id: UUID4, request: PatchToolRequest -) -> ResourceUpdatedResponse: - parameters = ( - request.function.parameters.model_dump() if request.function.parameters else {} - ) - - embeddings = await embed( - [ - function_embed_instruction - + (request.function.description or "") - + "\nParameters: " - + json.dumps(parameters) - ], - join_inputs=True, - ) - - try: - resp = [ - row.to_dict() - for _, row in patch_tool_by_id_query( - agent_id=agent_id, - tool_id=tool_id, - function=request.function, - embedding=embeddings[0] if embeddings else [], - ).iterrows() - ][0] - - return ResourceUpdatedResponse( - id=resp["tool_id"], - updated_at=resp["updated_at"], - ) - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Agent or tool not found", - ) - except QueryException as e: - if e.code == "transact::assertion_failure": - raise AgentToolNotFoundError(agent_id, tool_id) - - raise - - -@router.delete("/agents/{agent_id}/memories/{memory_id}", tags=["agents"]) -async def delete_memories(agent_id: UUID4, memory_id: UUID4) -> ResourceDeletedResponse: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - ) - - return ResourceDeletedResponse(id=memory_id, deleted_at=utcnow()) diff --git a/agents-api/agents_api/routers/agents/update_agent.py b/agents-api/agents_api/routers/agents/update_agent.py new file mode 100644 index 000000000..9dde0939a --- /dev/null +++ b/agents-api/agents_api/routers/agents/update_agent.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter, Depends, HTTPException +from pydantic import UUID4 +from starlette.status import HTTP_404_NOT_FOUND, HTTP_200_OK + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.agent.update_agent import update_agent_query +from agents_api.common.exceptions.agents import AgentNotFoundError +from agents_api.autogen.openapi_model import UpdateAgentRequest, ResourceUpdatedResponse + +router = APIRouter() + +@router.put("/agents/{agent_id}", response_model=ResourceUpdatedResponse, status_code=HTTP_200_OK, tags=["agents"]) +async def update_agent( + agent_id: UUID4, + request: UpdateAgentRequest, + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], +) -> ResourceUpdatedResponse: + try: + updated_agent = update_agent_query( + agent_id=agent_id, + developer_id=x_developer_id, + name=request.name, + about=request.about, + model=request.model, + default_settings=request.default_settings, + metadata=request.metadata, + instructions=request.instructions, + ) + return ResourceUpdatedResponse(**updated_agent) + except AgentNotFoundError as e: + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail=str(e) + ) diff --git a/agents-api/agents_api/routers/sessions/__init__.py b/agents-api/agents_api/routers/sessions/__init__.py index fa07d0740..af9233c56 100644 --- a/agents-api/agents_api/routers/sessions/__init__.py +++ b/agents-api/agents_api/routers/sessions/__init__.py @@ -1 +1,3 @@ -from .routers import router # noqa: F401 +from fastapi import APIRouter + +router = APIRouter() diff --git a/agents-api/agents_api/routers/sessions/create_session.py b/agents-api/agents_api/routers/sessions/create_session.py new file mode 100644 index 000000000..76ade50da --- /dev/null +++ b/agents-api/agents_api/routers/sessions/create_session.py @@ -0,0 +1,33 @@ +from fastapi import APIRouter, HTTPException, status, Depends +from pydantic import BaseModel, UUID4 +from typing import Annotated +from uuid import uuid4 + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.session.create_session import create_session_query +from agents_api.autogen.openapi_model import CreateSessionRequest, ResourceCreatedResponse + +router = APIRouter() + +@router.post("/sessions", status_code=HTTP_201_CREATED, tags=["sessions"]) +async def create_session( + request: CreateSessionRequest, + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], +) -> ResourceCreatedResponse: + session_id = uuid4() + resp: pd.DataFrame = create_session_query( + session_id=session_id, + developer_id=x_developer_id, + agent_id=request.agent_id, + user_id=request.user_id, + situation=request.situation, + metadata=request.metadata or {}, + render_templates=request.render_templates or False, + token_budget=request.token_budget, + context_overflow=request.context_overflow, + ) + + return ResourceCreatedResponse( + id=resp["session_id"][0], + created_at=resp["created_at"][0], + ) diff --git a/agents-api/agents_api/routers/sessions/delete_session.py b/agents-api/agents_api/routers/sessions/delete_session.py new file mode 100644 index 000000000..8ba77a986 --- /dev/null +++ b/agents-api/agents_api/routers/sessions/delete_session.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter, Depends, HTTPException +from pydantic import UUID4 +from starlette.status import HTTP_202_ACCEPTED + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.session.delete_session import delete_session_query +from agents_api.common.exceptions.sessions import SessionNotFoundError +from agents_api.autogen.openapi_model import ResourceDeletedResponse +from agents_api.common.utils.datetime import utcnow + +router = APIRouter() + +@router.delete("/sessions/{session_id}", status_code=HTTP_202_ACCEPTED, tags=["sessions"]) +async def delete_session( + session_id: UUID4, x_developer_id: Annotated[UUID4, Depends(get_developer_id)] +) -> ResourceDeletedResponse: + try: + delete_session_query(x_developer_id, session_id) + except SessionNotFoundError as e: + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail=str(e) + ) + + return ResourceDeletedResponse(id=session_id, deleted_at=utcnow()) diff --git a/agents-api/agents_api/routers/sessions/get_session.py b/agents-api/agents_api/routers/sessions/get_session.py new file mode 100644 index 000000000..49279e0d6 --- /dev/null +++ b/agents-api/agents_api/routers/sessions/get_session.py @@ -0,0 +1,27 @@ +from fastapi import APIRouter, Depends, HTTPException +from pydantic import UUID4 +from typing import Annotated + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.session.get_session import get_session_query +from agents_api.autogen.openapi_model import Session + +router = APIRouter() + +@router.get("/sessions/{session_id}", tags=["sessions"]) +async def get_session( + session_id: UUID4, x_developer_id: Annotated[UUID4, Depends(get_developer_id)] +) -> Session: + try: + res = [ + row.to_dict() + for _, row in get_session_query( + developer_id=x_developer_id, session_id=session_id + ).iterrows() + ][0] + return Session(**res) + except (IndexError, KeyError): + raise HTTPException( + status_code=404, + detail="Session not found", + ) diff --git a/agents-api/agents_api/routers/sessions/list_sessions.py b/agents-api/agents_api/routers/sessions/list_sessions.py new file mode 100644 index 000000000..ec125116f --- /dev/null +++ b/agents-api/agents_api/routers/sessions/list_sessions.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter, Depends +from typing import Annotated +from pydantic import UUID4 +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.session.list_sessions import list_sessions_query +from agents_api.autogen.openapi_model import Session + +router = APIRouter() + +class SessionList(BaseModel): + items: list[Session] + +@router.get("/sessions", tags=["sessions"]) +async def list_sessions( + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], + limit: int = 100, + offset: int = 0, + metadata_filter: str = "{}", +) -> SessionList: + try: + metadata_filter = json.loads(metadata_filter) + except JSONDecodeError: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="metadata_filter is not a valid JSON", + ) + + query_results = list_sessions_query( + x_developer_id, limit, offset, metadata_filter=metadata_filter + ) + + return SessionList( + items=[Session(**row.to_dict()) for _, row in query_results.iterrows()] + ) diff --git a/agents-api/agents_api/routers/sessions/patch_session.py b/agents-api/agents_api/routers/sessions/patch_session.py new file mode 100644 index 000000000..8f6ee7d21 --- /dev/null +++ b/agents-api/agents_api/routers/sessions/patch_session.py @@ -0,0 +1,41 @@ +from fastapi import APIRouter, Depends, HTTPException +from pydantic import UUID4 +from starlette.status import HTTP_404_NOT_FOUND + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.session.patch_session import patch_session_query +from agents_api.autogen.openapi_model import PatchSessionRequest, ResourceUpdatedResponse +from agents_api.common.exceptions.sessions import SessionNotFoundError + +router = APIRouter() + +@router.patch("/sessions/{session_id}", tags=["sessions"]) +async def patch_session( + session_id: UUID4, + request: PatchSessionRequest, + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], +) -> ResourceUpdatedResponse: + try: + resp = patch_session_query( + session_id=session_id, + developer_id=x_developer_id, + situation=request.situation, + metadata=request.metadata, + token_budget=request.token_budget, + context_overflow=request.context_overflow, + ) + + return ResourceUpdatedResponse( + id=resp["session_id"][0], + updated_at=resp["updated_at"][0][0], + ) + except (IndexError, KeyError): + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail="Session not found", + ) + except SessionNotFoundError as e: + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail=str(e), + ) diff --git a/agents-api/agents_api/routers/sessions/routers.py b/agents-api/agents_api/routers/sessions/routers.py deleted file mode 100644 index ad87d3aaa..000000000 --- a/agents-api/agents_api/routers/sessions/routers.py +++ /dev/null @@ -1,320 +0,0 @@ -import json -from json import JSONDecodeError -from typing import Annotated -from uuid import uuid4 - - -from fastapi import APIRouter, HTTPException, status, BackgroundTasks, Depends -import pandas as pd -from pydantic import BaseModel -from pydantic import UUID4 -from starlette.status import HTTP_201_CREATED, HTTP_202_ACCEPTED - -from pycozo.client import QueryException -from agents_api.common.exceptions.sessions import SessionNotFoundError -from agents_api.common.utils.datetime import utcnow -from agents_api.models.session.get_session import get_session_query -from agents_api.models.session.create_session import create_session_query -from agents_api.models.session.list_sessions import list_sessions_query -from agents_api.models.session.delete_session import delete_session_query -from agents_api.dependencies.developer_id import get_developer_id -from agents_api.models.entry.get_entries import get_entries_query -from agents_api.models.entry.delete_entries import delete_entries_query -from agents_api.models.session.update_session import update_session_query -from agents_api.models.session.patch_session import patch_session_query -from agents_api.autogen.openapi_model import ( - CreateSessionRequest, - UpdateSessionRequest, - Session, - ChatInput, - Suggestion, - ChatMLMessage, - ResourceCreatedResponse, - ResourceDeletedResponse, - ResourceUpdatedResponse, - ChatResponse, - FinishReason, - CompletionUsage, - Stop, - PatchSessionRequest, -) -from .protocol import Settings -from .session import RecursiveSummarizationSession - - -router = APIRouter() - - -class SessionList(BaseModel): - items: list[Session] - - -class SuggestionList(BaseModel): - items: list[Suggestion] - - -class ChatMLMessageList(BaseModel): - items: list[ChatMLMessage] - - -@router.get("/sessions/{session_id}", tags=["sessions"]) -async def get_session( - session_id: UUID4, x_developer_id: Annotated[UUID4, Depends(get_developer_id)] -) -> Session: - try: - res = [ - row.to_dict() - for _, row in get_session_query( - developer_id=x_developer_id, session_id=session_id - ).iterrows() - ][0] - return Session(**res) - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Session not found", - ) - - -@router.post("/sessions", status_code=HTTP_201_CREATED, tags=["sessions"]) -async def create_session( - request: CreateSessionRequest, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> ResourceCreatedResponse: - session_id = uuid4() - resp: pd.DataFrame = create_session_query( - session_id=session_id, - developer_id=x_developer_id, - agent_id=request.agent_id, - user_id=request.user_id, - situation=request.situation, - metadata=request.metadata or {}, - render_templates=request.render_templates or False, - token_budget=request.token_budget, - context_overflow=request.context_overflow, - ) - - return ResourceCreatedResponse( - id=resp["session_id"][0], - created_at=resp["created_at"][0], - ) - - -@router.get("/sessions", tags=["sessions"]) -async def list_sessions( - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], - limit: int = 100, - offset: int = 0, - metadata_filter: str = "{}", -) -> SessionList: - try: - metadata_filter = json.loads(metadata_filter) - except JSONDecodeError: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="metadata_filter is not a valid JSON", - ) - - query_results = list_sessions_query( - x_developer_id, limit, offset, metadata_filter=metadata_filter - ) - - return SessionList( - items=[Session(**row.to_dict()) for _, row in query_results.iterrows()] - ) - - -@router.delete( - "/sessions/{session_id}", status_code=HTTP_202_ACCEPTED, tags=["sessions"] -) -async def delete_session( - session_id: UUID4, x_developer_id: Annotated[UUID4, Depends(get_developer_id)] -) -> ResourceDeletedResponse: - try: - delete_session_query(x_developer_id, session_id) - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Session not found", - ) - - return ResourceDeletedResponse(id=session_id, deleted_at=utcnow()) - - -@router.put("/sessions/{session_id}", tags=["sessions"]) -async def update_session( - session_id: UUID4, - request: UpdateSessionRequest, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> ResourceUpdatedResponse: - try: - resp = update_session_query( - session_id=session_id, - developer_id=x_developer_id, - situation=request.situation, - metadata=request.metadata, - token_budget=request.token_budget, - context_overflow=request.context_overflow, - ) - - return ResourceUpdatedResponse( - id=resp["session_id"][0], - updated_at=resp["updated_at"][0][0], - ) - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Session not found", - ) - except QueryException as e: - # the code is not so informative now, but it may be a good solution in the future - if e.code in ("transact::assertion_failure", "eval::assert_some_failure"): - raise SessionNotFoundError(x_developer_id, session_id) - - raise - - -@router.patch("/sessions/{session_id}", tags=["sessions"]) -async def patch_session( - session_id: UUID4, - request: PatchSessionRequest, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> ResourceUpdatedResponse: - try: - resp = patch_session_query( - session_id=session_id, - developer_id=x_developer_id, - situation=request.situation, - metadata=request.metadata, - token_budget=request.token_budget, - context_overflow=request.context_overflow, - ) - - return ResourceUpdatedResponse( - id=resp["session_id"][0], - updated_at=resp["updated_at"][0][0], - ) - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Session not found", - ) - except QueryException as e: - # the code is not so informative now, but it may be a good solution in the future - if e.code in ("transact::assertion_failure", "eval::assert_some_failure"): - raise SessionNotFoundError(x_developer_id, session_id) - - raise - - -@router.get("/sessions/{session_id}/suggestions", tags=["sessions"]) -async def get_suggestions( - session_id: UUID4, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], - limit: int = 100, - offset: int = 0, -) -> SuggestionList: - return SuggestionList(items=[]) - - -@router.get("/sessions/{session_id}/history", tags=["sessions"]) -async def get_history( - session_id: UUID4, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], - limit: int = 100, - offset: int = 0, -) -> ChatMLMessageList: - try: - items = [] - for _, row in get_entries_query( - session_id=session_id, limit=limit, offset=offset - ).iterrows(): - row_dict = row.to_dict() - row_dict["id"] = row_dict["entry_id"] - items.append(ChatMLMessage(**row_dict)) - - return ChatMLMessageList( - items=items, - ) - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Session not found", - ) - - -@router.delete( - "/sessions/{session_id}/history", status_code=HTTP_202_ACCEPTED, tags=["sessions"] -) -async def delete_history( - session_id: UUID4, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> ResourceDeletedResponse: - try: - delete_entries_query(session_id=session_id) - except QueryException as e: - if e.code == "transact::assertion_failure": - raise SessionNotFoundError( - developer_id=x_developer_id, session_id=session_id - ) - - raise - - return ResourceDeletedResponse(id=session_id, deleted_at=utcnow()) - - -@router.post("/sessions/{session_id}/chat", tags=["sessions"]) -async def session_chat( - session_id: UUID4, - request: ChatInput, - background_tasks: BackgroundTasks, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> ChatResponse: - session = RecursiveSummarizationSession( - developer_id=x_developer_id, - session_id=session_id, - ) - - stop = request.stop - if isinstance(request.stop, Stop): - stop = request.stop.root - - settings = Settings( - model="", - frequency_penalty=request.frequency_penalty, - length_penalty=request.length_penalty, - logit_bias=request.logit_bias, - max_tokens=request.max_tokens, - presence_penalty=request.presence_penalty, - repetition_penalty=request.repetition_penalty, - response_format=request.response_format, - seed=request.seed, - stop=stop, - stream=request.stream, - temperature=request.temperature, - top_p=request.top_p, - remember=request.remember, - recall=request.recall, - min_p=request.min_p, - preset=request.preset, - ) - response, new_entry, bg_task, doc_ids = await session.run( - request.messages, settings - ) - - jobs = None - if bg_task: - job_id = uuid4() - jobs = {job_id} - background_tasks.add_task(bg_task, session_id, job_id) - - resp = [ChatMLMessage(**new_entry.model_dump())] - - return ChatResponse( - id=uuid4(), - finish_reason=FinishReason[response.choices[0].finish_reason], - response=[resp], - usage=CompletionUsage(**response.usage.model_dump()), - jobs=jobs, - doc_ids=doc_ids, - ) diff --git a/agents-api/agents_api/routers/sessions/update_session.py b/agents-api/agents_api/routers/sessions/update_session.py new file mode 100644 index 000000000..921da89f1 --- /dev/null +++ b/agents-api/agents_api/routers/sessions/update_session.py @@ -0,0 +1,41 @@ +from fastapi import APIRouter, Depends, HTTPException +from pydantic import UUID4 +from starlette.status import HTTP_404_NOT_FOUND + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.session.update_session import update_session_query +from agents_api.autogen.openapi_model import UpdateSessionRequest, ResourceUpdatedResponse +from agents_api.common.exceptions.sessions import SessionNotFoundError + +router = APIRouter() + +@router.put("/sessions/{session_id}", tags=["sessions"]) +async def update_session( + session_id: UUID4, + request: UpdateSessionRequest, + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], +) -> ResourceUpdatedResponse: + try: + resp = update_session_query( + session_id=session_id, + developer_id=x_developer_id, + situation=request.situation, + metadata=request.metadata, + token_budget=request.token_budget, + context_overflow=request.context_overflow, + ) + + return ResourceUpdatedResponse( + id=resp["session_id"][0], + updated_at=resp["updated_at"][0][0], + ) + except (IndexError, KeyError): + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail="Session not found", + ) + except SessionNotFoundError as e: + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail=str(e), + ) diff --git a/agents-api/agents_api/routers/users/__init__.py b/agents-api/agents_api/routers/users/__init__.py index fa07d0740..af9233c56 100644 --- a/agents-api/agents_api/routers/users/__init__.py +++ b/agents-api/agents_api/routers/users/__init__.py @@ -1 +1,3 @@ -from .routers import router # noqa: F401 +from fastapi import APIRouter + +router = APIRouter() diff --git a/agents-api/agents_api/routers/users/create_user.py b/agents-api/agents_api/routers/users/create_user.py new file mode 100644 index 000000000..c32b4b5bb --- /dev/null +++ b/agents-api/agents_api/routers/users/create_user.py @@ -0,0 +1,28 @@ +from fastapi import APIRouter, HTTPException, status, Depends +from pydantic import BaseModel, UUID4 +from typing import Optional +from uuid import uuid4 + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.user.create_user import create_user_query +from agents_api.autogen.openapi_model import CreateUserRequest, ResourceCreatedResponse + +router = APIRouter() + +@router.post("/users", status_code=HTTP_201_CREATED, tags=["users"]) +async def create_user( + request: CreateUserRequest, + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], +) -> ResourceCreatedResponse: + user_id = uuid4() + created_user = create_user_query( + developer_id=x_developer_id, + user_id=user_id, + name=request.name, + about=request.about, + metadata=request.metadata + ) + return ResourceCreatedResponse( + id=str(user_id), + created_at=created_user["created_at"] + ) diff --git a/agents-api/agents_api/routers/users/delete_user.py b/agents-api/agents_api/routers/users/delete_user.py new file mode 100644 index 000000000..d64549573 --- /dev/null +++ b/agents-api/agents_api/routers/users/delete_user.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter, HTTPException, Depends +from pydantic import UUID4 +from starlette.status import HTTP_202_ACCEPTED + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.common.exceptions.users import UserNotFoundError +from agents_api.models.user.delete_user import delete_user_query +from agents_api.autogen.openapi_model import ResourceDeletedResponse +from agents_api.common.utils.datetime import utcnow + +router = APIRouter() + +@router.delete("/users/{user_id}", status_code=HTTP_202_ACCEPTED, tags=["users"]) +async def delete_user( + user_id: UUID4, x_developer_id: Annotated[UUID4, Depends(get_developer_id)] +) -> ResourceDeletedResponse: + try: + delete_user_query(x_developer_id, user_id) + except UserNotFoundError as e: + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail=str(e) + ) + + return ResourceDeletedResponse(id=user_id, deleted_at=utcnow()) diff --git a/agents-api/agents_api/routers/users/get_user_details.py b/agents-api/agents_api/routers/users/get_user_details.py new file mode 100644 index 000000000..5150bedc1 --- /dev/null +++ b/agents-api/agents_api/routers/users/get_user_details.py @@ -0,0 +1,37 @@ +from fastapi import APIRouter, HTTPException, Depends +from pydantic import UUID4 +from typing import Annotated + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.user.get_user import get_user_query +from agents_api.autogen.openapi_model import User +from agents_api.common.exceptions.users import UserNotFoundError + +router = APIRouter() + +@router.get("/users/{user_id}", tags=["users"]) +async def get_user_details( + user_id: UUID4, + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], +) -> User: + try: + resp = [ + row.to_dict() + for _, row in get_user_query( + developer_id=x_developer_id, + user_id=user_id, + ).iterrows() + ][0] + + return User(**resp) + except (IndexError, KeyError): + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found", + ) + except QueryException as e: + # the code is not so informative now, but it may be a good solution in the future + if e.code == "transact::assertion_failure": + raise UserNotFoundError(x_developer_id, user_id) + + raise diff --git a/agents-api/agents_api/routers/users/list_users.py b/agents-api/agents_api/routers/users/list_users.py new file mode 100644 index 000000000..d1e1679e8 --- /dev/null +++ b/agents-api/agents_api/routers/users/list_users.py @@ -0,0 +1,36 @@ +from fastapi import APIRouter, HTTPException, Depends +from pydantic import UUID4 +from typing import Annotated, List + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.models.user.list_users import list_users_query +from agents_api.autogen.openapi_model import User + +router = APIRouter() + +@router.get("/users", tags=["users"]) +async def list_users( + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], + limit: int = 100, + offset: int = 0, + metadata_filter: str = "{}", +) -> List[User]: + try: + metadata_filter = json.loads(metadata_filter) + except JSONDecodeError: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="metadata_filter is not a valid JSON", + ) + + users = [ + User(**row.to_dict()) + for _, row in list_users_query( + developer_id=x_developer_id, + limit=limit, + offset=offset, + metadata_filter=metadata_filter, + ).iterrows() + ] + + return users diff --git a/agents-api/agents_api/routers/users/patch_user.py b/agents-api/agents_api/routers/users/patch_user.py new file mode 100644 index 000000000..097126266 --- /dev/null +++ b/agents-api/agents_api/routers/users/patch_user.py @@ -0,0 +1,40 @@ +from fastapi import APIRouter, HTTPException, Depends +from pydantic import UUID4 +from starlette.status import HTTP_404_NOT_FOUND + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.common.exceptions.users import UserNotFoundError +from agents_api.models.user.patch_user import patch_user_query +from agents_api.autogen.openapi_model import PatchUserRequest, ResourceUpdatedResponse + +router = APIRouter() + +@router.patch("/users/{user_id}", tags=["users"]) +async def patch_user( + user_id: UUID4, + request: PatchUserRequest, + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], +) -> ResourceUpdatedResponse: + try: + resp = patch_user_query( + developer_id=x_developer_id, + user_id=user_id, + name=request.name, + about=request.about, + metadata=request.metadata, + ) + + return ResourceUpdatedResponse( + id=resp["user_id"][0], + updated_at=resp["updated_at"][0], + ) + except (IndexError, KeyError): + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail="User not found", + ) + except UserNotFoundError as e: + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail=str(e) + ) diff --git a/agents-api/agents_api/routers/users/routers.py b/agents-api/agents_api/routers/users/routers.py deleted file mode 100644 index a90dde8de..000000000 --- a/agents-api/agents_api/routers/users/routers.py +++ /dev/null @@ -1,340 +0,0 @@ -import json -from json import JSONDecodeError -from typing import Annotated -from uuid import uuid4, UUID - -from agents_api.autogen.openapi_model import ContentItem -from fastapi import APIRouter, HTTPException, status, Depends -import pandas as pd -from pycozo.client import QueryException -from pydantic import UUID4, BaseModel -from starlette.status import HTTP_201_CREATED, HTTP_202_ACCEPTED - -from agents_api.clients.cozo import client -from agents_api.common.utils.datetime import utcnow -from agents_api.common.exceptions.users import UserNotFoundError, UserDocNotFoundError -from agents_api.models.user.create_user import create_user_query -from agents_api.models.user.list_users import list_users_query -from agents_api.models.user.update_user import update_user_query -from agents_api.models.user.patch_user import patch_user_query -from agents_api.models.user.get_user import get_user_query -from agents_api.models.docs.create_docs import ( - create_docs_query, -) -from agents_api.models.docs.list_docs import ( - list_docs_snippets_by_owner_query, - ensure_owner_exists_query, -) -from agents_api.models.docs.delete_docs import ( - delete_docs_by_id_query, -) -from agents_api.models.docs.get_docs import ( - get_docs_snippets_by_id_query, -) -from agents_api.dependencies.developer_id import get_developer_id -from agents_api.autogen.openapi_model import ( - User, - CreateUserRequest, - UpdateUserRequest, - ResourceCreatedResponse, - ResourceDeletedResponse, - ResourceUpdatedResponse, - CreateDoc, - Doc, - PatchUserRequest, -) -from ...clients.temporal import run_embed_docs_task - - -class UserList(BaseModel): - items: list[User] - - -class DocsList(BaseModel): - items: list[Doc] - - -router = APIRouter() -snippet_embed_instruction = "Encode this passage for retrieval: " - - -@router.delete("/users/{user_id}", status_code=HTTP_202_ACCEPTED, tags=["users"]) -async def delete_user( - user_id: UUID4, x_developer_id: Annotated[UUID4, Depends(get_developer_id)] -) -> ResourceDeletedResponse: - try: - client.rm( - "users", - { - "user_id": str(user_id), - "developer_id": str(x_developer_id), - }, - ) - except QueryException as e: - # the code is not so informative now, but it may be a good solution in the future - if e.code == "transact::assertion_failure": - raise UserNotFoundError(x_developer_id, user_id) - - raise - - return ResourceDeletedResponse(id=user_id, deleted_at=utcnow()) - - -@router.put("/users/{user_id}", tags=["users"]) -async def update_user( - user_id: UUID4, - request: UpdateUserRequest, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> ResourceUpdatedResponse: - try: - resp = update_user_query( - developer_id=x_developer_id, - user_id=user_id, - name=request.name, - about=request.about, - metadata=request.metadata, - ) - - return ResourceUpdatedResponse( - id=resp["user_id"][0], - updated_at=resp["updated_at"][0], - ) - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="User not found", - ) - except QueryException as e: - # the code is not so informative now, but it may be a good solution in the future - if e.code in ("transact::assertion_failure", "eval::assert_some_failure"): - raise UserNotFoundError(x_developer_id, user_id) - - raise - - -@router.patch("/users/{user_id}", tags=["users"]) -async def patch_user( - user_id: UUID4, - request: PatchUserRequest, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> ResourceUpdatedResponse: - try: - resp = patch_user_query( - developer_id=x_developer_id, - user_id=user_id, - name=request.name, - about=request.about, - metadata=request.metadata, - ) - - return ResourceUpdatedResponse( - id=resp["user_id"][0], - updated_at=resp["updated_at"][0], - ) - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="User not found", - ) - except QueryException as e: - # the code is not so informative now, but it may be a good solution in the future - if e.code == "transact::assertion_failure": - raise UserNotFoundError(x_developer_id, user_id) - - raise - - -@router.get("/users/{user_id}", tags=["users"]) -async def get_user_details( - user_id: UUID4, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> User: - try: - resp = [ - row.to_dict() - for _, row in get_user_query( - developer_id=x_developer_id, - user_id=user_id, - ).iterrows() - ][0] - - return User(**resp) - except (IndexError, KeyError): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="User not found", - ) - except QueryException as e: - # the code is not so informative now, but it may be a good solution in the future - if e.code == "transact::assertion_failure": - raise UserNotFoundError(x_developer_id, user_id) - - raise - - -@router.post("/users", status_code=HTTP_201_CREATED, tags=["users"]) -async def create_user( - request: CreateUserRequest, - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], -) -> ResourceCreatedResponse: - resp = create_user_query( - developer_id=x_developer_id, - user_id=uuid4(), - name=request.name, - about=request.about or "", - metadata=request.metadata or {}, - ) - - new_user_id = UUID(resp["user_id"][0], version=4) - docs = request.docs or [] - job_ids = [uuid4()] * len(docs) - for job_id, doc in zip(job_ids, docs): - content = [ - (c.model_dump() if isinstance(c, ContentItem) else c) - for c in ([doc.content] if isinstance(doc.content, str) else doc.content) - ] - docs_resp = create_docs_query( - owner_type="user", - owner_id=new_user_id, - id=uuid4(), - title=doc.title, - content=content, - metadata=doc.metadata or {}, - ) - - doc_id = docs_resp["doc_id"][0] - - await run_embed_docs_task( - doc_id=doc_id, title=doc.title, content=content, job_id=job_id - ) - - return ResourceCreatedResponse( - id=new_user_id, - created_at=resp["created_at"][0], - jobs=set(job_ids), - ) - - -@router.get("/users", tags=["users"]) -async def list_users( - x_developer_id: Annotated[UUID4, Depends(get_developer_id)], - limit: int = 100, - offset: int = 0, - metadata_filter: str = "{}", -) -> UserList: - try: - metadata_filter = json.loads(metadata_filter) - except JSONDecodeError: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="metadata_filter is not a valid JSON", - ) - - return UserList( - items=[ - User(**row.to_dict()) - for _, row in list_users_query( - developer_id=x_developer_id, - limit=limit, - offset=offset, - metadata_filter=metadata_filter, - ).iterrows() - ] - ) - - -@router.post("/users/{user_id}/docs", tags=["users"]) -async def create_docs(user_id: UUID4, request: CreateDoc) -> ResourceCreatedResponse: - doc_id = uuid4() - content = [ - (c.model_dump() if isinstance(c, ContentItem) else c) - for c in ( - [request.content] if isinstance(request.content, str) else request.content - ) - ] - resp: pd.DataFrame = create_docs_query( - owner_type="user", - owner_id=user_id, - id=doc_id, - title=request.title, - content=content, - metadata=request.metadata or {}, - ) - - job_id = uuid4() - doc_id = resp["doc_id"][0] - res = ResourceCreatedResponse( - id=doc_id, - created_at=resp["created_at"][0], - jobs={job_id}, - ) - - await run_embed_docs_task( - doc_id=doc_id, title=request.title, content=content, job_id=job_id - ) - - return res - - -@router.get("/users/{user_id}/docs", tags=["users"]) -async def list_docs( - user_id: UUID4, limit: int = 100, offset: int = 0, metadata_filter: str = "{}" -) -> DocsList: - try: - metadata_filter = json.loads(metadata_filter) - except JSONDecodeError: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="metadata_filter is not a valid JSON", - ) - - if not len(list(ensure_owner_exists_query("user", user_id).iterrows())): - raise UserNotFoundError("", user_id) - - resp = list_docs_snippets_by_owner_query( - owner_type="user", - owner_id=user_id, - metadata_filter=metadata_filter, - ) - - return DocsList( - items=[ - Doc( - id=row["doc_id"], - title=row["title"], - content=row["snippet"], - created_at=row["created_at"], - metadata=row.get("metadata"), - ) - for _, row in resp.iterrows() - ] - ) - - -@router.delete("/users/{user_id}/docs/{doc_id}", tags=["users"]) -async def delete_docs(user_id: UUID4, doc_id: UUID4) -> ResourceDeletedResponse: - resp = get_docs_snippets_by_id_query( - owner_type="user", - doc_id=doc_id, - ) - - if not resp.size: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Docs not found", - ) - - try: - delete_docs_by_id_query( - owner_type="user", - owner_id=user_id, - doc_id=doc_id, - ) - - except QueryException as e: - if e.code == "transact::assertion_failure": - raise UserDocNotFoundError(user_id, doc_id) - - raise - - return ResourceDeletedResponse(id=doc_id, deleted_at=utcnow()) diff --git a/agents-api/agents_api/routers/users/update_user.py b/agents-api/agents_api/routers/users/update_user.py new file mode 100644 index 000000000..0b4fe0926 --- /dev/null +++ b/agents-api/agents_api/routers/users/update_user.py @@ -0,0 +1,40 @@ +from fastapi import APIRouter, HTTPException, Depends +from pydantic import UUID4 +from starlette.status import HTTP_404_NOT_FOUND + +from agents_api.dependencies.developer_id import get_developer_id +from agents_api.common.exceptions.users import UserNotFoundError +from agents_api.models.user.update_user import update_user_query +from agents_api.autogen.openapi_model import UpdateUserRequest, ResourceUpdatedResponse + +router = APIRouter() + +@router.put("/users/{user_id}", tags=["users"]) +async def update_user( + user_id: UUID4, + request: UpdateUserRequest, + x_developer_id: Annotated[UUID4, Depends(get_developer_id)], +) -> ResourceUpdatedResponse: + try: + resp = update_user_query( + developer_id=x_developer_id, + user_id=user_id, + name=request.name, + about=request.about, + metadata=request.metadata, + ) + + return ResourceUpdatedResponse( + id=resp["user_id"][0], + updated_at=resp["updated_at"][0], + ) + except (IndexError, KeyError): + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail="User not found", + ) + except UserNotFoundError as e: + raise HTTPException( + status_code=HTTP_404_NOT_FOUND, + detail=str(e) + )