letta-nightly 0.5.1.dev20241105104128__py3-none-any.whl → 0.5.2.dev20241107104040__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 +1 -1
- letta/agent.py +14 -4
- letta/agent_store/db.py +22 -20
- letta/cli/cli.py +14 -1
- letta/client/client.py +27 -14
- letta/constants.py +3 -0
- letta/functions/functions.py +1 -1
- letta/helpers/tool_rule_solver.py +1 -2
- letta/log.py +1 -1
- letta/main.py +3 -0
- letta/metadata.py +2 -0
- letta/orm/agents_tags.py +28 -0
- letta/orm/base.py +5 -2
- letta/orm/mixins.py +2 -53
- letta/orm/organization.py +4 -1
- letta/orm/sqlalchemy_base.py +22 -45
- letta/orm/tool.py +3 -2
- letta/orm/user.py +3 -1
- letta/schemas/agent.py +5 -0
- letta/schemas/agents_tags.py +33 -0
- letta/schemas/block.py +3 -3
- letta/schemas/letta_response.py +110 -0
- letta/schemas/llm_config.py +7 -1
- letta/schemas/memory.py +4 -0
- letta/schemas/organization.py +4 -4
- letta/schemas/tool.py +13 -9
- letta/schemas/tool_rule.py +12 -2
- letta/schemas/user.py +1 -1
- letta/server/rest_api/app.py +4 -1
- letta/server/rest_api/routers/v1/agents.py +7 -122
- letta/server/rest_api/routers/v1/organizations.py +2 -1
- letta/server/rest_api/routers/v1/tools.py +3 -2
- letta/server/rest_api/routers/v1/users.py +14 -2
- letta/server/server.py +75 -44
- letta/services/agents_tags_manager.py +64 -0
- letta/services/organization_manager.py +4 -4
- letta/services/tool_manager.py +22 -30
- letta/services/user_manager.py +3 -3
- {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/METADATA +5 -2
- {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/RECORD +43 -40
- {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/entry_points.txt +0 -0
letta/schemas/agent.py
CHANGED
|
@@ -64,6 +64,9 @@ class AgentState(BaseAgent, validate_assignment=True):
|
|
|
64
64
|
# tool rules
|
|
65
65
|
tool_rules: Optional[List[BaseToolRule]] = Field(default=None, description="The list of tool rules.")
|
|
66
66
|
|
|
67
|
+
# tags
|
|
68
|
+
tags: Optional[List[str]] = Field(None, description="The tags associated with the agent.")
|
|
69
|
+
|
|
67
70
|
# system prompt
|
|
68
71
|
system: str = Field(..., description="The system prompt used by the agent.")
|
|
69
72
|
|
|
@@ -108,6 +111,7 @@ class CreateAgent(BaseAgent):
|
|
|
108
111
|
memory: Optional[Memory] = Field(None, description="The in-context memory of the agent.")
|
|
109
112
|
tools: Optional[List[str]] = Field(None, description="The tools used by the agent.")
|
|
110
113
|
tool_rules: Optional[List[BaseToolRule]] = Field(None, description="The tool rules governing the agent.")
|
|
114
|
+
tags: Optional[List[str]] = Field(None, description="The tags associated with the agent.")
|
|
111
115
|
system: Optional[str] = Field(None, description="The system prompt used by the agent.")
|
|
112
116
|
agent_type: Optional[AgentType] = Field(None, description="The type of agent.")
|
|
113
117
|
llm_config: Optional[LLMConfig] = Field(None, description="The LLM configuration used by the agent.")
|
|
@@ -148,6 +152,7 @@ class UpdateAgentState(BaseAgent):
|
|
|
148
152
|
id: str = Field(..., description="The id of the agent.")
|
|
149
153
|
name: Optional[str] = Field(None, description="The name of the agent.")
|
|
150
154
|
tools: Optional[List[str]] = Field(None, description="The tools used by the agent.")
|
|
155
|
+
tags: Optional[List[str]] = Field(None, description="The tags associated with the agent.")
|
|
151
156
|
system: Optional[str] = Field(None, description="The system prompt used by the agent.")
|
|
152
157
|
llm_config: Optional[LLMConfig] = Field(None, description="The LLM configuration used by the agent.")
|
|
153
158
|
embedding_config: Optional[EmbeddingConfig] = Field(None, description="The embedding configuration used by the agent.")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from letta.schemas.letta_base import LettaBase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentsTagsBase(LettaBase):
|
|
10
|
+
__id_prefix__ = "agents_tags"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgentsTags(AgentsTagsBase):
|
|
14
|
+
"""
|
|
15
|
+
Schema representing the relationship between tags and agents.
|
|
16
|
+
|
|
17
|
+
Parameters:
|
|
18
|
+
agent_id (str): The ID of the associated agent.
|
|
19
|
+
tag_id (str): The ID of the associated tag.
|
|
20
|
+
tag_name (str): The name of the tag.
|
|
21
|
+
created_at (datetime): The date this relationship was created.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
id: str = AgentsTagsBase.generate_id_field()
|
|
25
|
+
agent_id: str = Field(..., description="The ID of the associated agent.")
|
|
26
|
+
tag: str = Field(..., description="The name of the tag.")
|
|
27
|
+
created_at: Optional[datetime] = Field(None, description="The creation date of the association.")
|
|
28
|
+
updated_at: Optional[datetime] = Field(None, description="The update date of the tag.")
|
|
29
|
+
is_deleted: bool = Field(False, description="Whether this tag is deleted or not.")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AgentsTagsCreate(AgentsTagsBase):
|
|
33
|
+
tag: str = Field(..., description="The tag name.")
|
letta/schemas/block.py
CHANGED
|
@@ -18,7 +18,7 @@ class BaseBlock(LettaBase, validate_assignment=True):
|
|
|
18
18
|
limit: int = Field(2000, description="Character limit of the block.")
|
|
19
19
|
|
|
20
20
|
# template data (optional)
|
|
21
|
-
template_name: Optional[str] = Field(None, description="Name of the block if it is a template.")
|
|
21
|
+
template_name: Optional[str] = Field(None, description="Name of the block if it is a template.", alias="name")
|
|
22
22
|
template: bool = Field(False, description="Whether the block is a template (e.g. saved human/persona options).")
|
|
23
23
|
|
|
24
24
|
# context window label
|
|
@@ -58,11 +58,11 @@ class Block(BaseBlock):
|
|
|
58
58
|
A Block represents a reserved section of the LLM's context window which is editable. `Block` objects contained in the `Memory` object, which is able to edit the Block values.
|
|
59
59
|
|
|
60
60
|
Parameters:
|
|
61
|
-
|
|
61
|
+
label (str): The label of the block (e.g. 'human', 'persona'). This defines a category for the block.
|
|
62
62
|
value (str): The value of the block. This is the string that is represented in the context window.
|
|
63
63
|
limit (int): The character limit of the block.
|
|
64
|
+
template_name (str): The name of the block template (if it is a template).
|
|
64
65
|
template (bool): Whether the block is a template (e.g. saved human/persona options). Non-template blocks are not stored in the database and are ephemeral, while templated blocks are stored in the database.
|
|
65
|
-
label (str): The label of the block (e.g. 'human', 'persona'). This defines a category for the block.
|
|
66
66
|
description (str): Description of the block.
|
|
67
67
|
metadata_ (Dict): Metadata of the block.
|
|
68
68
|
user_id (str): The unique identifier of the user associated with the block.
|
letta/schemas/letta_response.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import html
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
1
4
|
from typing import List, Union
|
|
2
5
|
|
|
3
6
|
from pydantic import BaseModel, Field
|
|
@@ -34,6 +37,113 @@ class LettaResponse(BaseModel):
|
|
|
34
37
|
indent=4,
|
|
35
38
|
)
|
|
36
39
|
|
|
40
|
+
def _repr_html_(self):
|
|
41
|
+
def get_formatted_content(msg):
|
|
42
|
+
if msg.message_type == "internal_monologue":
|
|
43
|
+
return f'<div class="content"><span class="internal-monologue">{html.escape(msg.internal_monologue)}</span></div>'
|
|
44
|
+
elif msg.message_type == "function_call":
|
|
45
|
+
args = format_json(msg.function_call.arguments)
|
|
46
|
+
return f'<div class="content"><span class="function-name">{html.escape(msg.function_call.name)}</span>({args})</div>'
|
|
47
|
+
elif msg.message_type == "function_return":
|
|
48
|
+
|
|
49
|
+
return_value = format_json(msg.function_return)
|
|
50
|
+
# return f'<div class="status-line">Status: {html.escape(msg.status)}</div><div class="content">{return_value}</div>'
|
|
51
|
+
return f'<div class="content">{return_value}</div>'
|
|
52
|
+
elif msg.message_type == "user_message":
|
|
53
|
+
if is_json(msg.message):
|
|
54
|
+
return f'<div class="content">{format_json(msg.message)}</div>'
|
|
55
|
+
else:
|
|
56
|
+
return f'<div class="content">{html.escape(msg.message)}</div>'
|
|
57
|
+
elif msg.message_type in ["assistant_message", "system_message"]:
|
|
58
|
+
return f'<div class="content">{html.escape(msg.message)}</div>'
|
|
59
|
+
else:
|
|
60
|
+
return f'<div class="content">{html.escape(str(msg))}</div>'
|
|
61
|
+
|
|
62
|
+
def is_json(string):
|
|
63
|
+
try:
|
|
64
|
+
json.loads(string)
|
|
65
|
+
return True
|
|
66
|
+
except ValueError:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
def format_json(json_str):
|
|
70
|
+
try:
|
|
71
|
+
parsed = json.loads(json_str)
|
|
72
|
+
formatted = json.dumps(parsed, indent=2, ensure_ascii=False)
|
|
73
|
+
formatted = formatted.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
74
|
+
formatted = formatted.replace("\n", "<br>").replace(" ", " ")
|
|
75
|
+
formatted = re.sub(r'(".*?"):', r'<span class="json-key">\1</span>:', formatted)
|
|
76
|
+
formatted = re.sub(r': (".*?")', r': <span class="json-string">\1</span>', formatted)
|
|
77
|
+
formatted = re.sub(r": (\d+)", r': <span class="json-number">\1</span>', formatted)
|
|
78
|
+
formatted = re.sub(r": (true|false)", r': <span class="json-boolean">\1</span>', formatted)
|
|
79
|
+
return formatted
|
|
80
|
+
except json.JSONDecodeError:
|
|
81
|
+
return html.escape(json_str)
|
|
82
|
+
|
|
83
|
+
html_output = """
|
|
84
|
+
<style>
|
|
85
|
+
.message-container, .usage-container {
|
|
86
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
87
|
+
max-width: 800px;
|
|
88
|
+
margin: 20px auto;
|
|
89
|
+
background-color: #1e1e1e;
|
|
90
|
+
border-radius: 8px;
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
color: #d4d4d4;
|
|
93
|
+
}
|
|
94
|
+
.message, .usage-stats {
|
|
95
|
+
padding: 10px 15px;
|
|
96
|
+
border-bottom: 1px solid #3a3a3a;
|
|
97
|
+
}
|
|
98
|
+
.message:last-child, .usage-stats:last-child {
|
|
99
|
+
border-bottom: none;
|
|
100
|
+
}
|
|
101
|
+
.title {
|
|
102
|
+
font-weight: bold;
|
|
103
|
+
margin-bottom: 5px;
|
|
104
|
+
color: #ffffff;
|
|
105
|
+
text-transform: uppercase;
|
|
106
|
+
font-size: 0.9em;
|
|
107
|
+
}
|
|
108
|
+
.content {
|
|
109
|
+
background-color: #2d2d2d;
|
|
110
|
+
border-radius: 4px;
|
|
111
|
+
padding: 5px 10px;
|
|
112
|
+
font-family: 'Consolas', 'Courier New', monospace;
|
|
113
|
+
white-space: pre-wrap;
|
|
114
|
+
}
|
|
115
|
+
.json-key, .function-name, .json-boolean { color: #9cdcfe; }
|
|
116
|
+
.json-string { color: #ce9178; }
|
|
117
|
+
.json-number { color: #b5cea8; }
|
|
118
|
+
.internal-monologue { font-style: italic; }
|
|
119
|
+
</style>
|
|
120
|
+
<div class="message-container">
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
for msg in self.messages:
|
|
124
|
+
content = get_formatted_content(msg)
|
|
125
|
+
title = msg.message_type.replace("_", " ").upper()
|
|
126
|
+
html_output += f"""
|
|
127
|
+
<div class="message">
|
|
128
|
+
<div class="title">{title}</div>
|
|
129
|
+
{content}
|
|
130
|
+
</div>
|
|
131
|
+
"""
|
|
132
|
+
html_output += "</div>"
|
|
133
|
+
|
|
134
|
+
# Formatting the usage statistics
|
|
135
|
+
usage_html = json.dumps(self.usage.model_dump(), indent=2)
|
|
136
|
+
html_output += f"""
|
|
137
|
+
<div class="usage-container">
|
|
138
|
+
<div class="usage-stats">
|
|
139
|
+
<div class="title">USAGE STATISTICS</div>
|
|
140
|
+
<div class="content">{format_json(usage_html)}</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
return html_output
|
|
146
|
+
|
|
37
147
|
|
|
38
148
|
# The streaming response is either [DONE], [DONE_STEP], [DONE], an error, or a LettaMessage
|
|
39
149
|
LettaStreamingResponse = Union[LettaMessage, MessageStreamStatus, LettaUsageStatistics]
|
letta/schemas/llm_config.py
CHANGED
|
@@ -13,7 +13,7 @@ class LLMConfig(BaseModel):
|
|
|
13
13
|
model_endpoint (str): The endpoint for the model.
|
|
14
14
|
model_wrapper (str): The wrapper for the model. This is used to wrap additional text around the input/output of the model. This is useful for text-to-text completions, such as the Completions API in OpenAI.
|
|
15
15
|
context_window (int): The context window size for the model.
|
|
16
|
-
put_inner_thoughts_in_kwargs (bool): Puts
|
|
16
|
+
put_inner_thoughts_in_kwargs (bool): Puts `inner_thoughts` as a kwarg in the function call if this is set to True. This helps with function calling performance and also the generation of inner thoughts.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
# TODO: 🤮 don't default to a vendor! bug city!
|
|
@@ -67,6 +67,12 @@ class LLMConfig(BaseModel):
|
|
|
67
67
|
|
|
68
68
|
@classmethod
|
|
69
69
|
def default_config(cls, model_name: str):
|
|
70
|
+
"""
|
|
71
|
+
Convinience function to generate a default `LLMConfig` from a model name. Only some models are supported in this function.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
model_name (str): The name of the model (gpt-4, gpt-4o-mini, letta).
|
|
75
|
+
"""
|
|
70
76
|
if model_name == "gpt-4":
|
|
71
77
|
return cls(
|
|
72
78
|
model="gpt-4",
|
letta/schemas/memory.py
CHANGED
|
@@ -106,6 +106,10 @@ class Memory(BaseModel, validate_assignment=True):
|
|
|
106
106
|
# New format
|
|
107
107
|
obj.prompt_template = state["prompt_template"]
|
|
108
108
|
for key, value in state["memory"].items():
|
|
109
|
+
# TODO: This is migration code, please take a look at a later time to get rid of this
|
|
110
|
+
if "name" in value:
|
|
111
|
+
value["template_name"] = value["name"]
|
|
112
|
+
value.pop("name")
|
|
109
113
|
obj.memory[key] = Block(**value)
|
|
110
114
|
else:
|
|
111
115
|
# Old format (pre-template)
|
letta/schemas/organization.py
CHANGED
|
@@ -4,16 +4,16 @@ from typing import Optional
|
|
|
4
4
|
from pydantic import Field
|
|
5
5
|
|
|
6
6
|
from letta.schemas.letta_base import LettaBase
|
|
7
|
-
from letta.utils import get_utc_time
|
|
7
|
+
from letta.utils import create_random_username, get_utc_time
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class OrganizationBase(LettaBase):
|
|
11
|
-
__id_prefix__ = "
|
|
11
|
+
__id_prefix__ = "org"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class Organization(OrganizationBase):
|
|
15
|
-
id: str =
|
|
16
|
-
name: str = Field(
|
|
15
|
+
id: str = OrganizationBase.generate_id_field()
|
|
16
|
+
name: str = Field(create_random_username(), description="The name of the organization.")
|
|
17
17
|
created_at: Optional[datetime] = Field(default_factory=get_utc_time, description="The creation date of the organization.")
|
|
18
18
|
|
|
19
19
|
|
letta/schemas/tool.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from typing import Dict, List, Optional
|
|
2
2
|
|
|
3
|
-
from composio import LogLevel
|
|
4
3
|
from pydantic import Field
|
|
5
4
|
|
|
6
5
|
from letta.functions.helpers import (
|
|
@@ -33,21 +32,21 @@ class Tool(BaseTool):
|
|
|
33
32
|
|
|
34
33
|
"""
|
|
35
34
|
|
|
36
|
-
id: str =
|
|
35
|
+
id: str = BaseTool.generate_id_field()
|
|
37
36
|
description: Optional[str] = Field(None, description="The description of the tool.")
|
|
38
37
|
source_type: Optional[str] = Field(None, description="The type of the source code.")
|
|
39
38
|
module: Optional[str] = Field(None, description="The module of the function.")
|
|
40
|
-
organization_id: str = Field(
|
|
41
|
-
name: str = Field(
|
|
42
|
-
tags: List[str] = Field(
|
|
39
|
+
organization_id: Optional[str] = Field(None, description="The unique identifier of the organization associated with the tool.")
|
|
40
|
+
name: Optional[str] = Field(None, description="The name of the function.")
|
|
41
|
+
tags: List[str] = Field([], description="Metadata tags.")
|
|
43
42
|
|
|
44
43
|
# code
|
|
45
44
|
source_code: str = Field(..., description="The source code of the function.")
|
|
46
|
-
json_schema: Dict = Field(
|
|
45
|
+
json_schema: Optional[Dict] = Field(None, description="The JSON schema of the function.")
|
|
47
46
|
|
|
48
47
|
# metadata fields
|
|
49
|
-
created_by_id: str = Field(
|
|
50
|
-
last_updated_by_id: str = Field(
|
|
48
|
+
created_by_id: Optional[str] = Field(None, description="The id of the user that made this Tool.")
|
|
49
|
+
last_updated_by_id: Optional[str] = Field(None, description="The id of the user that made this Tool.")
|
|
51
50
|
|
|
52
51
|
def to_dict(self):
|
|
53
52
|
"""
|
|
@@ -68,7 +67,7 @@ class ToolCreate(LettaBase):
|
|
|
68
67
|
tags: List[str] = Field([], description="Metadata tags.")
|
|
69
68
|
module: Optional[str] = Field(None, description="The source code of the function.")
|
|
70
69
|
source_code: str = Field(..., description="The source code of the function.")
|
|
71
|
-
source_type: str = Field(
|
|
70
|
+
source_type: str = Field("python", description="The source type of the function.")
|
|
72
71
|
json_schema: Optional[Dict] = Field(
|
|
73
72
|
None, description="The JSON schema of the function (auto-generated from source_code if not provided)"
|
|
74
73
|
)
|
|
@@ -86,6 +85,7 @@ class ToolCreate(LettaBase):
|
|
|
86
85
|
Returns:
|
|
87
86
|
Tool: A Letta Tool initialized with attributes derived from the Composio tool.
|
|
88
87
|
"""
|
|
88
|
+
from composio import LogLevel
|
|
89
89
|
from composio_langchain import ComposioToolSet
|
|
90
90
|
|
|
91
91
|
composio_toolset = ComposioToolSet(logging_level=LogLevel.ERROR)
|
|
@@ -216,3 +216,7 @@ class ToolUpdate(LettaBase):
|
|
|
216
216
|
json_schema: Optional[Dict] = Field(
|
|
217
217
|
None, description="The JSON schema of the function (auto-generated from source_code if not provided)"
|
|
218
218
|
)
|
|
219
|
+
|
|
220
|
+
class Config:
|
|
221
|
+
extra = "ignore" # Allows extra fields without validation errors
|
|
222
|
+
# TODO: Remove this, and clean usage of ToolUpdate everywhere else
|
letta/schemas/tool_rule.py
CHANGED
|
@@ -11,15 +11,25 @@ class BaseToolRule(LettaBase):
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class ToolRule(BaseToolRule):
|
|
14
|
+
"""
|
|
15
|
+
A ToolRule represents a tool that can be invoked by the agent.
|
|
16
|
+
"""
|
|
17
|
+
|
|
14
18
|
type: str = Field("ToolRule")
|
|
15
19
|
children: List[str] = Field(..., description="The children tools that can be invoked.")
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
class InitToolRule(BaseToolRule):
|
|
23
|
+
"""
|
|
24
|
+
Represents the initial tool rule configuration.
|
|
25
|
+
"""
|
|
26
|
+
|
|
19
27
|
type: str = Field("InitToolRule")
|
|
20
|
-
"""Represents the initial tool rule configuration."""
|
|
21
28
|
|
|
22
29
|
|
|
23
30
|
class TerminalToolRule(BaseToolRule):
|
|
31
|
+
"""
|
|
32
|
+
Represents a terminal tool rule configuration where if this tool gets called, it must end the agent loop.
|
|
33
|
+
"""
|
|
34
|
+
|
|
24
35
|
type: str = Field("TerminalToolRule")
|
|
25
|
-
"""Represents a terminal tool rule configuration where if this tool gets called, it must end the agent loop."""
|
letta/schemas/user.py
CHANGED
|
@@ -21,7 +21,7 @@ class User(UserBase):
|
|
|
21
21
|
created_at (datetime): The creation date of the user.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
-
id: str =
|
|
24
|
+
id: str = UserBase.generate_id_field()
|
|
25
25
|
organization_id: Optional[str] = Field(OrganizationManager.DEFAULT_ORG_ID, description="The organization id of the user")
|
|
26
26
|
name: str = Field(..., description="The name of the user.")
|
|
27
27
|
created_at: Optional[datetime] = Field(default_factory=datetime.utcnow, description="The creation date of the user.")
|
letta/server/rest_api/app.py
CHANGED
|
@@ -8,6 +8,7 @@ import uvicorn
|
|
|
8
8
|
from fastapi import FastAPI
|
|
9
9
|
from starlette.middleware.cors import CORSMiddleware
|
|
10
10
|
|
|
11
|
+
from letta.__init__ import __version__
|
|
11
12
|
from letta.constants import ADMIN_PREFIX, API_PREFIX, OPENAI_API_PREFIX
|
|
12
13
|
from letta.schemas.letta_response import LettaResponse
|
|
13
14
|
from letta.server.constants import REST_DEFAULT_PORT
|
|
@@ -66,6 +67,7 @@ def create_application() -> "FastAPI":
|
|
|
66
67
|
"""the application start routine"""
|
|
67
68
|
# global server
|
|
68
69
|
# server = SyncServer(default_interface_factory=lambda: interface())
|
|
70
|
+
print(f"\n[[ Letta server // v{__version__} ]]")
|
|
69
71
|
|
|
70
72
|
app = FastAPI(
|
|
71
73
|
swagger_ui_parameters={"docExpansion": "none"},
|
|
@@ -78,6 +80,7 @@ def create_application() -> "FastAPI":
|
|
|
78
80
|
|
|
79
81
|
if "--ade" in sys.argv:
|
|
80
82
|
settings.cors_origins.append("https://app.letta.com")
|
|
83
|
+
print(f"▶ View using ADE at: https://app.letta.com/local-project/agents")
|
|
81
84
|
|
|
82
85
|
app.add_middleware(
|
|
83
86
|
CORSMiddleware,
|
|
@@ -179,7 +182,7 @@ def start_server(
|
|
|
179
182
|
# Add the handler to the logger
|
|
180
183
|
server_logger.addHandler(stream_handler)
|
|
181
184
|
|
|
182
|
-
print(f"
|
|
185
|
+
print(f"▶ Server running at: http://{host or 'localhost'}:{port or REST_DEFAULT_PORT}\n")
|
|
183
186
|
uvicorn.run(
|
|
184
187
|
app,
|
|
185
188
|
host=host or "localhost",
|
|
@@ -40,6 +40,8 @@ router = APIRouter(prefix="/agents", tags=["agents"])
|
|
|
40
40
|
|
|
41
41
|
@router.get("/", response_model=List[AgentState], operation_id="list_agents")
|
|
42
42
|
def list_agents(
|
|
43
|
+
name: Optional[str] = Query(None, description="Name of the agent"),
|
|
44
|
+
tags: Optional[List[str]] = Query(None, description="List of tags to filter agents by"),
|
|
43
45
|
server: "SyncServer" = Depends(get_letta_server),
|
|
44
46
|
user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
45
47
|
):
|
|
@@ -49,7 +51,11 @@ def list_agents(
|
|
|
49
51
|
"""
|
|
50
52
|
actor = server.get_user_or_default(user_id=user_id)
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
agents = server.list_agents(user_id=actor.id, tags=tags)
|
|
55
|
+
# TODO: move this logic to the ORM
|
|
56
|
+
if name:
|
|
57
|
+
agents = [a for a in agents if a.name == name]
|
|
58
|
+
return agents
|
|
53
59
|
|
|
54
60
|
|
|
55
61
|
@router.get("/{agent_id}/context", response_model=ContextWindowOverview, operation_id="get_agent_context_window")
|
|
@@ -529,124 +535,3 @@ async def send_message_to_agent(
|
|
|
529
535
|
|
|
530
536
|
traceback.print_exc()
|
|
531
537
|
raise HTTPException(status_code=500, detail=f"{e}")
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
##### MISSING #######
|
|
535
|
-
|
|
536
|
-
# @router.post("/{agent_id}/command")
|
|
537
|
-
# def run_command(
|
|
538
|
-
# agent_id: "UUID",
|
|
539
|
-
# command: "AgentCommandRequest",
|
|
540
|
-
#
|
|
541
|
-
# server: "SyncServer" = Depends(get_letta_server),
|
|
542
|
-
# ):
|
|
543
|
-
# """
|
|
544
|
-
# Execute a command on a specified agent.
|
|
545
|
-
|
|
546
|
-
# 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.
|
|
547
|
-
|
|
548
|
-
# Raises an HTTPException for any processing errors.
|
|
549
|
-
# """
|
|
550
|
-
# actor = server.get_current_user()
|
|
551
|
-
#
|
|
552
|
-
# response = server.run_command(user_id=actor.id,
|
|
553
|
-
# agent_id=agent_id,
|
|
554
|
-
# command=command.command)
|
|
555
|
-
|
|
556
|
-
# return AgentCommandResponse(response=response)
|
|
557
|
-
|
|
558
|
-
# @router.get("/{agent_id}/config")
|
|
559
|
-
# def get_agent_config(
|
|
560
|
-
# agent_id: "UUID",
|
|
561
|
-
#
|
|
562
|
-
# server: "SyncServer" = Depends(get_letta_server),
|
|
563
|
-
# ):
|
|
564
|
-
# """
|
|
565
|
-
# Retrieve the configuration for a specific agent.
|
|
566
|
-
|
|
567
|
-
# This endpoint fetches the configuration details for a given agent, identified by the user and agent IDs.
|
|
568
|
-
# """
|
|
569
|
-
# actor = server.get_current_user()
|
|
570
|
-
#
|
|
571
|
-
# if not server.ms.get_agent(user_id=actor.id, agent_id=agent_id):
|
|
572
|
-
## agent does not exist
|
|
573
|
-
# raise HTTPException(status_code=404, detail=f"Agent agent_id={agent_id} not found.")
|
|
574
|
-
|
|
575
|
-
# agent_state = server.get_agent_config(user_id=actor.id, agent_id=agent_id)
|
|
576
|
-
## get sources
|
|
577
|
-
# attached_sources = server.list_attached_sources(agent_id=agent_id)
|
|
578
|
-
|
|
579
|
-
## configs
|
|
580
|
-
# llm_config = LLMConfig(**vars(agent_state.llm_config))
|
|
581
|
-
# embedding_config = EmbeddingConfig(**vars(agent_state.embedding_config))
|
|
582
|
-
|
|
583
|
-
# return GetAgentResponse(
|
|
584
|
-
# agent_state=AgentState(
|
|
585
|
-
# id=agent_state.id,
|
|
586
|
-
# name=agent_state.name,
|
|
587
|
-
# user_id=agent_state.user_id,
|
|
588
|
-
# llm_config=llm_config,
|
|
589
|
-
# embedding_config=embedding_config,
|
|
590
|
-
# state=agent_state.state,
|
|
591
|
-
# created_at=int(agent_state.created_at.timestamp()),
|
|
592
|
-
# tools=agent_state.tools,
|
|
593
|
-
# system=agent_state.system,
|
|
594
|
-
# metadata=agent_state._metadata,
|
|
595
|
-
# ),
|
|
596
|
-
# last_run_at=None, # TODO
|
|
597
|
-
# sources=attached_sources,
|
|
598
|
-
# )
|
|
599
|
-
|
|
600
|
-
# @router.patch("/{agent_id}/rename", response_model=GetAgentResponse)
|
|
601
|
-
# def update_agent_name(
|
|
602
|
-
# agent_id: "UUID",
|
|
603
|
-
# agent_rename: AgentRenameRequest,
|
|
604
|
-
#
|
|
605
|
-
# server: "SyncServer" = Depends(get_letta_server),
|
|
606
|
-
# ):
|
|
607
|
-
# """
|
|
608
|
-
# Updates the name of a specific agent.
|
|
609
|
-
|
|
610
|
-
# This changes the name of the agent in the database but does NOT edit the agent's persona.
|
|
611
|
-
# """
|
|
612
|
-
# valid_name = agent_rename.agent_name
|
|
613
|
-
# actor = server.get_current_user()
|
|
614
|
-
#
|
|
615
|
-
# agent_state = server.rename_agent(user_id=actor.id, agent_id=agent_id, new_agent_name=valid_name)
|
|
616
|
-
## get sources
|
|
617
|
-
# attached_sources = server.list_attached_sources(agent_id=agent_id)
|
|
618
|
-
# llm_config = LLMConfig(**vars(agent_state.llm_config))
|
|
619
|
-
# embedding_config = EmbeddingConfig(**vars(agent_state.embedding_config))
|
|
620
|
-
|
|
621
|
-
# return GetAgentResponse(
|
|
622
|
-
# agent_state=AgentState(
|
|
623
|
-
# id=agent_state.id,
|
|
624
|
-
# name=agent_state.name,
|
|
625
|
-
# user_id=agent_state.user_id,
|
|
626
|
-
# llm_config=llm_config,
|
|
627
|
-
# embedding_config=embedding_config,
|
|
628
|
-
# state=agent_state.state,
|
|
629
|
-
# created_at=int(agent_state.created_at.timestamp()),
|
|
630
|
-
# tools=agent_state.tools,
|
|
631
|
-
# system=agent_state.system,
|
|
632
|
-
# ),
|
|
633
|
-
# last_run_at=None, # TODO
|
|
634
|
-
# sources=attached_sources,
|
|
635
|
-
# )
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
# @router.get("/{agent_id}/archival/all", response_model=GetAgentArchivalMemoryResponse)
|
|
639
|
-
# def get_agent_archival_memory_all(
|
|
640
|
-
# agent_id: "UUID",
|
|
641
|
-
#
|
|
642
|
-
# server: "SyncServer" = Depends(get_letta_server),
|
|
643
|
-
# ):
|
|
644
|
-
# """
|
|
645
|
-
# Retrieve the memories in an agent's archival memory store (non-paginated, returns all entries at once).
|
|
646
|
-
# """
|
|
647
|
-
# actor = server.get_current_user()
|
|
648
|
-
#
|
|
649
|
-
# archival_memories = server.get_all_archival_memories(user_id=actor.id, agent_id=agent_id)
|
|
650
|
-
# print("archival_memories:", archival_memories)
|
|
651
|
-
# archival_memory_objects = [ArchivalMemoryObject(id=passage["id"], contents=passage["contents"]) for passage in archival_memories]
|
|
652
|
-
# return GetAgentArchivalMemoryResponse(archival_memory=archival_memory_objects)
|
|
@@ -38,7 +38,8 @@ def create_org(
|
|
|
38
38
|
"""
|
|
39
39
|
Create a new org in the database
|
|
40
40
|
"""
|
|
41
|
-
org =
|
|
41
|
+
org = Organization(**request.model_dump())
|
|
42
|
+
org = server.organization_manager.create_organization(pydantic_org=org)
|
|
42
43
|
return org
|
|
43
44
|
|
|
44
45
|
|
|
@@ -89,7 +89,8 @@ def create_tool(
|
|
|
89
89
|
actor = server.get_user_or_default(user_id=user_id)
|
|
90
90
|
|
|
91
91
|
# Send request to create the tool
|
|
92
|
-
|
|
92
|
+
tool = Tool(**request.model_dump())
|
|
93
|
+
return server.tool_manager.create_or_update_tool(pydantic_tool=tool, actor=actor)
|
|
93
94
|
|
|
94
95
|
|
|
95
96
|
@router.patch("/{tool_id}", response_model=Tool, operation_id="update_tool")
|
|
@@ -103,7 +104,7 @@ def update_tool(
|
|
|
103
104
|
Update an existing tool
|
|
104
105
|
"""
|
|
105
106
|
actor = server.get_user_or_default(user_id=user_id)
|
|
106
|
-
return server.tool_manager.update_tool_by_id(tool_id,
|
|
107
|
+
return server.tool_manager.update_tool_by_id(tool_id=tool_id, tool_update=request, actor=actor)
|
|
107
108
|
|
|
108
109
|
|
|
109
110
|
@router.post("/add-base-tools", response_model=List[Tool], operation_id="add_base_tools")
|
|
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, List, Optional
|
|
|
3
3
|
from fastapi import APIRouter, Body, Depends, HTTPException, Query
|
|
4
4
|
|
|
5
5
|
from letta.schemas.api_key import APIKey, APIKeyCreate
|
|
6
|
-
from letta.schemas.user import User, UserCreate
|
|
6
|
+
from letta.schemas.user import User, UserCreate, UserUpdate
|
|
7
7
|
from letta.server.rest_api.utils import get_letta_server
|
|
8
8
|
|
|
9
9
|
# from letta.server.schemas.users import (
|
|
@@ -51,8 +51,20 @@ def create_user(
|
|
|
51
51
|
"""
|
|
52
52
|
Create a new user in the database
|
|
53
53
|
"""
|
|
54
|
+
user = User(**request.model_dump())
|
|
55
|
+
user = server.user_manager.create_user(user)
|
|
56
|
+
return user
|
|
57
|
+
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
@router.put("/", tags=["admin"], response_model=User, operation_id="update_user")
|
|
60
|
+
def update_user(
|
|
61
|
+
user: UserUpdate = Body(...),
|
|
62
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
63
|
+
):
|
|
64
|
+
"""
|
|
65
|
+
Update a user in the database
|
|
66
|
+
"""
|
|
67
|
+
user = server.user_manager.update_user(user)
|
|
56
68
|
return user
|
|
57
69
|
|
|
58
70
|
|