letta-nightly 0.5.0.dev20241021104213__py3-none-any.whl → 0.5.0.dev20241023104105__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 +7 -2
- letta/agent_store/db.py +4 -2
- letta/cli/cli_config.py +2 -2
- letta/client/client.py +13 -0
- letta/constants.py +4 -1
- letta/embeddings.py +34 -16
- letta/llm_api/azure_openai.py +44 -4
- letta/llm_api/helpers.py +45 -19
- letta/llm_api/openai.py +24 -5
- letta/metadata.py +1 -59
- letta/orm/__all__.py +0 -0
- letta/orm/__init__.py +0 -0
- letta/orm/base.py +75 -0
- letta/orm/enums.py +8 -0
- letta/orm/errors.py +2 -0
- letta/orm/mixins.py +40 -0
- letta/orm/organization.py +35 -0
- letta/orm/sqlalchemy_base.py +214 -0
- letta/schemas/organization.py +3 -3
- letta/server/rest_api/interface.py +245 -98
- letta/server/rest_api/routers/v1/agents.py +11 -3
- letta/server/rest_api/routers/v1/organizations.py +4 -5
- letta/server/server.py +10 -25
- letta/services/__init__.py +0 -0
- letta/services/organization_manager.py +66 -0
- letta/streaming_utils.py +270 -0
- {letta_nightly-0.5.0.dev20241021104213.dist-info → letta_nightly-0.5.0.dev20241023104105.dist-info}/METADATA +2 -1
- {letta_nightly-0.5.0.dev20241021104213.dist-info → letta_nightly-0.5.0.dev20241023104105.dist-info}/RECORD +31 -22
- letta/base.py +0 -3
- letta/client/admin.py +0 -171
- {letta_nightly-0.5.0.dev20241021104213.dist-info → letta_nightly-0.5.0.dev20241023104105.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.0.dev20241021104213.dist-info → letta_nightly-0.5.0.dev20241023104105.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.0.dev20241021104213.dist-info → letta_nightly-0.5.0.dev20241023104105.dist-info}/entry_points.txt +0 -0
letta/streaming_utils.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from letta.constants import DEFAULT_MESSAGE_TOOL_KWARG
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class JSONInnerThoughtsExtractor:
|
|
7
|
+
"""
|
|
8
|
+
A class to process incoming JSON fragments and extract 'inner_thoughts' separately from the main JSON.
|
|
9
|
+
|
|
10
|
+
This handler processes JSON fragments incrementally, parsing out the value associated with a specified key (default is 'inner_thoughts'). It maintains two separate buffers:
|
|
11
|
+
|
|
12
|
+
- `main_json`: Accumulates the JSON data excluding the 'inner_thoughts' key-value pair.
|
|
13
|
+
- `inner_thoughts`: Accumulates the value associated with the 'inner_thoughts' key.
|
|
14
|
+
|
|
15
|
+
**Parameters:**
|
|
16
|
+
|
|
17
|
+
- `inner_thoughts_key` (str): The key to extract from the JSON (default is 'inner_thoughts').
|
|
18
|
+
- `wait_for_first_key` (bool): If `True`, holds back main JSON output until after the 'inner_thoughts' value is processed.
|
|
19
|
+
|
|
20
|
+
**Functionality:**
|
|
21
|
+
|
|
22
|
+
- **Stateful Parsing:** Maintains parsing state across fragments.
|
|
23
|
+
- **String Handling:** Correctly processes strings, escape sequences, and quotation marks.
|
|
24
|
+
- **Selective Extraction:** Identifies and extracts the value of the specified key.
|
|
25
|
+
- **Fragment Processing:** Handles data that arrives in chunks.
|
|
26
|
+
|
|
27
|
+
**Usage:**
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
extractor = JSONInnerThoughtsExtractor(wait_for_first_key=True)
|
|
31
|
+
for fragment in fragments:
|
|
32
|
+
updates_main_json, updates_inner_thoughts = extractor.process_fragment(fragment)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, inner_thoughts_key="inner_thoughts", wait_for_first_key=False):
|
|
38
|
+
self.inner_thoughts_key = inner_thoughts_key
|
|
39
|
+
self.wait_for_first_key = wait_for_first_key
|
|
40
|
+
self.main_buffer = ""
|
|
41
|
+
self.inner_thoughts_buffer = ""
|
|
42
|
+
self.state = "start" # Possible states: start, key, colon, value, comma_or_end, end
|
|
43
|
+
self.in_string = False
|
|
44
|
+
self.escaped = False
|
|
45
|
+
self.current_key = ""
|
|
46
|
+
self.is_inner_thoughts_value = False
|
|
47
|
+
self.inner_thoughts_processed = False
|
|
48
|
+
self.hold_main_json = wait_for_first_key
|
|
49
|
+
self.main_json_held_buffer = ""
|
|
50
|
+
|
|
51
|
+
def process_fragment(self, fragment):
|
|
52
|
+
updates_main_json = ""
|
|
53
|
+
updates_inner_thoughts = ""
|
|
54
|
+
i = 0
|
|
55
|
+
while i < len(fragment):
|
|
56
|
+
c = fragment[i]
|
|
57
|
+
if self.escaped:
|
|
58
|
+
self.escaped = False
|
|
59
|
+
if self.in_string:
|
|
60
|
+
if self.state == "key":
|
|
61
|
+
self.current_key += c
|
|
62
|
+
elif self.state == "value":
|
|
63
|
+
if self.is_inner_thoughts_value:
|
|
64
|
+
updates_inner_thoughts += c
|
|
65
|
+
self.inner_thoughts_buffer += c
|
|
66
|
+
else:
|
|
67
|
+
if self.hold_main_json:
|
|
68
|
+
self.main_json_held_buffer += c
|
|
69
|
+
else:
|
|
70
|
+
updates_main_json += c
|
|
71
|
+
self.main_buffer += c
|
|
72
|
+
else:
|
|
73
|
+
if not self.is_inner_thoughts_value:
|
|
74
|
+
if self.hold_main_json:
|
|
75
|
+
self.main_json_held_buffer += c
|
|
76
|
+
else:
|
|
77
|
+
updates_main_json += c
|
|
78
|
+
self.main_buffer += c
|
|
79
|
+
elif c == "\\":
|
|
80
|
+
self.escaped = True
|
|
81
|
+
if self.in_string:
|
|
82
|
+
if self.state == "key":
|
|
83
|
+
self.current_key += c
|
|
84
|
+
elif self.state == "value":
|
|
85
|
+
if self.is_inner_thoughts_value:
|
|
86
|
+
updates_inner_thoughts += c
|
|
87
|
+
self.inner_thoughts_buffer += c
|
|
88
|
+
else:
|
|
89
|
+
if self.hold_main_json:
|
|
90
|
+
self.main_json_held_buffer += c
|
|
91
|
+
else:
|
|
92
|
+
updates_main_json += c
|
|
93
|
+
self.main_buffer += c
|
|
94
|
+
else:
|
|
95
|
+
if not self.is_inner_thoughts_value:
|
|
96
|
+
if self.hold_main_json:
|
|
97
|
+
self.main_json_held_buffer += c
|
|
98
|
+
else:
|
|
99
|
+
updates_main_json += c
|
|
100
|
+
self.main_buffer += c
|
|
101
|
+
elif c == '"':
|
|
102
|
+
if not self.escaped:
|
|
103
|
+
self.in_string = not self.in_string
|
|
104
|
+
if self.in_string:
|
|
105
|
+
if self.state in ["start", "comma_or_end"]:
|
|
106
|
+
self.state = "key"
|
|
107
|
+
self.current_key = ""
|
|
108
|
+
# Release held main_json when starting to process the next key
|
|
109
|
+
if self.wait_for_first_key and self.hold_main_json and self.inner_thoughts_processed:
|
|
110
|
+
updates_main_json += self.main_json_held_buffer
|
|
111
|
+
self.main_buffer += self.main_json_held_buffer
|
|
112
|
+
self.main_json_held_buffer = ""
|
|
113
|
+
self.hold_main_json = False
|
|
114
|
+
else:
|
|
115
|
+
if self.state == "key":
|
|
116
|
+
self.state = "colon"
|
|
117
|
+
elif self.state == "value":
|
|
118
|
+
# End of value
|
|
119
|
+
if self.is_inner_thoughts_value:
|
|
120
|
+
self.inner_thoughts_processed = True
|
|
121
|
+
# Do not release held main_json here
|
|
122
|
+
else:
|
|
123
|
+
if self.hold_main_json:
|
|
124
|
+
self.main_json_held_buffer += '"'
|
|
125
|
+
else:
|
|
126
|
+
updates_main_json += '"'
|
|
127
|
+
self.main_buffer += '"'
|
|
128
|
+
self.state = "comma_or_end"
|
|
129
|
+
else:
|
|
130
|
+
self.escaped = False
|
|
131
|
+
if self.in_string:
|
|
132
|
+
if self.state == "key":
|
|
133
|
+
self.current_key += '"'
|
|
134
|
+
elif self.state == "value":
|
|
135
|
+
if self.is_inner_thoughts_value:
|
|
136
|
+
updates_inner_thoughts += '"'
|
|
137
|
+
self.inner_thoughts_buffer += '"'
|
|
138
|
+
else:
|
|
139
|
+
if self.hold_main_json:
|
|
140
|
+
self.main_json_held_buffer += '"'
|
|
141
|
+
else:
|
|
142
|
+
updates_main_json += '"'
|
|
143
|
+
self.main_buffer += '"'
|
|
144
|
+
elif self.in_string:
|
|
145
|
+
if self.state == "key":
|
|
146
|
+
self.current_key += c
|
|
147
|
+
elif self.state == "value":
|
|
148
|
+
if self.is_inner_thoughts_value:
|
|
149
|
+
updates_inner_thoughts += c
|
|
150
|
+
self.inner_thoughts_buffer += c
|
|
151
|
+
else:
|
|
152
|
+
if self.hold_main_json:
|
|
153
|
+
self.main_json_held_buffer += c
|
|
154
|
+
else:
|
|
155
|
+
updates_main_json += c
|
|
156
|
+
self.main_buffer += c
|
|
157
|
+
else:
|
|
158
|
+
if c == ":" and self.state == "colon":
|
|
159
|
+
self.state = "value"
|
|
160
|
+
self.is_inner_thoughts_value = self.current_key == self.inner_thoughts_key
|
|
161
|
+
if self.is_inner_thoughts_value:
|
|
162
|
+
pass # Do not include 'inner_thoughts' key in main_json
|
|
163
|
+
else:
|
|
164
|
+
key_colon = f'"{self.current_key}":'
|
|
165
|
+
if self.hold_main_json:
|
|
166
|
+
self.main_json_held_buffer += key_colon + '"'
|
|
167
|
+
else:
|
|
168
|
+
updates_main_json += key_colon + '"'
|
|
169
|
+
self.main_buffer += key_colon + '"'
|
|
170
|
+
elif c == "," and self.state == "comma_or_end":
|
|
171
|
+
if self.is_inner_thoughts_value:
|
|
172
|
+
# Inner thoughts value ended
|
|
173
|
+
self.is_inner_thoughts_value = False
|
|
174
|
+
self.state = "start"
|
|
175
|
+
# Do not release held main_json here
|
|
176
|
+
else:
|
|
177
|
+
if self.hold_main_json:
|
|
178
|
+
self.main_json_held_buffer += c
|
|
179
|
+
else:
|
|
180
|
+
updates_main_json += c
|
|
181
|
+
self.main_buffer += c
|
|
182
|
+
self.state = "start"
|
|
183
|
+
elif c == "{":
|
|
184
|
+
if not self.is_inner_thoughts_value:
|
|
185
|
+
if self.hold_main_json:
|
|
186
|
+
self.main_json_held_buffer += c
|
|
187
|
+
else:
|
|
188
|
+
updates_main_json += c
|
|
189
|
+
self.main_buffer += c
|
|
190
|
+
elif c == "}":
|
|
191
|
+
self.state = "end"
|
|
192
|
+
if self.hold_main_json:
|
|
193
|
+
self.main_json_held_buffer += c
|
|
194
|
+
else:
|
|
195
|
+
updates_main_json += c
|
|
196
|
+
self.main_buffer += c
|
|
197
|
+
else:
|
|
198
|
+
if self.state == "value":
|
|
199
|
+
if self.is_inner_thoughts_value:
|
|
200
|
+
updates_inner_thoughts += c
|
|
201
|
+
self.inner_thoughts_buffer += c
|
|
202
|
+
else:
|
|
203
|
+
if self.hold_main_json:
|
|
204
|
+
self.main_json_held_buffer += c
|
|
205
|
+
else:
|
|
206
|
+
updates_main_json += c
|
|
207
|
+
self.main_buffer += c
|
|
208
|
+
i += 1
|
|
209
|
+
|
|
210
|
+
return updates_main_json, updates_inner_thoughts
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def main_json(self):
|
|
214
|
+
return self.main_buffer
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def inner_thoughts(self):
|
|
218
|
+
return self.inner_thoughts_buffer
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class FunctionArgumentsStreamHandler:
|
|
222
|
+
"""State machine that can process a stream of"""
|
|
223
|
+
|
|
224
|
+
def __init__(self, json_key=DEFAULT_MESSAGE_TOOL_KWARG):
|
|
225
|
+
self.json_key = json_key
|
|
226
|
+
self.reset()
|
|
227
|
+
|
|
228
|
+
def reset(self):
|
|
229
|
+
self.in_message = False
|
|
230
|
+
self.key_buffer = ""
|
|
231
|
+
self.accumulating = False
|
|
232
|
+
self.message_started = False
|
|
233
|
+
|
|
234
|
+
def process_json_chunk(self, chunk: str) -> Optional[str]:
|
|
235
|
+
"""Process a chunk from the function arguments and return the plaintext version"""
|
|
236
|
+
|
|
237
|
+
# Use strip to handle only leading and trailing whitespace in control structures
|
|
238
|
+
if self.accumulating:
|
|
239
|
+
clean_chunk = chunk.strip()
|
|
240
|
+
if self.json_key in self.key_buffer:
|
|
241
|
+
if ":" in clean_chunk:
|
|
242
|
+
self.in_message = True
|
|
243
|
+
self.accumulating = False
|
|
244
|
+
return None
|
|
245
|
+
self.key_buffer += clean_chunk
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
if self.in_message:
|
|
249
|
+
if chunk.strip() == '"' and self.message_started:
|
|
250
|
+
self.in_message = False
|
|
251
|
+
self.message_started = False
|
|
252
|
+
return None
|
|
253
|
+
if not self.message_started and chunk.strip() == '"':
|
|
254
|
+
self.message_started = True
|
|
255
|
+
return None
|
|
256
|
+
if self.message_started:
|
|
257
|
+
if chunk.strip().endswith('"'):
|
|
258
|
+
self.in_message = False
|
|
259
|
+
return chunk.rstrip('"\n')
|
|
260
|
+
return chunk
|
|
261
|
+
|
|
262
|
+
if chunk.strip() == "{":
|
|
263
|
+
self.key_buffer = ""
|
|
264
|
+
self.accumulating = True
|
|
265
|
+
return None
|
|
266
|
+
if chunk.strip() == "}":
|
|
267
|
+
self.in_message = False
|
|
268
|
+
self.message_started = False
|
|
269
|
+
return None
|
|
270
|
+
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: letta-nightly
|
|
3
|
-
Version: 0.5.0.
|
|
3
|
+
Version: 0.5.0.dev20241023104105
|
|
4
4
|
Summary: Create LLM agents with long-term memory and custom tools
|
|
5
5
|
License: Apache License
|
|
6
6
|
Author: Letta Team
|
|
@@ -55,6 +55,7 @@ Requires-Dist: prettytable (>=3.9.0,<4.0.0)
|
|
|
55
55
|
Requires-Dist: pyautogen (==0.2.22) ; extra == "autogen"
|
|
56
56
|
Requires-Dist: pydantic (>=2.7.4,<3.0.0)
|
|
57
57
|
Requires-Dist: pydantic-settings (>=2.2.1,<3.0.0)
|
|
58
|
+
Requires-Dist: pyhumps (>=3.8.0,<4.0.0)
|
|
58
59
|
Requires-Dist: pymilvus (>=2.4.3,<3.0.0) ; extra == "milvus"
|
|
59
60
|
Requires-Dist: pyright (>=1.1.347,<2.0.0) ; extra == "dev"
|
|
60
61
|
Requires-Dist: pytest-asyncio (>=0.23.2,<0.24.0) ; extra == "dev"
|
|
@@ -1,29 +1,27 @@
|
|
|
1
|
-
letta/__init__.py,sha256=
|
|
1
|
+
letta/__init__.py,sha256=kRTa2BzRf4EWmx6MkOXUyaisXDj7-6NkmY22gd7h_xs,1014
|
|
2
2
|
letta/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
|
|
3
3
|
letta/agent.py,sha256=PRMDj0vZu5TGj-k2apeWcWYzJnBR36prdMXTKWnRYc8,72997
|
|
4
4
|
letta/agent_store/chroma.py,sha256=upR5zGnGs6I6btulEYbiZdGG87BgKjxUJOQZ4Y-RQ_M,12492
|
|
5
|
-
letta/agent_store/db.py,sha256=
|
|
5
|
+
letta/agent_store/db.py,sha256=mTY3YWUs5n17479GMeTmxN8DAyzbBweVcypyNsK_slg,23435
|
|
6
6
|
letta/agent_store/lancedb.py,sha256=i63d4VZwj9UIOTNs5f0JZ_r5yZD-jKWz4FAH4RMpXOE,5104
|
|
7
7
|
letta/agent_store/milvus.py,sha256=xUu-D9a6N10MuGJ-R-QWR2IHX77ueqAp88tV4gg9B4M,8470
|
|
8
8
|
letta/agent_store/qdrant.py,sha256=6_33V-FEDpT9LG5zmr6-3y9slw1YFLswxpahiyMkvHA,7880
|
|
9
9
|
letta/agent_store/storage.py,sha256=4gKvMRYBGm9cwyaDOzljxDKgqr4MxGXcC4yGhAdKcAA,6693
|
|
10
|
-
letta/base.py,sha256=Ba-wt8p59bLmeUONkYSo5MhrkH-_HdT4zE1Y9MVGrSQ,83
|
|
11
10
|
letta/benchmark/benchmark.py,sha256=ebvnwfp3yezaXOQyGXkYCDYpsmre-b9hvNtnyx4xkG0,3701
|
|
12
11
|
letta/benchmark/constants.py,sha256=aXc5gdpMGJT327VuxsT5FngbCK2J41PQYeICBO7g_RE,536
|
|
13
12
|
letta/cli/cli.py,sha256=A5u87nx6g7n_KfIfU2nmjWd2Wq8f5YnCvSBH86bOk28,16149
|
|
14
|
-
letta/cli/cli_config.py,sha256=
|
|
13
|
+
letta/cli/cli_config.py,sha256=ynsezKawQ0l95rymlv-AJ3QjbqiyaXZyuzsDfBYwMwg,8537
|
|
15
14
|
letta/cli/cli_load.py,sha256=x4L8s15GwIW13xrhKYFWHo_y-IVGtoPDHWWKcHDRP10,4587
|
|
16
15
|
letta/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
letta/client/
|
|
18
|
-
letta/client/client.py,sha256=Alx_m9b4ZX_A3G7XtOa5Lgbxtf9PmDhCkUf3QDK0jS0,93065
|
|
16
|
+
letta/client/client.py,sha256=CLOdsr3rVfWHQBPG-NN4sHKKn0Cn0VqIcjtHGxYaix4,93462
|
|
19
17
|
letta/client/streaming.py,sha256=bfWlUu7z7EoPfKxBqIarYxGKyrL7Pj79BlliToqcCgI,4592
|
|
20
18
|
letta/client/utils.py,sha256=OJlAKWrldc4I6M1WpcTWNtPJ4wfxlzlZqWLfCozkFtI,2872
|
|
21
19
|
letta/config.py,sha256=j2I90fOh9d9__kOYObwTDLbvVwYR50rIql5nzrvREKg,19161
|
|
22
|
-
letta/constants.py,sha256=
|
|
20
|
+
letta/constants.py,sha256=Y7MNpkAZfQ1Mo-AhgFiA_dFQLDCiZT6ebCG_n0R_0FQ,6784
|
|
23
21
|
letta/credentials.py,sha256=D9mlcPsdDWlIIXQQD8wSPE9M_QvsRrb0p3LB5i9OF5Q,5806
|
|
24
22
|
letta/data_sources/connectors.py,sha256=qO81ASB6V-vDPthfHYtZiyqcQDQPTT0NuD8hVwC6xI0,9907
|
|
25
23
|
letta/data_sources/connectors_helper.py,sha256=2TQjCt74fCgT5sw1AP8PalDEk06jPBbhrPG4HVr-WLs,3371
|
|
26
|
-
letta/embeddings.py,sha256=
|
|
24
|
+
letta/embeddings.py,sha256=qPt8kB-wmuRIg1py7DHnQGJpw3DmQHJ505FJvc0K6Yk,8873
|
|
27
25
|
letta/errors.py,sha256=cDOo4cSYL-LA0w0b0GdsxXd5k2I1LLOY8nhtXk9YqYs,2875
|
|
28
26
|
letta/functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
27
|
letta/functions/function_sets/base.py,sha256=N4QmOjL6gDEyOg67ocF6zVKM-NquTo-yXG_T8r18buA,6440
|
|
@@ -37,14 +35,14 @@ letta/humans/examples/cs_phd.txt,sha256=9C9ZAV_VuG7GB31ksy3-_NAyk8rjE6YtVOkhp08k
|
|
|
37
35
|
letta/interface.py,sha256=QI4hFP0WrNsgM5qX6TbnhH1ZZxsLYr5DaccuxpEQ8S4,12768
|
|
38
36
|
letta/llm_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
37
|
letta/llm_api/anthropic.py,sha256=DTBYPvByj-mfbrkZeAa4PjVEI8gg0p_v15a2h_I-Rqo,12883
|
|
40
|
-
letta/llm_api/azure_openai.py,sha256=
|
|
38
|
+
letta/llm_api/azure_openai.py,sha256=Y1HKPog1XzM_f7ujUK_Gv2zQkoy5pU-1bKiUnvSxSrs,6297
|
|
41
39
|
letta/llm_api/azure_openai_constants.py,sha256=oXtKrgBFHf744gyt5l1thILXgyi8NDNUrKEa2GGGpjw,278
|
|
42
40
|
letta/llm_api/cohere.py,sha256=vDRd-SUGp1t_JUIdwC3RkIhwMl0OY7n-tAU9uPORYkY,14826
|
|
43
41
|
letta/llm_api/google_ai.py,sha256=3xZ074nSOCC22c15yerA5ngWzh0ex4wxeI-6faNbHPE,17708
|
|
44
|
-
letta/llm_api/helpers.py,sha256=
|
|
42
|
+
letta/llm_api/helpers.py,sha256=sGCmNA1U_7-AhRFgvT668jdp_xyzSliKQYbTvRR6O7c,9812
|
|
45
43
|
letta/llm_api/llm_api_tools.py,sha256=GEBO7Dlt7xtAQud1sVsigKZKPpLOZOt2IWL8LwcNV4o,14869
|
|
46
44
|
letta/llm_api/mistral.py,sha256=fHdfD9ug-rQIk2qn8tRKay1U6w9maF11ryhKi91FfXM,1593
|
|
47
|
-
letta/llm_api/openai.py,sha256=
|
|
45
|
+
letta/llm_api/openai.py,sha256=0hwAZhpjrmubYy549hvjzhw6zK_aBhSVuIQN0OsTq-w,23705
|
|
48
46
|
letta/local_llm/README.md,sha256=hFJyw5B0TU2jrh9nb0zGZMgdH-Ei1dSRfhvPQG_NSoU,168
|
|
49
47
|
letta/local_llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
48
|
letta/local_llm/chat_completion_proxy.py,sha256=SiohxsjGTku4vOryOZx7I0t0xoO_sUuhXgoe62fKq3c,12995
|
|
@@ -85,10 +83,18 @@ letta/local_llm/webui/settings.py,sha256=gmLHfiOl1u4JmlAZU2d2O8YKF9lafdakyjwR_ft
|
|
|
85
83
|
letta/log.py,sha256=QHquDnL7oUAvdKlAwUlCK9zXKDMUjrU9WA0bxnMsP0Y,2101
|
|
86
84
|
letta/main.py,sha256=yHgM1lltQZvbE8k0QDQMmVyJiWEj07ZTOYIBHDxE_DQ,18709
|
|
87
85
|
letta/memory.py,sha256=6q1x3-PY-PeXzAt6hvP-UF1ajvroPZ7XW-5nLy-JhMo,17657
|
|
88
|
-
letta/metadata.py,sha256=
|
|
86
|
+
letta/metadata.py,sha256=dcQk1dx5ugO73I6bcOMpNapbFtphSiaG_80yL3rIU-U,34811
|
|
89
87
|
letta/o1_agent.py,sha256=0jospImZUKhuQZ0cop0INj8xI6cxhxNffGA8iloHyfU,3114
|
|
90
88
|
letta/openai_backcompat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
89
|
letta/openai_backcompat/openai_object.py,sha256=Y1ZS1sATP60qxJiOsjOP3NbwSzuzvkNAvb3DeuhM5Uk,13490
|
|
90
|
+
letta/orm/__all__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
|
+
letta/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
92
|
+
letta/orm/base.py,sha256=9k7mwKDApJNpbrk6oXosQ7amlpeYhDzArdYTZ6BhfpQ,2405
|
|
93
|
+
letta/orm/enums.py,sha256=KfHcFt_fR6GUmSlmfsa-TetvmuRxGESNve8MStRYW64,145
|
|
94
|
+
letta/orm/errors.py,sha256=dYyFwsBuEOS11Wjdz3AAoR4OhejSXNBdQhU71pxymd0,111
|
|
95
|
+
letta/orm/mixins.py,sha256=nOG8Bt4cP75dPPOnI35efFrBZ2OSa6bVRuEFrUuJfBw,1224
|
|
96
|
+
letta/orm/organization.py,sha256=JykIMHJ_g3JVgBaRliBVlqe9iMoahHqSlfwWZv_LDxA,1695
|
|
97
|
+
letta/orm/sqlalchemy_base.py,sha256=YJJBqZ3S6Gp7fr_nBTXBtOt_5rKk-zBBuBUMTAgyRWk,9337
|
|
92
98
|
letta/persistence_manager.py,sha256=LlLgEDpSafCPAiyKmuq0NvVAnfBkZo6TWbGIKYQjQBs,5200
|
|
93
99
|
letta/personas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
94
100
|
letta/personas/examples/anna_pa.txt,sha256=zgiNdSNhy1HQy58cF_6RFPzcg2i37F9v38YuL1CW40A,1849
|
|
@@ -134,7 +140,7 @@ letta/schemas/openai/chat_completion_response.py,sha256=05FRfm1EsVivyeWo2aoJk34h
|
|
|
134
140
|
letta/schemas/openai/chat_completions.py,sha256=V0ZPIIk-ds3O6MAkNHMz8zh1hqMFSPrTcYr88WDYzWE,3588
|
|
135
141
|
letta/schemas/openai/embedding_response.py,sha256=WKIZpXab1Av7v6sxKG8feW3ZtpQUNosmLVSuhXYa_xU,357
|
|
136
142
|
letta/schemas/openai/openai.py,sha256=Hilo5BiLAGabzxCwnwfzK5QrWqwYD8epaEKFa4Pwndk,7970
|
|
137
|
-
letta/schemas/organization.py,sha256=
|
|
143
|
+
letta/schemas/organization.py,sha256=0MOUbaiDGTXkzTjaALLI2wj4E1bL5V_0jjI6jEgFKlA,635
|
|
138
144
|
letta/schemas/passage.py,sha256=eYQMxD_XjHAi72jmqcGBU4wM4VZtSU0XK8uhQxxN3Ug,3563
|
|
139
145
|
letta/schemas/source.py,sha256=hB4Ai6Nj8dFdbxv5_Qaf4uN_cmdGmnzgc-4QnHXcV3o,2562
|
|
140
146
|
letta/schemas/tool.py,sha256=m8jWIsPUhekoQcjX7U_Y5vwhhQqSKn748RcXNXRiLGg,10329
|
|
@@ -151,7 +157,7 @@ letta/server/rest_api/app.py,sha256=JNmDnvp9fP--hJPtPpEWgQT-14O1YOceZbWELr2vedA,
|
|
|
151
157
|
letta/server/rest_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
152
158
|
letta/server/rest_api/auth/index.py,sha256=fQBGyVylGSRfEMLQ17cZzrHd5Y1xiVylvPqH5Rl-lXQ,1378
|
|
153
159
|
letta/server/rest_api/auth_token.py,sha256=725EFEIiNj4dh70hrSd94UysmFD8vcJLrTRfNHkzxDo,774
|
|
154
|
-
letta/server/rest_api/interface.py,sha256=
|
|
160
|
+
letta/server/rest_api/interface.py,sha256=BX0Wa4yO_6KiDzU6GNkW7C_zL10xPgsn0kpId7Z3OJ8,46276
|
|
155
161
|
letta/server/rest_api/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
156
162
|
letta/server/rest_api/routers/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
157
163
|
letta/server/rest_api/routers/openai/assistants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -161,18 +167,18 @@ letta/server/rest_api/routers/openai/assistants/threads.py,sha256=WXVGBaBvSNPB7Z
|
|
|
161
167
|
letta/server/rest_api/routers/openai/chat_completions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
162
168
|
letta/server/rest_api/routers/openai/chat_completions/chat_completions.py,sha256=-uye6cm4SnoQGwxhr1N1FrSXOlnO2Hvbfj6k8JSc45k,4918
|
|
163
169
|
letta/server/rest_api/routers/v1/__init__.py,sha256=sqlVZa-u9DJwdRsp0_8YUGrac9DHguIB4wETlEDRylA,666
|
|
164
|
-
letta/server/rest_api/routers/v1/agents.py,sha256=
|
|
170
|
+
letta/server/rest_api/routers/v1/agents.py,sha256=BY4rQOcwsi_WiWh6DwrO8Vz6Nu2JgMBKSHxiBqlaaYY,25397
|
|
165
171
|
letta/server/rest_api/routers/v1/blocks.py,sha256=0WekE_yBD2U3jYgPxI0DCFjACWavCAlvm_Ybw5SZBnw,2583
|
|
166
172
|
letta/server/rest_api/routers/v1/health.py,sha256=pKCuVESlVOhGIb4VC4K-H82eZqfghmT6kvj2iOkkKuc,401
|
|
167
173
|
letta/server/rest_api/routers/v1/jobs.py,sha256=a-j0v-5A0un0pVCOHpfeWnzpOWkVDQO6ti42k_qAlZY,2272
|
|
168
174
|
letta/server/rest_api/routers/v1/llms.py,sha256=TcyvSx6MEM3je5F4DysL7ligmssL_pFlJaaO4uL95VY,877
|
|
169
|
-
letta/server/rest_api/routers/v1/organizations.py,sha256=
|
|
175
|
+
letta/server/rest_api/routers/v1/organizations.py,sha256=yIni1i1Yurcbu0SwURm01IEEmlTgiWQ7ci6BefNZT_I,2025
|
|
170
176
|
letta/server/rest_api/routers/v1/sources.py,sha256=eY_pk9jRL2Y9yIZdsTjH6EuKsfH1neaTU15MKNL0dvw,8749
|
|
171
177
|
letta/server/rest_api/routers/v1/tools.py,sha256=vxE4b5juoiBiNWmplktuv6GEgenCkKBRov-t6usUJ9A,3665
|
|
172
178
|
letta/server/rest_api/routers/v1/users.py,sha256=Y2rDvHOG1B5FLSOjutY3R22vt48IngbZ-9h8CohG5rc,3378
|
|
173
179
|
letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
|
|
174
180
|
letta/server/rest_api/utils.py,sha256=Fc2ZGKzLaBa2sEtSTVjJ8D5M0xIwsWC0CVAOIJaD3rY,2176
|
|
175
|
-
letta/server/server.py,sha256=
|
|
181
|
+
letta/server/server.py,sha256=TI9VItwqQ0M-zBHQJwdSKlx5aDMDGczK6vOzagy2zeA,91063
|
|
176
182
|
letta/server/startup.sh,sha256=jeGV7B_PS0hS-tT6o6GpACrUbV9WV1NI2L9aLoUDDtc,311
|
|
177
183
|
letta/server/static_files/assets/index-3ab03d5b.css,sha256=OrA9W4iKJ5h2Wlr7GwdAT4wow0CM8hVit1yOxEL49Qw,54295
|
|
178
184
|
letta/server/static_files/assets/index-d6b3669a.js,sha256=i1nHReU0RPnj-a5W0nNPV4Y9bQ0FOW0ztjMz8a2AE-Y,1821560
|
|
@@ -185,12 +191,15 @@ letta/server/ws_api/example_client.py,sha256=95AA5UFgTlNJ0FUQkLxli8dKNx48MNm3eWG
|
|
|
185
191
|
letta/server/ws_api/interface.py,sha256=TWl9vkcMCnLsUtgsuENZ-ku2oMDA-OUTzLh_yNRoMa4,4120
|
|
186
192
|
letta/server/ws_api/protocol.py,sha256=M_-gM5iuDBwa1cuN2IGNCG5GxMJwU2d3XW93XALv9s8,1821
|
|
187
193
|
letta/server/ws_api/server.py,sha256=C2Kv48PCwl46DQFb0ZP30s86KJLQ6dZk2AhWQEZn9pY,6004
|
|
194
|
+
letta/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
195
|
+
letta/services/organization_manager.py,sha256=834mNQDVaf6TKx-icAMeOgiYaq1NXtuM7je3SjULlYI,3036
|
|
188
196
|
letta/settings.py,sha256=gNdH-Ty6f-Nfz2j9ZMZFRQHac2KzgsxLZNt5l_TiAyo,3301
|
|
189
197
|
letta/streaming_interface.py,sha256=_FPUWy58j50evHcpXyd7zB1wWqeCc71NCFeWh_TBvnw,15736
|
|
198
|
+
letta/streaming_utils.py,sha256=329fsvj1ZN0r0LpQtmMPZ2vSxkDBIUUwvGHZFkjm2I8,11745
|
|
190
199
|
letta/system.py,sha256=buKYPqG5n2x41hVmWpu6JUpyd7vTWED9Km2_M7dLrvk,6960
|
|
191
200
|
letta/utils.py,sha256=SXLEYhyp3gHyIjrxNIKNZZ5ittKo3KOj6zxgC_Trex0,31012
|
|
192
|
-
letta_nightly-0.5.0.
|
|
193
|
-
letta_nightly-0.5.0.
|
|
194
|
-
letta_nightly-0.5.0.
|
|
195
|
-
letta_nightly-0.5.0.
|
|
196
|
-
letta_nightly-0.5.0.
|
|
201
|
+
letta_nightly-0.5.0.dev20241023104105.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
|
|
202
|
+
letta_nightly-0.5.0.dev20241023104105.dist-info/METADATA,sha256=n2RNxyAEkgg6neEBHsFKxoUpeu9o4qlPSIdJ3ynWx0M,10660
|
|
203
|
+
letta_nightly-0.5.0.dev20241023104105.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
204
|
+
letta_nightly-0.5.0.dev20241023104105.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
|
|
205
|
+
letta_nightly-0.5.0.dev20241023104105.dist-info/RECORD,,
|
letta/base.py
DELETED
letta/client/admin.py
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
2
|
-
|
|
3
|
-
import requests
|
|
4
|
-
from requests import HTTPError
|
|
5
|
-
|
|
6
|
-
from letta.functions.functions import parse_source_code
|
|
7
|
-
from letta.functions.schema_generator import generate_schema
|
|
8
|
-
from letta.schemas.api_key import APIKey, APIKeyCreate
|
|
9
|
-
from letta.schemas.organization import Organization, OrganizationCreate
|
|
10
|
-
from letta.schemas.user import User, UserCreate
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Admin:
|
|
14
|
-
"""
|
|
15
|
-
Admin client allows admin-level operations on the Letta server.
|
|
16
|
-
- Creating users
|
|
17
|
-
- Generating user keys
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
def __init__(
|
|
21
|
-
self,
|
|
22
|
-
base_url: str,
|
|
23
|
-
token: str,
|
|
24
|
-
api_prefix: str = "v1",
|
|
25
|
-
):
|
|
26
|
-
self.base_url = base_url
|
|
27
|
-
self.api_prefix = api_prefix
|
|
28
|
-
self.token = token
|
|
29
|
-
self.headers = {"accept": "application/json", "content-type": "application/json", "authorization": f"Bearer {token}"}
|
|
30
|
-
|
|
31
|
-
def get_users(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[User]:
|
|
32
|
-
params = {}
|
|
33
|
-
if cursor:
|
|
34
|
-
params["cursor"] = str(cursor)
|
|
35
|
-
if limit:
|
|
36
|
-
params["limit"] = limit
|
|
37
|
-
response = requests.get(f"{self.base_url}/{self.api_prefix}/admin/users", params=params, headers=self.headers)
|
|
38
|
-
if response.status_code != 200:
|
|
39
|
-
raise HTTPError(response.json())
|
|
40
|
-
return [User(**user) for user in response.json()]
|
|
41
|
-
|
|
42
|
-
def create_key(self, user_id: str, key_name: Optional[str] = None) -> APIKey:
|
|
43
|
-
request = APIKeyCreate(user_id=user_id, name=key_name)
|
|
44
|
-
response = requests.post(f"{self.base_url}/{self.api_prefix}/admin/users/keys", headers=self.headers, json=request.model_dump())
|
|
45
|
-
if response.status_code != 200:
|
|
46
|
-
raise HTTPError(response.json())
|
|
47
|
-
return APIKey(**response.json())
|
|
48
|
-
|
|
49
|
-
def get_keys(self, user_id: str) -> List[APIKey]:
|
|
50
|
-
params = {"user_id": str(user_id)}
|
|
51
|
-
response = requests.get(f"{self.base_url}/{self.api_prefix}/admin/users/keys", params=params, headers=self.headers)
|
|
52
|
-
if response.status_code != 200:
|
|
53
|
-
raise HTTPError(response.json())
|
|
54
|
-
return [APIKey(**key) for key in response.json()]
|
|
55
|
-
|
|
56
|
-
def delete_key(self, api_key: str) -> APIKey:
|
|
57
|
-
params = {"api_key": api_key}
|
|
58
|
-
response = requests.delete(f"{self.base_url}/{self.api_prefix}/admin/users/keys", params=params, headers=self.headers)
|
|
59
|
-
if response.status_code != 200:
|
|
60
|
-
raise HTTPError(response.json())
|
|
61
|
-
return APIKey(**response.json())
|
|
62
|
-
|
|
63
|
-
def create_user(self, name: Optional[str] = None, org_id: Optional[str] = None) -> User:
|
|
64
|
-
request = UserCreate(name=name, org_id=org_id)
|
|
65
|
-
response = requests.post(f"{self.base_url}/{self.api_prefix}/admin/users", headers=self.headers, json=request.model_dump())
|
|
66
|
-
if response.status_code != 200:
|
|
67
|
-
raise HTTPError(response.json())
|
|
68
|
-
response_json = response.json()
|
|
69
|
-
return User(**response_json)
|
|
70
|
-
|
|
71
|
-
def delete_user(self, user_id: str) -> User:
|
|
72
|
-
params = {"user_id": str(user_id)}
|
|
73
|
-
response = requests.delete(f"{self.base_url}/{self.api_prefix}/admin/users", params=params, headers=self.headers)
|
|
74
|
-
if response.status_code != 200:
|
|
75
|
-
raise HTTPError(response.json())
|
|
76
|
-
return User(**response.json())
|
|
77
|
-
|
|
78
|
-
def create_organization(self, name: Optional[str] = None) -> Organization:
|
|
79
|
-
request = OrganizationCreate(name=name)
|
|
80
|
-
response = requests.post(f"{self.base_url}/{self.api_prefix}/admin/orgs", headers=self.headers, json=request.model_dump())
|
|
81
|
-
if response.status_code != 200:
|
|
82
|
-
raise HTTPError(response.json())
|
|
83
|
-
response_json = response.json()
|
|
84
|
-
return Organization(**response_json)
|
|
85
|
-
|
|
86
|
-
def delete_organization(self, org_id: str) -> Organization:
|
|
87
|
-
params = {"org_id": str(org_id)}
|
|
88
|
-
response = requests.delete(f"{self.base_url}/{self.api_prefix}/admin/orgs", params=params, headers=self.headers)
|
|
89
|
-
if response.status_code != 200:
|
|
90
|
-
raise HTTPError(response.json())
|
|
91
|
-
return Organization(**response.json())
|
|
92
|
-
|
|
93
|
-
def get_organizations(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[Organization]:
|
|
94
|
-
params = {}
|
|
95
|
-
if cursor:
|
|
96
|
-
params["cursor"] = str(cursor)
|
|
97
|
-
if limit:
|
|
98
|
-
params["limit"] = limit
|
|
99
|
-
response = requests.get(f"{self.base_url}/{self.api_prefix}/admin/orgs", params=params, headers=self.headers)
|
|
100
|
-
if response.status_code != 200:
|
|
101
|
-
raise HTTPError(response.json())
|
|
102
|
-
return [Organization(**org) for org in response.json()]
|
|
103
|
-
|
|
104
|
-
def _reset_server(self):
|
|
105
|
-
# DANGER: this will delete all users and keys
|
|
106
|
-
# clear all state associated with users
|
|
107
|
-
# TODO: clear out all agents, presets, etc.
|
|
108
|
-
users = self.get_users()
|
|
109
|
-
for user in users:
|
|
110
|
-
keys = self.get_keys(user.id)
|
|
111
|
-
for key in keys:
|
|
112
|
-
self.delete_key(key.key)
|
|
113
|
-
self.delete_user(user.id)
|
|
114
|
-
|
|
115
|
-
# tools
|
|
116
|
-
def create_tool(
|
|
117
|
-
self,
|
|
118
|
-
func,
|
|
119
|
-
name: Optional[str] = None,
|
|
120
|
-
update: Optional[bool] = True, # TODO: actually use this
|
|
121
|
-
tags: Optional[List[str]] = None,
|
|
122
|
-
):
|
|
123
|
-
"""Create a tool
|
|
124
|
-
Args:
|
|
125
|
-
func (callable): The function to create a tool for.
|
|
126
|
-
tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
|
|
127
|
-
update (bool, optional): Update the tool if it already exists. Defaults to True.
|
|
128
|
-
Returns:
|
|
129
|
-
Tool object
|
|
130
|
-
"""
|
|
131
|
-
|
|
132
|
-
# TODO: check if tool already exists
|
|
133
|
-
# TODO: how to load modules?
|
|
134
|
-
# parse source code/schema
|
|
135
|
-
source_code = parse_source_code(func)
|
|
136
|
-
json_schema = generate_schema(func, name)
|
|
137
|
-
source_type = "python"
|
|
138
|
-
json_schema["name"]
|
|
139
|
-
|
|
140
|
-
if "memory" in tags:
|
|
141
|
-
# special modifications to memory functions
|
|
142
|
-
# self.memory -> self.memory.memory, since Agent.memory.memory needs to be modified (not BaseMemory.memory)
|
|
143
|
-
source_code = source_code.replace("self.memory", "self.memory.memory")
|
|
144
|
-
|
|
145
|
-
# create data
|
|
146
|
-
data = {"source_code": source_code, "source_type": source_type, "tags": tags, "json_schema": json_schema}
|
|
147
|
-
CreateToolRequest(**data) # validate
|
|
148
|
-
|
|
149
|
-
# make REST request
|
|
150
|
-
response = requests.post(f"{self.base_url}/{self.api_prefix}/admin/tools", json=data, headers=self.headers)
|
|
151
|
-
if response.status_code != 200:
|
|
152
|
-
raise ValueError(f"Failed to create tool: {response.text}")
|
|
153
|
-
return ToolModel(**response.json())
|
|
154
|
-
|
|
155
|
-
def list_tools(self):
|
|
156
|
-
response = requests.get(f"{self.base_url}/{self.api_prefix}/admin/tools", headers=self.headers)
|
|
157
|
-
return ListToolsResponse(**response.json()).tools
|
|
158
|
-
|
|
159
|
-
def delete_tool(self, name: str):
|
|
160
|
-
response = requests.delete(f"{self.base_url}/{self.api_prefix}/admin/tools/{name}", headers=self.headers)
|
|
161
|
-
if response.status_code != 200:
|
|
162
|
-
raise ValueError(f"Failed to delete tool: {response.text}")
|
|
163
|
-
return response.json()
|
|
164
|
-
|
|
165
|
-
def get_tool(self, name: str):
|
|
166
|
-
response = requests.get(f"{self.base_url}/{self.api_prefix}/admin/tools/{name}", headers=self.headers)
|
|
167
|
-
if response.status_code == 404:
|
|
168
|
-
return None
|
|
169
|
-
elif response.status_code != 200:
|
|
170
|
-
raise ValueError(f"Failed to get tool: {response.text}")
|
|
171
|
-
return ToolModel(**response.json())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|