letta-nightly 0.1.7.dev20240924104148__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 +24 -0
- letta/__main__.py +3 -0
- letta/agent.py +1427 -0
- letta/agent_store/chroma.py +295 -0
- letta/agent_store/db.py +546 -0
- letta/agent_store/lancedb.py +177 -0
- letta/agent_store/milvus.py +198 -0
- letta/agent_store/qdrant.py +201 -0
- letta/agent_store/storage.py +188 -0
- letta/benchmark/benchmark.py +96 -0
- letta/benchmark/constants.py +14 -0
- letta/cli/cli.py +689 -0
- letta/cli/cli_config.py +1282 -0
- letta/cli/cli_load.py +166 -0
- letta/client/__init__.py +0 -0
- letta/client/admin.py +171 -0
- letta/client/client.py +2360 -0
- letta/client/streaming.py +90 -0
- letta/client/utils.py +61 -0
- letta/config.py +484 -0
- letta/configs/anthropic.json +13 -0
- letta/configs/letta_hosted.json +11 -0
- letta/configs/openai.json +12 -0
- letta/constants.py +134 -0
- letta/credentials.py +140 -0
- letta/data_sources/connectors.py +247 -0
- letta/embeddings.py +218 -0
- letta/errors.py +26 -0
- letta/functions/__init__.py +0 -0
- letta/functions/function_sets/base.py +174 -0
- letta/functions/function_sets/extras.py +132 -0
- letta/functions/functions.py +105 -0
- letta/functions/schema_generator.py +205 -0
- letta/humans/__init__.py +0 -0
- letta/humans/examples/basic.txt +1 -0
- letta/humans/examples/cs_phd.txt +9 -0
- letta/interface.py +314 -0
- letta/llm_api/__init__.py +0 -0
- letta/llm_api/anthropic.py +383 -0
- letta/llm_api/azure_openai.py +155 -0
- letta/llm_api/cohere.py +396 -0
- letta/llm_api/google_ai.py +468 -0
- letta/llm_api/llm_api_tools.py +485 -0
- letta/llm_api/openai.py +470 -0
- letta/local_llm/README.md +3 -0
- letta/local_llm/__init__.py +0 -0
- letta/local_llm/chat_completion_proxy.py +279 -0
- letta/local_llm/constants.py +31 -0
- letta/local_llm/function_parser.py +68 -0
- letta/local_llm/grammars/__init__.py +0 -0
- letta/local_llm/grammars/gbnf_grammar_generator.py +1324 -0
- letta/local_llm/grammars/json.gbnf +26 -0
- letta/local_llm/grammars/json_func_calls_with_inner_thoughts.gbnf +32 -0
- letta/local_llm/groq/api.py +97 -0
- letta/local_llm/json_parser.py +202 -0
- letta/local_llm/koboldcpp/api.py +62 -0
- letta/local_llm/koboldcpp/settings.py +23 -0
- letta/local_llm/llamacpp/api.py +58 -0
- letta/local_llm/llamacpp/settings.py +22 -0
- letta/local_llm/llm_chat_completion_wrappers/__init__.py +0 -0
- letta/local_llm/llm_chat_completion_wrappers/airoboros.py +452 -0
- letta/local_llm/llm_chat_completion_wrappers/chatml.py +470 -0
- letta/local_llm/llm_chat_completion_wrappers/configurable_wrapper.py +387 -0
- letta/local_llm/llm_chat_completion_wrappers/dolphin.py +246 -0
- letta/local_llm/llm_chat_completion_wrappers/llama3.py +345 -0
- letta/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py +156 -0
- letta/local_llm/llm_chat_completion_wrappers/wrapper_base.py +11 -0
- letta/local_llm/llm_chat_completion_wrappers/zephyr.py +345 -0
- letta/local_llm/lmstudio/api.py +100 -0
- letta/local_llm/lmstudio/settings.py +29 -0
- letta/local_llm/ollama/api.py +88 -0
- letta/local_llm/ollama/settings.py +32 -0
- letta/local_llm/settings/__init__.py +0 -0
- letta/local_llm/settings/deterministic_mirostat.py +45 -0
- letta/local_llm/settings/settings.py +72 -0
- letta/local_llm/settings/simple.py +28 -0
- letta/local_llm/utils.py +265 -0
- letta/local_llm/vllm/api.py +63 -0
- letta/local_llm/webui/api.py +60 -0
- letta/local_llm/webui/legacy_api.py +58 -0
- letta/local_llm/webui/legacy_settings.py +23 -0
- letta/local_llm/webui/settings.py +24 -0
- letta/log.py +76 -0
- letta/main.py +437 -0
- letta/memory.py +440 -0
- letta/metadata.py +884 -0
- letta/openai_backcompat/__init__.py +0 -0
- letta/openai_backcompat/openai_object.py +437 -0
- letta/persistence_manager.py +148 -0
- letta/personas/__init__.py +0 -0
- letta/personas/examples/anna_pa.txt +13 -0
- letta/personas/examples/google_search_persona.txt +15 -0
- letta/personas/examples/memgpt_doc.txt +6 -0
- letta/personas/examples/memgpt_starter.txt +4 -0
- letta/personas/examples/sam.txt +14 -0
- letta/personas/examples/sam_pov.txt +14 -0
- letta/personas/examples/sam_simple_pov_gpt35.txt +13 -0
- letta/personas/examples/sqldb/test.db +0 -0
- letta/prompts/__init__.py +0 -0
- letta/prompts/gpt_summarize.py +14 -0
- letta/prompts/gpt_system.py +26 -0
- letta/prompts/system/memgpt_base.txt +49 -0
- letta/prompts/system/memgpt_chat.txt +58 -0
- letta/prompts/system/memgpt_chat_compressed.txt +13 -0
- letta/prompts/system/memgpt_chat_fstring.txt +51 -0
- letta/prompts/system/memgpt_doc.txt +50 -0
- letta/prompts/system/memgpt_gpt35_extralong.txt +53 -0
- letta/prompts/system/memgpt_intuitive_knowledge.txt +31 -0
- letta/prompts/system/memgpt_modified_chat.txt +23 -0
- letta/pytest.ini +0 -0
- letta/schemas/agent.py +117 -0
- letta/schemas/api_key.py +21 -0
- letta/schemas/block.py +135 -0
- letta/schemas/document.py +21 -0
- letta/schemas/embedding_config.py +54 -0
- letta/schemas/enums.py +35 -0
- letta/schemas/job.py +38 -0
- letta/schemas/letta_base.py +80 -0
- letta/schemas/letta_message.py +175 -0
- letta/schemas/letta_request.py +23 -0
- letta/schemas/letta_response.py +28 -0
- letta/schemas/llm_config.py +54 -0
- letta/schemas/memory.py +224 -0
- letta/schemas/message.py +727 -0
- letta/schemas/openai/chat_completion_request.py +123 -0
- letta/schemas/openai/chat_completion_response.py +136 -0
- letta/schemas/openai/chat_completions.py +123 -0
- letta/schemas/openai/embedding_response.py +11 -0
- letta/schemas/openai/openai.py +157 -0
- letta/schemas/organization.py +20 -0
- letta/schemas/passage.py +80 -0
- letta/schemas/source.py +62 -0
- letta/schemas/tool.py +143 -0
- letta/schemas/usage.py +18 -0
- letta/schemas/user.py +33 -0
- letta/server/__init__.py +0 -0
- letta/server/constants.py +6 -0
- letta/server/rest_api/__init__.py +0 -0
- letta/server/rest_api/admin/__init__.py +0 -0
- letta/server/rest_api/admin/agents.py +21 -0
- letta/server/rest_api/admin/tools.py +83 -0
- letta/server/rest_api/admin/users.py +98 -0
- letta/server/rest_api/app.py +193 -0
- letta/server/rest_api/auth/__init__.py +0 -0
- letta/server/rest_api/auth/index.py +43 -0
- letta/server/rest_api/auth_token.py +22 -0
- letta/server/rest_api/interface.py +726 -0
- letta/server/rest_api/routers/__init__.py +0 -0
- letta/server/rest_api/routers/openai/__init__.py +0 -0
- letta/server/rest_api/routers/openai/assistants/__init__.py +0 -0
- letta/server/rest_api/routers/openai/assistants/assistants.py +115 -0
- letta/server/rest_api/routers/openai/assistants/schemas.py +121 -0
- letta/server/rest_api/routers/openai/assistants/threads.py +336 -0
- letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +131 -0
- letta/server/rest_api/routers/v1/__init__.py +15 -0
- letta/server/rest_api/routers/v1/agents.py +543 -0
- letta/server/rest_api/routers/v1/blocks.py +73 -0
- letta/server/rest_api/routers/v1/jobs.py +46 -0
- letta/server/rest_api/routers/v1/llms.py +28 -0
- letta/server/rest_api/routers/v1/organizations.py +61 -0
- letta/server/rest_api/routers/v1/sources.py +199 -0
- letta/server/rest_api/routers/v1/tools.py +103 -0
- letta/server/rest_api/routers/v1/users.py +109 -0
- letta/server/rest_api/static_files.py +74 -0
- letta/server/rest_api/utils.py +69 -0
- letta/server/server.py +1995 -0
- letta/server/startup.sh +8 -0
- letta/server/static_files/assets/index-0cbf7ad5.js +274 -0
- letta/server/static_files/assets/index-156816da.css +1 -0
- letta/server/static_files/assets/index-486e3228.js +274 -0
- letta/server/static_files/favicon.ico +0 -0
- letta/server/static_files/index.html +39 -0
- letta/server/static_files/memgpt_logo_transparent.png +0 -0
- letta/server/utils.py +46 -0
- letta/server/ws_api/__init__.py +0 -0
- letta/server/ws_api/example_client.py +104 -0
- letta/server/ws_api/interface.py +108 -0
- letta/server/ws_api/protocol.py +100 -0
- letta/server/ws_api/server.py +145 -0
- letta/settings.py +165 -0
- letta/streaming_interface.py +396 -0
- letta/system.py +207 -0
- letta/utils.py +1065 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/LICENSE +190 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/METADATA +98 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/RECORD +189 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/WHEEL +4 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/entry_points.txt +3 -0
letta/llm_api/cohere.py
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import List, Optional, Union
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from letta.local_llm.utils import count_tokens
|
|
8
|
+
from letta.schemas.message import Message
|
|
9
|
+
from letta.schemas.openai.chat_completion_request import ChatCompletionRequest, Tool
|
|
10
|
+
from letta.schemas.openai.chat_completion_response import (
|
|
11
|
+
ChatCompletionResponse,
|
|
12
|
+
Choice,
|
|
13
|
+
FunctionCall,
|
|
14
|
+
)
|
|
15
|
+
from letta.schemas.openai.chat_completion_response import (
|
|
16
|
+
Message as ChoiceMessage, # NOTE: avoid conflict with our own Letta Message datatype
|
|
17
|
+
)
|
|
18
|
+
from letta.schemas.openai.chat_completion_response import ToolCall, UsageStatistics
|
|
19
|
+
from letta.utils import get_tool_call_id, get_utc_time, json_dumps, smart_urljoin
|
|
20
|
+
|
|
21
|
+
BASE_URL = "https://api.cohere.ai/v1"
|
|
22
|
+
|
|
23
|
+
# models that we know will work with Letta
|
|
24
|
+
COHERE_VALID_MODEL_LIST = [
|
|
25
|
+
"command-r-plus",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def cohere_get_model_details(url: str, api_key: Union[str, None], model: str) -> int:
|
|
30
|
+
"""https://docs.cohere.com/reference/get-model"""
|
|
31
|
+
from letta.utils import printd
|
|
32
|
+
|
|
33
|
+
url = smart_urljoin(url, "models")
|
|
34
|
+
url = smart_urljoin(url, model)
|
|
35
|
+
headers = {
|
|
36
|
+
"accept": "application/json",
|
|
37
|
+
"authorization": f"bearer {api_key}",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
printd(f"Sending request to {url}")
|
|
41
|
+
try:
|
|
42
|
+
response = requests.get(url, headers=headers)
|
|
43
|
+
printd(f"response = {response}")
|
|
44
|
+
response.raise_for_status() # Raises HTTPError for 4XX/5XX status
|
|
45
|
+
response = response.json() # convert to dict from string
|
|
46
|
+
return response
|
|
47
|
+
except requests.exceptions.HTTPError as http_err:
|
|
48
|
+
# Handle HTTP errors (e.g., response 4XX, 5XX)
|
|
49
|
+
printd(f"Got HTTPError, exception={http_err}")
|
|
50
|
+
raise http_err
|
|
51
|
+
except requests.exceptions.RequestException as req_err:
|
|
52
|
+
# Handle other requests-related errors (e.g., connection error)
|
|
53
|
+
printd(f"Got RequestException, exception={req_err}")
|
|
54
|
+
raise req_err
|
|
55
|
+
except Exception as e:
|
|
56
|
+
# Handle other potential errors
|
|
57
|
+
printd(f"Got unknown Exception, exception={e}")
|
|
58
|
+
raise e
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def cohere_get_model_context_window(url: str, api_key: Union[str, None], model: str) -> int:
|
|
62
|
+
model_details = cohere_get_model_details(url=url, api_key=api_key, model=model)
|
|
63
|
+
return model_details["context_length"]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def cohere_get_model_list(url: str, api_key: Union[str, None]) -> dict:
|
|
67
|
+
"""https://docs.cohere.com/reference/list-models"""
|
|
68
|
+
from letta.utils import printd
|
|
69
|
+
|
|
70
|
+
url = smart_urljoin(url, "models")
|
|
71
|
+
headers = {
|
|
72
|
+
"accept": "application/json",
|
|
73
|
+
"authorization": f"bearer {api_key}",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
printd(f"Sending request to {url}")
|
|
77
|
+
try:
|
|
78
|
+
response = requests.get(url, headers=headers)
|
|
79
|
+
printd(f"response = {response}")
|
|
80
|
+
response.raise_for_status() # Raises HTTPError for 4XX/5XX status
|
|
81
|
+
response = response.json() # convert to dict from string
|
|
82
|
+
return response["models"]
|
|
83
|
+
except requests.exceptions.HTTPError as http_err:
|
|
84
|
+
# Handle HTTP errors (e.g., response 4XX, 5XX)
|
|
85
|
+
printd(f"Got HTTPError, exception={http_err}")
|
|
86
|
+
raise http_err
|
|
87
|
+
except requests.exceptions.RequestException as req_err:
|
|
88
|
+
# Handle other requests-related errors (e.g., connection error)
|
|
89
|
+
printd(f"Got RequestException, exception={req_err}")
|
|
90
|
+
raise req_err
|
|
91
|
+
except Exception as e:
|
|
92
|
+
# Handle other potential errors
|
|
93
|
+
printd(f"Got unknown Exception, exception={e}")
|
|
94
|
+
raise e
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def remap_finish_reason(finish_reason: str) -> str:
|
|
98
|
+
"""Remap Cohere's 'finish_reason' to OpenAI 'finish_reason'
|
|
99
|
+
|
|
100
|
+
OpenAI: 'stop', 'length', 'function_call', 'content_filter', null
|
|
101
|
+
see: https://platform.openai.com/docs/guides/text-generation/chat-completions-api
|
|
102
|
+
|
|
103
|
+
Cohere finish_reason is different but undocumented ???
|
|
104
|
+
"""
|
|
105
|
+
if finish_reason == "COMPLETE":
|
|
106
|
+
return "stop"
|
|
107
|
+
elif finish_reason == "MAX_TOKENS":
|
|
108
|
+
return "length"
|
|
109
|
+
# elif stop_reason == "tool_use":
|
|
110
|
+
# return "function_call"
|
|
111
|
+
else:
|
|
112
|
+
raise ValueError(f"Unexpected stop_reason: {finish_reason}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def convert_cohere_response_to_chatcompletion(
|
|
116
|
+
response_json: dict, # REST response from API
|
|
117
|
+
model: str, # Required since not returned
|
|
118
|
+
inner_thoughts_in_kwargs: Optional[bool] = True,
|
|
119
|
+
) -> ChatCompletionResponse:
|
|
120
|
+
"""
|
|
121
|
+
Example response from command-r-plus:
|
|
122
|
+
response.json = {
|
|
123
|
+
'response_id': '28c47751-acce-41cd-8c89-c48a15ac33cf',
|
|
124
|
+
'text': '',
|
|
125
|
+
'generation_id': '84209c9e-2868-4984-82c5-063b748b7776',
|
|
126
|
+
'chat_history': [
|
|
127
|
+
{
|
|
128
|
+
'role': 'CHATBOT',
|
|
129
|
+
'message': 'Bootup sequence complete. Persona activated. Testing messaging functionality.'
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
'role': 'SYSTEM',
|
|
133
|
+
'message': '{"status": "OK", "message": null, "time": "2024-04-11 11:22:36 PM PDT-0700"}'
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
'finish_reason': 'COMPLETE',
|
|
137
|
+
'meta': {
|
|
138
|
+
'api_version': {'version': '1'},
|
|
139
|
+
'billed_units': {'input_tokens': 692, 'output_tokens': 20},
|
|
140
|
+
'tokens': {'output_tokens': 20}
|
|
141
|
+
},
|
|
142
|
+
'tool_calls': [
|
|
143
|
+
{
|
|
144
|
+
'name': 'send_message',
|
|
145
|
+
'parameters': {
|
|
146
|
+
'message': "Hello Chad, it's Sam. How are you feeling today?"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
"""
|
|
152
|
+
if "billed_units" in response_json["meta"]:
|
|
153
|
+
prompt_tokens = response_json["meta"]["billed_units"]["input_tokens"]
|
|
154
|
+
completion_tokens = response_json["meta"]["billed_units"]["output_tokens"]
|
|
155
|
+
else:
|
|
156
|
+
# For some reason input_tokens not included in 'meta' 'tokens' dict?
|
|
157
|
+
prompt_tokens = count_tokens(json_dumps(response_json["chat_history"])) # NOTE: this is a very rough approximation
|
|
158
|
+
completion_tokens = response_json["meta"]["tokens"]["output_tokens"]
|
|
159
|
+
|
|
160
|
+
finish_reason = remap_finish_reason(response_json["finish_reason"])
|
|
161
|
+
|
|
162
|
+
if "tool_calls" in response_json and response_json["tool_calls"] is not None:
|
|
163
|
+
inner_thoughts = []
|
|
164
|
+
tool_calls = []
|
|
165
|
+
for tool_call_response in response_json["tool_calls"]:
|
|
166
|
+
function_name = tool_call_response["name"]
|
|
167
|
+
function_args = tool_call_response["parameters"]
|
|
168
|
+
if inner_thoughts_in_kwargs:
|
|
169
|
+
from letta.local_llm.constants import INNER_THOUGHTS_KWARG
|
|
170
|
+
|
|
171
|
+
assert INNER_THOUGHTS_KWARG in function_args
|
|
172
|
+
# NOTE:
|
|
173
|
+
inner_thoughts.append(function_args.pop(INNER_THOUGHTS_KWARG))
|
|
174
|
+
|
|
175
|
+
tool_calls.append(
|
|
176
|
+
ToolCall(
|
|
177
|
+
id=get_tool_call_id(),
|
|
178
|
+
type="function",
|
|
179
|
+
function=FunctionCall(
|
|
180
|
+
name=function_name,
|
|
181
|
+
arguments=json.dumps(function_args),
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# NOTE: no multi-call support for now
|
|
187
|
+
assert len(tool_calls) == 1, tool_calls
|
|
188
|
+
content = inner_thoughts[0]
|
|
189
|
+
|
|
190
|
+
else:
|
|
191
|
+
# raise NotImplementedError(f"Expected a tool call response from Cohere API")
|
|
192
|
+
content = response_json["text"]
|
|
193
|
+
tool_calls = None
|
|
194
|
+
|
|
195
|
+
# In Cohere API empty string == null
|
|
196
|
+
content = None if content == "" else content
|
|
197
|
+
assert content is not None or tool_calls is not None, "Response message must have either content or tool_calls"
|
|
198
|
+
|
|
199
|
+
choice = Choice(
|
|
200
|
+
index=0,
|
|
201
|
+
finish_reason=finish_reason,
|
|
202
|
+
message=ChoiceMessage(
|
|
203
|
+
role="assistant",
|
|
204
|
+
content=content,
|
|
205
|
+
tool_calls=tool_calls,
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return ChatCompletionResponse(
|
|
210
|
+
id=response_json["response_id"],
|
|
211
|
+
choices=[choice],
|
|
212
|
+
created=get_utc_time(),
|
|
213
|
+
model=model,
|
|
214
|
+
usage=UsageStatistics(
|
|
215
|
+
prompt_tokens=prompt_tokens,
|
|
216
|
+
completion_tokens=completion_tokens,
|
|
217
|
+
total_tokens=prompt_tokens + completion_tokens,
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def convert_tools_to_cohere_format(tools: List[Tool], inner_thoughts_in_kwargs: Optional[bool] = True) -> List[dict]:
|
|
223
|
+
"""See: https://docs.cohere.com/reference/chat
|
|
224
|
+
|
|
225
|
+
OpenAI style:
|
|
226
|
+
"tools": [{
|
|
227
|
+
"type": "function",
|
|
228
|
+
"function": {
|
|
229
|
+
"name": "find_movies",
|
|
230
|
+
"description": "find ....",
|
|
231
|
+
"parameters": {
|
|
232
|
+
"type": "object",
|
|
233
|
+
"properties": {
|
|
234
|
+
PARAM: {
|
|
235
|
+
"type": PARAM_TYPE, # eg "string"
|
|
236
|
+
"description": PARAM_DESCRIPTION,
|
|
237
|
+
},
|
|
238
|
+
...
|
|
239
|
+
},
|
|
240
|
+
"required": List[str],
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}]
|
|
244
|
+
|
|
245
|
+
Cohere style:
|
|
246
|
+
"tools": [{
|
|
247
|
+
"name": "find_movies",
|
|
248
|
+
"description": "find ....",
|
|
249
|
+
"parameter_definitions": {
|
|
250
|
+
PARAM_NAME: {
|
|
251
|
+
"description": PARAM_DESCRIPTION,
|
|
252
|
+
"type": PARAM_TYPE, # eg "string"
|
|
253
|
+
"required": <boolean>,
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
}
|
|
257
|
+
}]
|
|
258
|
+
"""
|
|
259
|
+
tools_dict_list = []
|
|
260
|
+
for tool in tools:
|
|
261
|
+
tools_dict_list.append(
|
|
262
|
+
{
|
|
263
|
+
"name": tool.function.name,
|
|
264
|
+
"description": tool.function.description,
|
|
265
|
+
"parameter_definitions": {
|
|
266
|
+
p_name: {
|
|
267
|
+
"description": p_fields["description"],
|
|
268
|
+
"type": p_fields["type"],
|
|
269
|
+
"required": p_name in tool.function.parameters["required"],
|
|
270
|
+
}
|
|
271
|
+
for p_name, p_fields in tool.function.parameters["properties"].items()
|
|
272
|
+
},
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
if inner_thoughts_in_kwargs:
|
|
277
|
+
# NOTE: since Cohere doesn't allow "text" in the response when a tool call happens, if we want
|
|
278
|
+
# a simultaneous CoT + tool call we need to put it inside a kwarg
|
|
279
|
+
from letta.local_llm.constants import (
|
|
280
|
+
INNER_THOUGHTS_KWARG,
|
|
281
|
+
INNER_THOUGHTS_KWARG_DESCRIPTION,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
for cohere_tool in tools_dict_list:
|
|
285
|
+
cohere_tool["parameter_definitions"][INNER_THOUGHTS_KWARG] = {
|
|
286
|
+
"description": INNER_THOUGHTS_KWARG_DESCRIPTION,
|
|
287
|
+
"type": "string",
|
|
288
|
+
"required": True,
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return tools_dict_list
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def cohere_chat_completions_request(
|
|
295
|
+
url: str,
|
|
296
|
+
api_key: str,
|
|
297
|
+
chat_completion_request: ChatCompletionRequest,
|
|
298
|
+
) -> ChatCompletionResponse:
|
|
299
|
+
"""https://docs.cohere.com/docs/multi-step-tool-use"""
|
|
300
|
+
from letta.utils import printd
|
|
301
|
+
|
|
302
|
+
url = smart_urljoin(url, "chat")
|
|
303
|
+
headers = {
|
|
304
|
+
"Content-Type": "application/json",
|
|
305
|
+
"Authorization": f"bearer {api_key}",
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
# convert the tools
|
|
309
|
+
cohere_tools = None if chat_completion_request.tools is None else convert_tools_to_cohere_format(chat_completion_request.tools)
|
|
310
|
+
|
|
311
|
+
# pydantic -> dict
|
|
312
|
+
data = chat_completion_request.model_dump(exclude_none=True)
|
|
313
|
+
|
|
314
|
+
if "functions" in data:
|
|
315
|
+
raise ValueError(f"'functions' unexpected in Anthropic API payload")
|
|
316
|
+
|
|
317
|
+
# If tools == None, strip from the payload
|
|
318
|
+
if "tools" in data and data["tools"] is None:
|
|
319
|
+
data.pop("tools")
|
|
320
|
+
data.pop("tool_choice", None) # extra safe, should exist always (default="auto")
|
|
321
|
+
|
|
322
|
+
# Convert messages to Cohere format
|
|
323
|
+
msg_objs = [Message.dict_to_message(user_id=uuid.uuid4(), agent_id=uuid.uuid4(), openai_message_dict=m) for m in data["messages"]]
|
|
324
|
+
|
|
325
|
+
# System message 0 should instead be a "preamble"
|
|
326
|
+
# See: https://docs.cohere.com/reference/chat
|
|
327
|
+
# The chat_history parameter should not be used for SYSTEM messages in most cases. Instead, to add a SYSTEM role message at the beginning of a conversation, the preamble parameter should be used.
|
|
328
|
+
assert msg_objs[0].role == "system", msg_objs[0]
|
|
329
|
+
preamble = msg_objs[0].text
|
|
330
|
+
|
|
331
|
+
# data["messages"] = [m.to_cohere_dict() for m in msg_objs[1:]]
|
|
332
|
+
data["messages"] = []
|
|
333
|
+
for m in msg_objs[1:]:
|
|
334
|
+
ms = m.to_cohere_dict() # NOTE: returns List[dict]
|
|
335
|
+
data["messages"].extend(ms)
|
|
336
|
+
|
|
337
|
+
assert data["messages"][-1]["role"] == "USER", data["messages"][-1]
|
|
338
|
+
data = {
|
|
339
|
+
"preamble": preamble,
|
|
340
|
+
"chat_history": data["messages"][:-1],
|
|
341
|
+
"message": data["messages"][-1]["message"],
|
|
342
|
+
"tools": cohere_tools,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
# Move 'system' to the top level
|
|
346
|
+
# 'messages: Unexpected role "system". The Messages API accepts a top-level `system` parameter, not "system" as an input message role.'
|
|
347
|
+
# assert data["messages"][0]["role"] == "system", f"Expected 'system' role in messages[0]:\n{data['messages'][0]}"
|
|
348
|
+
# data["system"] = data["messages"][0]["content"]
|
|
349
|
+
# data["messages"] = data["messages"][1:]
|
|
350
|
+
|
|
351
|
+
# Convert to Anthropic format
|
|
352
|
+
# msg_objs = [Message.dict_to_message(user_id=uuid.uuid4(), agent_id=uuid.uuid4(), openai_message_dict=m) for m in data["messages"]]
|
|
353
|
+
# data["messages"] = [m.to_anthropic_dict(inner_thoughts_xml_tag=inner_thoughts_xml_tag) for m in msg_objs]
|
|
354
|
+
|
|
355
|
+
# Handling Anthropic special requirement for 'user' message in front
|
|
356
|
+
# messages: first message must use the "user" role'
|
|
357
|
+
# if data["messages"][0]["role"] != "user":
|
|
358
|
+
# data["messages"] = [{"role": "user", "content": DUMMY_FIRST_USER_MESSAGE}] + data["messages"]
|
|
359
|
+
|
|
360
|
+
# Handle Anthropic's restriction on alternating user/assistant messages
|
|
361
|
+
# data["messages"] = merge_tool_results_into_user_messages(data["messages"])
|
|
362
|
+
|
|
363
|
+
# Anthropic also wants max_tokens in the input
|
|
364
|
+
# It's also part of ChatCompletions
|
|
365
|
+
# assert "max_tokens" in data, data
|
|
366
|
+
|
|
367
|
+
# Remove extra fields used by OpenAI but not Anthropic
|
|
368
|
+
# data.pop("frequency_penalty", None)
|
|
369
|
+
# data.pop("logprobs", None)
|
|
370
|
+
# data.pop("n", None)
|
|
371
|
+
# data.pop("top_p", None)
|
|
372
|
+
# data.pop("presence_penalty", None)
|
|
373
|
+
# data.pop("user", None)
|
|
374
|
+
# data.pop("tool_choice", None)
|
|
375
|
+
|
|
376
|
+
printd(f"Sending request to {url}")
|
|
377
|
+
try:
|
|
378
|
+
response = requests.post(url, headers=headers, json=data)
|
|
379
|
+
printd(f"response = {response}")
|
|
380
|
+
response.raise_for_status() # Raises HTTPError for 4XX/5XX status
|
|
381
|
+
response = response.json() # convert to dict from string
|
|
382
|
+
printd(f"response.json = {response}")
|
|
383
|
+
response = convert_cohere_response_to_chatcompletion(response_json=response, model=chat_completion_request.model)
|
|
384
|
+
return response
|
|
385
|
+
except requests.exceptions.HTTPError as http_err:
|
|
386
|
+
# Handle HTTP errors (e.g., response 4XX, 5XX)
|
|
387
|
+
printd(f"Got HTTPError, exception={http_err}, payload={data}")
|
|
388
|
+
raise http_err
|
|
389
|
+
except requests.exceptions.RequestException as req_err:
|
|
390
|
+
# Handle other requests-related errors (e.g., connection error)
|
|
391
|
+
printd(f"Got RequestException, exception={req_err}")
|
|
392
|
+
raise req_err
|
|
393
|
+
except Exception as e:
|
|
394
|
+
# Handle other potential errors
|
|
395
|
+
printd(f"Got unknown Exception, exception={e}")
|
|
396
|
+
raise e
|