letta-nightly 0.5.4.dev20241129104215__py3-none-any.whl → 0.5.4.dev20241201104110__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/agent.py +45 -44
- letta/functions/functions.py +40 -9
- letta/functions/schema_generator.py +269 -29
- letta/llm_api/helpers.py +99 -5
- letta/llm_api/openai.py +8 -2
- letta/local_llm/utils.py +1 -1
- letta/server/rest_api/app.py +1 -1
- letta/services/tool_execution_sandbox.py +21 -7
- {letta_nightly-0.5.4.dev20241129104215.dist-info → letta_nightly-0.5.4.dev20241201104110.dist-info}/METADATA +1 -1
- {letta_nightly-0.5.4.dev20241129104215.dist-info → letta_nightly-0.5.4.dev20241201104110.dist-info}/RECORD +13 -13
- {letta_nightly-0.5.4.dev20241129104215.dist-info → letta_nightly-0.5.4.dev20241201104110.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.4.dev20241129104215.dist-info → letta_nightly-0.5.4.dev20241201104110.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.4.dev20241129104215.dist-info → letta_nightly-0.5.4.dev20241201104110.dist-info}/entry_points.txt +0 -0
letta/agent.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import inspect
|
|
3
|
+
import time
|
|
3
4
|
import traceback
|
|
4
5
|
import warnings
|
|
5
6
|
from abc import ABC, abstractmethod
|
|
@@ -566,60 +567,60 @@ class Agent(BaseAgent):
|
|
|
566
567
|
self,
|
|
567
568
|
message_sequence: List[Message],
|
|
568
569
|
function_call: str = "auto",
|
|
569
|
-
first_message: bool = False,
|
|
570
|
+
first_message: bool = False,
|
|
570
571
|
stream: bool = False, # TODO move to config?
|
|
571
|
-
fail_on_empty_response: bool = False,
|
|
572
572
|
empty_response_retry_limit: int = 3,
|
|
573
|
+
backoff_factor: float = 0.5, # delay multiplier for exponential backoff
|
|
574
|
+
max_delay: float = 10.0, # max delay between retries
|
|
573
575
|
) -> ChatCompletionResponse:
|
|
574
|
-
"""Get response from LLM API"""
|
|
575
|
-
|
|
576
|
+
"""Get response from LLM API with robust retry mechanism."""
|
|
577
|
+
|
|
576
578
|
allowed_tool_names = self.tool_rules_solver.get_allowed_tool_names()
|
|
579
|
+
allowed_functions = (
|
|
580
|
+
self.functions if not allowed_tool_names else [func for func in self.functions if func["name"] in allowed_tool_names]
|
|
581
|
+
)
|
|
577
582
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
+
for attempt in range(1, empty_response_retry_limit + 1):
|
|
584
|
+
try:
|
|
585
|
+
response = create(
|
|
586
|
+
llm_config=self.agent_state.llm_config,
|
|
587
|
+
messages=message_sequence,
|
|
588
|
+
user_id=self.agent_state.user_id,
|
|
589
|
+
functions=allowed_functions,
|
|
590
|
+
functions_python=self.functions_python,
|
|
591
|
+
function_call=function_call,
|
|
592
|
+
first_message=first_message,
|
|
593
|
+
stream=stream,
|
|
594
|
+
stream_interface=self.interface,
|
|
595
|
+
)
|
|
583
596
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
llm_config=self.agent_state.llm_config,
|
|
588
|
-
messages=message_sequence,
|
|
589
|
-
user_id=self.agent_state.user_id,
|
|
590
|
-
functions=allowed_functions,
|
|
591
|
-
functions_python=self.functions_python,
|
|
592
|
-
function_call=function_call,
|
|
593
|
-
# hint
|
|
594
|
-
first_message=first_message,
|
|
595
|
-
# streaming
|
|
596
|
-
stream=stream,
|
|
597
|
-
stream_interface=self.interface,
|
|
598
|
-
)
|
|
597
|
+
# These bottom two are retryable
|
|
598
|
+
if len(response.choices) == 0 or response.choices[0] is None:
|
|
599
|
+
raise ValueError(f"API call returned an empty message: {response}")
|
|
599
600
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
message_sequence, function_call, first_message, stream, fail_on_empty_response, empty_response_retry_limit - 1
|
|
609
|
-
)
|
|
601
|
+
if response.choices[0].finish_reason not in ["stop", "function_call", "tool_calls"]:
|
|
602
|
+
if response.choices[0].finish_reason == "length":
|
|
603
|
+
# This is not retryable, hence RuntimeError v.s. ValueError
|
|
604
|
+
raise RuntimeError("Finish reason was length (maximum context length)")
|
|
605
|
+
else:
|
|
606
|
+
raise ValueError(f"Bad finish reason from API: {response.choices[0].finish_reason}")
|
|
607
|
+
|
|
608
|
+
return response
|
|
610
609
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
610
|
+
except ValueError as ve:
|
|
611
|
+
if attempt >= empty_response_retry_limit:
|
|
612
|
+
warnings.warn(f"Retry limit reached. Final error: {ve}")
|
|
613
|
+
break
|
|
614
|
+
else:
|
|
615
|
+
delay = min(backoff_factor * (2 ** (attempt - 1)), max_delay)
|
|
616
|
+
warnings.warn(f"Attempt {attempt} failed: {ve}. Retrying in {delay} seconds...")
|
|
617
|
+
time.sleep(delay)
|
|
614
618
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
raise
|
|
619
|
+
except Exception as e:
|
|
620
|
+
# For non-retryable errors, exit immediately
|
|
621
|
+
raise e
|
|
618
622
|
|
|
619
|
-
|
|
620
|
-
return response
|
|
621
|
-
except Exception as e:
|
|
622
|
-
raise e
|
|
623
|
+
raise Exception("Retries exhausted and no valid response received.")
|
|
623
624
|
|
|
624
625
|
def _handle_ai_response(
|
|
625
626
|
self,
|
letta/functions/functions.py
CHANGED
|
@@ -11,23 +11,54 @@ from letta.functions.schema_generator import generate_schema
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def derive_openai_json_schema(source_code: str, name: Optional[str] = None) -> dict:
|
|
14
|
-
|
|
14
|
+
"""Derives the OpenAI JSON schema for a given function source code.
|
|
15
|
+
|
|
16
|
+
First, attempts to execute the source code in a custom environment with only the necessary imports.
|
|
17
|
+
Then, it generates the schema from the function's docstring and signature.
|
|
18
|
+
"""
|
|
15
19
|
try:
|
|
16
20
|
# Define a custom environment with necessary imports
|
|
17
|
-
env = {
|
|
18
|
-
|
|
21
|
+
env = {
|
|
22
|
+
"Optional": Optional,
|
|
23
|
+
"List": List,
|
|
24
|
+
"Dict": Dict,
|
|
25
|
+
# To support Pydantic models
|
|
26
|
+
# "BaseModel": BaseModel,
|
|
27
|
+
# "Field": Field,
|
|
28
|
+
}
|
|
19
29
|
env.update(globals())
|
|
30
|
+
|
|
31
|
+
# print("About to execute source code...")
|
|
20
32
|
exec(source_code, env)
|
|
33
|
+
# print("Source code executed successfully")
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
functions = [f for f in env if callable(env[f]) and not f.startswith("__")]
|
|
36
|
+
if not functions:
|
|
37
|
+
raise LettaToolCreateError("No callable functions found in source code")
|
|
24
38
|
|
|
25
|
-
#
|
|
39
|
+
# print(f"Found functions: {functions}")
|
|
26
40
|
func = env[functions[-1]]
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
|
|
42
|
+
if not hasattr(func, "__doc__") or not func.__doc__:
|
|
43
|
+
raise LettaToolCreateError(f"Function {func.__name__} missing docstring")
|
|
44
|
+
|
|
45
|
+
# print("About to generate schema...")
|
|
46
|
+
try:
|
|
47
|
+
schema = generate_schema(func, name=name)
|
|
48
|
+
# print("Schema generated successfully")
|
|
49
|
+
return schema
|
|
50
|
+
except TypeError as e:
|
|
51
|
+
raise LettaToolCreateError(f"Type error in schema generation: {str(e)}")
|
|
52
|
+
except ValueError as e:
|
|
53
|
+
raise LettaToolCreateError(f"Value error in schema generation: {str(e)}")
|
|
54
|
+
except Exception as e:
|
|
55
|
+
raise LettaToolCreateError(f"Unexpected error in schema generation: {str(e)}")
|
|
56
|
+
|
|
29
57
|
except Exception as e:
|
|
30
|
-
|
|
58
|
+
import traceback
|
|
59
|
+
|
|
60
|
+
traceback.print_exc()
|
|
61
|
+
raise LettaToolCreateError(f"Schema generation failed: {str(e)}") from e
|
|
31
62
|
|
|
32
63
|
|
|
33
64
|
def parse_source_code(func) -> str:
|
|
@@ -22,7 +22,7 @@ def optional_length(annotation):
|
|
|
22
22
|
raise ValueError("The annotation is not an Optional type")
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def type_to_json_schema_type(py_type):
|
|
25
|
+
def type_to_json_schema_type(py_type) -> dict:
|
|
26
26
|
"""
|
|
27
27
|
Maps a Python type to a JSON schema type.
|
|
28
28
|
Specifically handles typing.Optional and common Python types.
|
|
@@ -36,36 +36,87 @@ def type_to_json_schema_type(py_type):
|
|
|
36
36
|
# Extract and map the inner type
|
|
37
37
|
return type_to_json_schema_type(type_args[0])
|
|
38
38
|
|
|
39
|
+
# Handle Union types (except Optional which is handled above)
|
|
40
|
+
if get_origin(py_type) is Union:
|
|
41
|
+
# TODO support mapping Unions to anyOf
|
|
42
|
+
raise NotImplementedError("General Union types are not yet supported")
|
|
43
|
+
|
|
44
|
+
# Handle array types
|
|
45
|
+
origin = get_origin(py_type)
|
|
46
|
+
if py_type == list or origin in (list, List):
|
|
47
|
+
args = get_args(py_type)
|
|
48
|
+
|
|
49
|
+
if args and inspect.isclass(args[0]) and issubclass(args[0], BaseModel):
|
|
50
|
+
# If it's a list of Pydantic models, return an array with the model schema as items
|
|
51
|
+
return {
|
|
52
|
+
"type": "array",
|
|
53
|
+
"items": pydantic_model_to_json_schema(args[0]),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Otherwise, recursively call the basic type checker
|
|
57
|
+
return {
|
|
58
|
+
"type": "array",
|
|
59
|
+
# get the type of the items in the list
|
|
60
|
+
"items": type_to_json_schema_type(args[0]),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Handle object types
|
|
64
|
+
if py_type == dict or origin in (dict, Dict):
|
|
65
|
+
args = get_args(py_type)
|
|
66
|
+
if not args:
|
|
67
|
+
# Generic dict without type arguments
|
|
68
|
+
return {
|
|
69
|
+
"type": "object",
|
|
70
|
+
# "properties": {}
|
|
71
|
+
}
|
|
72
|
+
else:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"Dictionary types {py_type} with nested type arguments are not supported (consider using a Pydantic model instead)"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# NOTE: the below code works for generic JSON schema parsing, but there's a problem with the key inference
|
|
78
|
+
# when it comes to OpenAI function schema generation so it doesn't make sense to allow for dict[str, Any] type hints
|
|
79
|
+
# key_type, value_type = args
|
|
80
|
+
|
|
81
|
+
# # Ensure dict keys are strings
|
|
82
|
+
# # Otherwise there's no JSON schema equivalent
|
|
83
|
+
# if key_type != str:
|
|
84
|
+
# raise ValueError("Dictionary keys must be strings for OpenAI function schema compatibility")
|
|
85
|
+
|
|
86
|
+
# # Handle value type to determine property schema
|
|
87
|
+
# value_schema = {}
|
|
88
|
+
# if inspect.isclass(value_type) and issubclass(value_type, BaseModel):
|
|
89
|
+
# value_schema = pydantic_model_to_json_schema(value_type)
|
|
90
|
+
# else:
|
|
91
|
+
# value_schema = type_to_json_schema_type(value_type)
|
|
92
|
+
|
|
93
|
+
# # NOTE: the problem lies here - the key is always "key_placeholder"
|
|
94
|
+
# return {"type": "object", "properties": {"key_placeholder": value_schema}}
|
|
95
|
+
|
|
96
|
+
# Handle direct Pydantic models
|
|
97
|
+
if inspect.isclass(py_type) and issubclass(py_type, BaseModel):
|
|
98
|
+
return pydantic_model_to_json_schema(py_type)
|
|
99
|
+
|
|
39
100
|
# Mapping of Python types to JSON schema types
|
|
40
101
|
type_map = {
|
|
41
102
|
# Basic types
|
|
103
|
+
# Optional, Union, and collections are handled above ^
|
|
42
104
|
int: "integer",
|
|
43
105
|
str: "string",
|
|
44
106
|
bool: "boolean",
|
|
45
107
|
float: "number",
|
|
46
|
-
# Collections
|
|
47
|
-
List[str]: "array",
|
|
48
|
-
List[int]: "array",
|
|
49
|
-
list: "array",
|
|
50
|
-
tuple: "array",
|
|
51
|
-
set: "array",
|
|
52
|
-
# Dictionaries
|
|
53
|
-
dict: "object",
|
|
54
|
-
Dict[str, Any]: "object",
|
|
55
|
-
# Special types
|
|
56
108
|
None: "null",
|
|
57
|
-
type(None): "null",
|
|
58
|
-
# Optional types
|
|
59
|
-
# Optional[str]: "string", # NOTE: caught above ^
|
|
60
|
-
Union[str, None]: "string",
|
|
61
109
|
}
|
|
62
110
|
if py_type not in type_map:
|
|
63
111
|
raise ValueError(f"Python type {py_type} has no corresponding JSON schema type - full map: {type_map}")
|
|
64
|
-
|
|
65
|
-
|
|
112
|
+
else:
|
|
113
|
+
return {"type": type_map[py_type]}
|
|
66
114
|
|
|
67
115
|
|
|
68
|
-
def pydantic_model_to_open_ai(model):
|
|
116
|
+
def pydantic_model_to_open_ai(model: Type[BaseModel]) -> dict:
|
|
117
|
+
"""
|
|
118
|
+
Converts a Pydantic model as a singular arg to a JSON schema object for use in OpenAI function calling.
|
|
119
|
+
"""
|
|
69
120
|
schema = model.model_json_schema()
|
|
70
121
|
docstring = parse(model.__doc__ or "")
|
|
71
122
|
parameters = {k: v for k, v in schema.items() if k not in ("title", "description")}
|
|
@@ -80,7 +131,7 @@ def pydantic_model_to_open_ai(model):
|
|
|
80
131
|
if docstring.short_description:
|
|
81
132
|
schema["description"] = docstring.short_description
|
|
82
133
|
else:
|
|
83
|
-
raise
|
|
134
|
+
raise ValueError(f"No description found in docstring or description field (model: {model}, docstring: {docstring})")
|
|
84
135
|
|
|
85
136
|
return {
|
|
86
137
|
"name": schema["title"],
|
|
@@ -89,6 +140,159 @@ def pydantic_model_to_open_ai(model):
|
|
|
89
140
|
}
|
|
90
141
|
|
|
91
142
|
|
|
143
|
+
def pydantic_model_to_json_schema(model: Type[BaseModel]) -> dict:
|
|
144
|
+
"""
|
|
145
|
+
Converts a Pydantic model (as an arg that already is annotated) to a JSON schema object for use in OpenAI function calling.
|
|
146
|
+
|
|
147
|
+
An example of a Pydantic model as an arg:
|
|
148
|
+
|
|
149
|
+
class Step(BaseModel):
|
|
150
|
+
name: str = Field(
|
|
151
|
+
...,
|
|
152
|
+
description="Name of the step.",
|
|
153
|
+
)
|
|
154
|
+
key: str = Field(
|
|
155
|
+
...,
|
|
156
|
+
description="Unique identifier for the step.",
|
|
157
|
+
)
|
|
158
|
+
description: str = Field(
|
|
159
|
+
...,
|
|
160
|
+
description="An exhaustic description of what this step is trying to achieve and accomplish.",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def create_task_plan(steps: list[Step]):
|
|
164
|
+
'''
|
|
165
|
+
Creates a task plan for the current task.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
steps: List of steps to add to the task plan.
|
|
169
|
+
...
|
|
170
|
+
|
|
171
|
+
Should result in:
|
|
172
|
+
{
|
|
173
|
+
"name": "create_task_plan",
|
|
174
|
+
"description": "Creates a task plan for the current task.",
|
|
175
|
+
"parameters": {
|
|
176
|
+
"type": "object",
|
|
177
|
+
"properties": {
|
|
178
|
+
"steps": { # <= this is the name of the arg
|
|
179
|
+
"type": "object",
|
|
180
|
+
"description": "List of steps to add to the task plan.",
|
|
181
|
+
"properties": {
|
|
182
|
+
"name": {
|
|
183
|
+
"type": "str",
|
|
184
|
+
"description": "Name of the step.",
|
|
185
|
+
},
|
|
186
|
+
"key": {
|
|
187
|
+
"type": "str",
|
|
188
|
+
"description": "Unique identifier for the step.",
|
|
189
|
+
},
|
|
190
|
+
"description": {
|
|
191
|
+
"type": "str",
|
|
192
|
+
"description": "An exhaustic description of what this step is trying to achieve and accomplish.",
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
"required": ["name", "key", "description"],
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
"required": ["steps"],
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Specifically, the result of pydantic_model_to_json_schema(steps) (where `steps` is an instance of BaseModel) is:
|
|
203
|
+
{
|
|
204
|
+
"type": "object",
|
|
205
|
+
"properties": {
|
|
206
|
+
"name": {
|
|
207
|
+
"type": "str",
|
|
208
|
+
"description": "Name of the step."
|
|
209
|
+
},
|
|
210
|
+
"key": {
|
|
211
|
+
"type": "str",
|
|
212
|
+
"description": "Unique identifier for the step."
|
|
213
|
+
},
|
|
214
|
+
"description": {
|
|
215
|
+
"type": "str",
|
|
216
|
+
"description": "An exhaustic description of what this step is trying to achieve and accomplish."
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
"required": ["name", "key", "description"],
|
|
220
|
+
}
|
|
221
|
+
"""
|
|
222
|
+
schema = model.model_json_schema()
|
|
223
|
+
|
|
224
|
+
def clean_property(prop: dict) -> dict:
|
|
225
|
+
"""Clean up a property schema to match desired format"""
|
|
226
|
+
|
|
227
|
+
if "description" not in prop:
|
|
228
|
+
raise ValueError(f"Property {prop} lacks a 'description' key")
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
"type": "string" if prop["type"] == "string" else prop["type"],
|
|
232
|
+
"description": prop["description"],
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
def resolve_ref(ref: str, schema: dict) -> dict:
|
|
236
|
+
"""Resolve a $ref reference in the schema"""
|
|
237
|
+
if not ref.startswith("#/$defs/"):
|
|
238
|
+
raise ValueError(f"Unexpected reference format: {ref}")
|
|
239
|
+
|
|
240
|
+
model_name = ref.split("/")[-1]
|
|
241
|
+
if model_name not in schema.get("$defs", {}):
|
|
242
|
+
raise ValueError(f"Reference {model_name} not found in schema definitions")
|
|
243
|
+
|
|
244
|
+
return schema["$defs"][model_name]
|
|
245
|
+
|
|
246
|
+
def clean_schema(schema_part: dict, full_schema: dict) -> dict:
|
|
247
|
+
"""Clean up a schema part, handling references and nested structures"""
|
|
248
|
+
# Handle $ref
|
|
249
|
+
if "$ref" in schema_part:
|
|
250
|
+
schema_part = resolve_ref(schema_part["$ref"], full_schema)
|
|
251
|
+
|
|
252
|
+
if "type" not in schema_part:
|
|
253
|
+
raise ValueError(f"Schema part lacks a 'type' key: {schema_part}")
|
|
254
|
+
|
|
255
|
+
# Handle array type
|
|
256
|
+
if schema_part["type"] == "array":
|
|
257
|
+
items_schema = schema_part["items"]
|
|
258
|
+
if "$ref" in items_schema:
|
|
259
|
+
items_schema = resolve_ref(items_schema["$ref"], full_schema)
|
|
260
|
+
return {"type": "array", "items": clean_schema(items_schema, full_schema), "description": schema_part.get("description", "")}
|
|
261
|
+
|
|
262
|
+
# Handle object type
|
|
263
|
+
if schema_part["type"] == "object":
|
|
264
|
+
if "properties" not in schema_part:
|
|
265
|
+
raise ValueError(f"Object schema lacks 'properties' key: {schema_part}")
|
|
266
|
+
|
|
267
|
+
properties = {}
|
|
268
|
+
for name, prop in schema_part["properties"].items():
|
|
269
|
+
if "items" in prop: # Handle arrays
|
|
270
|
+
if "description" not in prop:
|
|
271
|
+
raise ValueError(f"Property {prop} lacks a 'description' key")
|
|
272
|
+
properties[name] = {
|
|
273
|
+
"type": "array",
|
|
274
|
+
"items": clean_schema(prop["items"], full_schema),
|
|
275
|
+
"description": prop["description"],
|
|
276
|
+
}
|
|
277
|
+
else:
|
|
278
|
+
properties[name] = clean_property(prop)
|
|
279
|
+
|
|
280
|
+
pydantic_model_schema_dict = {
|
|
281
|
+
"type": "object",
|
|
282
|
+
"properties": properties,
|
|
283
|
+
"required": schema_part.get("required", []),
|
|
284
|
+
}
|
|
285
|
+
if "description" in schema_part:
|
|
286
|
+
pydantic_model_schema_dict["description"] = schema_part["description"]
|
|
287
|
+
|
|
288
|
+
return pydantic_model_schema_dict
|
|
289
|
+
|
|
290
|
+
# Handle primitive types
|
|
291
|
+
return clean_property(schema_part)
|
|
292
|
+
|
|
293
|
+
return clean_schema(schema_part=schema, full_schema=schema)
|
|
294
|
+
|
|
295
|
+
|
|
92
296
|
def generate_schema(function, name: Optional[str] = None, description: Optional[str] = None) -> dict:
|
|
93
297
|
# Get the signature of the function
|
|
94
298
|
sig = inspect.signature(function)
|
|
@@ -126,24 +330,60 @@ def generate_schema(function, name: Optional[str] = None, description: Optional[
|
|
|
126
330
|
if not param_doc or not param_doc.description:
|
|
127
331
|
raise ValueError(f"Parameter '{param.name}' in function '{function.__name__}' lacks a description in the docstring")
|
|
128
332
|
|
|
129
|
-
|
|
130
|
-
|
|
333
|
+
# If the parameter is a pydantic model, we need to unpack the Pydantic model type into a JSON schema object
|
|
334
|
+
# if inspect.isclass(param.annotation) and issubclass(param.annotation, BaseModel):
|
|
335
|
+
if (
|
|
336
|
+
(inspect.isclass(param.annotation) or inspect.isclass(get_origin(param.annotation) or param.annotation))
|
|
337
|
+
and not get_origin(param.annotation)
|
|
338
|
+
and issubclass(param.annotation, BaseModel)
|
|
339
|
+
):
|
|
340
|
+
# print("Generating schema for pydantic model:", param.annotation)
|
|
341
|
+
# Extract the properties from the pydantic model
|
|
342
|
+
schema["parameters"]["properties"][param.name] = pydantic_model_to_json_schema(param.annotation)
|
|
343
|
+
schema["parameters"]["properties"][param.name]["description"] = param_doc.description
|
|
344
|
+
|
|
345
|
+
# Otherwise, we convert the Python typing to JSON schema types
|
|
346
|
+
# NOTE: important - if a dict or list, the internal type can be a Pydantic model itself
|
|
347
|
+
# however in that
|
|
131
348
|
else:
|
|
132
|
-
#
|
|
349
|
+
# print("Generating schema for non-pydantic model:", param.annotation)
|
|
350
|
+
# Grab the description for the parameter from the extended docstring
|
|
351
|
+
# If it doesn't exist, we should raise an error
|
|
133
352
|
param_doc = next((d for d in docstring.params if d.arg_name == param.name), None)
|
|
134
|
-
if param_doc:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"description
|
|
139
|
-
|
|
140
|
-
|
|
353
|
+
if not param_doc:
|
|
354
|
+
raise ValueError(f"Parameter '{param.name}' in function '{function.__name__}' lacks a description in the docstring")
|
|
355
|
+
elif not isinstance(param_doc.description, str):
|
|
356
|
+
raise ValueError(
|
|
357
|
+
f"Parameter '{param.name}' in function '{function.__name__}' has a description in the docstring that is not a string (type: {type(param_doc.description)})"
|
|
358
|
+
)
|
|
359
|
+
else:
|
|
360
|
+
# If it's a string or a basic type, then all you need is: (1) type, (2) description
|
|
361
|
+
# If it's a more complex type, then you also need either:
|
|
362
|
+
# - for array, you need "items", each of which has "type"
|
|
363
|
+
# - for a dict, you need "properties", which has keys which each have "type"
|
|
364
|
+
if param.annotation != inspect.Parameter.empty:
|
|
365
|
+
param_generated_schema = type_to_json_schema_type(param.annotation)
|
|
366
|
+
else:
|
|
367
|
+
# TODO why are we inferring here?
|
|
368
|
+
param_generated_schema = {"type": "string"}
|
|
369
|
+
|
|
370
|
+
# Add in the description
|
|
371
|
+
param_generated_schema["description"] = param_doc.description
|
|
372
|
+
|
|
373
|
+
# Add the schema to the function arg key
|
|
374
|
+
schema["parameters"]["properties"][param.name] = param_generated_schema
|
|
375
|
+
|
|
376
|
+
# If the parameter doesn't have a default value, it is required (so we need to add it to the required list)
|
|
377
|
+
if param.default == inspect.Parameter.empty and not is_optional(param.annotation):
|
|
141
378
|
schema["parameters"]["required"].append(param.name)
|
|
142
379
|
|
|
380
|
+
# TODO what's going on here?
|
|
381
|
+
# If the parameter is a list of strings we need to hard cast to "string" instead of `str`
|
|
143
382
|
if get_origin(param.annotation) is list:
|
|
144
383
|
if get_args(param.annotation)[0] is str:
|
|
145
384
|
schema["parameters"]["properties"][param.name]["items"] = {"type": "string"}
|
|
146
385
|
|
|
386
|
+
# TODO is this not duplicating the other append directly above?
|
|
147
387
|
if param.annotation == inspect.Parameter.empty:
|
|
148
388
|
schema["parameters"]["required"].append(param.name)
|
|
149
389
|
|
letta/llm_api/helpers.py
CHANGED
|
@@ -11,7 +11,55 @@ from letta.schemas.openai.chat_completion_response import ChatCompletionResponse
|
|
|
11
11
|
from letta.utils import json_dumps, printd
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def
|
|
14
|
+
def _convert_to_structured_output_helper(property: dict) -> dict:
|
|
15
|
+
"""Convert a single JSON schema property to structured output format (recursive)"""
|
|
16
|
+
|
|
17
|
+
if "type" not in property:
|
|
18
|
+
raise ValueError(f"Property {property} is missing a type")
|
|
19
|
+
param_type = property["type"]
|
|
20
|
+
|
|
21
|
+
if "description" not in property:
|
|
22
|
+
# raise ValueError(f"Property {property} is missing a description")
|
|
23
|
+
param_description = None
|
|
24
|
+
else:
|
|
25
|
+
param_description = property["description"]
|
|
26
|
+
|
|
27
|
+
if param_type == "object":
|
|
28
|
+
if "properties" not in property:
|
|
29
|
+
raise ValueError(f"Property {property} of type object is missing properties")
|
|
30
|
+
properties = property["properties"]
|
|
31
|
+
property_dict = {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"properties": {k: _convert_to_structured_output_helper(v) for k, v in properties.items()},
|
|
34
|
+
"additionalProperties": False,
|
|
35
|
+
"required": list(properties.keys()),
|
|
36
|
+
}
|
|
37
|
+
if param_description is not None:
|
|
38
|
+
property_dict["description"] = param_description
|
|
39
|
+
return property_dict
|
|
40
|
+
|
|
41
|
+
elif param_type == "array":
|
|
42
|
+
if "items" not in property:
|
|
43
|
+
raise ValueError(f"Property {property} of type array is missing items")
|
|
44
|
+
items = property["items"]
|
|
45
|
+
property_dict = {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": _convert_to_structured_output_helper(items),
|
|
48
|
+
}
|
|
49
|
+
if param_description is not None:
|
|
50
|
+
property_dict["description"] = param_description
|
|
51
|
+
return property_dict
|
|
52
|
+
|
|
53
|
+
else:
|
|
54
|
+
property_dict = {
|
|
55
|
+
"type": param_type, # simple type
|
|
56
|
+
}
|
|
57
|
+
if param_description is not None:
|
|
58
|
+
property_dict["description"] = param_description
|
|
59
|
+
return property_dict
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def convert_to_structured_output(openai_function: dict, allow_optional: bool = False) -> dict:
|
|
15
63
|
"""Convert function call objects to structured output objects
|
|
16
64
|
|
|
17
65
|
See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas
|
|
@@ -22,17 +70,63 @@ def convert_to_structured_output(openai_function: dict) -> dict:
|
|
|
22
70
|
"name": openai_function["name"],
|
|
23
71
|
"description": description,
|
|
24
72
|
"strict": True,
|
|
25
|
-
"parameters": {
|
|
73
|
+
"parameters": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"properties": {},
|
|
76
|
+
"additionalProperties": False,
|
|
77
|
+
"required": [],
|
|
78
|
+
},
|
|
26
79
|
}
|
|
27
80
|
|
|
81
|
+
# This code needs to be able to handle nested properties
|
|
82
|
+
# For example, the param details may have "type" + "description",
|
|
83
|
+
# but if "type" is "object" we expected "properties", where each property has details
|
|
84
|
+
# and if "type" is "array" we expect "items": <type>
|
|
28
85
|
for param, details in openai_function["parameters"]["properties"].items():
|
|
29
|
-
|
|
86
|
+
|
|
87
|
+
param_type = details["type"]
|
|
88
|
+
description = details["description"]
|
|
89
|
+
|
|
90
|
+
if param_type == "object":
|
|
91
|
+
if "properties" not in details:
|
|
92
|
+
# Structured outputs requires the properties on dicts be specified ahead of time
|
|
93
|
+
raise ValueError(f"Property {param} of type object is missing properties")
|
|
94
|
+
structured_output["parameters"]["properties"][param] = {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"description": description,
|
|
97
|
+
"properties": {k: _convert_to_structured_output_helper(v) for k, v in details["properties"].items()},
|
|
98
|
+
"additionalProperties": False,
|
|
99
|
+
"required": list(details["properties"].keys()),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
elif param_type == "array":
|
|
103
|
+
structured_output["parameters"]["properties"][param] = {
|
|
104
|
+
"type": "array",
|
|
105
|
+
"description": description,
|
|
106
|
+
"items": _convert_to_structured_output_helper(details["items"]),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
else:
|
|
110
|
+
structured_output["parameters"]["properties"][param] = {
|
|
111
|
+
"type": param_type, # simple type
|
|
112
|
+
"description": description,
|
|
113
|
+
}
|
|
30
114
|
|
|
31
115
|
if "enum" in details:
|
|
32
116
|
structured_output["parameters"]["properties"][param]["enum"] = details["enum"]
|
|
33
117
|
|
|
34
|
-
|
|
35
|
-
|
|
118
|
+
if not allow_optional:
|
|
119
|
+
# Add all properties to required list
|
|
120
|
+
structured_output["parameters"]["required"] = list(structured_output["parameters"]["properties"].keys())
|
|
121
|
+
|
|
122
|
+
else:
|
|
123
|
+
# See what parameters exist that aren't required
|
|
124
|
+
# Those are implied "optional" types
|
|
125
|
+
# For those types, turn each of them into a union type with "null"
|
|
126
|
+
# e.g.
|
|
127
|
+
# "type": "string" -> "type": ["string", "null"]
|
|
128
|
+
# TODO
|
|
129
|
+
raise NotImplementedError
|
|
36
130
|
|
|
37
131
|
return structured_output
|
|
38
132
|
|
letta/llm_api/openai.py
CHANGED
|
@@ -477,7 +477,10 @@ def openai_chat_completions_request_stream(
|
|
|
477
477
|
if "tools" in data:
|
|
478
478
|
for tool in data["tools"]:
|
|
479
479
|
# tool["strict"] = True
|
|
480
|
-
|
|
480
|
+
try:
|
|
481
|
+
tool["function"] = convert_to_structured_output(tool["function"])
|
|
482
|
+
except ValueError as e:
|
|
483
|
+
warnings.warn(f"Failed to convert tool function to structured output, tool={tool}, error={e}")
|
|
481
484
|
|
|
482
485
|
# print(f"\n\n\n\nData[tools]: {json.dumps(data['tools'], indent=2)}")
|
|
483
486
|
|
|
@@ -533,7 +536,10 @@ def openai_chat_completions_request(
|
|
|
533
536
|
|
|
534
537
|
if "tools" in data:
|
|
535
538
|
for tool in data["tools"]:
|
|
536
|
-
|
|
539
|
+
try:
|
|
540
|
+
tool["function"] = convert_to_structured_output(tool["function"])
|
|
541
|
+
except ValueError as e:
|
|
542
|
+
warnings.warn(f"Failed to convert tool function to structured output, tool={tool}, error={e}")
|
|
537
543
|
|
|
538
544
|
response_json = make_post_request(url, headers, data)
|
|
539
545
|
return ChatCompletionResponse(**response_json)
|
letta/local_llm/utils.py
CHANGED
|
@@ -121,7 +121,7 @@ def num_tokens_from_functions(functions: List[dict], model: str = "gpt-4"):
|
|
|
121
121
|
function_tokens += 3
|
|
122
122
|
function_tokens += len(encoding.encode(o))
|
|
123
123
|
else:
|
|
124
|
-
|
|
124
|
+
warnings.warn(f"num_tokens_from_functions: Unsupported field {field} in function {function}")
|
|
125
125
|
function_tokens += 11
|
|
126
126
|
|
|
127
127
|
num_tokens += function_tokens
|
letta/server/rest_api/app.py
CHANGED
|
@@ -134,7 +134,7 @@ def create_application() -> "FastAPI":
|
|
|
134
134
|
|
|
135
135
|
if "--ade" in sys.argv:
|
|
136
136
|
settings.cors_origins.append("https://app.letta.com")
|
|
137
|
-
print(f"▶ View using ADE at: https://app.letta.com/
|
|
137
|
+
print(f"▶ View using ADE at: https://app.letta.com/development-servers/local/dashboard")
|
|
138
138
|
|
|
139
139
|
if "--secure" in sys.argv:
|
|
140
140
|
print(f"▶ Using secure mode with password: {random_password}")
|
|
@@ -276,6 +276,25 @@ class ToolExecutionSandbox:
|
|
|
276
276
|
|
|
277
277
|
return code
|
|
278
278
|
|
|
279
|
+
def _convert_param_to_value(self, param_type: str, raw_value: str) -> str:
|
|
280
|
+
|
|
281
|
+
if param_type == "string":
|
|
282
|
+
value = '"' + raw_value + '"'
|
|
283
|
+
|
|
284
|
+
elif param_type == "integer" or param_type == "boolean" or param_type == "number":
|
|
285
|
+
value = raw_value
|
|
286
|
+
|
|
287
|
+
elif param_type == "array":
|
|
288
|
+
value = raw_value
|
|
289
|
+
|
|
290
|
+
elif param_type == "object":
|
|
291
|
+
value = raw_value
|
|
292
|
+
|
|
293
|
+
else:
|
|
294
|
+
raise TypeError(f"Unsupported type: {param_type}, raw_value={raw_value}")
|
|
295
|
+
|
|
296
|
+
return str(value)
|
|
297
|
+
|
|
279
298
|
def initialize_param(self, name: str, raw_value: str) -> str:
|
|
280
299
|
params = self.tool.json_schema["parameters"]["properties"]
|
|
281
300
|
spec = params.get(name)
|
|
@@ -287,14 +306,9 @@ class ToolExecutionSandbox:
|
|
|
287
306
|
if param_type is None and spec.get("parameters"):
|
|
288
307
|
param_type = spec["parameters"].get("type")
|
|
289
308
|
|
|
290
|
-
|
|
291
|
-
value = '"' + raw_value + '"'
|
|
292
|
-
elif param_type == "integer" or param_type == "boolean":
|
|
293
|
-
value = raw_value
|
|
294
|
-
else:
|
|
295
|
-
raise TypeError(f"unsupported type: {param_type}")
|
|
309
|
+
value = self._convert_param_to_value(param_type, raw_value)
|
|
296
310
|
|
|
297
|
-
return name + " = " +
|
|
311
|
+
return name + " = " + value + "\n"
|
|
298
312
|
|
|
299
313
|
def invoke_function_call(self, inject_agent_state: bool) -> str:
|
|
300
314
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
letta/__init__.py,sha256=FGDVS-TIQ5GQN2d3DaYbDaEaDCH54TpwzX5lSn60KwY,1035
|
|
2
2
|
letta/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
|
|
3
|
-
letta/agent.py,sha256
|
|
3
|
+
letta/agent.py,sha256=9M0emIYjOlE3ZlJJYReDYZO84Atnd1ds7Je5KOEYcjE,77519
|
|
4
4
|
letta/agent_store/chroma.py,sha256=-kCEMBFKmqCyFeIETYf7RN-khGddsip2FAhSzNqaC7U,12537
|
|
5
5
|
letta/agent_store/db.py,sha256=n15t8qhHfqhtFDxSQg_9uwvMntpWml8Jz_Y-ofL0loQ,23467
|
|
6
6
|
letta/agent_store/lancedb.py,sha256=i63d4VZwj9UIOTNs5f0JZ_r5yZD-jKWz4FAH4RMpXOE,5104
|
|
@@ -26,9 +26,9 @@ letta/errors.py,sha256=mFeTpZP37otDMr68s9hyGOnafJPrWeblQOI79cgP4nQ,3209
|
|
|
26
26
|
letta/functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
letta/functions/function_sets/base.py,sha256=9Rs8SNrtUgqYtlmztE1gVO6FEn864u8t-X1qik24nps,8096
|
|
28
28
|
letta/functions/function_sets/extras.py,sha256=Jik3UiDqYTm4Lam1XPTvuVjvgUHwIAhopsnbmVhGMBg,4732
|
|
29
|
-
letta/functions/functions.py,sha256=
|
|
29
|
+
letta/functions/functions.py,sha256=qCoU9w51uXC8NODHr6dj_tz69NB4924I9PToN2yh2NA,5418
|
|
30
30
|
letta/functions/helpers.py,sha256=K84kqAN1RXZIhjb7-btS0C2p-SInYNv6FvSfo-16Y6g,8578
|
|
31
|
-
letta/functions/schema_generator.py,sha256=
|
|
31
|
+
letta/functions/schema_generator.py,sha256=Y0rQjJBI8Z5fSKmT71EGXtHpIvNb3dMM5X00TP89tlY,19330
|
|
32
32
|
letta/helpers/__init__.py,sha256=p0luQ1Oe3Skc6sH4O58aHHA3Qbkyjifpuq0DZ1GAY0U,59
|
|
33
33
|
letta/helpers/tool_rule_solver.py,sha256=YCwawbRUQw10ZVR17WYXo8b5roxdGe-B5nNVMqlAgBE,4826
|
|
34
34
|
letta/humans/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -41,10 +41,10 @@ letta/llm_api/azure_openai.py,sha256=Y1HKPog1XzM_f7ujUK_Gv2zQkoy5pU-1bKiUnvSxSrs
|
|
|
41
41
|
letta/llm_api/azure_openai_constants.py,sha256=oXtKrgBFHf744gyt5l1thILXgyi8NDNUrKEa2GGGpjw,278
|
|
42
42
|
letta/llm_api/cohere.py,sha256=vDRd-SUGp1t_JUIdwC3RkIhwMl0OY7n-tAU9uPORYkY,14826
|
|
43
43
|
letta/llm_api/google_ai.py,sha256=xKz9JDZs3m6yzSfcgCAAUD_rjI20BBIINoiSvlcnOw0,17621
|
|
44
|
-
letta/llm_api/helpers.py,sha256=
|
|
44
|
+
letta/llm_api/helpers.py,sha256=F8xZDZgDojWX5v-0vakyeUQyCyBr1HmzmsITRdOsmVg,13457
|
|
45
45
|
letta/llm_api/llm_api_tools.py,sha256=h2eudFygI6yFIOaA5Q9GmhiwMPq2mHQyhoSHbn57CCE,16866
|
|
46
46
|
letta/llm_api/mistral.py,sha256=fHdfD9ug-rQIk2qn8tRKay1U6w9maF11ryhKi91FfXM,1593
|
|
47
|
-
letta/llm_api/openai.py,sha256=
|
|
47
|
+
letta/llm_api/openai.py,sha256=Z3xNoJPtplzNU5Lj8JkQg8lJkSb18QKIpFTfLRoaK5E,24180
|
|
48
48
|
letta/local_llm/README.md,sha256=hFJyw5B0TU2jrh9nb0zGZMgdH-Ei1dSRfhvPQG_NSoU,168
|
|
49
49
|
letta/local_llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
50
|
letta/local_llm/chat_completion_proxy.py,sha256=SiohxsjGTku4vOryOZx7I0t0xoO_sUuhXgoe62fKq3c,12995
|
|
@@ -76,7 +76,7 @@ letta/local_llm/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
|
76
76
|
letta/local_llm/settings/deterministic_mirostat.py,sha256=kgRikcxYHfIbPFydHW6W7IO9jmp6NeA7JNAhnI3DPsc,1221
|
|
77
77
|
letta/local_llm/settings/settings.py,sha256=ZAbzDpu2WsBXjVGXJ-TKUpS99VTI__3EoZml9KqYef0,2971
|
|
78
78
|
letta/local_llm/settings/simple.py,sha256=HAO2jBJ_hJCEsXWIJcD0sckR0tI0zs3x2CPdf6ORQLs,719
|
|
79
|
-
letta/local_llm/utils.py,sha256=
|
|
79
|
+
letta/local_llm/utils.py,sha256=4nS5I2PpUm20QK4E-fgamEUaDXJsvxpCPIOCr-PVB0M,13148
|
|
80
80
|
letta/local_llm/vllm/api.py,sha256=2kAGZjc_GH9ILJnVRq-45yfsfKELVfbC9VEl_cIC6vg,2590
|
|
81
81
|
letta/local_llm/webui/api.py,sha256=kkxncdCFq1vjgvaHOoQ__j7rcDPgC1F64KcEm94Y6Rs,2639
|
|
82
82
|
letta/local_llm/webui/legacy_api.py,sha256=k3H3y4qp2Fs-XmP24iSIEyvq6wjWFWBzklY3-wRAJNI,2335
|
|
@@ -164,7 +164,7 @@ letta/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
164
164
|
letta/server/constants.py,sha256=yAdGbLkzlOU_dLTx0lKDmAnj0ZgRXCEaIcPJWO69eaE,92
|
|
165
165
|
letta/server/generate_openapi_schema.sh,sha256=0OtBhkC1g6CobVmNEd_m2B6sTdppjbJLXaM95icejvE,371
|
|
166
166
|
letta/server/rest_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
167
|
-
letta/server/rest_api/app.py,sha256=
|
|
167
|
+
letta/server/rest_api/app.py,sha256=U5plRL_-doYlHSv071DeUl3GNA1UsLWsVaEfGrLKZUU,7570
|
|
168
168
|
letta/server/rest_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
169
169
|
letta/server/rest_api/auth/index.py,sha256=fQBGyVylGSRfEMLQ17cZzrHd5Y1xiVylvPqH5Rl-lXQ,1378
|
|
170
170
|
letta/server/rest_api/auth_token.py,sha256=725EFEIiNj4dh70hrSd94UysmFD8vcJLrTRfNHkzxDo,774
|
|
@@ -211,7 +211,7 @@ letta/services/organization_manager.py,sha256=OfE2_NMmhqXURX4sg7hCOiFQVQpV5ZiPu7
|
|
|
211
211
|
letta/services/per_agent_lock_manager.py,sha256=02iw5e-xoLiKGqqn2KdJdk-QlrDHPz5oMuEs1ibwXHA,540
|
|
212
212
|
letta/services/sandbox_config_manager.py,sha256=9BCu59nHR4nIMFXgFyEMOY2UTmZvBMS3GlDBWWCHB4I,12648
|
|
213
213
|
letta/services/source_manager.py,sha256=StX5Wfd7XSCKJet8qExIu3GMoI-eMIbEarAeTv2gq0s,6555
|
|
214
|
-
letta/services/tool_execution_sandbox.py,sha256=
|
|
214
|
+
letta/services/tool_execution_sandbox.py,sha256=y14mlb8r3mppAb5l977t0zy2ywCCjOboFuqSvT41X1M,13457
|
|
215
215
|
letta/services/tool_manager.py,sha256=FVCB9R3NFahh-KE5jROzf6J9WEgqhqGoDk5RpWjlgjg,7835
|
|
216
216
|
letta/services/tool_sandbox_env/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
217
217
|
letta/services/user_manager.py,sha256=UJa0hqCjz0yXtvrCR8OVBqlSR5lC_Ejn-uG__58zLds,4398
|
|
@@ -220,8 +220,8 @@ letta/streaming_interface.py,sha256=_FPUWy58j50evHcpXyd7zB1wWqeCc71NCFeWh_TBvnw,
|
|
|
220
220
|
letta/streaming_utils.py,sha256=329fsvj1ZN0r0LpQtmMPZ2vSxkDBIUUwvGHZFkjm2I8,11745
|
|
221
221
|
letta/system.py,sha256=buKYPqG5n2x41hVmWpu6JUpyd7vTWED9Km2_M7dLrvk,6960
|
|
222
222
|
letta/utils.py,sha256=COwQLAt02eEM9tjp6p5kN8YeTqGXr714l5BvffLVCLU,32376
|
|
223
|
-
letta_nightly-0.5.4.
|
|
224
|
-
letta_nightly-0.5.4.
|
|
225
|
-
letta_nightly-0.5.4.
|
|
226
|
-
letta_nightly-0.5.4.
|
|
227
|
-
letta_nightly-0.5.4.
|
|
223
|
+
letta_nightly-0.5.4.dev20241201104110.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
|
|
224
|
+
letta_nightly-0.5.4.dev20241201104110.dist-info/METADATA,sha256=5QfUql5hNMn_8aOPuyG3cVU4Gm8JcVX8GFdrM1DBPkI,11515
|
|
225
|
+
letta_nightly-0.5.4.dev20241201104110.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
226
|
+
letta_nightly-0.5.4.dev20241201104110.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
|
|
227
|
+
letta_nightly-0.5.4.dev20241201104110.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|