letta-nightly 0.6.27.dev20250220104103__py3-none-any.whl → 0.6.29.dev20250221033538__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of letta-nightly might be problematic. Click here for more details.
- letta/__init__.py +1 -1
- letta/agent.py +19 -2
- letta/client/client.py +2 -0
- letta/constants.py +2 -0
- letta/functions/schema_generator.py +6 -6
- letta/helpers/converters.py +153 -0
- letta/helpers/tool_rule_solver.py +11 -1
- letta/llm_api/anthropic.py +10 -5
- letta/llm_api/aws_bedrock.py +1 -1
- letta/llm_api/deepseek.py +303 -0
- letta/llm_api/helpers.py +20 -10
- letta/llm_api/llm_api_tools.py +85 -2
- letta/llm_api/openai.py +16 -1
- letta/local_llm/chat_completion_proxy.py +15 -2
- letta/local_llm/lmstudio/api.py +75 -1
- letta/orm/__init__.py +2 -0
- letta/orm/agent.py +11 -4
- letta/orm/custom_columns.py +31 -110
- letta/orm/identities_agents.py +13 -0
- letta/orm/identity.py +60 -0
- letta/orm/organization.py +2 -0
- letta/orm/sqlalchemy_base.py +4 -0
- letta/schemas/agent.py +11 -1
- letta/schemas/identity.py +67 -0
- letta/schemas/llm_config.py +2 -0
- letta/schemas/message.py +1 -1
- letta/schemas/openai/chat_completion_response.py +2 -0
- letta/schemas/providers.py +72 -1
- letta/schemas/tool_rule.py +9 -1
- letta/serialize_schemas/__init__.py +1 -0
- letta/serialize_schemas/agent.py +36 -0
- letta/serialize_schemas/base.py +12 -0
- letta/serialize_schemas/custom_fields.py +69 -0
- letta/serialize_schemas/message.py +15 -0
- letta/server/db.py +111 -0
- letta/server/rest_api/app.py +8 -0
- letta/server/rest_api/chat_completions_interface.py +45 -21
- letta/server/rest_api/interface.py +114 -9
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +98 -24
- letta/server/rest_api/routers/v1/__init__.py +2 -0
- letta/server/rest_api/routers/v1/agents.py +14 -3
- letta/server/rest_api/routers/v1/identities.py +121 -0
- letta/server/rest_api/utils.py +183 -4
- letta/server/server.py +23 -117
- letta/services/agent_manager.py +53 -6
- letta/services/block_manager.py +1 -1
- letta/services/identity_manager.py +156 -0
- letta/services/job_manager.py +1 -1
- letta/services/message_manager.py +1 -1
- letta/services/organization_manager.py +1 -1
- letta/services/passage_manager.py +1 -1
- letta/services/provider_manager.py +1 -1
- letta/services/sandbox_config_manager.py +1 -1
- letta/services/source_manager.py +1 -1
- letta/services/step_manager.py +1 -1
- letta/services/tool_manager.py +1 -1
- letta/services/user_manager.py +1 -1
- letta/settings.py +3 -0
- letta/streaming_interface.py +6 -2
- letta/tracing.py +205 -0
- letta/utils.py +4 -0
- {letta_nightly-0.6.27.dev20250220104103.dist-info → letta_nightly-0.6.29.dev20250221033538.dist-info}/METADATA +9 -2
- {letta_nightly-0.6.27.dev20250220104103.dist-info → letta_nightly-0.6.29.dev20250221033538.dist-info}/RECORD +66 -52
- {letta_nightly-0.6.27.dev20250220104103.dist-info → letta_nightly-0.6.29.dev20250221033538.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.27.dev20250220104103.dist-info → letta_nightly-0.6.29.dev20250221033538.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.27.dev20250220104103.dist-info → letta_nightly-0.6.29.dev20250221033538.dist-info}/entry_points.txt +0 -0
letta/__init__.py
CHANGED
letta/agent.py
CHANGED
|
@@ -60,6 +60,7 @@ from letta.services.tool_manager import ToolManager
|
|
|
60
60
|
from letta.settings import summarizer_settings
|
|
61
61
|
from letta.streaming_interface import StreamingRefreshCLIInterface
|
|
62
62
|
from letta.system import get_heartbeat, get_token_limit_warning, package_function_response, package_summarize_message, package_user_message
|
|
63
|
+
from letta.tracing import trace_method
|
|
63
64
|
from letta.utils import (
|
|
64
65
|
count_tokens,
|
|
65
66
|
get_friendly_error_msg,
|
|
@@ -309,6 +310,7 @@ class Agent(BaseAgent):
|
|
|
309
310
|
# Return updated messages
|
|
310
311
|
return messages
|
|
311
312
|
|
|
313
|
+
@trace_method("Get AI Reply")
|
|
312
314
|
def _get_ai_reply(
|
|
313
315
|
self,
|
|
314
316
|
message_sequence: List[Message],
|
|
@@ -320,6 +322,7 @@ class Agent(BaseAgent):
|
|
|
320
322
|
max_delay: float = 10.0, # max delay between retries
|
|
321
323
|
step_count: Optional[int] = None,
|
|
322
324
|
last_function_failed: bool = False,
|
|
325
|
+
put_inner_thoughts_first: bool = True,
|
|
323
326
|
) -> ChatCompletionResponse:
|
|
324
327
|
"""Get response from LLM API with robust retry mechanism."""
|
|
325
328
|
log_telemetry(self.logger, "_get_ai_reply start")
|
|
@@ -365,6 +368,7 @@ class Agent(BaseAgent):
|
|
|
365
368
|
force_tool_call=force_tool_call,
|
|
366
369
|
stream=stream,
|
|
367
370
|
stream_interface=self.interface,
|
|
371
|
+
put_inner_thoughts_first=put_inner_thoughts_first,
|
|
368
372
|
)
|
|
369
373
|
log_telemetry(self.logger, "_get_ai_reply create finish")
|
|
370
374
|
|
|
@@ -399,6 +403,7 @@ class Agent(BaseAgent):
|
|
|
399
403
|
log_telemetry(self.logger, "_handle_ai_response finish catch-all exception")
|
|
400
404
|
raise Exception("Retries exhausted and no valid response received.")
|
|
401
405
|
|
|
406
|
+
@trace_method("Handle AI Response")
|
|
402
407
|
def _handle_ai_response(
|
|
403
408
|
self,
|
|
404
409
|
response_message: ChatCompletionMessage, # TODO should we eventually move the Message creation outside of this function?
|
|
@@ -492,7 +497,10 @@ class Agent(BaseAgent):
|
|
|
492
497
|
try:
|
|
493
498
|
raw_function_args = function_call.arguments
|
|
494
499
|
function_args = parse_json(raw_function_args)
|
|
495
|
-
|
|
500
|
+
if not isinstance(function_args, dict):
|
|
501
|
+
raise ValueError(f"Function arguments are not a dictionary: {function_args} (raw={raw_function_args})")
|
|
502
|
+
except Exception as e:
|
|
503
|
+
print(e)
|
|
496
504
|
error_msg = f"Error parsing JSON for function '{function_name}' arguments: {function_call.arguments}"
|
|
497
505
|
function_response = "None" # more like "never ran?"
|
|
498
506
|
messages = self._handle_function_error_response(
|
|
@@ -627,15 +635,22 @@ class Agent(BaseAgent):
|
|
|
627
635
|
elif self.tool_rules_solver.is_terminal_tool(function_name):
|
|
628
636
|
heartbeat_request = False
|
|
629
637
|
|
|
638
|
+
# if continue tool rule, then must request a heartbeat
|
|
639
|
+
# TODO: dont even include heartbeats in the args
|
|
640
|
+
if self.tool_rules_solver.is_continue_tool(function_name):
|
|
641
|
+
heartbeat_request = True
|
|
642
|
+
|
|
630
643
|
log_telemetry(self.logger, "_handle_ai_response finish")
|
|
631
644
|
return messages, heartbeat_request, function_failed
|
|
632
645
|
|
|
646
|
+
@trace_method("Agent Step")
|
|
633
647
|
def step(
|
|
634
648
|
self,
|
|
635
649
|
messages: Union[Message, List[Message]],
|
|
636
650
|
# additional args
|
|
637
651
|
chaining: bool = True,
|
|
638
652
|
max_chaining_steps: Optional[int] = None,
|
|
653
|
+
put_inner_thoughts_first: bool = True,
|
|
639
654
|
**kwargs,
|
|
640
655
|
) -> LettaUsageStatistics:
|
|
641
656
|
"""Run Agent.step in a loop, handling chaining via heartbeat requests and function failures"""
|
|
@@ -650,6 +665,7 @@ class Agent(BaseAgent):
|
|
|
650
665
|
kwargs["last_function_failed"] = function_failed
|
|
651
666
|
step_response = self.inner_step(
|
|
652
667
|
messages=next_input_message,
|
|
668
|
+
put_inner_thoughts_first=put_inner_thoughts_first,
|
|
653
669
|
**kwargs,
|
|
654
670
|
)
|
|
655
671
|
|
|
@@ -731,9 +747,9 @@ class Agent(BaseAgent):
|
|
|
731
747
|
metadata: Optional[dict] = None,
|
|
732
748
|
summarize_attempt_count: int = 0,
|
|
733
749
|
last_function_failed: bool = False,
|
|
750
|
+
put_inner_thoughts_first: bool = True,
|
|
734
751
|
) -> AgentStepResponse:
|
|
735
752
|
"""Runs a single step in the agent loop (generates at most one LLM call)"""
|
|
736
|
-
|
|
737
753
|
try:
|
|
738
754
|
|
|
739
755
|
# Extract job_id from metadata if present
|
|
@@ -766,6 +782,7 @@ class Agent(BaseAgent):
|
|
|
766
782
|
stream=stream,
|
|
767
783
|
step_count=step_count,
|
|
768
784
|
last_function_failed=last_function_failed,
|
|
785
|
+
put_inner_thoughts_first=put_inner_thoughts_first,
|
|
769
786
|
)
|
|
770
787
|
if not response:
|
|
771
788
|
# EDGE CASE: Function call failed AND there's no tools left for agent to call -> return early
|
letta/client/client.py
CHANGED
|
@@ -2351,6 +2351,7 @@ class LocalClient(AbstractClient):
|
|
|
2351
2351
|
tool_rules: Optional[List[BaseToolRule]] = None,
|
|
2352
2352
|
include_base_tools: Optional[bool] = True,
|
|
2353
2353
|
include_multi_agent_tools: bool = False,
|
|
2354
|
+
include_base_tool_rules: bool = True,
|
|
2354
2355
|
# metadata
|
|
2355
2356
|
metadata: Optional[Dict] = {"human:": DEFAULT_HUMAN, "persona": DEFAULT_PERSONA},
|
|
2356
2357
|
description: Optional[str] = None,
|
|
@@ -2402,6 +2403,7 @@ class LocalClient(AbstractClient):
|
|
|
2402
2403
|
"tool_rules": tool_rules,
|
|
2403
2404
|
"include_base_tools": include_base_tools,
|
|
2404
2405
|
"include_multi_agent_tools": include_multi_agent_tools,
|
|
2406
|
+
"include_base_tool_rules": include_base_tool_rules,
|
|
2405
2407
|
"system": system,
|
|
2406
2408
|
"agent_type": agent_type,
|
|
2407
2409
|
"llm_config": llm_config if llm_config else self._default_llm_config,
|
letta/constants.py
CHANGED
|
@@ -86,6 +86,8 @@ NON_USER_MSG_PREFIX = "[This is an automated system message hidden from the user
|
|
|
86
86
|
# The max amount of tokens supported by the underlying model (eg 8k for gpt-4 and Mistral 7B)
|
|
87
87
|
LLM_MAX_TOKENS = {
|
|
88
88
|
"DEFAULT": 8192,
|
|
89
|
+
"deepseek-chat": 64000,
|
|
90
|
+
"deepseek-reasoner": 64000,
|
|
89
91
|
## OpenAI models: https://platform.openai.com/docs/models/overview
|
|
90
92
|
# "o1-preview
|
|
91
93
|
"chatgpt-4o-latest": 128000,
|
|
@@ -394,12 +394,12 @@ def generate_schema(function, name: Optional[str] = None, description: Optional[
|
|
|
394
394
|
# append the heartbeat
|
|
395
395
|
# TODO: don't hard-code
|
|
396
396
|
# TODO: if terminal, don't include this
|
|
397
|
-
if function.__name__ not in ["send_message"]:
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
397
|
+
# if function.__name__ not in ["send_message"]:
|
|
398
|
+
schema["parameters"]["properties"]["request_heartbeat"] = {
|
|
399
|
+
"type": "boolean",
|
|
400
|
+
"description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function.",
|
|
401
|
+
}
|
|
402
|
+
schema["parameters"]["required"].append("request_heartbeat")
|
|
403
403
|
|
|
404
404
|
return schema
|
|
405
405
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall as OpenAIToolCall
|
|
6
|
+
from openai.types.chat.chat_completion_message_tool_call import Function as OpenAIFunction
|
|
7
|
+
from sqlalchemy import Dialect
|
|
8
|
+
|
|
9
|
+
from letta.schemas.embedding_config import EmbeddingConfig
|
|
10
|
+
from letta.schemas.enums import ToolRuleType
|
|
11
|
+
from letta.schemas.llm_config import LLMConfig
|
|
12
|
+
from letta.schemas.tool_rule import ChildToolRule, ConditionalToolRule, ContinueToolRule, InitToolRule, TerminalToolRule, ToolRule
|
|
13
|
+
|
|
14
|
+
# --------------------------
|
|
15
|
+
# LLMConfig Serialization
|
|
16
|
+
# --------------------------
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def serialize_llm_config(config: Union[Optional[LLMConfig], Dict]) -> Optional[Dict]:
|
|
20
|
+
"""Convert an LLMConfig object into a JSON-serializable dictionary."""
|
|
21
|
+
if config and isinstance(config, LLMConfig):
|
|
22
|
+
return config.model_dump()
|
|
23
|
+
return config
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def deserialize_llm_config(data: Optional[Dict]) -> Optional[LLMConfig]:
|
|
27
|
+
"""Convert a dictionary back into an LLMConfig object."""
|
|
28
|
+
return LLMConfig(**data) if data else None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# --------------------------
|
|
32
|
+
# EmbeddingConfig Serialization
|
|
33
|
+
# --------------------------
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def serialize_embedding_config(config: Union[Optional[EmbeddingConfig], Dict]) -> Optional[Dict]:
|
|
37
|
+
"""Convert an EmbeddingConfig object into a JSON-serializable dictionary."""
|
|
38
|
+
if config and isinstance(config, EmbeddingConfig):
|
|
39
|
+
return config.model_dump()
|
|
40
|
+
return config
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def deserialize_embedding_config(data: Optional[Dict]) -> Optional[EmbeddingConfig]:
|
|
44
|
+
"""Convert a dictionary back into an EmbeddingConfig object."""
|
|
45
|
+
return EmbeddingConfig(**data) if data else None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# --------------------------
|
|
49
|
+
# ToolRule Serialization
|
|
50
|
+
# --------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def serialize_tool_rules(tool_rules: Optional[List[ToolRule]]) -> List[Dict[str, Any]]:
|
|
54
|
+
"""Convert a list of ToolRules into a JSON-serializable format."""
|
|
55
|
+
|
|
56
|
+
if not tool_rules:
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
data = [{**rule.model_dump(), "type": rule.type.value} for rule in tool_rules] # Convert Enum to string for JSON compatibility
|
|
60
|
+
|
|
61
|
+
# Validate ToolRule structure
|
|
62
|
+
for rule_data in data:
|
|
63
|
+
if rule_data["type"] == ToolRuleType.constrain_child_tools.value and "children" not in rule_data:
|
|
64
|
+
raise ValueError(f"Invalid ToolRule serialization: 'children' field missing for rule {rule_data}")
|
|
65
|
+
|
|
66
|
+
return data
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def deserialize_tool_rules(data: Optional[List[Dict]]) -> List[Union[ChildToolRule, InitToolRule, TerminalToolRule, ConditionalToolRule]]:
|
|
70
|
+
"""Convert a list of dictionaries back into ToolRule objects."""
|
|
71
|
+
if not data:
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
return [deserialize_tool_rule(rule_data) for rule_data in data]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def deserialize_tool_rule(data: Dict) -> Union[ChildToolRule, InitToolRule, TerminalToolRule, ConditionalToolRule, ContinueToolRule]:
|
|
78
|
+
"""Deserialize a dictionary to the appropriate ToolRule subclass based on 'type'."""
|
|
79
|
+
rule_type = ToolRuleType(data.get("type"))
|
|
80
|
+
|
|
81
|
+
if rule_type == ToolRuleType.run_first:
|
|
82
|
+
return InitToolRule(**data)
|
|
83
|
+
elif rule_type == ToolRuleType.exit_loop:
|
|
84
|
+
return TerminalToolRule(**data)
|
|
85
|
+
elif rule_type == ToolRuleType.constrain_child_tools:
|
|
86
|
+
return ChildToolRule(**data)
|
|
87
|
+
elif rule_type == ToolRuleType.conditional:
|
|
88
|
+
return ConditionalToolRule(**data)
|
|
89
|
+
elif rule_type == ToolRuleType.continue_loop:
|
|
90
|
+
return ContinueToolRule(**data)
|
|
91
|
+
raise ValueError(f"Unknown ToolRule type: {rule_type}")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# --------------------------
|
|
95
|
+
# ToolCall Serialization
|
|
96
|
+
# --------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def serialize_tool_calls(tool_calls: Optional[List[Union[OpenAIToolCall, dict]]]) -> List[Dict]:
|
|
100
|
+
"""Convert a list of OpenAI ToolCall objects into JSON-serializable format."""
|
|
101
|
+
if not tool_calls:
|
|
102
|
+
return []
|
|
103
|
+
|
|
104
|
+
serialized_calls = []
|
|
105
|
+
for call in tool_calls:
|
|
106
|
+
if isinstance(call, OpenAIToolCall):
|
|
107
|
+
serialized_calls.append(call.model_dump())
|
|
108
|
+
elif isinstance(call, dict):
|
|
109
|
+
serialized_calls.append(call) # Already a dictionary, leave it as-is
|
|
110
|
+
else:
|
|
111
|
+
raise TypeError(f"Unexpected tool call type: {type(call)}")
|
|
112
|
+
|
|
113
|
+
return serialized_calls
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def deserialize_tool_calls(data: Optional[List[Dict]]) -> List[OpenAIToolCall]:
|
|
117
|
+
"""Convert a JSON list back into OpenAIToolCall objects."""
|
|
118
|
+
if not data:
|
|
119
|
+
return []
|
|
120
|
+
|
|
121
|
+
calls = []
|
|
122
|
+
for item in data:
|
|
123
|
+
func_data = item.pop("function", None)
|
|
124
|
+
tool_call_function = OpenAIFunction(**func_data) if func_data else None
|
|
125
|
+
calls.append(OpenAIToolCall(function=tool_call_function, **item))
|
|
126
|
+
|
|
127
|
+
return calls
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# --------------------------
|
|
131
|
+
# Vector Serialization
|
|
132
|
+
# --------------------------
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def serialize_vector(vector: Optional[Union[List[float], np.ndarray]]) -> Optional[bytes]:
|
|
136
|
+
"""Convert a NumPy array or list into a base64-encoded byte string."""
|
|
137
|
+
if vector is None:
|
|
138
|
+
return None
|
|
139
|
+
if isinstance(vector, list):
|
|
140
|
+
vector = np.array(vector, dtype=np.float32)
|
|
141
|
+
|
|
142
|
+
return base64.b64encode(vector.tobytes())
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def deserialize_vector(data: Optional[bytes], dialect: Dialect) -> Optional[np.ndarray]:
|
|
146
|
+
"""Convert a base64-encoded byte string back into a NumPy array."""
|
|
147
|
+
if not data:
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
if dialect.name == "sqlite":
|
|
151
|
+
data = base64.b64decode(data)
|
|
152
|
+
|
|
153
|
+
return np.frombuffer(data, dtype=np.float32)
|
|
@@ -4,7 +4,7 @@ from typing import List, Optional, Union
|
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
6
|
from letta.schemas.enums import ToolRuleType
|
|
7
|
-
from letta.schemas.tool_rule import BaseToolRule, ChildToolRule, ConditionalToolRule, InitToolRule, TerminalToolRule
|
|
7
|
+
from letta.schemas.tool_rule import BaseToolRule, ChildToolRule, ConditionalToolRule, ContinueToolRule, InitToolRule, TerminalToolRule
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class ToolRuleValidationError(Exception):
|
|
@@ -18,6 +18,9 @@ class ToolRulesSolver(BaseModel):
|
|
|
18
18
|
init_tool_rules: List[InitToolRule] = Field(
|
|
19
19
|
default_factory=list, description="Initial tool rules to be used at the start of tool execution."
|
|
20
20
|
)
|
|
21
|
+
continue_tool_rules: List[ContinueToolRule] = Field(
|
|
22
|
+
default_factory=list, description="Continue tool rules to be used to continue tool execution."
|
|
23
|
+
)
|
|
21
24
|
tool_rules: List[Union[ChildToolRule, ConditionalToolRule]] = Field(
|
|
22
25
|
default_factory=list, description="Standard tool rules for controlling execution sequence and allowed transitions."
|
|
23
26
|
)
|
|
@@ -43,6 +46,9 @@ class ToolRulesSolver(BaseModel):
|
|
|
43
46
|
elif rule.type == ToolRuleType.exit_loop:
|
|
44
47
|
assert isinstance(rule, TerminalToolRule)
|
|
45
48
|
self.terminal_tool_rules.append(rule)
|
|
49
|
+
elif rule.type == ToolRuleType.continue_loop:
|
|
50
|
+
assert isinstance(rule, ContinueToolRule)
|
|
51
|
+
self.continue_tool_rules.append(rule)
|
|
46
52
|
|
|
47
53
|
def update_tool_usage(self, tool_name: str):
|
|
48
54
|
"""Update the internal state to track the last tool called."""
|
|
@@ -80,6 +86,10 @@ class ToolRulesSolver(BaseModel):
|
|
|
80
86
|
"""Check if the tool has children tools"""
|
|
81
87
|
return any(rule.tool_name == tool_name for rule in self.tool_rules)
|
|
82
88
|
|
|
89
|
+
def is_continue_tool(self, tool_name):
|
|
90
|
+
"""Check if the tool is defined as a continue tool in the tool rules."""
|
|
91
|
+
return any(rule.tool_name == tool_name for rule in self.continue_tool_rules)
|
|
92
|
+
|
|
83
93
|
def validate_conditional_tool(self, rule: ConditionalToolRule):
|
|
84
94
|
"""
|
|
85
95
|
Validate a conditional tool rule
|
letta/llm_api/anthropic.py
CHANGED
|
@@ -519,6 +519,7 @@ def _prepare_anthropic_request(
|
|
|
519
519
|
prefix_fill: bool = True,
|
|
520
520
|
# if true, put COT inside the tool calls instead of inside the content
|
|
521
521
|
put_inner_thoughts_in_kwargs: bool = False,
|
|
522
|
+
bedrock: bool = False,
|
|
522
523
|
) -> dict:
|
|
523
524
|
"""Prepare the request data for Anthropic API format."""
|
|
524
525
|
|
|
@@ -606,10 +607,11 @@ def _prepare_anthropic_request(
|
|
|
606
607
|
# NOTE: cannot prefill with tools for opus:
|
|
607
608
|
# Your API request included an `assistant` message in the final position, which would pre-fill the `assistant` response. When using tools with "claude-3-opus-20240229"
|
|
608
609
|
if prefix_fill and not put_inner_thoughts_in_kwargs and "opus" not in data["model"]:
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
610
|
+
if not bedrock: # not support for bedrock
|
|
611
|
+
data["messages"].append(
|
|
612
|
+
# Start the thinking process for the assistant
|
|
613
|
+
{"role": "assistant", "content": f"<{inner_thoughts_xml_tag}>"},
|
|
614
|
+
)
|
|
613
615
|
|
|
614
616
|
# Validate max_tokens
|
|
615
617
|
assert "max_tokens" in data, data
|
|
@@ -651,13 +653,16 @@ def anthropic_bedrock_chat_completions_request(
|
|
|
651
653
|
inner_thoughts_xml_tag: Optional[str] = "thinking",
|
|
652
654
|
) -> ChatCompletionResponse:
|
|
653
655
|
"""Make a chat completion request to Anthropic via AWS Bedrock."""
|
|
654
|
-
data = _prepare_anthropic_request(data, inner_thoughts_xml_tag)
|
|
656
|
+
data = _prepare_anthropic_request(data, inner_thoughts_xml_tag, bedrock=True)
|
|
655
657
|
|
|
656
658
|
# Get the client
|
|
657
659
|
client = get_bedrock_client()
|
|
658
660
|
|
|
659
661
|
# Make the request
|
|
660
662
|
try:
|
|
663
|
+
# bedrock does not support certain args
|
|
664
|
+
data["tool_choice"] = {"type": "any"}
|
|
665
|
+
|
|
661
666
|
response = client.messages.create(**data)
|
|
662
667
|
return convert_anthropic_response_to_chatcompletion(response=response, inner_thoughts_xml_tag=inner_thoughts_xml_tag)
|
|
663
668
|
except PermissionDeniedError:
|
letta/llm_api/aws_bedrock.py
CHANGED
|
@@ -10,7 +10,7 @@ def has_valid_aws_credentials() -> bool:
|
|
|
10
10
|
"""
|
|
11
11
|
Check if AWS credentials are properly configured.
|
|
12
12
|
"""
|
|
13
|
-
valid_aws_credentials = os.getenv("
|
|
13
|
+
valid_aws_credentials = os.getenv("AWS_ACCESS_KEY") and os.getenv("AWS_SECRET_ACCESS_KEY") and os.getenv("AWS_REGION")
|
|
14
14
|
return valid_aws_credentials
|
|
15
15
|
|
|
16
16
|
|