letta-nightly 0.1.7.dev20240924104148__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of letta-nightly might be problematic. Click here for more details.
- letta/__init__.py +24 -0
- letta/__main__.py +3 -0
- letta/agent.py +1427 -0
- letta/agent_store/chroma.py +295 -0
- letta/agent_store/db.py +546 -0
- letta/agent_store/lancedb.py +177 -0
- letta/agent_store/milvus.py +198 -0
- letta/agent_store/qdrant.py +201 -0
- letta/agent_store/storage.py +188 -0
- letta/benchmark/benchmark.py +96 -0
- letta/benchmark/constants.py +14 -0
- letta/cli/cli.py +689 -0
- letta/cli/cli_config.py +1282 -0
- letta/cli/cli_load.py +166 -0
- letta/client/__init__.py +0 -0
- letta/client/admin.py +171 -0
- letta/client/client.py +2360 -0
- letta/client/streaming.py +90 -0
- letta/client/utils.py +61 -0
- letta/config.py +484 -0
- letta/configs/anthropic.json +13 -0
- letta/configs/letta_hosted.json +11 -0
- letta/configs/openai.json +12 -0
- letta/constants.py +134 -0
- letta/credentials.py +140 -0
- letta/data_sources/connectors.py +247 -0
- letta/embeddings.py +218 -0
- letta/errors.py +26 -0
- letta/functions/__init__.py +0 -0
- letta/functions/function_sets/base.py +174 -0
- letta/functions/function_sets/extras.py +132 -0
- letta/functions/functions.py +105 -0
- letta/functions/schema_generator.py +205 -0
- letta/humans/__init__.py +0 -0
- letta/humans/examples/basic.txt +1 -0
- letta/humans/examples/cs_phd.txt +9 -0
- letta/interface.py +314 -0
- letta/llm_api/__init__.py +0 -0
- letta/llm_api/anthropic.py +383 -0
- letta/llm_api/azure_openai.py +155 -0
- letta/llm_api/cohere.py +396 -0
- letta/llm_api/google_ai.py +468 -0
- letta/llm_api/llm_api_tools.py +485 -0
- letta/llm_api/openai.py +470 -0
- letta/local_llm/README.md +3 -0
- letta/local_llm/__init__.py +0 -0
- letta/local_llm/chat_completion_proxy.py +279 -0
- letta/local_llm/constants.py +31 -0
- letta/local_llm/function_parser.py +68 -0
- letta/local_llm/grammars/__init__.py +0 -0
- letta/local_llm/grammars/gbnf_grammar_generator.py +1324 -0
- letta/local_llm/grammars/json.gbnf +26 -0
- letta/local_llm/grammars/json_func_calls_with_inner_thoughts.gbnf +32 -0
- letta/local_llm/groq/api.py +97 -0
- letta/local_llm/json_parser.py +202 -0
- letta/local_llm/koboldcpp/api.py +62 -0
- letta/local_llm/koboldcpp/settings.py +23 -0
- letta/local_llm/llamacpp/api.py +58 -0
- letta/local_llm/llamacpp/settings.py +22 -0
- letta/local_llm/llm_chat_completion_wrappers/__init__.py +0 -0
- letta/local_llm/llm_chat_completion_wrappers/airoboros.py +452 -0
- letta/local_llm/llm_chat_completion_wrappers/chatml.py +470 -0
- letta/local_llm/llm_chat_completion_wrappers/configurable_wrapper.py +387 -0
- letta/local_llm/llm_chat_completion_wrappers/dolphin.py +246 -0
- letta/local_llm/llm_chat_completion_wrappers/llama3.py +345 -0
- letta/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py +156 -0
- letta/local_llm/llm_chat_completion_wrappers/wrapper_base.py +11 -0
- letta/local_llm/llm_chat_completion_wrappers/zephyr.py +345 -0
- letta/local_llm/lmstudio/api.py +100 -0
- letta/local_llm/lmstudio/settings.py +29 -0
- letta/local_llm/ollama/api.py +88 -0
- letta/local_llm/ollama/settings.py +32 -0
- letta/local_llm/settings/__init__.py +0 -0
- letta/local_llm/settings/deterministic_mirostat.py +45 -0
- letta/local_llm/settings/settings.py +72 -0
- letta/local_llm/settings/simple.py +28 -0
- letta/local_llm/utils.py +265 -0
- letta/local_llm/vllm/api.py +63 -0
- letta/local_llm/webui/api.py +60 -0
- letta/local_llm/webui/legacy_api.py +58 -0
- letta/local_llm/webui/legacy_settings.py +23 -0
- letta/local_llm/webui/settings.py +24 -0
- letta/log.py +76 -0
- letta/main.py +437 -0
- letta/memory.py +440 -0
- letta/metadata.py +884 -0
- letta/openai_backcompat/__init__.py +0 -0
- letta/openai_backcompat/openai_object.py +437 -0
- letta/persistence_manager.py +148 -0
- letta/personas/__init__.py +0 -0
- letta/personas/examples/anna_pa.txt +13 -0
- letta/personas/examples/google_search_persona.txt +15 -0
- letta/personas/examples/memgpt_doc.txt +6 -0
- letta/personas/examples/memgpt_starter.txt +4 -0
- letta/personas/examples/sam.txt +14 -0
- letta/personas/examples/sam_pov.txt +14 -0
- letta/personas/examples/sam_simple_pov_gpt35.txt +13 -0
- letta/personas/examples/sqldb/test.db +0 -0
- letta/prompts/__init__.py +0 -0
- letta/prompts/gpt_summarize.py +14 -0
- letta/prompts/gpt_system.py +26 -0
- letta/prompts/system/memgpt_base.txt +49 -0
- letta/prompts/system/memgpt_chat.txt +58 -0
- letta/prompts/system/memgpt_chat_compressed.txt +13 -0
- letta/prompts/system/memgpt_chat_fstring.txt +51 -0
- letta/prompts/system/memgpt_doc.txt +50 -0
- letta/prompts/system/memgpt_gpt35_extralong.txt +53 -0
- letta/prompts/system/memgpt_intuitive_knowledge.txt +31 -0
- letta/prompts/system/memgpt_modified_chat.txt +23 -0
- letta/pytest.ini +0 -0
- letta/schemas/agent.py +117 -0
- letta/schemas/api_key.py +21 -0
- letta/schemas/block.py +135 -0
- letta/schemas/document.py +21 -0
- letta/schemas/embedding_config.py +54 -0
- letta/schemas/enums.py +35 -0
- letta/schemas/job.py +38 -0
- letta/schemas/letta_base.py +80 -0
- letta/schemas/letta_message.py +175 -0
- letta/schemas/letta_request.py +23 -0
- letta/schemas/letta_response.py +28 -0
- letta/schemas/llm_config.py +54 -0
- letta/schemas/memory.py +224 -0
- letta/schemas/message.py +727 -0
- letta/schemas/openai/chat_completion_request.py +123 -0
- letta/schemas/openai/chat_completion_response.py +136 -0
- letta/schemas/openai/chat_completions.py +123 -0
- letta/schemas/openai/embedding_response.py +11 -0
- letta/schemas/openai/openai.py +157 -0
- letta/schemas/organization.py +20 -0
- letta/schemas/passage.py +80 -0
- letta/schemas/source.py +62 -0
- letta/schemas/tool.py +143 -0
- letta/schemas/usage.py +18 -0
- letta/schemas/user.py +33 -0
- letta/server/__init__.py +0 -0
- letta/server/constants.py +6 -0
- letta/server/rest_api/__init__.py +0 -0
- letta/server/rest_api/admin/__init__.py +0 -0
- letta/server/rest_api/admin/agents.py +21 -0
- letta/server/rest_api/admin/tools.py +83 -0
- letta/server/rest_api/admin/users.py +98 -0
- letta/server/rest_api/app.py +193 -0
- letta/server/rest_api/auth/__init__.py +0 -0
- letta/server/rest_api/auth/index.py +43 -0
- letta/server/rest_api/auth_token.py +22 -0
- letta/server/rest_api/interface.py +726 -0
- letta/server/rest_api/routers/__init__.py +0 -0
- letta/server/rest_api/routers/openai/__init__.py +0 -0
- letta/server/rest_api/routers/openai/assistants/__init__.py +0 -0
- letta/server/rest_api/routers/openai/assistants/assistants.py +115 -0
- letta/server/rest_api/routers/openai/assistants/schemas.py +121 -0
- letta/server/rest_api/routers/openai/assistants/threads.py +336 -0
- letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +131 -0
- letta/server/rest_api/routers/v1/__init__.py +15 -0
- letta/server/rest_api/routers/v1/agents.py +543 -0
- letta/server/rest_api/routers/v1/blocks.py +73 -0
- letta/server/rest_api/routers/v1/jobs.py +46 -0
- letta/server/rest_api/routers/v1/llms.py +28 -0
- letta/server/rest_api/routers/v1/organizations.py +61 -0
- letta/server/rest_api/routers/v1/sources.py +199 -0
- letta/server/rest_api/routers/v1/tools.py +103 -0
- letta/server/rest_api/routers/v1/users.py +109 -0
- letta/server/rest_api/static_files.py +74 -0
- letta/server/rest_api/utils.py +69 -0
- letta/server/server.py +1995 -0
- letta/server/startup.sh +8 -0
- letta/server/static_files/assets/index-0cbf7ad5.js +274 -0
- letta/server/static_files/assets/index-156816da.css +1 -0
- letta/server/static_files/assets/index-486e3228.js +274 -0
- letta/server/static_files/favicon.ico +0 -0
- letta/server/static_files/index.html +39 -0
- letta/server/static_files/memgpt_logo_transparent.png +0 -0
- letta/server/utils.py +46 -0
- letta/server/ws_api/__init__.py +0 -0
- letta/server/ws_api/example_client.py +104 -0
- letta/server/ws_api/interface.py +108 -0
- letta/server/ws_api/protocol.py +100 -0
- letta/server/ws_api/server.py +145 -0
- letta/settings.py +165 -0
- letta/streaming_interface.py +396 -0
- letta/system.py +207 -0
- letta/utils.py +1065 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/LICENSE +190 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/METADATA +98 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/RECORD +189 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/WHEEL +4 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Body, Depends, HTTPException
|
|
5
|
+
|
|
6
|
+
from letta.schemas.enums import MessageRole
|
|
7
|
+
from letta.schemas.letta_message import FunctionCall, LettaMessage
|
|
8
|
+
from letta.schemas.openai.chat_completion_request import ChatCompletionRequest
|
|
9
|
+
from letta.schemas.openai.chat_completion_response import (
|
|
10
|
+
ChatCompletionResponse,
|
|
11
|
+
Choice,
|
|
12
|
+
Message,
|
|
13
|
+
UsageStatistics,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# TODO this belongs in a controller!
|
|
17
|
+
from letta.server.rest_api.routers.v1.agents import send_message_to_agent
|
|
18
|
+
from letta.server.rest_api.utils import get_letta_server
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
from letta.server.server import SyncServer
|
|
24
|
+
from letta.utils import get_utc_time
|
|
25
|
+
|
|
26
|
+
router = APIRouter(prefix="/v1/chat/completions", tags=["chat_completions"])
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@router.post("/", response_model=ChatCompletionResponse)
|
|
30
|
+
async def create_chat_completion(
|
|
31
|
+
completion_request: ChatCompletionRequest = Body(...),
|
|
32
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
33
|
+
):
|
|
34
|
+
"""Send a message to a Letta agent via a /chat/completions completion_request
|
|
35
|
+
The bearer token will be used to identify the user.
|
|
36
|
+
The 'user' field in the completion_request should be set to the agent ID.
|
|
37
|
+
"""
|
|
38
|
+
actor = server.get_current_user()
|
|
39
|
+
agent_id = completion_request.user
|
|
40
|
+
if agent_id is None:
|
|
41
|
+
raise HTTPException(status_code=400, detail="Must pass agent_id in the 'user' field")
|
|
42
|
+
|
|
43
|
+
messages = completion_request.messages
|
|
44
|
+
if messages is None:
|
|
45
|
+
raise HTTPException(status_code=400, detail="'messages' field must not be empty")
|
|
46
|
+
if len(messages) > 1:
|
|
47
|
+
raise HTTPException(status_code=400, detail="'messages' field must be a list of length 1")
|
|
48
|
+
if messages[0].role != "user":
|
|
49
|
+
raise HTTPException(status_code=400, detail="'messages[0].role' must be a 'user'")
|
|
50
|
+
|
|
51
|
+
input_message = completion_request.messages[0]
|
|
52
|
+
if completion_request.stream:
|
|
53
|
+
print("Starting streaming OpenAI proxy response")
|
|
54
|
+
|
|
55
|
+
# TODO(charles) support multimodal parts
|
|
56
|
+
assert isinstance(input_message.content, str)
|
|
57
|
+
|
|
58
|
+
return await send_message_to_agent(
|
|
59
|
+
server=server,
|
|
60
|
+
agent_id=agent_id,
|
|
61
|
+
user_id=actor.id,
|
|
62
|
+
role=MessageRole(input_message.role),
|
|
63
|
+
message=input_message.content,
|
|
64
|
+
# Turn streaming ON
|
|
65
|
+
stream_steps=True,
|
|
66
|
+
stream_tokens=True,
|
|
67
|
+
# Turn on ChatCompletion mode (eg remaps send_message to content)
|
|
68
|
+
chat_completion_mode=True,
|
|
69
|
+
return_message_object=False,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
else:
|
|
73
|
+
print("Starting non-streaming OpenAI proxy response")
|
|
74
|
+
|
|
75
|
+
# TODO(charles) support multimodal parts
|
|
76
|
+
assert isinstance(input_message.content, str)
|
|
77
|
+
|
|
78
|
+
response_messages = await send_message_to_agent(
|
|
79
|
+
server=server,
|
|
80
|
+
agent_id=agent_id,
|
|
81
|
+
user_id=actor.id,
|
|
82
|
+
role=MessageRole(input_message.role),
|
|
83
|
+
message=input_message.content,
|
|
84
|
+
# Turn streaming OFF
|
|
85
|
+
stream_steps=False,
|
|
86
|
+
stream_tokens=False,
|
|
87
|
+
return_message_object=False,
|
|
88
|
+
)
|
|
89
|
+
# print(response_messages)
|
|
90
|
+
|
|
91
|
+
# Concatenate all send_message outputs together
|
|
92
|
+
id = ""
|
|
93
|
+
visible_message_str = ""
|
|
94
|
+
created_at = None
|
|
95
|
+
for letta_msg in response_messages.messages:
|
|
96
|
+
assert isinstance(letta_msg, LettaMessage)
|
|
97
|
+
if isinstance(letta_msg, FunctionCall):
|
|
98
|
+
if letta_msg.name and letta_msg.name == "send_message":
|
|
99
|
+
try:
|
|
100
|
+
letta_function_call_args = json.loads(letta_msg.arguments)
|
|
101
|
+
visible_message_str += letta_function_call_args["message"]
|
|
102
|
+
id = letta_msg.id
|
|
103
|
+
created_at = letta_msg.date
|
|
104
|
+
except:
|
|
105
|
+
print(f"Failed to parse Letta message: {str(letta_msg)}")
|
|
106
|
+
else:
|
|
107
|
+
print(f"Skipping function_call: {str(letta_msg)}")
|
|
108
|
+
else:
|
|
109
|
+
print(f"Skipping message: {str(letta_msg)}")
|
|
110
|
+
|
|
111
|
+
response = ChatCompletionResponse(
|
|
112
|
+
id=id,
|
|
113
|
+
created=created_at if created_at else get_utc_time(),
|
|
114
|
+
choices=[
|
|
115
|
+
Choice(
|
|
116
|
+
finish_reason="stop",
|
|
117
|
+
index=0,
|
|
118
|
+
message=Message(
|
|
119
|
+
role="assistant",
|
|
120
|
+
content=visible_message_str,
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
],
|
|
124
|
+
# TODO add real usage
|
|
125
|
+
usage=UsageStatistics(
|
|
126
|
+
completion_tokens=0,
|
|
127
|
+
prompt_tokens=0,
|
|
128
|
+
total_tokens=0,
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
return response
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from letta.server.rest_api.routers.v1.agents import router as agents_router
|
|
2
|
+
from letta.server.rest_api.routers.v1.blocks import router as blocks_router
|
|
3
|
+
from letta.server.rest_api.routers.v1.jobs import router as jobs_router
|
|
4
|
+
from letta.server.rest_api.routers.v1.llms import router as llm_router
|
|
5
|
+
from letta.server.rest_api.routers.v1.sources import router as sources_router
|
|
6
|
+
from letta.server.rest_api.routers.v1.tools import router as tools_router
|
|
7
|
+
|
|
8
|
+
ROUTERS = [
|
|
9
|
+
tools_router,
|
|
10
|
+
sources_router,
|
|
11
|
+
agents_router,
|
|
12
|
+
llm_router,
|
|
13
|
+
blocks_router,
|
|
14
|
+
jobs_router,
|
|
15
|
+
]
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Body, Depends, HTTPException, Query, status
|
|
6
|
+
from fastapi.responses import JSONResponse, StreamingResponse
|
|
7
|
+
from starlette.responses import StreamingResponse
|
|
8
|
+
|
|
9
|
+
from letta.schemas.agent import AgentState, CreateAgent, UpdateAgentState
|
|
10
|
+
from letta.schemas.enums import MessageRole, MessageStreamStatus
|
|
11
|
+
from letta.schemas.letta_message import (
|
|
12
|
+
LegacyLettaMessage,
|
|
13
|
+
LettaMessage,
|
|
14
|
+
LettaMessageUnion,
|
|
15
|
+
)
|
|
16
|
+
from letta.schemas.letta_request import LettaRequest
|
|
17
|
+
from letta.schemas.letta_response import LettaResponse
|
|
18
|
+
from letta.schemas.memory import (
|
|
19
|
+
ArchivalMemorySummary,
|
|
20
|
+
BasicBlockMemory,
|
|
21
|
+
CreateArchivalMemory,
|
|
22
|
+
Memory,
|
|
23
|
+
RecallMemorySummary,
|
|
24
|
+
)
|
|
25
|
+
from letta.schemas.message import Message, UpdateMessage
|
|
26
|
+
from letta.schemas.passage import Passage
|
|
27
|
+
from letta.schemas.source import Source
|
|
28
|
+
from letta.server.rest_api.interface import StreamingServerInterface
|
|
29
|
+
from letta.server.rest_api.utils import get_letta_server, sse_async_generator
|
|
30
|
+
from letta.server.server import SyncServer
|
|
31
|
+
from letta.utils import deduplicate
|
|
32
|
+
|
|
33
|
+
# These can be forward refs, but because Fastapi needs them at runtime the must be imported normally
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
router = APIRouter(prefix="/agents", tags=["agents"])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@router.get("/", response_model=List[AgentState], operation_id="list_agents")
|
|
40
|
+
def list_agents(
|
|
41
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
List all agents associated with a given user.
|
|
45
|
+
This endpoint retrieves a list of all agents and their configurations associated with the specified user ID.
|
|
46
|
+
"""
|
|
47
|
+
actor = server.get_current_user()
|
|
48
|
+
|
|
49
|
+
return server.list_agents(user_id=actor.id)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@router.post("/", response_model=AgentState, operation_id="create_agent")
|
|
53
|
+
def create_agent(
|
|
54
|
+
agent: CreateAgent = Body(...),
|
|
55
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
Create a new agent with the specified configuration.
|
|
59
|
+
"""
|
|
60
|
+
actor = server.get_current_user()
|
|
61
|
+
agent.user_id = actor.id
|
|
62
|
+
# TODO: sarah make general
|
|
63
|
+
# TODO: eventually remove this
|
|
64
|
+
assert agent.memory is not None # TODO: dont force this, can be None (use default human/person)
|
|
65
|
+
blocks = agent.memory.get_blocks()
|
|
66
|
+
agent.memory = BasicBlockMemory(blocks=blocks)
|
|
67
|
+
|
|
68
|
+
return server.create_agent(agent, user_id=actor.id)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@router.patch("/{agent_id}", response_model=AgentState, operation_id="update_agent")
|
|
72
|
+
def update_agent(
|
|
73
|
+
agent_id: str,
|
|
74
|
+
update_agent: UpdateAgentState = Body(...),
|
|
75
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
76
|
+
):
|
|
77
|
+
"""Update an exsiting agent"""
|
|
78
|
+
actor = server.get_current_user()
|
|
79
|
+
|
|
80
|
+
update_agent.id = agent_id
|
|
81
|
+
return server.update_agent(update_agent, user_id=actor.id)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@router.get("/{agent_id}", response_model=AgentState, operation_id="get_agent")
|
|
85
|
+
def get_agent_state(
|
|
86
|
+
agent_id: str,
|
|
87
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
88
|
+
):
|
|
89
|
+
"""
|
|
90
|
+
Get the state of the agent.
|
|
91
|
+
"""
|
|
92
|
+
actor = server.get_current_user()
|
|
93
|
+
|
|
94
|
+
if not server.ms.get_agent(user_id=actor.id, agent_id=agent_id):
|
|
95
|
+
# agent does not exist
|
|
96
|
+
raise HTTPException(status_code=404, detail=f"Agent agent_id={agent_id} not found.")
|
|
97
|
+
|
|
98
|
+
return server.get_agent_state(user_id=actor.id, agent_id=agent_id)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@router.delete("/{agent_id}", response_model=None, operation_id="delete_agent")
|
|
102
|
+
def delete_agent(
|
|
103
|
+
agent_id: str,
|
|
104
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
105
|
+
):
|
|
106
|
+
"""
|
|
107
|
+
Delete an agent.
|
|
108
|
+
"""
|
|
109
|
+
actor = server.get_current_user()
|
|
110
|
+
|
|
111
|
+
return server.delete_agent(user_id=actor.id, agent_id=agent_id)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@router.get("/{agent_id}/sources", response_model=List[Source], operation_id="get_agent_sources")
|
|
115
|
+
def get_agent_sources(
|
|
116
|
+
agent_id: str,
|
|
117
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
118
|
+
):
|
|
119
|
+
"""
|
|
120
|
+
Get the sources associated with an agent.
|
|
121
|
+
"""
|
|
122
|
+
server.get_current_user()
|
|
123
|
+
|
|
124
|
+
return server.list_attached_sources(agent_id)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@router.get("/{agent_id}/memory/messages", response_model=List[Message], operation_id="list_agent_in_context_messages")
|
|
128
|
+
def get_agent_in_context_messages(
|
|
129
|
+
agent_id: str,
|
|
130
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
131
|
+
):
|
|
132
|
+
"""
|
|
133
|
+
Retrieve the messages in the context of a specific agent.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
return server.get_in_context_messages(agent_id=agent_id)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@router.get("/{agent_id}/memory", response_model=Memory, operation_id="get_agent_memory")
|
|
140
|
+
def get_agent_memory(
|
|
141
|
+
agent_id: str,
|
|
142
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
143
|
+
):
|
|
144
|
+
"""
|
|
145
|
+
Retrieve the memory state of a specific agent.
|
|
146
|
+
This endpoint fetches the current memory state of the agent identified by the user ID and agent ID.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
return server.get_agent_memory(agent_id=agent_id)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@router.patch("/{agent_id}/memory", response_model=Memory, operation_id="update_agent_memory")
|
|
153
|
+
def update_agent_memory(
|
|
154
|
+
agent_id: str,
|
|
155
|
+
request: Dict = Body(...),
|
|
156
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
157
|
+
):
|
|
158
|
+
"""
|
|
159
|
+
Update the core memory of a specific agent.
|
|
160
|
+
This endpoint accepts new memory contents (human and persona) and updates the core memory of the agent identified by the user ID and agent ID.
|
|
161
|
+
"""
|
|
162
|
+
actor = server.get_current_user()
|
|
163
|
+
|
|
164
|
+
memory = server.update_agent_core_memory(user_id=actor.id, agent_id=agent_id, new_memory_contents=request)
|
|
165
|
+
return memory
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@router.get("/{agent_id}/memory/recall", response_model=RecallMemorySummary, operation_id="get_agent_recall_memory_summary")
|
|
169
|
+
def get_agent_recall_memory_summary(
|
|
170
|
+
agent_id: str,
|
|
171
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
172
|
+
):
|
|
173
|
+
"""
|
|
174
|
+
Retrieve the summary of the recall memory of a specific agent.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
return server.get_recall_memory_summary(agent_id=agent_id)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@router.get("/{agent_id}/memory/archival", response_model=ArchivalMemorySummary, operation_id="get_agent_archival_memory_summary")
|
|
181
|
+
def get_agent_archival_memory_summary(
|
|
182
|
+
agent_id: str,
|
|
183
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
184
|
+
):
|
|
185
|
+
"""
|
|
186
|
+
Retrieve the summary of the archival memory of a specific agent.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
return server.get_archival_memory_summary(agent_id=agent_id)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@router.get("/{agent_id}/archival", response_model=List[Passage], operation_id="list_agent_archival_memory")
|
|
193
|
+
def get_agent_archival_memory(
|
|
194
|
+
agent_id: str,
|
|
195
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
196
|
+
after: Optional[int] = Query(None, description="Unique ID of the memory to start the query range at."),
|
|
197
|
+
before: Optional[int] = Query(None, description="Unique ID of the memory to end the query range at."),
|
|
198
|
+
limit: Optional[int] = Query(None, description="How many results to include in the response."),
|
|
199
|
+
):
|
|
200
|
+
"""
|
|
201
|
+
Retrieve the memories in an agent's archival memory store (paginated query).
|
|
202
|
+
"""
|
|
203
|
+
actor = server.get_current_user()
|
|
204
|
+
|
|
205
|
+
# TODO need to add support for non-postgres here
|
|
206
|
+
# chroma will throw:
|
|
207
|
+
# raise ValueError("Cannot run get_all_cursor with chroma")
|
|
208
|
+
|
|
209
|
+
return server.get_agent_archival_cursor(
|
|
210
|
+
user_id=actor.id,
|
|
211
|
+
agent_id=agent_id,
|
|
212
|
+
after=after,
|
|
213
|
+
before=before,
|
|
214
|
+
limit=limit,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@router.post("/{agent_id}/archival", response_model=List[Passage], operation_id="create_agent_archival_memory")
|
|
219
|
+
def insert_agent_archival_memory(
|
|
220
|
+
agent_id: str,
|
|
221
|
+
request: CreateArchivalMemory = Body(...),
|
|
222
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
223
|
+
):
|
|
224
|
+
"""
|
|
225
|
+
Insert a memory into an agent's archival memory store.
|
|
226
|
+
"""
|
|
227
|
+
actor = server.get_current_user()
|
|
228
|
+
|
|
229
|
+
return server.insert_archival_memory(user_id=actor.id, agent_id=agent_id, memory_contents=request.text)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# TODO(ethan): query or path parameter for memory_id?
|
|
233
|
+
# @router.delete("/{agent_id}/archival")
|
|
234
|
+
@router.delete("/{agent_id}/archival/{memory_id}", response_model=None, operation_id="delete_agent_archival_memory")
|
|
235
|
+
def delete_agent_archival_memory(
|
|
236
|
+
agent_id: str,
|
|
237
|
+
memory_id: str,
|
|
238
|
+
# memory_id: str = Query(..., description="Unique ID of the memory to be deleted."),
|
|
239
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
240
|
+
):
|
|
241
|
+
"""
|
|
242
|
+
Delete a memory from an agent's archival memory store.
|
|
243
|
+
"""
|
|
244
|
+
actor = server.get_current_user()
|
|
245
|
+
|
|
246
|
+
server.delete_archival_memory(user_id=actor.id, agent_id=agent_id, memory_id=memory_id)
|
|
247
|
+
return JSONResponse(status_code=status.HTTP_200_OK, content={"message": f"Memory id={memory_id} successfully deleted"})
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@router.get("/{agent_id}/messages", response_model=Union[List[Message], List[LettaMessageUnion]], operation_id="list_agent_messages")
|
|
251
|
+
def get_agent_messages(
|
|
252
|
+
agent_id: str,
|
|
253
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
254
|
+
before: Optional[str] = Query(None, description="Message before which to retrieve the returned messages."),
|
|
255
|
+
limit: int = Query(10, description="Maximum number of messages to retrieve."),
|
|
256
|
+
msg_object: bool = Query(False, description="If true, returns Message objects. If false, return LettaMessage objects."),
|
|
257
|
+
):
|
|
258
|
+
"""
|
|
259
|
+
Retrieve message history for an agent.
|
|
260
|
+
"""
|
|
261
|
+
actor = server.get_current_user()
|
|
262
|
+
|
|
263
|
+
return server.get_agent_recall_cursor(
|
|
264
|
+
user_id=actor.id,
|
|
265
|
+
agent_id=agent_id,
|
|
266
|
+
before=before,
|
|
267
|
+
limit=limit,
|
|
268
|
+
reverse=True,
|
|
269
|
+
return_message_object=msg_object,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@router.patch("/{agent_id}/messages/{message_id}", response_model=Message, operation_id="update_agent_message")
|
|
274
|
+
def update_message(
|
|
275
|
+
agent_id: str,
|
|
276
|
+
message_id: str,
|
|
277
|
+
request: UpdateMessage = Body(...),
|
|
278
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
279
|
+
):
|
|
280
|
+
"""
|
|
281
|
+
Update the details of a message associated with an agent.
|
|
282
|
+
"""
|
|
283
|
+
assert request.id == message_id, f"Message ID mismatch: {request.id} != {message_id}"
|
|
284
|
+
return server.update_agent_message(agent_id=agent_id, request=request)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@router.post("/{agent_id}/messages", response_model=LettaResponse, operation_id="create_agent_message")
|
|
288
|
+
async def send_message(
|
|
289
|
+
agent_id: str,
|
|
290
|
+
server: SyncServer = Depends(get_letta_server),
|
|
291
|
+
request: LettaRequest = Body(...),
|
|
292
|
+
):
|
|
293
|
+
"""
|
|
294
|
+
Process a user message and return the agent's response.
|
|
295
|
+
This endpoint accepts a message from a user and processes it through the agent.
|
|
296
|
+
It can optionally stream the response if 'stream_steps' or 'stream_tokens' is set to True.
|
|
297
|
+
"""
|
|
298
|
+
actor = server.get_current_user()
|
|
299
|
+
|
|
300
|
+
# TODO(charles): support sending multiple messages
|
|
301
|
+
assert len(request.messages) == 1, f"Multiple messages not supported: {request.messages}"
|
|
302
|
+
message = request.messages[0]
|
|
303
|
+
|
|
304
|
+
return await send_message_to_agent(
|
|
305
|
+
server=server,
|
|
306
|
+
agent_id=agent_id,
|
|
307
|
+
user_id=actor.id,
|
|
308
|
+
role=message.role,
|
|
309
|
+
message=message.text,
|
|
310
|
+
stream_steps=request.stream_steps,
|
|
311
|
+
stream_tokens=request.stream_tokens,
|
|
312
|
+
return_message_object=request.return_message_object,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# TODO: move this into server.py?
|
|
317
|
+
async def send_message_to_agent(
|
|
318
|
+
server: SyncServer,
|
|
319
|
+
agent_id: str,
|
|
320
|
+
user_id: str,
|
|
321
|
+
role: MessageRole,
|
|
322
|
+
message: str,
|
|
323
|
+
stream_steps: bool,
|
|
324
|
+
stream_tokens: bool,
|
|
325
|
+
return_message_object: bool, # Should be True for Python Client, False for REST API
|
|
326
|
+
chat_completion_mode: Optional[bool] = False,
|
|
327
|
+
timestamp: Optional[datetime] = None,
|
|
328
|
+
# related to whether or not we return `LettaMessage`s or `Message`s
|
|
329
|
+
) -> Union[StreamingResponse, LettaResponse]:
|
|
330
|
+
"""Split off into a separate function so that it can be imported in the /chat/completion proxy."""
|
|
331
|
+
# TODO: @charles is this the correct way to handle?
|
|
332
|
+
include_final_message = True
|
|
333
|
+
|
|
334
|
+
# determine role
|
|
335
|
+
if role == MessageRole.user:
|
|
336
|
+
message_func = server.user_message
|
|
337
|
+
elif role == MessageRole.system:
|
|
338
|
+
message_func = server.system_message
|
|
339
|
+
else:
|
|
340
|
+
raise HTTPException(status_code=500, detail=f"Bad role {role}")
|
|
341
|
+
|
|
342
|
+
if not stream_steps and stream_tokens:
|
|
343
|
+
raise HTTPException(status_code=400, detail="stream_steps must be 'true' if stream_tokens is 'true'")
|
|
344
|
+
|
|
345
|
+
# For streaming response
|
|
346
|
+
try:
|
|
347
|
+
|
|
348
|
+
# TODO: move this logic into server.py
|
|
349
|
+
|
|
350
|
+
# Get the generator object off of the agent's streaming interface
|
|
351
|
+
# This will be attached to the POST SSE request used under-the-hood
|
|
352
|
+
letta_agent = server._get_or_load_agent(agent_id=agent_id)
|
|
353
|
+
streaming_interface = letta_agent.interface
|
|
354
|
+
if not isinstance(streaming_interface, StreamingServerInterface):
|
|
355
|
+
raise ValueError(f"Agent has wrong type of interface: {type(streaming_interface)}")
|
|
356
|
+
|
|
357
|
+
# Disable token streaming if not OpenAI
|
|
358
|
+
# TODO: cleanup this logic
|
|
359
|
+
if server.server_llm_config.model_endpoint_type != "openai" or "inference.memgpt.ai" in server.server_llm_config.model_endpoint:
|
|
360
|
+
print("Warning: token streaming is only supported for OpenAI models. Setting to False.")
|
|
361
|
+
stream_tokens = False
|
|
362
|
+
|
|
363
|
+
# Enable token-streaming within the request if desired
|
|
364
|
+
streaming_interface.streaming_mode = stream_tokens
|
|
365
|
+
# "chatcompletion mode" does some remapping and ignores inner thoughts
|
|
366
|
+
streaming_interface.streaming_chat_completion_mode = chat_completion_mode
|
|
367
|
+
|
|
368
|
+
# streaming_interface.allow_assistant_message = stream
|
|
369
|
+
# streaming_interface.function_call_legacy_mode = stream
|
|
370
|
+
|
|
371
|
+
# Offload the synchronous message_func to a separate thread
|
|
372
|
+
streaming_interface.stream_start()
|
|
373
|
+
task = asyncio.create_task(
|
|
374
|
+
asyncio.to_thread(message_func, user_id=user_id, agent_id=agent_id, message=message, timestamp=timestamp)
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
if stream_steps:
|
|
378
|
+
if return_message_object:
|
|
379
|
+
# TODO implement returning `Message`s in a stream, not just `LettaMessage` format
|
|
380
|
+
raise NotImplementedError
|
|
381
|
+
|
|
382
|
+
# return a stream
|
|
383
|
+
return StreamingResponse(
|
|
384
|
+
sse_async_generator(streaming_interface.get_generator(), finish_message=include_final_message),
|
|
385
|
+
media_type="text/event-stream",
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
else:
|
|
389
|
+
# buffer the stream, then return the list
|
|
390
|
+
generated_stream = []
|
|
391
|
+
async for message in streaming_interface.get_generator():
|
|
392
|
+
assert (
|
|
393
|
+
isinstance(message, LettaMessage) or isinstance(message, LegacyLettaMessage) or isinstance(message, MessageStreamStatus)
|
|
394
|
+
), type(message)
|
|
395
|
+
generated_stream.append(message)
|
|
396
|
+
if message == MessageStreamStatus.done:
|
|
397
|
+
break
|
|
398
|
+
|
|
399
|
+
# Get rid of the stream status messages
|
|
400
|
+
filtered_stream = [d for d in generated_stream if not isinstance(d, MessageStreamStatus)]
|
|
401
|
+
usage = await task
|
|
402
|
+
|
|
403
|
+
# By default the stream will be messages of type LettaMessage or LettaLegacyMessage
|
|
404
|
+
# If we want to convert these to Message, we can use the attached IDs
|
|
405
|
+
# NOTE: we will need to de-duplicate the Messsage IDs though (since Assistant->Inner+Func_Call)
|
|
406
|
+
# TODO: eventually update the interface to use `Message` and `MessageChunk` (new) inside the deque instead
|
|
407
|
+
if return_message_object:
|
|
408
|
+
message_ids = [m.id for m in filtered_stream]
|
|
409
|
+
message_ids = deduplicate(message_ids)
|
|
410
|
+
message_objs = [server.get_agent_message(agent_id=agent_id, message_id=m_id) for m_id in message_ids]
|
|
411
|
+
return LettaResponse(messages=message_objs, usage=usage)
|
|
412
|
+
else:
|
|
413
|
+
return LettaResponse(messages=filtered_stream, usage=usage)
|
|
414
|
+
|
|
415
|
+
except HTTPException:
|
|
416
|
+
raise
|
|
417
|
+
except Exception as e:
|
|
418
|
+
print(e)
|
|
419
|
+
import traceback
|
|
420
|
+
|
|
421
|
+
traceback.print_exc()
|
|
422
|
+
raise HTTPException(status_code=500, detail=f"{e}")
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
##### MISSING #######
|
|
426
|
+
|
|
427
|
+
# @router.post("/{agent_id}/command")
|
|
428
|
+
# def run_command(
|
|
429
|
+
# agent_id: "UUID",
|
|
430
|
+
# command: "AgentCommandRequest",
|
|
431
|
+
#
|
|
432
|
+
# server: "SyncServer" = Depends(get_letta_server),
|
|
433
|
+
# ):
|
|
434
|
+
# """
|
|
435
|
+
# Execute a command on a specified agent.
|
|
436
|
+
|
|
437
|
+
# This endpoint receives a command to be executed on an agent. It uses the user and agent identifiers to authenticate and route the command appropriately.
|
|
438
|
+
|
|
439
|
+
# Raises an HTTPException for any processing errors.
|
|
440
|
+
# """
|
|
441
|
+
# actor = server.get_current_user()
|
|
442
|
+
#
|
|
443
|
+
# response = server.run_command(user_id=actor.id,
|
|
444
|
+
# agent_id=agent_id,
|
|
445
|
+
# command=command.command)
|
|
446
|
+
|
|
447
|
+
# return AgentCommandResponse(response=response)
|
|
448
|
+
|
|
449
|
+
# @router.get("/{agent_id}/config")
|
|
450
|
+
# def get_agent_config(
|
|
451
|
+
# agent_id: "UUID",
|
|
452
|
+
#
|
|
453
|
+
# server: "SyncServer" = Depends(get_letta_server),
|
|
454
|
+
# ):
|
|
455
|
+
# """
|
|
456
|
+
# Retrieve the configuration for a specific agent.
|
|
457
|
+
|
|
458
|
+
# This endpoint fetches the configuration details for a given agent, identified by the user and agent IDs.
|
|
459
|
+
# """
|
|
460
|
+
# actor = server.get_current_user()
|
|
461
|
+
#
|
|
462
|
+
# if not server.ms.get_agent(user_id=actor.id, agent_id=agent_id):
|
|
463
|
+
## agent does not exist
|
|
464
|
+
# raise HTTPException(status_code=404, detail=f"Agent agent_id={agent_id} not found.")
|
|
465
|
+
|
|
466
|
+
# agent_state = server.get_agent_config(user_id=actor.id, agent_id=agent_id)
|
|
467
|
+
## get sources
|
|
468
|
+
# attached_sources = server.list_attached_sources(agent_id=agent_id)
|
|
469
|
+
|
|
470
|
+
## configs
|
|
471
|
+
# llm_config = LLMConfig(**vars(agent_state.llm_config))
|
|
472
|
+
# embedding_config = EmbeddingConfig(**vars(agent_state.embedding_config))
|
|
473
|
+
|
|
474
|
+
# return GetAgentResponse(
|
|
475
|
+
# agent_state=AgentState(
|
|
476
|
+
# id=agent_state.id,
|
|
477
|
+
# name=agent_state.name,
|
|
478
|
+
# user_id=agent_state.user_id,
|
|
479
|
+
# llm_config=llm_config,
|
|
480
|
+
# embedding_config=embedding_config,
|
|
481
|
+
# state=agent_state.state,
|
|
482
|
+
# created_at=int(agent_state.created_at.timestamp()),
|
|
483
|
+
# tools=agent_state.tools,
|
|
484
|
+
# system=agent_state.system,
|
|
485
|
+
# metadata=agent_state._metadata,
|
|
486
|
+
# ),
|
|
487
|
+
# last_run_at=None, # TODO
|
|
488
|
+
# sources=attached_sources,
|
|
489
|
+
# )
|
|
490
|
+
|
|
491
|
+
# @router.patch("/{agent_id}/rename", response_model=GetAgentResponse)
|
|
492
|
+
# def update_agent_name(
|
|
493
|
+
# agent_id: "UUID",
|
|
494
|
+
# agent_rename: AgentRenameRequest,
|
|
495
|
+
#
|
|
496
|
+
# server: "SyncServer" = Depends(get_letta_server),
|
|
497
|
+
# ):
|
|
498
|
+
# """
|
|
499
|
+
# Updates the name of a specific agent.
|
|
500
|
+
|
|
501
|
+
# This changes the name of the agent in the database but does NOT edit the agent's persona.
|
|
502
|
+
# """
|
|
503
|
+
# valid_name = agent_rename.agent_name
|
|
504
|
+
# actor = server.get_current_user()
|
|
505
|
+
#
|
|
506
|
+
# agent_state = server.rename_agent(user_id=actor.id, agent_id=agent_id, new_agent_name=valid_name)
|
|
507
|
+
## get sources
|
|
508
|
+
# attached_sources = server.list_attached_sources(agent_id=agent_id)
|
|
509
|
+
# llm_config = LLMConfig(**vars(agent_state.llm_config))
|
|
510
|
+
# embedding_config = EmbeddingConfig(**vars(agent_state.embedding_config))
|
|
511
|
+
|
|
512
|
+
# return GetAgentResponse(
|
|
513
|
+
# agent_state=AgentState(
|
|
514
|
+
# id=agent_state.id,
|
|
515
|
+
# name=agent_state.name,
|
|
516
|
+
# user_id=agent_state.user_id,
|
|
517
|
+
# llm_config=llm_config,
|
|
518
|
+
# embedding_config=embedding_config,
|
|
519
|
+
# state=agent_state.state,
|
|
520
|
+
# created_at=int(agent_state.created_at.timestamp()),
|
|
521
|
+
# tools=agent_state.tools,
|
|
522
|
+
# system=agent_state.system,
|
|
523
|
+
# ),
|
|
524
|
+
# last_run_at=None, # TODO
|
|
525
|
+
# sources=attached_sources,
|
|
526
|
+
# )
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
# @router.get("/{agent_id}/archival/all", response_model=GetAgentArchivalMemoryResponse)
|
|
530
|
+
# def get_agent_archival_memory_all(
|
|
531
|
+
# agent_id: "UUID",
|
|
532
|
+
#
|
|
533
|
+
# server: "SyncServer" = Depends(get_letta_server),
|
|
534
|
+
# ):
|
|
535
|
+
# """
|
|
536
|
+
# Retrieve the memories in an agent's archival memory store (non-paginated, returns all entries at once).
|
|
537
|
+
# """
|
|
538
|
+
# actor = server.get_current_user()
|
|
539
|
+
#
|
|
540
|
+
# archival_memories = server.get_all_archival_memories(user_id=actor.id, agent_id=agent_id)
|
|
541
|
+
# print("archival_memories:", archival_memories)
|
|
542
|
+
# archival_memory_objects = [ArchivalMemoryObject(id=passage["id"], contents=passage["contents"]) for passage in archival_memories]
|
|
543
|
+
# return GetAgentArchivalMemoryResponse(archival_memory=archival_memory_objects)
|