letta-nightly 0.4.1.dev20241008104105__py3-none-any.whl → 0.4.1.dev20241010104112__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 +18 -2
- letta/agent_store/db.py +23 -7
- letta/cli/cli.py +2 -1
- letta/cli/cli_config.py +1 -1098
- letta/client/client.py +8 -1
- letta/client/utils.py +7 -2
- letta/credentials.py +2 -2
- letta/embeddings.py +3 -0
- letta/functions/schema_generator.py +1 -1
- letta/interface.py +6 -2
- letta/llm_api/anthropic.py +3 -24
- letta/llm_api/azure_openai.py +47 -98
- letta/llm_api/azure_openai_constants.py +10 -0
- letta/llm_api/google_ai.py +38 -63
- letta/llm_api/helpers.py +64 -2
- letta/llm_api/llm_api_tools.py +6 -15
- letta/llm_api/openai.py +6 -49
- letta/local_llm/constants.py +3 -0
- letta/main.py +1 -1
- letta/metadata.py +2 -0
- letta/providers.py +165 -31
- letta/schemas/agent.py +14 -0
- letta/schemas/llm_config.py +0 -3
- letta/schemas/openai/chat_completion_response.py +3 -0
- letta/schemas/tool.py +3 -3
- letta/server/rest_api/routers/openai/assistants/threads.py +5 -5
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +2 -2
- letta/server/rest_api/routers/v1/agents.py +11 -11
- letta/server/rest_api/routers/v1/blocks.py +2 -2
- letta/server/rest_api/routers/v1/jobs.py +2 -2
- letta/server/rest_api/routers/v1/sources.py +12 -12
- letta/server/rest_api/routers/v1/tools.py +6 -6
- letta/server/server.py +26 -7
- letta/settings.py +3 -112
- letta/streaming_interface.py +8 -4
- {letta_nightly-0.4.1.dev20241008104105.dist-info → letta_nightly-0.4.1.dev20241010104112.dist-info}/METADATA +1 -1
- {letta_nightly-0.4.1.dev20241008104105.dist-info → letta_nightly-0.4.1.dev20241010104112.dist-info}/RECORD +40 -42
- letta/configs/anthropic.json +0 -13
- letta/configs/letta_hosted.json +0 -11
- letta/configs/openai.json +0 -12
- {letta_nightly-0.4.1.dev20241008104105.dist-info → letta_nightly-0.4.1.dev20241010104112.dist-info}/LICENSE +0 -0
- {letta_nightly-0.4.1.dev20241008104105.dist-info → letta_nightly-0.4.1.dev20241010104112.dist-info}/WHEEL +0 -0
- {letta_nightly-0.4.1.dev20241008104105.dist-info → letta_nightly-0.4.1.dev20241010104112.dist-info}/entry_points.txt +0 -0
letta/client/client.py
CHANGED
|
@@ -9,7 +9,7 @@ from letta.constants import BASE_TOOLS, DEFAULT_HUMAN, DEFAULT_PERSONA
|
|
|
9
9
|
from letta.data_sources.connectors import DataConnector
|
|
10
10
|
from letta.functions.functions import parse_source_code
|
|
11
11
|
from letta.memory import get_memory_functions
|
|
12
|
-
from letta.schemas.agent import AgentState, CreateAgent, UpdateAgentState
|
|
12
|
+
from letta.schemas.agent import AgentState, AgentType, CreateAgent, UpdateAgentState
|
|
13
13
|
from letta.schemas.block import (
|
|
14
14
|
Block,
|
|
15
15
|
CreateBlock,
|
|
@@ -68,6 +68,7 @@ class AbstractClient(object):
|
|
|
68
68
|
def create_agent(
|
|
69
69
|
self,
|
|
70
70
|
name: Optional[str] = None,
|
|
71
|
+
agent_type: Optional[AgentType] = AgentType.memgpt_agent,
|
|
71
72
|
embedding_config: Optional[EmbeddingConfig] = None,
|
|
72
73
|
llm_config: Optional[LLMConfig] = None,
|
|
73
74
|
memory: Memory = ChatMemory(human=get_human_text(DEFAULT_HUMAN), persona=get_persona_text(DEFAULT_PERSONA)),
|
|
@@ -319,6 +320,8 @@ class RESTClient(AbstractClient):
|
|
|
319
320
|
def create_agent(
|
|
320
321
|
self,
|
|
321
322
|
name: Optional[str] = None,
|
|
323
|
+
# agent config
|
|
324
|
+
agent_type: Optional[AgentType] = AgentType.memgpt_agent,
|
|
322
325
|
# model configs
|
|
323
326
|
embedding_config: EmbeddingConfig = None,
|
|
324
327
|
llm_config: LLMConfig = None,
|
|
@@ -381,6 +384,7 @@ class RESTClient(AbstractClient):
|
|
|
381
384
|
memory=memory,
|
|
382
385
|
tools=tool_names,
|
|
383
386
|
system=system,
|
|
387
|
+
agent_type=agent_type,
|
|
384
388
|
llm_config=llm_config if llm_config else self._default_llm_config,
|
|
385
389
|
embedding_config=embedding_config if embedding_config else self._default_embedding_config,
|
|
386
390
|
)
|
|
@@ -1462,6 +1466,8 @@ class LocalClient(AbstractClient):
|
|
|
1462
1466
|
def create_agent(
|
|
1463
1467
|
self,
|
|
1464
1468
|
name: Optional[str] = None,
|
|
1469
|
+
# agent config
|
|
1470
|
+
agent_type: Optional[AgentType] = AgentType.memgpt_agent,
|
|
1465
1471
|
# model configs
|
|
1466
1472
|
embedding_config: EmbeddingConfig = None,
|
|
1467
1473
|
llm_config: LLMConfig = None,
|
|
@@ -1524,6 +1530,7 @@ class LocalClient(AbstractClient):
|
|
|
1524
1530
|
memory=memory,
|
|
1525
1531
|
tools=tool_names,
|
|
1526
1532
|
system=system,
|
|
1533
|
+
agent_type=agent_type,
|
|
1527
1534
|
llm_config=llm_config if llm_config else self._default_llm_config,
|
|
1528
1535
|
embedding_config=embedding_config if embedding_config else self._default_embedding_config,
|
|
1529
1536
|
),
|
letta/client/utils.py
CHANGED
|
@@ -2,6 +2,11 @@ from datetime import datetime
|
|
|
2
2
|
|
|
3
3
|
from IPython.display import HTML, display
|
|
4
4
|
|
|
5
|
+
from letta.local_llm.constants import (
|
|
6
|
+
ASSISTANT_MESSAGE_CLI_SYMBOL,
|
|
7
|
+
INNER_THOUGHTS_CLI_SYMBOL,
|
|
8
|
+
)
|
|
9
|
+
|
|
5
10
|
|
|
6
11
|
def pprint(messages):
|
|
7
12
|
"""Utility function for pretty-printing the output of client.send_message in notebooks"""
|
|
@@ -47,13 +52,13 @@ def pprint(messages):
|
|
|
47
52
|
html_content += f"<p><strong>🛠️ [{date_formatted}] Function Return ({return_status}):</strong></p>"
|
|
48
53
|
html_content += f"<p class='function-return'>{return_string}</p>"
|
|
49
54
|
elif "internal_monologue" in message:
|
|
50
|
-
html_content += f"<p><strong
|
|
55
|
+
html_content += f"<p><strong>{INNER_THOUGHTS_CLI_SYMBOL} [{date_formatted}] Internal Monologue:</strong></p>"
|
|
51
56
|
html_content += f"<p class='internal-monologue'>{message['internal_monologue']}</p>"
|
|
52
57
|
elif "function_call" in message:
|
|
53
58
|
html_content += f"<p><strong>🛠️ [[{date_formatted}] Function Call:</strong></p>"
|
|
54
59
|
html_content += f"<p class='function-call'>{message['function_call']}</p>"
|
|
55
60
|
elif "assistant_message" in message:
|
|
56
|
-
html_content += f"<p><strong
|
|
61
|
+
html_content += f"<p><strong>{ASSISTANT_MESSAGE_CLI_SYMBOL} [{date_formatted}] Assistant Message:</strong></p>"
|
|
57
62
|
html_content += f"<p class='assistant-message'>{message['assistant_message']}</p>"
|
|
58
63
|
html_content += "<br>"
|
|
59
64
|
html_content += "</div>"
|
letta/credentials.py
CHANGED
|
@@ -76,7 +76,7 @@ class LettaCredentials:
|
|
|
76
76
|
"azure_embedding_deployment": get_field(config, "azure", "embedding_deployment"),
|
|
77
77
|
# gemini
|
|
78
78
|
"google_ai_key": get_field(config, "google_ai", "key"),
|
|
79
|
-
"google_ai_service_endpoint": get_field(config, "google_ai", "service_endpoint"),
|
|
79
|
+
# "google_ai_service_endpoint": get_field(config, "google_ai", "service_endpoint"),
|
|
80
80
|
# anthropic
|
|
81
81
|
"anthropic_key": get_field(config, "anthropic", "key"),
|
|
82
82
|
# cohere
|
|
@@ -117,7 +117,7 @@ class LettaCredentials:
|
|
|
117
117
|
|
|
118
118
|
# gemini
|
|
119
119
|
set_field(config, "google_ai", "key", self.google_ai_key)
|
|
120
|
-
set_field(config, "google_ai", "service_endpoint", self.google_ai_service_endpoint)
|
|
120
|
+
# set_field(config, "google_ai", "service_endpoint", self.google_ai_service_endpoint)
|
|
121
121
|
|
|
122
122
|
# anthropic
|
|
123
123
|
set_field(config, "anthropic", "key", self.anthropic_key)
|
letta/embeddings.py
CHANGED
|
@@ -91,6 +91,9 @@ class EmbeddingEndpoint:
|
|
|
91
91
|
raise ValueError(
|
|
92
92
|
f"Embeddings endpoint was provided an invalid URL (set to: '{base_url}'). Make sure embedding_endpoint is set correctly in your Letta config."
|
|
93
93
|
)
|
|
94
|
+
# TODO: find a neater solution - re-mapping for letta endpoint
|
|
95
|
+
if model == "letta-free":
|
|
96
|
+
model = "BAAI/bge-large-en-v1.5"
|
|
94
97
|
self.model_name = model
|
|
95
98
|
self._user = user
|
|
96
99
|
self._base_url = base_url
|
|
@@ -130,7 +130,7 @@ def generate_schema(function, name: Optional[str] = None, description: Optional[
|
|
|
130
130
|
if function.__name__ not in ["send_message", "pause_heartbeats"]:
|
|
131
131
|
schema["parameters"]["properties"]["request_heartbeat"] = {
|
|
132
132
|
"type": "boolean",
|
|
133
|
-
"description": "Request an immediate heartbeat after function execution. Set to
|
|
133
|
+
"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.",
|
|
134
134
|
}
|
|
135
135
|
schema["parameters"]["required"].append("request_heartbeat")
|
|
136
136
|
|
letta/interface.py
CHANGED
|
@@ -5,6 +5,10 @@ from typing import List, Optional
|
|
|
5
5
|
from colorama import Fore, Style, init
|
|
6
6
|
|
|
7
7
|
from letta.constants import CLI_WARNING_PREFIX
|
|
8
|
+
from letta.local_llm.constants import (
|
|
9
|
+
ASSISTANT_MESSAGE_CLI_SYMBOL,
|
|
10
|
+
INNER_THOUGHTS_CLI_SYMBOL,
|
|
11
|
+
)
|
|
8
12
|
from letta.schemas.message import Message
|
|
9
13
|
from letta.utils import json_loads, printd
|
|
10
14
|
|
|
@@ -79,14 +83,14 @@ class CLIInterface(AgentInterface):
|
|
|
79
83
|
@staticmethod
|
|
80
84
|
def internal_monologue(msg: str, msg_obj: Optional[Message] = None):
|
|
81
85
|
# ANSI escape code for italic is '\x1B[3m'
|
|
82
|
-
fstr = f"\x1B[3m{Fore.LIGHTBLACK_EX}
|
|
86
|
+
fstr = f"\x1B[3m{Fore.LIGHTBLACK_EX}{INNER_THOUGHTS_CLI_SYMBOL} {{msg}}{Style.RESET_ALL}"
|
|
83
87
|
if STRIP_UI:
|
|
84
88
|
fstr = "{msg}"
|
|
85
89
|
print(fstr.format(msg=msg))
|
|
86
90
|
|
|
87
91
|
@staticmethod
|
|
88
92
|
def assistant_message(msg: str, msg_obj: Optional[Message] = None):
|
|
89
|
-
fstr = f"{Fore.YELLOW}{Style.BRIGHT}
|
|
93
|
+
fstr = f"{Fore.YELLOW}{Style.BRIGHT}{ASSISTANT_MESSAGE_CLI_SYMBOL} {Fore.YELLOW}{{msg}}{Style.RESET_ALL}"
|
|
90
94
|
if STRIP_UI:
|
|
91
95
|
fstr = "{msg}"
|
|
92
96
|
print(fstr.format(msg=msg))
|
letta/llm_api/anthropic.py
CHANGED
|
@@ -2,8 +2,7 @@ import json
|
|
|
2
2
|
import re
|
|
3
3
|
from typing import List, Optional, Union
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
from letta.llm_api.helpers import make_post_request
|
|
7
6
|
from letta.schemas.message import Message
|
|
8
7
|
from letta.schemas.openai.chat_completion_request import ChatCompletionRequest, Tool
|
|
9
8
|
from letta.schemas.openai.chat_completion_response import (
|
|
@@ -295,7 +294,6 @@ def anthropic_chat_completions_request(
|
|
|
295
294
|
inner_thoughts_xml_tag: Optional[str] = "thinking",
|
|
296
295
|
) -> ChatCompletionResponse:
|
|
297
296
|
"""https://docs.anthropic.com/claude/docs/tool-use"""
|
|
298
|
-
from letta.utils import printd
|
|
299
297
|
|
|
300
298
|
url = smart_urljoin(url, "messages")
|
|
301
299
|
headers = {
|
|
@@ -360,24 +358,5 @@ def anthropic_chat_completions_request(
|
|
|
360
358
|
data.pop("user", None)
|
|
361
359
|
data.pop("tool_choice", None)
|
|
362
360
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
response = requests.post(url, headers=headers, json=data)
|
|
366
|
-
printd(f"response = {response}")
|
|
367
|
-
response.raise_for_status() # Raises HTTPError for 4XX/5XX status
|
|
368
|
-
response = response.json() # convert to dict from string
|
|
369
|
-
printd(f"response.json = {response}")
|
|
370
|
-
response = convert_anthropic_response_to_chatcompletion(response_json=response, inner_thoughts_xml_tag=inner_thoughts_xml_tag)
|
|
371
|
-
return response
|
|
372
|
-
except requests.exceptions.HTTPError as http_err:
|
|
373
|
-
# Handle HTTP errors (e.g., response 4XX, 5XX)
|
|
374
|
-
printd(f"Got HTTPError, exception={http_err}, payload={data}")
|
|
375
|
-
raise http_err
|
|
376
|
-
except requests.exceptions.RequestException as req_err:
|
|
377
|
-
# Handle other requests-related errors (e.g., connection error)
|
|
378
|
-
printd(f"Got RequestException, exception={req_err}")
|
|
379
|
-
raise req_err
|
|
380
|
-
except Exception as e:
|
|
381
|
-
# Handle other potential errors
|
|
382
|
-
printd(f"Got unknown Exception, exception={e}")
|
|
383
|
-
raise e
|
|
361
|
+
response_json = make_post_request(url, headers, data)
|
|
362
|
+
return convert_anthropic_response_to_chatcompletion(response_json=response_json, inner_thoughts_xml_tag=inner_thoughts_xml_tag)
|
letta/llm_api/azure_openai.py
CHANGED
|
@@ -1,83 +1,69 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
|
|
3
1
|
import requests
|
|
4
2
|
|
|
3
|
+
from letta.llm_api.helpers import make_post_request
|
|
5
4
|
from letta.schemas.llm_config import LLMConfig
|
|
6
5
|
from letta.schemas.openai.chat_completion_response import ChatCompletionResponse
|
|
7
6
|
from letta.schemas.openai.chat_completions import ChatCompletionRequest
|
|
8
7
|
from letta.schemas.openai.embedding_response import EmbeddingResponse
|
|
9
8
|
from letta.settings import ModelSettings
|
|
10
|
-
from letta.utils import smart_urljoin
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"gpt-4-32k": "gpt-4-32k",
|
|
16
|
-
"gpt-3.5": "gpt-35-turbo",
|
|
17
|
-
"gpt-3.5-turbo": "gpt-35-turbo",
|
|
18
|
-
"gpt-3.5-turbo-16k": "gpt-35-turbo-16k",
|
|
19
|
-
"gpt-4o-mini": "gpt-4o-mini",
|
|
20
|
-
}
|
|
10
|
+
|
|
11
|
+
def get_azure_chat_completions_endpoint(base_url: str, model: str, api_version: str):
|
|
12
|
+
return f"{base_url}/openai/deployments/{model}/chat/completions?api-version={api_version}"
|
|
21
13
|
|
|
22
14
|
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
assert llm_config.model in MODEL_TO_AZURE_ENGINE, f"{llm_config.model} not in supported models: {list(MODEL_TO_AZURE_ENGINE.keys())}"
|
|
15
|
+
def get_azure_embeddings_endpoint(base_url: str, model: str, api_version: str):
|
|
16
|
+
return f"{base_url}/openai/deployments/{model}/embeddings?api-version={api_version}"
|
|
26
17
|
|
|
27
|
-
model = MODEL_TO_AZURE_ENGINE[llm_config.model]
|
|
28
|
-
return f"{model_settings.azure_base_url}/openai/deployments/{model}/chat/completions?api-version={llm_config.api_version}"
|
|
29
18
|
|
|
19
|
+
def get_azure_model_list_endpoint(base_url: str, api_version: str):
|
|
20
|
+
return f"{base_url}/openai/models?api-version={api_version}"
|
|
30
21
|
|
|
31
|
-
|
|
22
|
+
|
|
23
|
+
def azure_openai_get_model_list(base_url: str, api_key: str, api_version: str) -> list:
|
|
32
24
|
"""https://learn.microsoft.com/en-us/rest/api/azureopenai/models/list?view=rest-azureopenai-2023-05-15&tabs=HTTP"""
|
|
33
|
-
from letta.utils import printd
|
|
34
25
|
|
|
35
26
|
# https://xxx.openai.azure.com/openai/models?api-version=xxx
|
|
36
|
-
url = smart_urljoin(url, "openai")
|
|
37
|
-
url = smart_urljoin(url, f"models?api-version={api_version}")
|
|
38
|
-
|
|
39
27
|
headers = {"Content-Type": "application/json"}
|
|
40
28
|
if api_key is not None:
|
|
41
29
|
headers["api-key"] = f"{api_key}"
|
|
42
30
|
|
|
43
|
-
|
|
31
|
+
url = get_azure_model_list_endpoint(base_url, api_version)
|
|
44
32
|
try:
|
|
45
33
|
response = requests.get(url, headers=headers)
|
|
46
|
-
response.raise_for_status()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
raise e
|
|
34
|
+
response.raise_for_status()
|
|
35
|
+
except requests.RequestException as e:
|
|
36
|
+
raise RuntimeError(f"Failed to retrieve model list: {e}")
|
|
37
|
+
|
|
38
|
+
return response.json().get("data", [])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def azure_openai_get_chat_completion_model_list(base_url: str, api_key: str, api_version: str) -> list:
|
|
42
|
+
model_list = azure_openai_get_model_list(base_url, api_key, api_version)
|
|
43
|
+
# Extract models that support text generation
|
|
44
|
+
model_options = [m for m in model_list if m.get("capabilities").get("chat_completion") == True]
|
|
45
|
+
return model_options
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def azure_openai_get_embeddings_model_list(base_url: str, api_key: str, api_version: str, require_embedding_in_name: bool = True) -> list:
|
|
49
|
+
def valid_embedding_model(m: dict):
|
|
50
|
+
valid_name = True
|
|
51
|
+
if require_embedding_in_name:
|
|
52
|
+
valid_name = "embedding" in m["id"]
|
|
53
|
+
|
|
54
|
+
return m.get("capabilities").get("embeddings") == True and valid_name
|
|
55
|
+
|
|
56
|
+
model_list = azure_openai_get_model_list(base_url, api_key, api_version)
|
|
57
|
+
# Extract models that support embeddings
|
|
58
|
+
|
|
59
|
+
model_options = [m for m in model_list if valid_embedding_model(m)]
|
|
60
|
+
return model_options
|
|
74
61
|
|
|
75
62
|
|
|
76
63
|
def azure_openai_chat_completions_request(
|
|
77
64
|
model_settings: ModelSettings, llm_config: LLMConfig, api_key: str, chat_completion_request: ChatCompletionRequest
|
|
78
65
|
) -> ChatCompletionResponse:
|
|
79
66
|
"""https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions"""
|
|
80
|
-
from letta.utils import printd
|
|
81
67
|
|
|
82
68
|
assert api_key is not None, "Missing required field when calling Azure OpenAI"
|
|
83
69
|
|
|
@@ -93,59 +79,22 @@ def azure_openai_chat_completions_request(
|
|
|
93
79
|
data.pop("tools")
|
|
94
80
|
data.pop("tool_choice", None) # extra safe, should exist always (default="auto")
|
|
95
81
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
# NOTE: azure openai does not include "content" in the response when it is None, so we need to add it
|
|
104
|
-
if "content" not in response["choices"][0].get("message"):
|
|
105
|
-
response["choices"][0]["message"]["content"] = None
|
|
106
|
-
response = ChatCompletionResponse(**response) # convert to 'dot-dict' style which is the openai python client default
|
|
107
|
-
return response
|
|
108
|
-
except requests.exceptions.HTTPError as http_err:
|
|
109
|
-
# Handle HTTP errors (e.g., response 4XX, 5XX)
|
|
110
|
-
printd(f"Got HTTPError, exception={http_err}, payload={data}")
|
|
111
|
-
raise http_err
|
|
112
|
-
except requests.exceptions.RequestException as req_err:
|
|
113
|
-
# Handle other requests-related errors (e.g., connection error)
|
|
114
|
-
printd(f"Got RequestException, exception={req_err}")
|
|
115
|
-
raise req_err
|
|
116
|
-
except Exception as e:
|
|
117
|
-
# Handle other potential errors
|
|
118
|
-
printd(f"Got unknown Exception, exception={e}")
|
|
119
|
-
raise e
|
|
82
|
+
url = get_azure_chat_completions_endpoint(model_settings.azure_base_url, llm_config.model, model_settings.api_version)
|
|
83
|
+
response_json = make_post_request(url, headers, data)
|
|
84
|
+
# NOTE: azure openai does not include "content" in the response when it is None, so we need to add it
|
|
85
|
+
if "content" not in response_json["choices"][0].get("message"):
|
|
86
|
+
response_json["choices"][0]["message"]["content"] = None
|
|
87
|
+
response = ChatCompletionResponse(**response_json) # convert to 'dot-dict' style which is the openai python client default
|
|
88
|
+
return response
|
|
120
89
|
|
|
121
90
|
|
|
122
91
|
def azure_openai_embeddings_request(
|
|
123
92
|
resource_name: str, deployment_id: str, api_version: str, api_key: str, data: dict
|
|
124
93
|
) -> EmbeddingResponse:
|
|
125
94
|
"""https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#embeddings"""
|
|
126
|
-
from letta.utils import printd
|
|
127
95
|
|
|
128
96
|
url = f"https://{resource_name}.openai.azure.com/openai/deployments/{deployment_id}/embeddings?api-version={api_version}"
|
|
129
97
|
headers = {"Content-Type": "application/json", "api-key": f"{api_key}"}
|
|
130
98
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
response = requests.post(url, headers=headers, json=data)
|
|
134
|
-
printd(f"response = {response}")
|
|
135
|
-
response.raise_for_status() # Raises HTTPError for 4XX/5XX status
|
|
136
|
-
response = response.json() # convert to dict from string
|
|
137
|
-
printd(f"response.json = {response}")
|
|
138
|
-
response = EmbeddingResponse(**response) # convert to 'dot-dict' style which is the openai python client default
|
|
139
|
-
return response
|
|
140
|
-
except requests.exceptions.HTTPError as http_err:
|
|
141
|
-
# Handle HTTP errors (e.g., response 4XX, 5XX)
|
|
142
|
-
printd(f"Got HTTPError, exception={http_err}, payload={data}")
|
|
143
|
-
raise http_err
|
|
144
|
-
except requests.exceptions.RequestException as req_err:
|
|
145
|
-
# Handle other requests-related errors (e.g., connection error)
|
|
146
|
-
printd(f"Got RequestException, exception={req_err}")
|
|
147
|
-
raise req_err
|
|
148
|
-
except Exception as e:
|
|
149
|
-
# Handle other potential errors
|
|
150
|
-
printd(f"Got unknown Exception, exception={e}")
|
|
151
|
-
raise e
|
|
99
|
+
response_json = make_post_request(url, headers, data)
|
|
100
|
+
return EmbeddingResponse(**response_json)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
AZURE_MODEL_TO_CONTEXT_LENGTH = {
|
|
2
|
+
"babbage-002": 16384,
|
|
3
|
+
"davinci-002": 16384,
|
|
4
|
+
"gpt-35-turbo-0613": 4096,
|
|
5
|
+
"gpt-35-turbo-1106": 16385,
|
|
6
|
+
"gpt-35-turbo-0125": 16385,
|
|
7
|
+
"gpt-4-0613": 8192,
|
|
8
|
+
"gpt-4o-mini-2024-07-18": 128000,
|
|
9
|
+
"gpt-4o-2024-08-06": 128000,
|
|
10
|
+
}
|
letta/llm_api/google_ai.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import uuid
|
|
2
|
-
from typing import List, Optional
|
|
2
|
+
from typing import List, Optional, Tuple
|
|
3
3
|
|
|
4
4
|
import requests
|
|
5
5
|
|
|
6
6
|
from letta.constants import NON_USER_MSG_PREFIX
|
|
7
|
+
from letta.llm_api.helpers import make_post_request
|
|
7
8
|
from letta.local_llm.json_parser import clean_json_string_extra_backslash
|
|
8
9
|
from letta.local_llm.utils import count_tokens
|
|
9
10
|
from letta.schemas.openai.chat_completion_request import Tool
|
|
@@ -15,27 +16,41 @@ from letta.schemas.openai.chat_completion_response import (
|
|
|
15
16
|
ToolCall,
|
|
16
17
|
UsageStatistics,
|
|
17
18
|
)
|
|
18
|
-
from letta.utils import get_tool_call_id, get_utc_time
|
|
19
|
+
from letta.utils import get_tool_call_id, get_utc_time, json_dumps
|
|
19
20
|
|
|
20
|
-
# from letta.data_types import ToolCall
|
|
21
21
|
|
|
22
|
+
def get_gemini_endpoint_and_headers(
|
|
23
|
+
base_url: str, model: Optional[str], api_key: str, key_in_header: bool = True, generate_content: bool = False
|
|
24
|
+
) -> Tuple[str, dict]:
|
|
25
|
+
"""
|
|
26
|
+
Dynamically generate the model endpoint and headers.
|
|
27
|
+
"""
|
|
28
|
+
url = f"{base_url}/v1beta/models"
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
# Add the model
|
|
31
|
+
if model is not None:
|
|
32
|
+
url += f"/{model}"
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
# Add extension for generating content if we're hitting the LM
|
|
35
|
+
if generate_content:
|
|
36
|
+
url += ":generateContent"
|
|
30
37
|
|
|
38
|
+
# Decide if api key should be in header or not
|
|
31
39
|
# Two ways to pass the key: https://ai.google.dev/tutorials/setup
|
|
32
40
|
if key_in_header:
|
|
33
|
-
url = f"https://{service_endpoint}.googleapis.com/v1beta/models/{model}"
|
|
34
41
|
headers = {"Content-Type": "application/json", "x-goog-api-key": api_key}
|
|
35
42
|
else:
|
|
36
|
-
url
|
|
43
|
+
url += f"?key={api_key}"
|
|
37
44
|
headers = {"Content-Type": "application/json"}
|
|
38
45
|
|
|
46
|
+
return url, headers
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def google_ai_get_model_details(base_url: str, api_key: str, model: str, key_in_header: bool = True) -> List[dict]:
|
|
50
|
+
from letta.utils import printd
|
|
51
|
+
|
|
52
|
+
url, headers = get_gemini_endpoint_and_headers(base_url, model, api_key, key_in_header)
|
|
53
|
+
|
|
39
54
|
try:
|
|
40
55
|
response = requests.get(url, headers=headers)
|
|
41
56
|
printd(f"response = {response}")
|
|
@@ -66,25 +81,17 @@ def google_ai_get_model_details(service_endpoint: str, api_key: str, model: str,
|
|
|
66
81
|
raise e
|
|
67
82
|
|
|
68
83
|
|
|
69
|
-
def google_ai_get_model_context_window(
|
|
70
|
-
model_details = google_ai_get_model_details(
|
|
71
|
-
service_endpoint=service_endpoint, api_key=api_key, model=model, key_in_header=key_in_header
|
|
72
|
-
)
|
|
84
|
+
def google_ai_get_model_context_window(base_url: str, api_key: str, model: str, key_in_header: bool = True) -> int:
|
|
85
|
+
model_details = google_ai_get_model_details(base_url=base_url, api_key=api_key, model=model, key_in_header=key_in_header)
|
|
73
86
|
# TODO should this be:
|
|
74
87
|
# return model_details["inputTokenLimit"] + model_details["outputTokenLimit"]
|
|
75
88
|
return int(model_details["inputTokenLimit"])
|
|
76
89
|
|
|
77
90
|
|
|
78
|
-
def google_ai_get_model_list(
|
|
91
|
+
def google_ai_get_model_list(base_url: str, api_key: str, key_in_header: bool = True) -> List[dict]:
|
|
79
92
|
from letta.utils import printd
|
|
80
93
|
|
|
81
|
-
|
|
82
|
-
if key_in_header:
|
|
83
|
-
url = f"https://{service_endpoint}.googleapis.com/v1beta/models"
|
|
84
|
-
headers = {"Content-Type": "application/json", "x-goog-api-key": api_key}
|
|
85
|
-
else:
|
|
86
|
-
url = f"https://{service_endpoint}.googleapis.com/v1beta/models?key={api_key}"
|
|
87
|
-
headers = {"Content-Type": "application/json"}
|
|
94
|
+
url, headers = get_gemini_endpoint_and_headers(base_url, None, api_key, key_in_header)
|
|
88
95
|
|
|
89
96
|
try:
|
|
90
97
|
response = requests.get(url, headers=headers)
|
|
@@ -396,7 +403,7 @@ def convert_google_ai_response_to_chatcompletion(
|
|
|
396
403
|
|
|
397
404
|
# TODO convert 'data' type to pydantic
|
|
398
405
|
def google_ai_chat_completions_request(
|
|
399
|
-
|
|
406
|
+
base_url: str,
|
|
400
407
|
model: str,
|
|
401
408
|
api_key: str,
|
|
402
409
|
data: dict,
|
|
@@ -414,55 +421,23 @@ def google_ai_chat_completions_request(
|
|
|
414
421
|
This service has the following service endpoint and all URIs below are relative to this service endpoint:
|
|
415
422
|
https://xxx.googleapis.com
|
|
416
423
|
"""
|
|
417
|
-
from letta.utils import printd
|
|
418
424
|
|
|
419
|
-
assert service_endpoint is not None, "Missing service_endpoint when calling Google AI"
|
|
420
425
|
assert api_key is not None, "Missing api_key when calling Google AI"
|
|
421
|
-
assert model in SUPPORTED_MODELS, f"Model '{model}' not in supported models: {', '.join(SUPPORTED_MODELS)}"
|
|
422
426
|
|
|
423
|
-
|
|
424
|
-
if key_in_header:
|
|
425
|
-
url = f"https://{service_endpoint}.googleapis.com/v1beta/models/{model}:generateContent"
|
|
426
|
-
headers = {"Content-Type": "application/json", "x-goog-api-key": api_key}
|
|
427
|
-
else:
|
|
428
|
-
url = f"https://{service_endpoint}.googleapis.com/v1beta/models/{model}:generateContent?key={api_key}"
|
|
429
|
-
headers = {"Content-Type": "application/json"}
|
|
427
|
+
url, headers = get_gemini_endpoint_and_headers(base_url, model, api_key, key_in_header, generate_content=True)
|
|
430
428
|
|
|
431
429
|
# data["contents"][-1]["role"] = "model"
|
|
432
430
|
if add_postfunc_model_messages:
|
|
433
431
|
data["contents"] = add_dummy_model_messages(data["contents"])
|
|
434
432
|
|
|
435
|
-
|
|
433
|
+
response_json = make_post_request(url, headers, data)
|
|
436
434
|
try:
|
|
437
|
-
response = requests.post(url, headers=headers, json=data)
|
|
438
|
-
printd(f"response = {response}")
|
|
439
|
-
response.raise_for_status() # Raises HTTPError for 4XX/5XX status
|
|
440
|
-
response = response.json() # convert to dict from string
|
|
441
|
-
printd(f"response.json = {response}")
|
|
442
|
-
|
|
443
|
-
# Convert Google AI response to ChatCompletion style
|
|
444
435
|
return convert_google_ai_response_to_chatcompletion(
|
|
445
|
-
response_json=
|
|
446
|
-
model=model,
|
|
436
|
+
response_json=response_json,
|
|
437
|
+
model=data.get("model"),
|
|
447
438
|
input_messages=data["contents"],
|
|
448
439
|
pull_inner_thoughts_from_args=inner_thoughts_in_kwargs,
|
|
449
440
|
)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
printd(f"Got HTTPError, exception={http_err}, payload={data}")
|
|
454
|
-
# Print the HTTP status code
|
|
455
|
-
print(f"HTTP Error: {http_err.response.status_code}")
|
|
456
|
-
# Print the response content (error message from server)
|
|
457
|
-
print(f"Message: {http_err.response.text}")
|
|
458
|
-
raise http_err
|
|
459
|
-
|
|
460
|
-
except requests.exceptions.RequestException as req_err:
|
|
461
|
-
# Handle other requests-related errors (e.g., connection error)
|
|
462
|
-
printd(f"Got RequestException, exception={req_err}")
|
|
463
|
-
raise req_err
|
|
464
|
-
|
|
465
|
-
except Exception as e:
|
|
466
|
-
# Handle other potential errors
|
|
467
|
-
printd(f"Got unknown Exception, exception={e}")
|
|
468
|
-
raise e
|
|
441
|
+
except Exception as conversion_error:
|
|
442
|
+
print(f"Error during response conversion: {conversion_error}")
|
|
443
|
+
raise conversion_error
|
letta/llm_api/helpers.py
CHANGED
|
@@ -1,14 +1,76 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import json
|
|
3
3
|
import warnings
|
|
4
|
-
from typing import List, Union
|
|
4
|
+
from typing import Any, List, Union
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
7
|
|
|
8
8
|
from letta.constants import OPENAI_CONTEXT_WINDOW_ERROR_SUBSTRING
|
|
9
9
|
from letta.schemas.enums import OptionState
|
|
10
10
|
from letta.schemas.openai.chat_completion_response import ChatCompletionResponse, Choice
|
|
11
|
-
from letta.utils import json_dumps
|
|
11
|
+
from letta.utils import json_dumps, printd
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def make_post_request(url: str, headers: dict[str, str], data: dict[str, Any]) -> dict[str, Any]:
|
|
15
|
+
printd(f"Sending request to {url}")
|
|
16
|
+
try:
|
|
17
|
+
# Make the POST request
|
|
18
|
+
response = requests.post(url, headers=headers, json=data)
|
|
19
|
+
printd(f"Response status code: {response.status_code}")
|
|
20
|
+
|
|
21
|
+
# Raise for 4XX/5XX HTTP errors
|
|
22
|
+
response.raise_for_status()
|
|
23
|
+
|
|
24
|
+
# Check if the response content type indicates JSON and attempt to parse it
|
|
25
|
+
content_type = response.headers.get("Content-Type", "")
|
|
26
|
+
if "application/json" in content_type.lower():
|
|
27
|
+
try:
|
|
28
|
+
response_data = response.json() # Attempt to parse the response as JSON
|
|
29
|
+
printd(f"Response JSON: {response_data}")
|
|
30
|
+
except ValueError as json_err:
|
|
31
|
+
# Handle the case where the content type says JSON but the body is invalid
|
|
32
|
+
error_message = f"Failed to parse JSON despite Content-Type being {content_type}: {json_err}"
|
|
33
|
+
printd(error_message)
|
|
34
|
+
raise ValueError(error_message) from json_err
|
|
35
|
+
else:
|
|
36
|
+
error_message = f"Unexpected content type returned: {response.headers.get('Content-Type')}"
|
|
37
|
+
printd(error_message)
|
|
38
|
+
raise ValueError(error_message)
|
|
39
|
+
|
|
40
|
+
# Process the response using the callback function
|
|
41
|
+
return response_data
|
|
42
|
+
|
|
43
|
+
except requests.exceptions.HTTPError as http_err:
|
|
44
|
+
# HTTP errors (4XX, 5XX)
|
|
45
|
+
error_message = f"HTTP error occurred: {http_err}"
|
|
46
|
+
if http_err.response is not None:
|
|
47
|
+
error_message += f" | Status code: {http_err.response.status_code}, Message: {http_err.response.text}"
|
|
48
|
+
printd(error_message)
|
|
49
|
+
raise requests.exceptions.HTTPError(error_message) from http_err
|
|
50
|
+
|
|
51
|
+
except requests.exceptions.Timeout as timeout_err:
|
|
52
|
+
# Handle timeout errors
|
|
53
|
+
error_message = f"Request timed out: {timeout_err}"
|
|
54
|
+
printd(error_message)
|
|
55
|
+
raise requests.exceptions.Timeout(error_message) from timeout_err
|
|
56
|
+
|
|
57
|
+
except requests.exceptions.RequestException as req_err:
|
|
58
|
+
# Non-HTTP errors (e.g., connection, SSL errors)
|
|
59
|
+
error_message = f"Request failed: {req_err}"
|
|
60
|
+
printd(error_message)
|
|
61
|
+
raise requests.exceptions.RequestException(error_message) from req_err
|
|
62
|
+
|
|
63
|
+
except ValueError as val_err:
|
|
64
|
+
# Handle content-type or non-JSON response issues
|
|
65
|
+
error_message = f"ValueError: {val_err}"
|
|
66
|
+
printd(error_message)
|
|
67
|
+
raise ValueError(error_message) from val_err
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
# Catch any other unknown exceptions
|
|
71
|
+
error_message = f"An unexpected error occurred: {e}"
|
|
72
|
+
printd(error_message)
|
|
73
|
+
raise Exception(error_message) from e
|
|
12
74
|
|
|
13
75
|
|
|
14
76
|
# TODO update to use better types
|