letta-nightly 0.5.0.dev20241018104142__py3-none-any.whl → 0.5.0.dev20241019104023__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 +26 -2
- letta/client/client.py +41 -6
- letta/llm_api/openai.py +16 -5
- letta/local_llm/utils.py +22 -6
- letta/metadata.py +14 -5
- letta/schemas/memory.py +4 -0
- letta/schemas/openai/chat_completion_request.py +2 -2
- letta/server/rest_api/app.py +1 -0
- letta/server/rest_api/routers/v1/agents.py +14 -6
- letta/server/rest_api/routers/v1/tools.py +9 -6
- letta/server/server.py +30 -5
- {letta_nightly-0.5.0.dev20241018104142.dist-info → letta_nightly-0.5.0.dev20241019104023.dist-info}/METADATA +2 -2
- {letta_nightly-0.5.0.dev20241018104142.dist-info → letta_nightly-0.5.0.dev20241019104023.dist-info}/RECORD +16 -16
- {letta_nightly-0.5.0.dev20241018104142.dist-info → letta_nightly-0.5.0.dev20241019104023.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.0.dev20241018104142.dist-info → letta_nightly-0.5.0.dev20241019104023.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.0.dev20241018104142.dist-info → letta_nightly-0.5.0.dev20241019104023.dist-info}/entry_points.txt +0 -0
letta/agent.py
CHANGED
|
@@ -23,7 +23,7 @@ from letta.errors import LLMError
|
|
|
23
23
|
from letta.interface import AgentInterface
|
|
24
24
|
from letta.llm_api.helpers import is_context_overflow_error
|
|
25
25
|
from letta.llm_api.llm_api_tools import create
|
|
26
|
-
from letta.local_llm.utils import num_tokens_from_messages
|
|
26
|
+
from letta.local_llm.utils import num_tokens_from_functions, num_tokens_from_messages
|
|
27
27
|
from letta.memory import ArchivalMemory, RecallMemory, summarize_messages
|
|
28
28
|
from letta.metadata import MetadataStore
|
|
29
29
|
from letta.persistence_manager import LocalStateManager
|
|
@@ -33,6 +33,9 @@ from letta.schemas.embedding_config import EmbeddingConfig
|
|
|
33
33
|
from letta.schemas.enums import MessageRole
|
|
34
34
|
from letta.schemas.memory import ContextWindowOverview, Memory
|
|
35
35
|
from letta.schemas.message import Message, UpdateMessage
|
|
36
|
+
from letta.schemas.openai.chat_completion_request import (
|
|
37
|
+
Tool as ChatCompletionRequestTool,
|
|
38
|
+
)
|
|
36
39
|
from letta.schemas.openai.chat_completion_response import ChatCompletionResponse
|
|
37
40
|
from letta.schemas.openai.chat_completion_response import (
|
|
38
41
|
Message as ChatCompletionMessage,
|
|
@@ -1458,6 +1461,24 @@ class Agent(BaseAgent):
|
|
|
1458
1461
|
)
|
|
1459
1462
|
num_tokens_external_memory_summary = count_tokens(external_memory_summary)
|
|
1460
1463
|
|
|
1464
|
+
# tokens taken up by function definitions
|
|
1465
|
+
if self.functions:
|
|
1466
|
+
available_functions_definitions = [ChatCompletionRequestTool(type="function", function=f) for f in self.functions]
|
|
1467
|
+
num_tokens_available_functions_definitions = num_tokens_from_functions(functions=self.functions, model=self.model)
|
|
1468
|
+
else:
|
|
1469
|
+
available_functions_definitions = []
|
|
1470
|
+
num_tokens_available_functions_definitions = 0
|
|
1471
|
+
|
|
1472
|
+
num_tokens_used_total = (
|
|
1473
|
+
num_tokens_system # system prompt
|
|
1474
|
+
+ num_tokens_available_functions_definitions # function definitions
|
|
1475
|
+
+ num_tokens_core_memory # core memory
|
|
1476
|
+
+ num_tokens_external_memory_summary # metadata (statistics) about recall/archival
|
|
1477
|
+
+ num_tokens_summary_memory # summary of ongoing conversation
|
|
1478
|
+
+ num_tokens_messages # tokens taken by messages
|
|
1479
|
+
)
|
|
1480
|
+
assert isinstance(num_tokens_used_total, int)
|
|
1481
|
+
|
|
1461
1482
|
return ContextWindowOverview(
|
|
1462
1483
|
# context window breakdown (in messages)
|
|
1463
1484
|
num_messages=len(self._messages),
|
|
@@ -1466,7 +1487,7 @@ class Agent(BaseAgent):
|
|
|
1466
1487
|
num_tokens_external_memory_summary=num_tokens_external_memory_summary,
|
|
1467
1488
|
# top-level information
|
|
1468
1489
|
context_window_size_max=self.agent_state.llm_config.context_window,
|
|
1469
|
-
context_window_size_current=
|
|
1490
|
+
context_window_size_current=num_tokens_used_total,
|
|
1470
1491
|
# context window breakdown (in tokens)
|
|
1471
1492
|
num_tokens_system=num_tokens_system,
|
|
1472
1493
|
system_prompt=system_prompt,
|
|
@@ -1476,6 +1497,9 @@ class Agent(BaseAgent):
|
|
|
1476
1497
|
summary_memory=summary_memory,
|
|
1477
1498
|
num_tokens_messages=num_tokens_messages,
|
|
1478
1499
|
messages=self._messages,
|
|
1500
|
+
# related to functions
|
|
1501
|
+
num_tokens_functions_definitions=num_tokens_available_functions_definitions,
|
|
1502
|
+
functions_definitions=available_functions_definitions,
|
|
1479
1503
|
)
|
|
1480
1504
|
|
|
1481
1505
|
|
letta/client/client.py
CHANGED
|
@@ -96,6 +96,9 @@ class AbstractClient(object):
|
|
|
96
96
|
):
|
|
97
97
|
raise NotImplementedError
|
|
98
98
|
|
|
99
|
+
def get_tools_from_agent(self, agent_id: str):
|
|
100
|
+
raise NotImplementedError
|
|
101
|
+
|
|
99
102
|
def add_tool_to_agent(self, agent_id: str, tool_id: str):
|
|
100
103
|
raise NotImplementedError
|
|
101
104
|
|
|
@@ -197,7 +200,7 @@ class AbstractClient(object):
|
|
|
197
200
|
) -> Tool:
|
|
198
201
|
raise NotImplementedError
|
|
199
202
|
|
|
200
|
-
def list_tools(self) -> List[Tool]:
|
|
203
|
+
def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[Tool]:
|
|
201
204
|
raise NotImplementedError
|
|
202
205
|
|
|
203
206
|
def get_tool(self, id: str) -> Tool:
|
|
@@ -480,6 +483,21 @@ class RESTClient(AbstractClient):
|
|
|
480
483
|
raise ValueError(f"Failed to update agent: {response.text}")
|
|
481
484
|
return AgentState(**response.json())
|
|
482
485
|
|
|
486
|
+
def get_tools_from_agent(self, agent_id: str) -> List[Tool]:
|
|
487
|
+
"""
|
|
488
|
+
Get tools to an existing agent
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
agent_id (str): ID of the agent
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
List[Tool]: A List of Tool objs
|
|
495
|
+
"""
|
|
496
|
+
response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/tools", headers=self.headers)
|
|
497
|
+
if response.status_code != 200:
|
|
498
|
+
raise ValueError(f"Failed to get tools from agents: {response.text}")
|
|
499
|
+
return [Tool(**tool) for tool in response.json()]
|
|
500
|
+
|
|
483
501
|
def add_tool_to_agent(self, agent_id: str, tool_id: str):
|
|
484
502
|
"""
|
|
485
503
|
Add tool to an existing agent
|
|
@@ -1364,14 +1382,19 @@ class RESTClient(AbstractClient):
|
|
|
1364
1382
|
# raise ValueError(f"Failed to create tool: {response.text}")
|
|
1365
1383
|
# return ToolModel(**response.json())
|
|
1366
1384
|
|
|
1367
|
-
def list_tools(self) -> List[Tool]:
|
|
1385
|
+
def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[Tool]:
|
|
1368
1386
|
"""
|
|
1369
1387
|
List available tools for the user.
|
|
1370
1388
|
|
|
1371
1389
|
Returns:
|
|
1372
1390
|
tools (List[Tool]): List of tools
|
|
1373
1391
|
"""
|
|
1374
|
-
|
|
1392
|
+
params = {}
|
|
1393
|
+
if cursor:
|
|
1394
|
+
params["cursor"] = str(cursor)
|
|
1395
|
+
if limit:
|
|
1396
|
+
params["limit"] = limit
|
|
1397
|
+
response = requests.get(f"{self.base_url}/{self.api_prefix}/tools", params=params, headers=self.headers)
|
|
1375
1398
|
if response.status_code != 200:
|
|
1376
1399
|
raise ValueError(f"Failed to list tools: {response.text}")
|
|
1377
1400
|
return [Tool(**tool) for tool in response.json()]
|
|
@@ -1692,6 +1715,19 @@ class LocalClient(AbstractClient):
|
|
|
1692
1715
|
)
|
|
1693
1716
|
return agent_state
|
|
1694
1717
|
|
|
1718
|
+
def get_tools_from_agent(self, agent_id: str) -> List[Tool]:
|
|
1719
|
+
"""
|
|
1720
|
+
Get tools from an existing agent.
|
|
1721
|
+
|
|
1722
|
+
Args:
|
|
1723
|
+
agent_id (str): ID of the agent
|
|
1724
|
+
|
|
1725
|
+
Returns:
|
|
1726
|
+
List[Tool]: A list of Tool objs
|
|
1727
|
+
"""
|
|
1728
|
+
self.interface.clear()
|
|
1729
|
+
return self.server.get_tools_from_agent(agent_id=agent_id, user_id=self.user_id)
|
|
1730
|
+
|
|
1695
1731
|
def add_tool_to_agent(self, agent_id: str, tool_id: str):
|
|
1696
1732
|
"""
|
|
1697
1733
|
Add tool to an existing agent
|
|
@@ -2250,15 +2286,14 @@ class LocalClient(AbstractClient):
|
|
|
2250
2286
|
ToolUpdate(id=id, source_type=source_type, source_code=source_code, tags=tags, name=name), self.user_id
|
|
2251
2287
|
)
|
|
2252
2288
|
|
|
2253
|
-
def list_tools(self):
|
|
2289
|
+
def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[Tool]:
|
|
2254
2290
|
"""
|
|
2255
2291
|
List available tools for the user.
|
|
2256
2292
|
|
|
2257
2293
|
Returns:
|
|
2258
2294
|
tools (List[Tool]): List of tools
|
|
2259
2295
|
"""
|
|
2260
|
-
|
|
2261
|
-
return tools
|
|
2296
|
+
return self.server.list_tools(cursor=cursor, limit=limit, user_id=self.user_id)
|
|
2262
2297
|
|
|
2263
2298
|
def get_tool(self, id: str) -> Optional[Tool]:
|
|
2264
2299
|
"""
|
letta/llm_api/openai.py
CHANGED
|
@@ -18,8 +18,13 @@ from letta.local_llm.utils import num_tokens_from_functions, num_tokens_from_mes
|
|
|
18
18
|
from letta.schemas.llm_config import LLMConfig
|
|
19
19
|
from letta.schemas.message import Message as _Message
|
|
20
20
|
from letta.schemas.message import MessageRole as _MessageRole
|
|
21
|
+
from letta.schemas.openai.chat_completion_request import ChatCompletionRequest
|
|
21
22
|
from letta.schemas.openai.chat_completion_request import (
|
|
22
|
-
|
|
23
|
+
FunctionCall as ToolFunctionChoiceFunctionCall,
|
|
24
|
+
)
|
|
25
|
+
from letta.schemas.openai.chat_completion_request import (
|
|
26
|
+
Tool,
|
|
27
|
+
ToolFunctionChoice,
|
|
23
28
|
cast_message_to_subtype,
|
|
24
29
|
)
|
|
25
30
|
from letta.schemas.openai.chat_completion_response import (
|
|
@@ -100,10 +105,10 @@ def openai_get_model_list(
|
|
|
100
105
|
|
|
101
106
|
def build_openai_chat_completions_request(
|
|
102
107
|
llm_config: LLMConfig,
|
|
103
|
-
messages: List[
|
|
108
|
+
messages: List[_Message],
|
|
104
109
|
user_id: Optional[str],
|
|
105
110
|
functions: Optional[list],
|
|
106
|
-
function_call: str,
|
|
111
|
+
function_call: Optional[str],
|
|
107
112
|
use_tool_naming: bool,
|
|
108
113
|
max_tokens: Optional[int],
|
|
109
114
|
) -> ChatCompletionRequest:
|
|
@@ -124,11 +129,17 @@ def build_openai_chat_completions_request(
|
|
|
124
129
|
model = None
|
|
125
130
|
|
|
126
131
|
if use_tool_naming:
|
|
132
|
+
if function_call is None:
|
|
133
|
+
tool_choice = None
|
|
134
|
+
elif function_call not in ["none", "auto", "required"]:
|
|
135
|
+
tool_choice = ToolFunctionChoice(type="function", function=ToolFunctionChoiceFunctionCall(name=function_call))
|
|
136
|
+
else:
|
|
137
|
+
tool_choice = function_call
|
|
127
138
|
data = ChatCompletionRequest(
|
|
128
139
|
model=model,
|
|
129
140
|
messages=openai_message_list,
|
|
130
|
-
tools=[
|
|
131
|
-
tool_choice=
|
|
141
|
+
tools=[Tool(type="function", function=f) for f in functions] if functions else None,
|
|
142
|
+
tool_choice=tool_choice,
|
|
132
143
|
user=str(user_id),
|
|
133
144
|
max_tokens=max_tokens,
|
|
134
145
|
)
|
letta/local_llm/utils.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import warnings
|
|
3
|
-
from typing import List
|
|
3
|
+
from typing import List, Union
|
|
4
4
|
|
|
5
5
|
import requests
|
|
6
6
|
import tiktoken
|
|
@@ -11,6 +11,7 @@ import letta.local_llm.llm_chat_completion_wrappers.configurable_wrapper as conf
|
|
|
11
11
|
import letta.local_llm.llm_chat_completion_wrappers.dolphin as dolphin
|
|
12
12
|
import letta.local_llm.llm_chat_completion_wrappers.llama3 as llama3
|
|
13
13
|
import letta.local_llm.llm_chat_completion_wrappers.zephyr as zephyr
|
|
14
|
+
from letta.schemas.openai.chat_completion_request import Tool, ToolCall
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def post_json_auth_request(uri, json_payload, auth_type, auth_key):
|
|
@@ -123,7 +124,7 @@ def num_tokens_from_functions(functions: List[dict], model: str = "gpt-4"):
|
|
|
123
124
|
return num_tokens
|
|
124
125
|
|
|
125
126
|
|
|
126
|
-
def num_tokens_from_tool_calls(tool_calls: List[dict], model: str = "gpt-4"):
|
|
127
|
+
def num_tokens_from_tool_calls(tool_calls: Union[List[dict], List[ToolCall]], model: str = "gpt-4"):
|
|
127
128
|
"""Based on above code (num_tokens_from_functions).
|
|
128
129
|
|
|
129
130
|
Example to encode:
|
|
@@ -144,10 +145,25 @@ def num_tokens_from_tool_calls(tool_calls: List[dict], model: str = "gpt-4"):
|
|
|
144
145
|
|
|
145
146
|
num_tokens = 0
|
|
146
147
|
for tool_call in tool_calls:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
if isinstance(tool_call, dict):
|
|
149
|
+
tool_call_id = tool_call["id"]
|
|
150
|
+
tool_call_type = tool_call["type"]
|
|
151
|
+
tool_call_function = tool_call["function"]
|
|
152
|
+
tool_call_function_name = tool_call_function["name"]
|
|
153
|
+
tool_call_function_arguments = tool_call_function["arguments"]
|
|
154
|
+
elif isinstance(tool_call, Tool):
|
|
155
|
+
tool_call_id = tool_call.id
|
|
156
|
+
tool_call_type = tool_call.type
|
|
157
|
+
tool_call_function = tool_call.function
|
|
158
|
+
tool_call_function_name = tool_call_function.name
|
|
159
|
+
tool_call_function_arguments = tool_call_function.arguments
|
|
160
|
+
else:
|
|
161
|
+
raise ValueError(f"Unknown tool call type: {type(tool_call)}")
|
|
162
|
+
|
|
163
|
+
function_tokens = len(encoding.encode(tool_call_id))
|
|
164
|
+
function_tokens += 2 + len(encoding.encode(tool_call_type))
|
|
165
|
+
function_tokens += 2 + len(encoding.encode(tool_call_function_name))
|
|
166
|
+
function_tokens += 2 + len(encoding.encode(tool_call_function_arguments))
|
|
151
167
|
|
|
152
168
|
num_tokens += function_tokens
|
|
153
169
|
|
letta/metadata.py
CHANGED
|
@@ -14,7 +14,9 @@ from sqlalchemy import (
|
|
|
14
14
|
Integer,
|
|
15
15
|
String,
|
|
16
16
|
TypeDecorator,
|
|
17
|
+
asc,
|
|
17
18
|
desc,
|
|
19
|
+
or_,
|
|
18
20
|
)
|
|
19
21
|
from sqlalchemy.sql import func
|
|
20
22
|
|
|
@@ -707,12 +709,19 @@ class MetadataStore:
|
|
|
707
709
|
session.commit()
|
|
708
710
|
|
|
709
711
|
@enforce_types
|
|
710
|
-
|
|
711
|
-
def list_tools(self, user_id: Optional[str] = None) -> List[ToolModel]:
|
|
712
|
+
def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50, user_id: Optional[str] = None) -> List[ToolModel]:
|
|
712
713
|
with self.session_maker() as session:
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
714
|
+
# Query for public tools or user-specific tools
|
|
715
|
+
query = session.query(ToolModel).filter(or_(ToolModel.user_id == None, ToolModel.user_id == user_id))
|
|
716
|
+
|
|
717
|
+
# Apply cursor if provided (assuming cursor is an ID)
|
|
718
|
+
if cursor:
|
|
719
|
+
query = query.filter(ToolModel.id > cursor)
|
|
720
|
+
|
|
721
|
+
# Order by ID and apply limit
|
|
722
|
+
results = query.order_by(asc(ToolModel.id)).limit(limit).all()
|
|
723
|
+
|
|
724
|
+
# Convert to records
|
|
716
725
|
res = [r.to_record() for r in results]
|
|
717
726
|
return res
|
|
718
727
|
|
letta/schemas/memory.py
CHANGED
|
@@ -9,6 +9,7 @@ if TYPE_CHECKING:
|
|
|
9
9
|
|
|
10
10
|
from letta.schemas.block import Block
|
|
11
11
|
from letta.schemas.message import Message
|
|
12
|
+
from letta.schemas.openai.chat_completion_request import Tool
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class ContextWindowOverview(BaseModel):
|
|
@@ -41,6 +42,9 @@ class ContextWindowOverview(BaseModel):
|
|
|
41
42
|
num_tokens_summary_memory: int = Field(..., description="The number of tokens in the summary memory.")
|
|
42
43
|
summary_memory: Optional[str] = Field(None, description="The content of the summary memory.")
|
|
43
44
|
|
|
45
|
+
num_tokens_functions_definitions: int = Field(..., description="The number of tokens in the functions definitions.")
|
|
46
|
+
functions_definitions: Optional[List[Tool]] = Field(..., description="The content of the functions definitions.")
|
|
47
|
+
|
|
44
48
|
num_tokens_messages: int = Field(..., description="The number of tokens in the messages list.")
|
|
45
49
|
# TODO make list of messages?
|
|
46
50
|
# messages: List[dict] = Field(..., description="The messages in the context window.")
|
|
@@ -74,7 +74,7 @@ class ToolFunctionChoice(BaseModel):
|
|
|
74
74
|
function: FunctionCall
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
ToolChoice = Union[Literal["none", "auto"], ToolFunctionChoice]
|
|
77
|
+
ToolChoice = Union[Literal["none", "auto", "required"], ToolFunctionChoice]
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
## tools ##
|
|
@@ -117,7 +117,7 @@ class ChatCompletionRequest(BaseModel):
|
|
|
117
117
|
|
|
118
118
|
# function-calling related
|
|
119
119
|
tools: Optional[List[Tool]] = None
|
|
120
|
-
tool_choice: Optional[ToolChoice] = "none"
|
|
120
|
+
tool_choice: Optional[ToolChoice] = None # "none" means don't call a tool
|
|
121
121
|
# deprecated scheme
|
|
122
122
|
functions: Optional[List[FunctionSchema]] = None
|
|
123
123
|
function_call: Optional[FunctionCallChoice] = None
|
letta/server/rest_api/app.py
CHANGED
|
@@ -27,6 +27,7 @@ from letta.schemas.memory import (
|
|
|
27
27
|
from letta.schemas.message import Message, MessageCreate, UpdateMessage
|
|
28
28
|
from letta.schemas.passage import Passage
|
|
29
29
|
from letta.schemas.source import Source
|
|
30
|
+
from letta.schemas.tool import Tool
|
|
30
31
|
from letta.server.rest_api.interface import StreamingServerInterface
|
|
31
32
|
from letta.server.rest_api.utils import get_letta_server, sse_async_generator
|
|
32
33
|
from letta.server.server import SyncServer
|
|
@@ -100,6 +101,17 @@ def update_agent(
|
|
|
100
101
|
return server.update_agent(update_agent, user_id=actor.id)
|
|
101
102
|
|
|
102
103
|
|
|
104
|
+
@router.get("/{agent_id}/tools", response_model=List[Tool], operation_id="get_tools_from_agent")
|
|
105
|
+
def get_tools_from_agent(
|
|
106
|
+
agent_id: str,
|
|
107
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
108
|
+
user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
109
|
+
):
|
|
110
|
+
"""Get tools from an existing agent"""
|
|
111
|
+
actor = server.get_user_or_default(user_id=user_id)
|
|
112
|
+
return server.get_tools_from_agent(agent_id=agent_id, user_id=actor.id)
|
|
113
|
+
|
|
114
|
+
|
|
103
115
|
@router.patch("/{agent_id}/add-tool/{tool_id}", response_model=AgentState, operation_id="add_tool_to_agent")
|
|
104
116
|
def add_tool_to_agent(
|
|
105
117
|
agent_id: str,
|
|
@@ -107,10 +119,8 @@ def add_tool_to_agent(
|
|
|
107
119
|
server: "SyncServer" = Depends(get_letta_server),
|
|
108
120
|
user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
109
121
|
):
|
|
110
|
-
"""Add tools to an
|
|
122
|
+
"""Add tools to an existing agent"""
|
|
111
123
|
actor = server.get_user_or_default(user_id=user_id)
|
|
112
|
-
|
|
113
|
-
update_agent.id = agent_id
|
|
114
124
|
return server.add_tool_to_agent(agent_id=agent_id, tool_id=tool_id, user_id=actor.id)
|
|
115
125
|
|
|
116
126
|
|
|
@@ -121,10 +131,8 @@ def remove_tool_from_agent(
|
|
|
121
131
|
server: "SyncServer" = Depends(get_letta_server),
|
|
122
132
|
user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
123
133
|
):
|
|
124
|
-
"""Add tools to an
|
|
134
|
+
"""Add tools to an existing agent"""
|
|
125
135
|
actor = server.get_user_or_default(user_id=user_id)
|
|
126
|
-
|
|
127
|
-
update_agent.id = agent_id
|
|
128
136
|
return server.remove_tool_from_agent(agent_id=agent_id, tool_id=tool_id, user_id=actor.id)
|
|
129
137
|
|
|
130
138
|
|
|
@@ -59,18 +59,21 @@ def get_tool_id(
|
|
|
59
59
|
|
|
60
60
|
@router.get("/", response_model=List[Tool], operation_id="list_tools")
|
|
61
61
|
def list_all_tools(
|
|
62
|
+
cursor: Optional[str] = None,
|
|
63
|
+
limit: Optional[int] = 50,
|
|
62
64
|
server: SyncServer = Depends(get_letta_server),
|
|
63
65
|
user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
64
66
|
):
|
|
65
67
|
"""
|
|
66
68
|
Get a list of all tools available to agents created by a user
|
|
67
69
|
"""
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
try:
|
|
71
|
+
actor = server.get_user_or_default(user_id=user_id)
|
|
72
|
+
return server.list_tools(cursor=cursor, limit=limit, user_id=actor.id)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
# Log or print the full exception here for debugging
|
|
75
|
+
print(f"Error occurred: {e}")
|
|
76
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
74
77
|
|
|
75
78
|
|
|
76
79
|
@router.post("/", response_model=Tool, operation_id="create_tool")
|
letta/server/server.py
CHANGED
|
@@ -73,7 +73,12 @@ from letta.schemas.file import FileMetadata
|
|
|
73
73
|
from letta.schemas.job import Job
|
|
74
74
|
from letta.schemas.letta_message import LettaMessage
|
|
75
75
|
from letta.schemas.llm_config import LLMConfig
|
|
76
|
-
from letta.schemas.memory import
|
|
76
|
+
from letta.schemas.memory import (
|
|
77
|
+
ArchivalMemorySummary,
|
|
78
|
+
ContextWindowOverview,
|
|
79
|
+
Memory,
|
|
80
|
+
RecallMemorySummary,
|
|
81
|
+
)
|
|
77
82
|
from letta.schemas.message import Message, MessageCreate, MessageRole, UpdateMessage
|
|
78
83
|
from letta.schemas.organization import Organization, OrganizationCreate
|
|
79
84
|
from letta.schemas.passage import Passage
|
|
@@ -977,13 +982,24 @@ class SyncServer(Server):
|
|
|
977
982
|
# TODO: probably reload the agent somehow?
|
|
978
983
|
return letta_agent.agent_state
|
|
979
984
|
|
|
985
|
+
def get_tools_from_agent(self, agent_id: str, user_id: Optional[str]) -> List[Tool]:
|
|
986
|
+
"""Get tools from an existing agent"""
|
|
987
|
+
if self.ms.get_user(user_id=user_id) is None:
|
|
988
|
+
raise ValueError(f"User user_id={user_id} does not exist")
|
|
989
|
+
if self.ms.get_agent(agent_id=agent_id) is None:
|
|
990
|
+
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
991
|
+
|
|
992
|
+
# Get the agent object (loaded in memory)
|
|
993
|
+
letta_agent = self._get_or_load_agent(agent_id=agent_id)
|
|
994
|
+
return letta_agent.tools
|
|
995
|
+
|
|
980
996
|
def add_tool_to_agent(
|
|
981
997
|
self,
|
|
982
998
|
agent_id: str,
|
|
983
999
|
tool_id: str,
|
|
984
1000
|
user_id: str,
|
|
985
1001
|
):
|
|
986
|
-
"""
|
|
1002
|
+
"""Add tools from an existing agent"""
|
|
987
1003
|
if self.ms.get_user(user_id=user_id) is None:
|
|
988
1004
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
989
1005
|
if self.ms.get_agent(agent_id=agent_id) is None:
|
|
@@ -1022,7 +1038,7 @@ class SyncServer(Server):
|
|
|
1022
1038
|
tool_id: str,
|
|
1023
1039
|
user_id: str,
|
|
1024
1040
|
):
|
|
1025
|
-
"""
|
|
1041
|
+
"""Remove tools from an existing agent"""
|
|
1026
1042
|
if self.ms.get_user(user_id=user_id) is None:
|
|
1027
1043
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1028
1044
|
if self.ms.get_agent(agent_id=agent_id) is None:
|
|
@@ -1965,9 +1981,9 @@ class SyncServer(Server):
|
|
|
1965
1981
|
"""Delete a tool"""
|
|
1966
1982
|
self.ms.delete_tool(tool_id)
|
|
1967
1983
|
|
|
1968
|
-
def list_tools(self, user_id: str) -> List[Tool]:
|
|
1984
|
+
def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50, user_id: Optional[str] = None) -> List[Tool]:
|
|
1969
1985
|
"""List tools available to user_id"""
|
|
1970
|
-
tools = self.ms.list_tools(user_id)
|
|
1986
|
+
tools = self.ms.list_tools(cursor=cursor, limit=limit, user_id=user_id)
|
|
1971
1987
|
return tools
|
|
1972
1988
|
|
|
1973
1989
|
def add_default_tools(self, module_name="base", user_id: Optional[str] = None):
|
|
@@ -2166,3 +2182,12 @@ class SyncServer(Server):
|
|
|
2166
2182
|
|
|
2167
2183
|
def add_embedding_model(self, request: EmbeddingConfig) -> EmbeddingConfig:
|
|
2168
2184
|
"""Add a new embedding model"""
|
|
2185
|
+
|
|
2186
|
+
def get_agent_context_window(
|
|
2187
|
+
self,
|
|
2188
|
+
user_id: str,
|
|
2189
|
+
agent_id: str,
|
|
2190
|
+
) -> ContextWindowOverview:
|
|
2191
|
+
# Get the current message
|
|
2192
|
+
letta_agent = self._get_or_load_agent(agent_id=agent_id)
|
|
2193
|
+
return letta_agent.get_context_window()
|
|
@@ -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.dev20241019104023
|
|
4
4
|
Summary: Create LLM agents with long-term memory and custom tools
|
|
5
5
|
License: Apache License
|
|
6
6
|
Author: Letta Team
|
|
@@ -24,7 +24,7 @@ Requires-Dist: alembic (>=1.13.3,<2.0.0)
|
|
|
24
24
|
Requires-Dist: autoflake (>=2.3.0,<3.0.0) ; extra == "dev"
|
|
25
25
|
Requires-Dist: black[jupyter] (>=24.2.0,<25.0.0) ; extra == "dev"
|
|
26
26
|
Requires-Dist: chromadb (>=0.4.24,<0.5.0)
|
|
27
|
-
Requires-Dist: composio-core (>=0.5.
|
|
27
|
+
Requires-Dist: composio-core (>=0.5.34,<0.6.0) ; extra == "external-tools"
|
|
28
28
|
Requires-Dist: composio-langchain (>=0.5.28,<0.6.0) ; extra == "external-tools"
|
|
29
29
|
Requires-Dist: crewai (>=0.41.1,<0.42.0) ; extra == "external-tools"
|
|
30
30
|
Requires-Dist: crewai-tools (>=0.8.3,<0.9.0) ; extra == "external-tools"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
letta/__init__.py,sha256=cwav47GUQB8F9w0sHIDPe1nZMf_WL00KovBa9dZvSj4,996
|
|
2
2
|
letta/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
|
|
3
|
-
letta/agent.py,sha256=
|
|
3
|
+
letta/agent.py,sha256=bXs6gulZAriEa8Bs9bLguN-T5hGUYe0h8FDPBl6Oz7U,72880
|
|
4
4
|
letta/agent_store/chroma.py,sha256=upR5zGnGs6I6btulEYbiZdGG87BgKjxUJOQZ4Y-RQ_M,12492
|
|
5
5
|
letta/agent_store/db.py,sha256=54EpxQYX0lAWxrsO0iUKw2vibF8-62Khczns2vxIK-0,23307
|
|
6
6
|
letta/agent_store/lancedb.py,sha256=i63d4VZwj9UIOTNs5f0JZ_r5yZD-jKWz4FAH4RMpXOE,5104
|
|
@@ -15,7 +15,7 @@ letta/cli/cli_config.py,sha256=G7QqPNTtlQ4TdrXZrrFFGblZEhnkyrqN1Cl5z415C-g,8689
|
|
|
15
15
|
letta/cli/cli_load.py,sha256=x4L8s15GwIW13xrhKYFWHo_y-IVGtoPDHWWKcHDRP10,4587
|
|
16
16
|
letta/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
letta/client/admin.py,sha256=itdH1dGL143Je5tkZl8dQ1PavjepClar3QasxpbX1cI,7397
|
|
18
|
-
letta/client/client.py,sha256=
|
|
18
|
+
letta/client/client.py,sha256=Alx_m9b4ZX_A3G7XtOa5Lgbxtf9PmDhCkUf3QDK0jS0,93065
|
|
19
19
|
letta/client/streaming.py,sha256=bfWlUu7z7EoPfKxBqIarYxGKyrL7Pj79BlliToqcCgI,4592
|
|
20
20
|
letta/client/utils.py,sha256=OJlAKWrldc4I6M1WpcTWNtPJ4wfxlzlZqWLfCozkFtI,2872
|
|
21
21
|
letta/config.py,sha256=j2I90fOh9d9__kOYObwTDLbvVwYR50rIql5nzrvREKg,19161
|
|
@@ -44,7 +44,7 @@ letta/llm_api/google_ai.py,sha256=3xZ074nSOCC22c15yerA5ngWzh0ex4wxeI-6faNbHPE,17
|
|
|
44
44
|
letta/llm_api/helpers.py,sha256=8aG6LzB0T3NFlnab-RR2tj0ARUTMBHSd0icCur5-RCk,8813
|
|
45
45
|
letta/llm_api/llm_api_tools.py,sha256=GEBO7Dlt7xtAQud1sVsigKZKPpLOZOt2IWL8LwcNV4o,14869
|
|
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=er_cmGWeiMQsfFEXzG0DZhn8-Ftkh2q3nxtszXYRsbw,22195
|
|
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=fDBoEGKc1SdhbvwqkQ6hRk2I7VCW3Rzurq81QdbCb7s,12174
|
|
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
|
|
@@ -85,7 +85,7 @@ letta/local_llm/webui/settings.py,sha256=gmLHfiOl1u4JmlAZU2d2O8YKF9lafdakyjwR_ft
|
|
|
85
85
|
letta/log.py,sha256=QHquDnL7oUAvdKlAwUlCK9zXKDMUjrU9WA0bxnMsP0Y,2101
|
|
86
86
|
letta/main.py,sha256=yHgM1lltQZvbE8k0QDQMmVyJiWEj07ZTOYIBHDxE_DQ,18709
|
|
87
87
|
letta/memory.py,sha256=6q1x3-PY-PeXzAt6hvP-UF1ajvroPZ7XW-5nLy-JhMo,17657
|
|
88
|
-
letta/metadata.py,sha256=
|
|
88
|
+
letta/metadata.py,sha256=HIzNn9A28iD3d5Utrey8Z8CQ4KHmaD1iib2a_blLvds,37174
|
|
89
89
|
letta/o1_agent.py,sha256=0jospImZUKhuQZ0cop0INj8xI6cxhxNffGA8iloHyfU,3114
|
|
90
90
|
letta/openai_backcompat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
91
|
letta/openai_backcompat/openai_object.py,sha256=Y1ZS1sATP60qxJiOsjOP3NbwSzuzvkNAvb3DeuhM5Uk,13490
|
|
@@ -127,9 +127,9 @@ letta/schemas/letta_message.py,sha256=Slgxa59qZfdvqXuCVHOt03u-7JL456ZY-WLaK5UYYK
|
|
|
127
127
|
letta/schemas/letta_request.py,sha256=_oiDshc_AoFWIfXRk2VX5-AxO5vDlyN-9r-gnyLj_30,1890
|
|
128
128
|
letta/schemas/letta_response.py,sha256=_UJoO3UtC3F5DtQCHzdiGM1SHNPYPKvopIWqg8t5YZw,1564
|
|
129
129
|
letta/schemas/llm_config.py,sha256=eFA48vKBTO70qaob8pak2CWOH7TCQeqWuClkMBc2vbY,4172
|
|
130
|
-
letta/schemas/memory.py,sha256=
|
|
130
|
+
letta/schemas/memory.py,sha256=rBbfCps6Oi1p3eYDx8_bY34PQGXx69-pha0Uj4B9678,11650
|
|
131
131
|
letta/schemas/message.py,sha256=X0adFviO6sbobFns30M0Ym6DChRDVThaA82gqbzw3Jg,33531
|
|
132
|
-
letta/schemas/openai/chat_completion_request.py,sha256=
|
|
132
|
+
letta/schemas/openai/chat_completion_request.py,sha256=AOIwgbN3CZKVqkuXeMHeSa53u4h0wVq69t3T_LJ0vIE,3389
|
|
133
133
|
letta/schemas/openai/chat_completion_response.py,sha256=05FRfm1EsVivyeWo2aoJk34h3W4pAb4lBCPn1eujjcw,3916
|
|
134
134
|
letta/schemas/openai/chat_completions.py,sha256=V0ZPIIk-ds3O6MAkNHMz8zh1hqMFSPrTcYr88WDYzWE,3588
|
|
135
135
|
letta/schemas/openai/embedding_response.py,sha256=WKIZpXab1Av7v6sxKG8feW3ZtpQUNosmLVSuhXYa_xU,357
|
|
@@ -147,7 +147,7 @@ letta/server/rest_api/admin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
|
|
|
147
147
|
letta/server/rest_api/admin/agents.py,sha256=cFNDU4Z8wGpcWXuo5aBgX6CcxLzPpTFYnTIaiF-3qvw,564
|
|
148
148
|
letta/server/rest_api/admin/tools.py,sha256=HdXR_MRWkh3zMtb96Eaomp4rReNm3DirnXCNqAD7tNU,3093
|
|
149
149
|
letta/server/rest_api/admin/users.py,sha256=IIec8G2yxVZtSo8dYrQPktVj8XIsZMptxigxmgULKO8,3480
|
|
150
|
-
letta/server/rest_api/app.py,sha256=
|
|
150
|
+
letta/server/rest_api/app.py,sha256=JNmDnvp9fP--hJPtPpEWgQT-14O1YOceZbWELr2vedA,6207
|
|
151
151
|
letta/server/rest_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
152
152
|
letta/server/rest_api/auth/index.py,sha256=fQBGyVylGSRfEMLQ17cZzrHd5Y1xiVylvPqH5Rl-lXQ,1378
|
|
153
153
|
letta/server/rest_api/auth_token.py,sha256=725EFEIiNj4dh70hrSd94UysmFD8vcJLrTRfNHkzxDo,774
|
|
@@ -161,18 +161,18 @@ letta/server/rest_api/routers/openai/assistants/threads.py,sha256=WXVGBaBvSNPB7Z
|
|
|
161
161
|
letta/server/rest_api/routers/openai/chat_completions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
162
162
|
letta/server/rest_api/routers/openai/chat_completions/chat_completions.py,sha256=-uye6cm4SnoQGwxhr1N1FrSXOlnO2Hvbfj6k8JSc45k,4918
|
|
163
163
|
letta/server/rest_api/routers/v1/__init__.py,sha256=sqlVZa-u9DJwdRsp0_8YUGrac9DHguIB4wETlEDRylA,666
|
|
164
|
-
letta/server/rest_api/routers/v1/agents.py,sha256=
|
|
164
|
+
letta/server/rest_api/routers/v1/agents.py,sha256=Yoktva6_pSCRztUdZNZXdbnrp9L5OKnP5E1mZkbUAGw,25066
|
|
165
165
|
letta/server/rest_api/routers/v1/blocks.py,sha256=0WekE_yBD2U3jYgPxI0DCFjACWavCAlvm_Ybw5SZBnw,2583
|
|
166
166
|
letta/server/rest_api/routers/v1/health.py,sha256=pKCuVESlVOhGIb4VC4K-H82eZqfghmT6kvj2iOkkKuc,401
|
|
167
167
|
letta/server/rest_api/routers/v1/jobs.py,sha256=a-j0v-5A0un0pVCOHpfeWnzpOWkVDQO6ti42k_qAlZY,2272
|
|
168
168
|
letta/server/rest_api/routers/v1/llms.py,sha256=TcyvSx6MEM3je5F4DysL7ligmssL_pFlJaaO4uL95VY,877
|
|
169
169
|
letta/server/rest_api/routers/v1/organizations.py,sha256=i3S9E1hu2Zj9g0pRv6wnQhz1VJ_RMIHCrGzgwY-Wj3Y,1945
|
|
170
170
|
letta/server/rest_api/routers/v1/sources.py,sha256=eY_pk9jRL2Y9yIZdsTjH6EuKsfH1neaTU15MKNL0dvw,8749
|
|
171
|
-
letta/server/rest_api/routers/v1/tools.py,sha256=
|
|
171
|
+
letta/server/rest_api/routers/v1/tools.py,sha256=vxE4b5juoiBiNWmplktuv6GEgenCkKBRov-t6usUJ9A,3665
|
|
172
172
|
letta/server/rest_api/routers/v1/users.py,sha256=Y2rDvHOG1B5FLSOjutY3R22vt48IngbZ-9h8CohG5rc,3378
|
|
173
173
|
letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
|
|
174
174
|
letta/server/rest_api/utils.py,sha256=Fc2ZGKzLaBa2sEtSTVjJ8D5M0xIwsWC0CVAOIJaD3rY,2176
|
|
175
|
-
letta/server/server.py,sha256=
|
|
175
|
+
letta/server/server.py,sha256=fEPkRE1R1jBu6S_pMYi2NmoTOkDwa6NjOnNnJVmm5Cw,91491
|
|
176
176
|
letta/server/startup.sh,sha256=jeGV7B_PS0hS-tT6o6GpACrUbV9WV1NI2L9aLoUDDtc,311
|
|
177
177
|
letta/server/static_files/assets/index-3ab03d5b.css,sha256=OrA9W4iKJ5h2Wlr7GwdAT4wow0CM8hVit1yOxEL49Qw,54295
|
|
178
178
|
letta/server/static_files/assets/index-d6b3669a.js,sha256=i1nHReU0RPnj-a5W0nNPV4Y9bQ0FOW0ztjMz8a2AE-Y,1821560
|
|
@@ -189,8 +189,8 @@ letta/settings.py,sha256=gNdH-Ty6f-Nfz2j9ZMZFRQHac2KzgsxLZNt5l_TiAyo,3301
|
|
|
189
189
|
letta/streaming_interface.py,sha256=_FPUWy58j50evHcpXyd7zB1wWqeCc71NCFeWh_TBvnw,15736
|
|
190
190
|
letta/system.py,sha256=buKYPqG5n2x41hVmWpu6JUpyd7vTWED9Km2_M7dLrvk,6960
|
|
191
191
|
letta/utils.py,sha256=neUs7mxNfndzRL5XUxerr8Lic6w7qnyyvf8FBwMnyWw,30852
|
|
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.
|
|
192
|
+
letta_nightly-0.5.0.dev20241019104023.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
|
|
193
|
+
letta_nightly-0.5.0.dev20241019104023.dist-info/METADATA,sha256=NO5Tf5FycuYWmQyKH0UcDvJ7IzdbJcjl50nUu0W6hBQ,10620
|
|
194
|
+
letta_nightly-0.5.0.dev20241019104023.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
195
|
+
letta_nightly-0.5.0.dev20241019104023.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
|
|
196
|
+
letta_nightly-0.5.0.dev20241019104023.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|