* chore: deprecate identities/groups APIs and remove from SDK - Mark all /v1/identities/* endpoints as deprecated - Mark all /v1/groups/* endpoints as deprecated - Remove identities, groups, and batches resources from stainless.yml - Batch API remains active but hidden from SDK 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta <noreply@letta.com> * chore: update autogenerated SDK files * chore: regenerate SDK and OpenAPI spec Run `just stage-api` and `just publish-api` to sync generated files. 👾 Generated with [Letta Code](https://letta.com) Co-authored-by: Sarah Wooders <sarahwooders@users.noreply.github.com> * chore: remove schedule API from stainless SDK Remove schedule subresource from stainless.yml to hide scheduled messages endpoints from the SDK generation. 👾 Generated with [Letta Code](https://letta.com) Co-authored-by: Sarah Wooders <sarahwooders@users.noreply.github.com> --------- Co-authored-by: Letta <noreply@letta.com> Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com> Co-authored-by: Sarah Wooders <sarahwooders@users.noreply.github.com>
329 lines
13 KiB
Python
329 lines
13 KiB
Python
from typing import Annotated, List, Literal, Optional
|
|
|
|
from fastapi import APIRouter, Body, Depends, Header, Query, status
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import Field
|
|
|
|
from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
|
|
from letta.schemas.group import Group, GroupBase, GroupCreate, GroupUpdate, ManagerType
|
|
from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion
|
|
from letta.schemas.letta_request import LettaRequest, LettaStreamingRequest
|
|
from letta.schemas.letta_response import LettaResponse
|
|
from letta.schemas.message import BaseMessage
|
|
from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
|
|
from letta.server.server import SyncServer
|
|
from letta.validators import GroupId, MessageId
|
|
|
|
router = APIRouter(prefix="/groups", tags=["groups"])
|
|
|
|
|
|
@router.get("/", response_model=List[Group], operation_id="list_groups", deprecated=True)
|
|
async def list_groups(
|
|
server: "SyncServer" = Depends(get_letta_server),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
manager_type: Optional[ManagerType] = Query(None, description="Search groups by manager type"),
|
|
before: Optional[str] = Query(
|
|
None, description="Group ID cursor for pagination. Returns groups that come before this group ID in the specified sort order"
|
|
),
|
|
after: Optional[str] = Query(
|
|
None, description="Group ID cursor for pagination. Returns groups that come after this group ID in the specified sort order"
|
|
),
|
|
limit: Optional[int] = Query(50, description="Maximum number of groups to return"),
|
|
order: Literal["asc", "desc"] = Query(
|
|
"asc", description="Sort order for groups by creation time. 'asc' for oldest first, 'desc' for newest first"
|
|
),
|
|
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
|
|
project_id: Optional[str] = Query(None, description="Search groups by project id"),
|
|
show_hidden_groups: bool | None = Query(
|
|
False,
|
|
include_in_schema=False,
|
|
description="If set to True, include groups marked as hidden in the results.",
|
|
),
|
|
):
|
|
"""
|
|
Fetch all multi-agent groups matching query.
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
return await server.group_manager.list_groups_async(
|
|
actor=actor,
|
|
project_id=project_id,
|
|
manager_type=manager_type,
|
|
before=before,
|
|
after=after,
|
|
limit=limit,
|
|
ascending=(order == "asc"),
|
|
show_hidden_groups=show_hidden_groups,
|
|
)
|
|
|
|
|
|
@router.get("/count", response_model=int, operation_id="count_groups", deprecated=True)
|
|
async def count_groups(
|
|
server: SyncServer = Depends(get_letta_server),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
):
|
|
"""
|
|
Get the count of all groups associated with a given user.
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
return await server.group_manager.size(actor=actor)
|
|
|
|
|
|
@router.get("/{group_id}", response_model=Group, operation_id="retrieve_group", deprecated=True)
|
|
async def retrieve_group(
|
|
group_id: GroupId,
|
|
server: "SyncServer" = Depends(get_letta_server),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
):
|
|
"""
|
|
Retrieve the group by id.
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
return await server.group_manager.retrieve_group_async(group_id=group_id, actor=actor)
|
|
|
|
|
|
@router.post("/", response_model=Group, operation_id="create_group", deprecated=True)
|
|
async def create_group(
|
|
group: GroupCreate = Body(...),
|
|
server: "SyncServer" = Depends(get_letta_server),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
x_project: Optional[str] = Header(
|
|
None, alias="X-Project", description="The project slug to associate with the group (cloud only)."
|
|
), # Only handled by next js middleware
|
|
):
|
|
"""
|
|
Create a new multi-agent group with the specified configuration.
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
return await server.group_manager.create_group_async(group, actor=actor)
|
|
|
|
|
|
@router.patch("/{group_id}", response_model=Group, operation_id="modify_group", deprecated=True)
|
|
async def modify_group(
|
|
group_id: GroupId,
|
|
group: GroupUpdate = Body(...),
|
|
server: "SyncServer" = Depends(get_letta_server),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
x_project: Optional[str] = Header(
|
|
None, alias="X-Project", description="The project slug to associate with the group (cloud only)."
|
|
), # Only handled by next js middleware
|
|
):
|
|
"""
|
|
Create a new multi-agent group with the specified configuration.
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
return await server.group_manager.modify_group_async(group_id=group_id, group_update=group, actor=actor)
|
|
|
|
|
|
@router.delete("/{group_id}", response_model=None, operation_id="delete_group", deprecated=True)
|
|
async def delete_group(
|
|
group_id: GroupId,
|
|
server: "SyncServer" = Depends(get_letta_server),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
):
|
|
"""
|
|
Delete a multi-agent group.
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
await server.group_manager.delete_group_async(group_id=group_id, actor=actor)
|
|
return JSONResponse(status_code=status.HTTP_200_OK, content={"message": f"Group id={group_id} successfully deleted"})
|
|
|
|
|
|
@router.post(
|
|
"/{group_id}/messages",
|
|
response_model=LettaResponse,
|
|
operation_id="send_group_message",
|
|
deprecated=True,
|
|
)
|
|
async def send_group_message(
|
|
group_id: GroupId,
|
|
server: SyncServer = Depends(get_letta_server),
|
|
request: LettaRequest = Body(...),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
):
|
|
"""
|
|
Process a user message and return the group's response.
|
|
This endpoint accepts a message from a user and processes it through through agents in the group based on the specified pattern
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
result = await server.send_group_message_to_agent(
|
|
group_id=group_id,
|
|
actor=actor,
|
|
input_messages=request.messages,
|
|
stream_steps=False,
|
|
stream_tokens=False,
|
|
# Support for AssistantMessage
|
|
use_assistant_message=request.use_assistant_message,
|
|
assistant_message_tool_name=request.assistant_message_tool_name,
|
|
assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
|
|
)
|
|
return result
|
|
|
|
|
|
@router.post(
|
|
"/{group_id}/messages/stream",
|
|
response_model=None,
|
|
operation_id="send_group_message_streaming",
|
|
deprecated=True,
|
|
responses={
|
|
200: {
|
|
"description": "Successful response",
|
|
"content": {
|
|
"text/event-stream": {"description": "Server-Sent Events stream"},
|
|
},
|
|
}
|
|
},
|
|
)
|
|
async def send_group_message_streaming(
|
|
group_id: GroupId,
|
|
server: SyncServer = Depends(get_letta_server),
|
|
request: LettaStreamingRequest = Body(...),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
):
|
|
"""
|
|
Process a user message and return the group's responses.
|
|
This endpoint accepts a message from a user and processes it through agents in the group based on the specified pattern.
|
|
It will stream the steps of the response always, and stream the tokens if 'stream_tokens' is set to True.
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
result = await server.send_group_message_to_agent(
|
|
group_id=group_id,
|
|
actor=actor,
|
|
input_messages=request.messages,
|
|
stream_steps=True,
|
|
stream_tokens=request.stream_tokens,
|
|
# Support for AssistantMessage
|
|
use_assistant_message=request.use_assistant_message,
|
|
assistant_message_tool_name=request.assistant_message_tool_name,
|
|
assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
|
|
)
|
|
return result
|
|
|
|
|
|
GroupMessagesResponse = Annotated[
|
|
List[LettaMessageUnion], Field(json_schema_extra={"type": "array", "items": {"$ref": "#/components/schemas/LettaMessageUnion"}})
|
|
]
|
|
|
|
|
|
@router.patch("/{group_id}/messages/{message_id}", response_model=LettaMessageUnion, operation_id="modify_group_message", deprecated=True)
|
|
async def modify_group_message(
|
|
group_id: GroupId,
|
|
message_id: MessageId,
|
|
request: LettaMessageUpdateUnion = Body(...),
|
|
server: "SyncServer" = Depends(get_letta_server),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
):
|
|
"""
|
|
Update the details of a message associated with an agent.
|
|
"""
|
|
# TODO: support modifying tool calls/returns
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
return await server.message_manager.update_message_by_letta_message(message_id=message_id, letta_message_update=request, actor=actor)
|
|
|
|
|
|
@router.get("/{group_id}/messages", response_model=GroupMessagesResponse, operation_id="list_group_messages", deprecated=True)
|
|
async def list_group_messages(
|
|
group_id: GroupId,
|
|
before: Optional[str] = Query(
|
|
None,
|
|
description="Message ID cursor for pagination. Returns messages that come before this message ID in the specified sort order",
|
|
),
|
|
after: Optional[str] = Query(
|
|
None,
|
|
description="Message ID cursor for pagination. Returns messages that come after this message ID in the specified sort order",
|
|
),
|
|
limit: Optional[int] = Query(10, description="Maximum number of messages to retrieve"),
|
|
order: Literal["asc", "desc"] = Query(
|
|
"desc", description="Sort order for messages by creation time. 'asc' for oldest first, 'desc' for newest first"
|
|
),
|
|
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
|
|
use_assistant_message: bool = Query(True, description="Whether to use assistant messages", deprecated=True),
|
|
assistant_message_tool_name: str = Query(DEFAULT_MESSAGE_TOOL, description="The name of the designated message tool.", deprecated=True),
|
|
assistant_message_tool_kwarg: str = Query(DEFAULT_MESSAGE_TOOL_KWARG, description="The name of the message argument.", deprecated=True),
|
|
server: "SyncServer" = Depends(get_letta_server),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
):
|
|
"""
|
|
Retrieve message history for an agent.
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
group = await server.group_manager.retrieve_group_async(group_id=group_id, actor=actor)
|
|
if group.manager_agent_id:
|
|
return await server.get_agent_recall_async(
|
|
user_id=actor.id,
|
|
agent_id=group.manager_agent_id,
|
|
after=after,
|
|
before=before,
|
|
limit=limit,
|
|
group_id=group_id,
|
|
reverse=(order == "desc"),
|
|
return_message_object=False,
|
|
use_assistant_message=use_assistant_message,
|
|
assistant_message_tool_name=assistant_message_tool_name,
|
|
assistant_message_tool_kwarg=assistant_message_tool_kwarg,
|
|
)
|
|
else:
|
|
return await server.group_manager.list_group_messages_async(
|
|
group_id=group_id,
|
|
after=after,
|
|
before=before,
|
|
limit=limit,
|
|
ascending=(order == "asc"),
|
|
actor=actor,
|
|
use_assistant_message=use_assistant_message,
|
|
assistant_message_tool_name=assistant_message_tool_name,
|
|
assistant_message_tool_kwarg=assistant_message_tool_kwarg,
|
|
)
|
|
|
|
|
|
@router.patch("/{group_id}/reset-messages", response_model=None, operation_id="reset_group_messages", deprecated=True)
|
|
async def reset_group_messages(
|
|
group_id: GroupId,
|
|
server: "SyncServer" = Depends(get_letta_server),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
):
|
|
"""
|
|
Delete the group messages for all agents that are part of the multi-agent group.
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
await server.group_manager.reset_messages_async(group_id=group_id, actor=actor)
|
|
|
|
|
|
@router.patch("/{group_id}/blocks/attach/{block_id}", response_model=None, operation_id="attach_block_to_group", deprecated=True)
|
|
async def attach_block_to_group(
|
|
block_id: str,
|
|
group_id: GroupId,
|
|
server: "SyncServer" = Depends(get_letta_server),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
):
|
|
"""
|
|
Attach a block to a group.
|
|
This will add the block to the group and all agents within the group.
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
await server.group_manager.attach_block_async(
|
|
group_id=group_id,
|
|
block_id=block_id,
|
|
actor=actor,
|
|
)
|
|
return None
|
|
|
|
|
|
@router.patch("/{group_id}/blocks/detach/{block_id}", response_model=None, operation_id="detach_block_from_group", deprecated=True)
|
|
async def detach_block_from_group(
|
|
block_id: str,
|
|
group_id: GroupId,
|
|
server: "SyncServer" = Depends(get_letta_server),
|
|
headers: HeaderParams = Depends(get_headers),
|
|
):
|
|
"""
|
|
Detach a block from a group.
|
|
This will remove the block from the group and all agents within the group.
|
|
"""
|
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
await server.group_manager.detach_block_async(
|
|
group_id=group_id,
|
|
block_id=block_id,
|
|
actor=actor,
|
|
)
|
|
return None
|