feat: add new identity agent and blocks routes (#2934)

This commit is contained in:
cthomas
2025-09-15 20:49:35 -07:00
committed by GitHub
parent 401293c48b
commit f439457f72
3 changed files with 155 additions and 0 deletions

View File

@@ -955,6 +955,18 @@ paths:
- identities
- properties
x-fern-sdk-method-name: upsert
/v1/identities/{identity_id}/agents:
get:
x-fern-sdk-group-name:
- identities
- agents
x-fern-sdk-method-name: list
/v1/identities/{identity_id}/blocks:
get:
x-fern-sdk-group-name:
- identities
- blocks
x-fern-sdk-method-name: list
/v1/groups/:
get:
x-fern-sdk-group-name:

View File

@@ -3,6 +3,8 @@ from typing import TYPE_CHECKING, List, Literal, Optional
from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query
from letta.orm.errors import NoResultFound, UniqueConstraintViolationError
from letta.schemas.agent import AgentState
from letta.schemas.block import Block
from letta.schemas.identity import Identity, IdentityCreate, IdentityProperty, IdentityType, IdentityUpdate, IdentityUpsert
from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
@@ -192,3 +194,79 @@ async def delete_identity(
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"{e}")
@router.get("/{identity_id}/agents", response_model=List[AgentState], operation_id="list_agents_for_identity")
async def list_agents_for_identity(
identity_id: str,
before: Optional[str] = Query(
None,
description="Agent ID cursor for pagination. Returns agents that come before this agent ID in the specified sort order",
),
after: Optional[str] = Query(
None,
description="Agent ID cursor for pagination. Returns agents that come after this agent ID in the specified sort order",
),
limit: Optional[int] = Query(50, description="Maximum number of agents to return"),
order: Literal["asc", "desc"] = Query(
"desc", description="Sort order for agents by creation time. 'asc' for oldest first, 'desc' for newest first"
),
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):
"""
Get all agents associated with the specified identity.
"""
try:
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
return await server.identity_manager.list_agents_for_identity_async(
identity_id=identity_id,
before=before,
after=after,
limit=limit,
ascending=(order == "asc"),
actor=actor,
)
except NoResultFound as e:
raise HTTPException(status_code=404, detail=f"Identity with id={identity_id} not found")
except Exception as e:
raise HTTPException(status_code=500, detail=f"{e}")
@router.get("/{identity_id}/blocks", response_model=List[Block], operation_id="list_blocks_for_identity")
async def list_blocks_for_identity(
identity_id: str,
before: Optional[str] = Query(
None,
description="Block ID cursor for pagination. Returns blocks that come before this block ID in the specified sort order",
),
after: Optional[str] = Query(
None,
description="Block ID cursor for pagination. Returns blocks that come after this block ID in the specified sort order",
),
limit: Optional[int] = Query(50, description="Maximum number of blocks to return"),
order: Literal["asc", "desc"] = Query(
"desc", description="Sort order for blocks by creation time. 'asc' for oldest first, 'desc' for newest first"
),
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):
"""
Get all blocks associated with the specified identity.
"""
try:
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
return await server.identity_manager.list_blocks_for_identity_async(
identity_id=identity_id,
before=before,
after=after,
limit=limit,
ascending=(order == "asc"),
actor=actor,
)
except NoResultFound as e:
raise HTTPException(status_code=404, detail=f"Identity with id={identity_id} not found")
except Exception as e:
raise HTTPException(status_code=500, detail=f"{e}")

View File

@@ -1,3 +1,4 @@
import asyncio
from typing import List, Optional
from fastapi import HTTPException
@@ -9,6 +10,8 @@ from letta.orm.block import Block as BlockModel
from letta.orm.errors import UniqueConstraintViolationError
from letta.orm.identity import Identity as IdentityModel
from letta.otel.tracing import trace_method
from letta.schemas.agent import AgentState
from letta.schemas.block import Block
from letta.schemas.identity import (
Identity as PydanticIdentity,
IdentityCreate,
@@ -274,3 +277,65 @@ class IdentityManager:
current_ids = {item.id for item in current_relationship}
new_items = [item for item in found_items if item.id not in current_ids]
current_relationship.extend(new_items)
@enforce_types
@trace_method
async def list_agents_for_identity_async(
self,
identity_id: str,
before: Optional[str] = None,
after: Optional[str] = None,
limit: Optional[int] = 50,
ascending: bool = False,
actor: PydanticUser = None,
) -> List[AgentState]:
"""
Get all agents associated with the specified identity.
"""
async with db_registry.async_session() as session:
# First verify the identity exists and belongs to the user
identity = await IdentityModel.read_async(db_session=session, identifier=identity_id, actor=actor)
if identity is None:
raise HTTPException(status_code=404, detail=f"Identity with id={identity_id} not found")
# Get agents associated with this identity with pagination
agents = await AgentModel.list_async(
db_session=session,
before=before,
after=after,
limit=limit,
ascending=ascending,
identity_id=identity.id,
)
return await asyncio.gather(*[agent.to_pydantic_async() for agent in agents])
@enforce_types
@trace_method
async def list_blocks_for_identity_async(
self,
identity_id: str,
before: Optional[str] = None,
after: Optional[str] = None,
limit: Optional[int] = 50,
ascending: bool = False,
actor: PydanticUser = None,
) -> List[Block]:
"""
Get all blocks associated with the specified identity.
"""
async with db_registry.async_session() as session:
# First verify the identity exists and belongs to the user
identity = await IdentityModel.read_async(db_session=session, identifier=identity_id, actor=actor)
if identity is None:
raise HTTPException(status_code=404, detail=f"Identity with id={identity_id} not found")
# Get blocks associated with this identity with pagination
blocks = await BlockModel.list_async(
db_session=session,
before=before,
after=after,
limit=limit,
ascending=ascending,
identity_id=identity.id,
)
return [block.to_pydantic() for block in blocks]