feat: clean up block return object [LET-5784] (#5641)
* fix: fix deep research agent * chore: update blocks response * add message * update agents * update * use blockresponse * undo merge conflict * add internal agents and blocks * remove unnecessary internal agent route * fix utils server test --------- Co-authored-by: christinatong01 <christina@letta.com>
This commit is contained in:
committed by
Caren Thomas
parent
0ae3739af4
commit
85ed29274c
153
fern/assets/leaderboard.js
Normal file
153
fern/assets/leaderboard.js
Normal file
@@ -0,0 +1,153 @@
|
||||
/* ──────────────────────────────────────────────────────────
|
||||
assets/leaderboard.js
|
||||
Load via docs.yml → js: - path: assets/leaderboard.js
|
||||
(strategy: lazyOnload is fine)
|
||||
────────────────────────────────────────────────────────── */
|
||||
|
||||
import yaml from 'https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/+esm';
|
||||
|
||||
console.log('🏁 leaderboard.js loaded on', location.pathname);
|
||||
|
||||
const COST_CAP = 120;
|
||||
|
||||
/* ---------- helpers ---------- */
|
||||
const pct = (v) => Number(v).toPrecision(3) + '%';
|
||||
const cost = (v) => '$' + Number(v).toFixed(2);
|
||||
const ready = (cb) =>
|
||||
document.readyState === 'loading'
|
||||
? document.addEventListener('DOMContentLoaded', cb)
|
||||
: cb();
|
||||
|
||||
/* ---------- main ---------- */
|
||||
ready(async () => {
|
||||
// const host = document.getElementById('letta-leaderboard');
|
||||
// if (!host) {
|
||||
// console.warn('LB-script: #letta-leaderboard not found - bailing out.');
|
||||
// return;
|
||||
// }
|
||||
/* ---- wait for the leaderboard container to appear (SPA nav safe) ---- */
|
||||
const host = await new Promise((resolve, reject) => {
|
||||
const el = document.getElementById('letta-leaderboard');
|
||||
if (el) return resolve(el); // SSR / hard refresh path
|
||||
|
||||
const obs = new MutationObserver(() => {
|
||||
const found = document.getElementById('letta-leaderboard');
|
||||
if (found) {
|
||||
obs.disconnect();
|
||||
resolve(found); // CSR navigation path
|
||||
}
|
||||
});
|
||||
obs.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
setTimeout(() => {
|
||||
obs.disconnect();
|
||||
reject(new Error('#letta-leaderboard never appeared'));
|
||||
}, 5000); // safety timeout
|
||||
}).catch((err) => {
|
||||
console.warn('LB-script:', err.message);
|
||||
return null;
|
||||
});
|
||||
if (!host) return; // still no luck → give up
|
||||
|
||||
/* ----- figure out URL of data.yaml ----- */
|
||||
// const path = location.pathname.endsWith('/')
|
||||
// ? location.pathname
|
||||
// : location.pathname.replace(/[^/]*$/, ''); // strip file/slug
|
||||
// const dataUrl = `${location.origin}${path}data.yaml`;
|
||||
// const dataUrl = `${location.origin}/leaderboard/data.yaml`; // one-liner, always right
|
||||
// const dataUrl = `${location.origin}/assets/leaderboard.yaml`;
|
||||
// const dataUrl = `./assets/leaderboard.yaml`; // one-liner, always right
|
||||
// const dataUrl = `${location.origin}/data.yaml`; // one-liner, always right
|
||||
const dataUrl =
|
||||
'https://raw.githubusercontent.com/letta-ai/letta-evals/refs/heads/main/letta-leaderboard/leaderboard_results.yaml';
|
||||
// const dataUrl = 'https://cdn.jsdelivr.net/gh/letta-ai/letta-evals@latest/letta-leaderboard/leaderboard_results.yaml';
|
||||
|
||||
console.log('LB-script: fetching', dataUrl);
|
||||
|
||||
/* ----- fetch & parse YAML ----- */
|
||||
let rows;
|
||||
try {
|
||||
const resp = await fetch(dataUrl);
|
||||
console.log(`LB-script: status ${resp.status}`);
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
rows = yaml.load(await resp.text());
|
||||
} catch (err) {
|
||||
console.error('LB-script: failed to load YAML →', err);
|
||||
return;
|
||||
}
|
||||
|
||||
/* ----- wire up table ----- */
|
||||
const dir = Object.create(null);
|
||||
const tbody = document.getElementById('lb-body');
|
||||
const searchI = document.getElementById('lb-search');
|
||||
const headers = document.querySelectorAll('#lb-table thead th[data-key]');
|
||||
searchI.value = ''; // clear any persisted filter
|
||||
|
||||
const render = () => {
|
||||
const q = searchI.value.toLowerCase();
|
||||
tbody.innerHTML = rows
|
||||
.map((r) => {
|
||||
const over = r.total_cost > COST_CAP;
|
||||
const barW = over ? '100%' : (r.total_cost / COST_CAP) * 100 + '%';
|
||||
const costCls = over ? 'cost-high' : 'cost-ok';
|
||||
const warnIcon = over
|
||||
? `<span class="warn" title="Cost exceeds $${COST_CAP} cap - bar is clipped to full width">⚠</span>`
|
||||
: '';
|
||||
|
||||
return `
|
||||
<tr class="${q && !r.model.toLowerCase().includes(q) ? 'hidden' : ''}">
|
||||
<td style="padding:8px">${r.model}</td>
|
||||
|
||||
<td class="bar-cell avg metric">
|
||||
<div class="bar-viz" style="width:${r.average}%"></div>
|
||||
<span class="value">${pct(r.average)}</span>
|
||||
</td>
|
||||
|
||||
<td class="bar-cell ${costCls} metric">
|
||||
<div class="bar-viz" style="width:${barW}"></div>
|
||||
<span class="value">${cost(r.total_cost)}</span>
|
||||
${warnIcon}
|
||||
</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join('');
|
||||
};
|
||||
|
||||
const setIndicator = (activeKey) => {
|
||||
headers.forEach((h) => {
|
||||
h.classList.remove('asc', 'desc');
|
||||
if (h.dataset.key === activeKey) h.classList.add(dir[activeKey]);
|
||||
});
|
||||
};
|
||||
|
||||
/* initial sort ↓ */
|
||||
dir.average = 'desc';
|
||||
rows.sort((a, b) => b.average - a.average);
|
||||
setIndicator('average');
|
||||
render();
|
||||
|
||||
/* search */
|
||||
searchI.addEventListener('input', render);
|
||||
|
||||
/* column sorting */
|
||||
headers.forEach((th) => {
|
||||
const key = th.dataset.key;
|
||||
th.addEventListener('click', () => {
|
||||
const asc = dir[key] === 'desc';
|
||||
dir[key] = asc ? 'asc' : 'desc';
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const va = a[key],
|
||||
vb = b[key];
|
||||
const cmp =
|
||||
typeof va === 'number'
|
||||
? va - vb
|
||||
: String(va).localeCompare(String(vb));
|
||||
return asc ? cmp : -cmp;
|
||||
});
|
||||
|
||||
setIndicator(key);
|
||||
render();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1258,6 +1258,20 @@ paths:
|
||||
/v1/_internal_runs/:
|
||||
get:
|
||||
x-fern-ignore: true
|
||||
/v1/_internal_blocks/:
|
||||
get:
|
||||
x-fern-ignore: true
|
||||
post:
|
||||
x-fern-ignore: true
|
||||
/v1/_internal_blocks/{block_id}:
|
||||
delete:
|
||||
x-fern-ignore: true
|
||||
/v1/_internal_blocks/{block_id}/agents:
|
||||
get:
|
||||
x-fern-ignore: true
|
||||
/v1/_internal_agents/{agent_id}/core-memory/blocks/{block_label}:
|
||||
patch:
|
||||
x-fern-ignore: true
|
||||
/v1/projects:
|
||||
get:
|
||||
x-fern-sdk-group-name:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,9 +21,9 @@ class BaseBlock(LettaBase, validate_assignment=True):
|
||||
|
||||
project_id: Optional[str] = Field(None, description="The associated project id.")
|
||||
# template data (optional)
|
||||
template_name: Optional[str] = Field(None, description="Name of the block if it is a template.", alias="name")
|
||||
template_name: Optional[str] = Field(None, description="Name of the block if it is a template.")
|
||||
is_template: bool = Field(False, description="Whether the block is a template (e.g. saved human/persona options).")
|
||||
template_id: Optional[str] = Field(None, description="The id of the template.", alias="name")
|
||||
template_id: Optional[str] = Field(None, description="The id of the template.")
|
||||
base_template_id: Optional[str] = Field(None, description="The base template id of the block.")
|
||||
deployment_id: Optional[str] = Field(None, description="The id of the deployment.")
|
||||
entity_id: Optional[str] = Field(None, description="The id of the entity within the template.")
|
||||
@@ -102,6 +102,21 @@ class Block(BaseBlock):
|
||||
last_updated_by_id: Optional[str] = Field(None, description="The id of the user that last updated this Block.")
|
||||
|
||||
|
||||
class BlockResponse(Block):
|
||||
template_name: Optional[str] = Field(
|
||||
None, description="(Deprecated) The name of the block template (if it is a template).", deprecated=True
|
||||
)
|
||||
template_id: Optional[str] = Field(None, description="(Deprecated) The id of the template.", deprecated=True)
|
||||
base_template_id: Optional[str] = Field(None, description="(Deprecated) The base template id of the block.", deprecated=True)
|
||||
deployment_id: Optional[str] = Field(None, description="(Deprecated) The id of the deployment.", deprecated=True)
|
||||
entity_id: Optional[str] = Field(None, description="(Deprecated) The id of the entity within the template.", deprecated=True)
|
||||
preserve_on_migration: Optional[bool] = Field(
|
||||
False, description="(Deprecated) Preserve the block on template migration.", deprecated=True
|
||||
)
|
||||
read_only: bool = Field(False, description="(Deprecated) Whether the agent has read-only access to the block.", deprecated=True)
|
||||
hidden: Optional[bool] = Field(None, description="(Deprecated) If set to True, the block will be hidden.", deprecated=True)
|
||||
|
||||
|
||||
class FileBlock(Block):
|
||||
file_id: str = Field(..., description="Unique identifier of the file.")
|
||||
source_id: str = Field(..., description="Unique identifier of the source.")
|
||||
@@ -149,7 +164,7 @@ class CreateBlock(BaseBlock):
|
||||
project_id: Optional[str] = Field(None, description="The associated project id.")
|
||||
# block templates
|
||||
is_template: bool = False
|
||||
template_name: Optional[str] = Field(None, description="Name of the block if it is a template.", alias="name")
|
||||
template_name: Optional[str] = Field(None, description="Name of the block if it is a template.")
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
|
||||
@@ -7,6 +7,8 @@ from letta.server.rest_api.routers.v1.folders import router as folders_router
|
||||
from letta.server.rest_api.routers.v1.groups import router as groups_router
|
||||
from letta.server.rest_api.routers.v1.health import router as health_router
|
||||
from letta.server.rest_api.routers.v1.identities import router as identities_router
|
||||
from letta.server.rest_api.routers.v1.internal_agents import router as internal_agents_router
|
||||
from letta.server.rest_api.routers.v1.internal_blocks import router as internal_blocks_router
|
||||
from letta.server.rest_api.routers.v1.internal_runs import router as internal_runs_router
|
||||
from letta.server.rest_api.routers.v1.internal_templates import router as internal_templates_router
|
||||
from letta.server.rest_api.routers.v1.jobs import router as jobs_router
|
||||
@@ -32,6 +34,8 @@ ROUTERS = [
|
||||
chat_completions_router,
|
||||
groups_router,
|
||||
identities_router,
|
||||
internal_agents_router,
|
||||
internal_blocks_router,
|
||||
internal_runs_router,
|
||||
internal_templates_router,
|
||||
llm_router,
|
||||
|
||||
@@ -32,7 +32,7 @@ from letta.otel.context import get_ctx_attributes
|
||||
from letta.otel.metric_registry import MetricRegistry
|
||||
from letta.schemas.agent import AgentRelationships, AgentState, CreateAgent, UpdateAgent
|
||||
from letta.schemas.agent_file import AgentFileSchema
|
||||
from letta.schemas.block import BaseBlock, Block, BlockUpdate
|
||||
from letta.schemas.block import BaseBlock, Block, BlockResponse, BlockUpdate
|
||||
from letta.schemas.enums import AgentType, RunStatus
|
||||
from letta.schemas.file import AgentFileAttachment, FileMetadataBase, PaginatedAgentFiles
|
||||
from letta.schemas.group import Group
|
||||
@@ -915,7 +915,7 @@ async def retrieve_agent_memory(
|
||||
return await server.get_agent_memory_async(agent_id=agent_id, actor=actor)
|
||||
|
||||
|
||||
@router.get("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="retrieve_core_memory_block")
|
||||
@router.get("/{agent_id}/core-memory/blocks/{block_label}", response_model=BlockResponse, operation_id="retrieve_core_memory_block")
|
||||
async def retrieve_block_for_agent(
|
||||
block_label: str,
|
||||
agent_id: AgentId,
|
||||
@@ -930,7 +930,7 @@ async def retrieve_block_for_agent(
|
||||
return await server.agent_manager.get_block_with_label_async(agent_id=agent_id, block_label=block_label, actor=actor)
|
||||
|
||||
|
||||
@router.get("/{agent_id}/core-memory/blocks", response_model=list[Block], operation_id="list_core_memory_blocks")
|
||||
@router.get("/{agent_id}/core-memory/blocks", response_model=list[BlockResponse], operation_id="list_core_memory_blocks")
|
||||
async def list_blocks_for_agent(
|
||||
agent_id: AgentId,
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
@@ -962,7 +962,7 @@ async def list_blocks_for_agent(
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="modify_core_memory_block")
|
||||
@router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=BlockResponse, operation_id="modify_core_memory_block")
|
||||
async def modify_block_for_agent(
|
||||
block_label: str,
|
||||
agent_id: AgentId,
|
||||
|
||||
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Body, Depends, HTTPException, Query
|
||||
|
||||
from letta.orm.errors import NoResultFound
|
||||
from letta.schemas.agent import AgentRelationships, AgentState
|
||||
from letta.schemas.block import BaseBlock, Block, BlockUpdate, CreateBlock
|
||||
from letta.schemas.block import BaseBlock, Block, BlockResponse, BlockUpdate, CreateBlock
|
||||
from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
|
||||
from letta.server.server import SyncServer
|
||||
from letta.utils import is_1_0_sdk_version
|
||||
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
|
||||
router = APIRouter(prefix="/blocks", tags=["blocks"])
|
||||
|
||||
|
||||
@router.get("/", response_model=List[Block], operation_id="list_blocks")
|
||||
@router.get("/", response_model=List[BlockResponse], operation_id="list_blocks")
|
||||
async def list_blocks(
|
||||
# query parameters
|
||||
label: Optional[str] = Query(None, description="Labels to include (e.g. human, persona)"),
|
||||
@@ -117,7 +117,7 @@ async def count_blocks(
|
||||
return await server.block_manager.size_async(actor=actor)
|
||||
|
||||
|
||||
@router.post("/", response_model=Block, operation_id="create_block")
|
||||
@router.post("/", response_model=BlockResponse, operation_id="create_block")
|
||||
async def create_block(
|
||||
create_block: CreateBlock = Body(...),
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
@@ -128,7 +128,7 @@ async def create_block(
|
||||
return await server.block_manager.create_or_update_block_async(actor=actor, block=block)
|
||||
|
||||
|
||||
@router.patch("/{block_id}", response_model=Block, operation_id="modify_block")
|
||||
@router.patch("/{block_id}", response_model=BlockResponse, operation_id="modify_block")
|
||||
async def modify_block(
|
||||
block_id: BlockId,
|
||||
block_update: BlockUpdate = Body(...),
|
||||
@@ -149,7 +149,7 @@ async def delete_block(
|
||||
await server.block_manager.delete_block_async(block_id=block_id, actor=actor)
|
||||
|
||||
|
||||
@router.get("/{block_id}", response_model=Block, operation_id="retrieve_block")
|
||||
@router.get("/{block_id}", response_model=BlockResponse, operation_id="retrieve_block")
|
||||
async def retrieve_block(
|
||||
block_id: BlockId,
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
@@ -214,7 +214,7 @@ async def list_agents_for_block(
|
||||
return agents
|
||||
|
||||
|
||||
@router.patch("/{block_id}/identities/attach/{identity_id}", response_model=Block, operation_id="attach_identity_to_block")
|
||||
@router.patch("/{block_id}/identities/attach/{identity_id}", response_model=BlockResponse, operation_id="attach_identity_to_block")
|
||||
async def attach_identity_to_block(
|
||||
identity_id: str,
|
||||
block_id: BlockId,
|
||||
@@ -233,7 +233,7 @@ async def attach_identity_to_block(
|
||||
return await server.block_manager.get_block_by_id_async(block_id=block_id, actor=actor)
|
||||
|
||||
|
||||
@router.patch("/{block_id}/identities/detach/{identity_id}", response_model=Block, operation_id="detach_identity_from_block")
|
||||
@router.patch("/{block_id}/identities/detach/{identity_id}", response_model=BlockResponse, operation_id="detach_identity_from_block")
|
||||
async def detach_identity_from_block(
|
||||
identity_id: str,
|
||||
block_id: BlockId,
|
||||
|
||||
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Body, Depends, Header, Query
|
||||
|
||||
from letta.orm.errors import NoResultFound, UniqueConstraintViolationError
|
||||
from letta.schemas.agent import AgentRelationships, AgentState
|
||||
from letta.schemas.block import Block
|
||||
from letta.schemas.block import Block, BlockResponse
|
||||
from letta.schemas.identity import (
|
||||
Identity,
|
||||
IdentityCreate,
|
||||
@@ -188,7 +188,7 @@ async def list_agents_for_identity(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{identity_id}/blocks", response_model=List[Block], operation_id="list_blocks_for_identity")
|
||||
@router.get("/{identity_id}/blocks", response_model=List[BlockResponse], operation_id="list_blocks_for_identity")
|
||||
async def list_blocks_for_identity(
|
||||
identity_id: IdentityId,
|
||||
before: Optional[str] = Query(
|
||||
|
||||
31
letta/server/rest_api/routers/v1/internal_agents.py
Normal file
31
letta/server/rest_api/routers/v1/internal_agents.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from fastapi import APIRouter, Body, Depends
|
||||
|
||||
from letta.schemas.block import Block, BlockUpdate
|
||||
from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
|
||||
from letta.server.server import SyncServer
|
||||
from letta.validators import AgentId
|
||||
|
||||
router = APIRouter(prefix="/_internal_agents", tags=["_internal_agents"])
|
||||
|
||||
|
||||
@router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="modify_internal_core_memory_block")
|
||||
async def modify_block_for_agent(
|
||||
block_label: str,
|
||||
agent_id: AgentId,
|
||||
block_update: BlockUpdate = Body(...),
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
"""
|
||||
Updates a core memory block of an agent.
|
||||
"""
|
||||
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
||||
|
||||
block = await server.agent_manager.modify_block_by_label_async(
|
||||
agent_id=agent_id, block_label=block_label, block_update=block_update, actor=actor
|
||||
)
|
||||
|
||||
# This should also trigger a system prompt change in the agent
|
||||
await server.agent_manager.rebuild_system_prompt_async(agent_id=agent_id, actor=actor, force=True, update_timestamp=False)
|
||||
|
||||
return block
|
||||
177
letta/server/rest_api/routers/v1/internal_blocks.py
Normal file
177
letta/server/rest_api/routers/v1/internal_blocks.py
Normal file
@@ -0,0 +1,177 @@
|
||||
from typing import TYPE_CHECKING, List, Literal, Optional
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, Query
|
||||
|
||||
from letta.schemas.agent import AgentState
|
||||
from letta.schemas.block import Block, CreateBlock
|
||||
from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
|
||||
from letta.server.server import SyncServer
|
||||
from letta.utils import is_1_0_sdk_version
|
||||
from letta.validators import BlockId
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
router = APIRouter(prefix="/_internal_blocks", tags=["_internal_blocks"])
|
||||
|
||||
|
||||
@router.get("/", response_model=List[Block], operation_id="list_internal_blocks")
|
||||
async def list_blocks(
|
||||
# query parameters
|
||||
label: Optional[str] = Query(None, description="Labels to include (e.g. human, persona)"),
|
||||
templates_only: bool = Query(False, description="Whether to include only templates"),
|
||||
name: Optional[str] = Query(None, description="Name of the block"),
|
||||
identity_id: Optional[str] = Query(None, description="Search agents by identifier id"),
|
||||
identifier_keys: Optional[List[str]] = Query(None, description="Search agents by identifier keys"),
|
||||
project_id: Optional[str] = Query(None, description="Search blocks by project id"),
|
||||
limit: Optional[int] = Query(50, description="Number of blocks to return"),
|
||||
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",
|
||||
),
|
||||
order: Literal["asc", "desc"] = Query(
|
||||
"asc", 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"),
|
||||
label_search: Optional[str] = Query(
|
||||
None,
|
||||
description=("Search blocks by label. If provided, returns blocks that match this label. This is a full-text search on labels."),
|
||||
),
|
||||
description_search: Optional[str] = Query(
|
||||
None,
|
||||
description=(
|
||||
"Search blocks by description. If provided, returns blocks that match this description. "
|
||||
"This is a full-text search on block descriptions."
|
||||
),
|
||||
),
|
||||
value_search: Optional[str] = Query(
|
||||
None,
|
||||
description=("Search blocks by value. If provided, returns blocks that match this value."),
|
||||
),
|
||||
connected_to_agents_count_gt: Optional[int] = Query(
|
||||
None,
|
||||
description=(
|
||||
"Filter blocks by the number of connected agents. "
|
||||
"If provided, returns blocks that have more than this number of connected agents."
|
||||
),
|
||||
),
|
||||
connected_to_agents_count_lt: Optional[int] = Query(
|
||||
None,
|
||||
description=(
|
||||
"Filter blocks by the number of connected agents. "
|
||||
"If provided, returns blocks that have less than this number of connected agents."
|
||||
),
|
||||
),
|
||||
connected_to_agents_count_eq: Optional[List[int]] = Query(
|
||||
None,
|
||||
description=(
|
||||
"Filter blocks by the exact number of connected agents. "
|
||||
"If provided, returns blocks that have exactly this number of connected agents."
|
||||
),
|
||||
),
|
||||
show_hidden_blocks: bool | None = Query(
|
||||
False,
|
||||
include_in_schema=False,
|
||||
description="If set to True, include blocks marked as hidden in the results.",
|
||||
),
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
||||
return await server.block_manager.get_blocks_async(
|
||||
actor=actor,
|
||||
label=label,
|
||||
is_template=templates_only,
|
||||
value_search=value_search,
|
||||
label_search=label_search,
|
||||
description_search=description_search,
|
||||
template_name=name,
|
||||
identity_id=identity_id,
|
||||
identifier_keys=identifier_keys,
|
||||
project_id=project_id,
|
||||
before=before,
|
||||
connected_to_agents_count_gt=connected_to_agents_count_gt,
|
||||
connected_to_agents_count_lt=connected_to_agents_count_lt,
|
||||
connected_to_agents_count_eq=connected_to_agents_count_eq,
|
||||
limit=limit,
|
||||
after=after,
|
||||
ascending=(order == "asc"),
|
||||
show_hidden_blocks=show_hidden_blocks,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/", response_model=Block, operation_id="create_internal_block")
|
||||
async def create_block(
|
||||
create_block: CreateBlock = Body(...),
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
||||
block = Block(**create_block.model_dump())
|
||||
return await server.block_manager.create_or_update_block_async(actor=actor, block=block)
|
||||
|
||||
|
||||
@router.delete("/{block_id}", operation_id="delete_internal_block")
|
||||
async def delete_block(
|
||||
block_id: BlockId,
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
||||
await server.block_manager.delete_block_async(block_id=block_id, actor=actor)
|
||||
|
||||
|
||||
@router.get("/{block_id}/agents", response_model=List[AgentState], operation_id="list_agents_for_internal_block")
|
||||
async def list_agents_for_block(
|
||||
block_id: BlockId,
|
||||
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"),
|
||||
include_relationships: list[str] | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Specify which relational fields (e.g., 'tools', 'sources', 'memory') to include in the response. "
|
||||
"If not provided, all relationships are loaded by default. "
|
||||
"Using this can optimize performance by reducing unnecessary joins."
|
||||
"This is a legacy parameter, and no longer supported after 1.0.0 SDK versions."
|
||||
),
|
||||
),
|
||||
include: List[str] = Query(
|
||||
[],
|
||||
description=("Specify which relational fields to include in the response. No relationships are included by default."),
|
||||
),
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
"""
|
||||
Retrieves all agents associated with the specified block.
|
||||
Raises a 404 if the block does not exist.
|
||||
"""
|
||||
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
||||
if include_relationships is None and is_1_0_sdk_version(headers):
|
||||
include_relationships = [] # don't default include all if using new SDK version
|
||||
agents = await server.block_manager.get_agents_for_block_async(
|
||||
block_id=block_id,
|
||||
before=before,
|
||||
after=after,
|
||||
limit=limit,
|
||||
ascending=(order == "asc"),
|
||||
include_relationships=include_relationships,
|
||||
include=include,
|
||||
actor=actor,
|
||||
)
|
||||
return agents
|
||||
Reference in New Issue
Block a user