letta-nightly 0.6.16.dev20250128104041__py3-none-any.whl → 0.6.17.dev20250129174639__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 +0 -3
- letta/client/client.py +5 -5
- letta/client/streaming.py +29 -20
- letta/constants.py +1 -1
- letta/functions/function_sets/multi_agent.py +55 -49
- letta/functions/functions.py +0 -1
- letta/functions/helpers.py +149 -9
- letta/llm_api/llm_api_tools.py +20 -12
- letta/llm_api/openai.py +15 -13
- letta/orm/agent.py +14 -2
- letta/orm/job.py +1 -1
- letta/orm/sqlalchemy_base.py +12 -4
- letta/schemas/job.py +17 -1
- letta/schemas/letta_request.py +2 -7
- letta/schemas/llm_config.py +9 -0
- letta/schemas/message.py +51 -22
- letta/schemas/openai/chat_completion_response.py +2 -2
- letta/schemas/run.py +1 -2
- letta/server/rest_api/app.py +5 -1
- letta/server/rest_api/chat_completions_interface.py +256 -0
- letta/server/rest_api/optimistic_json_parser.py +185 -0
- letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +161 -0
- letta/server/rest_api/routers/v1/agents.py +22 -32
- letta/server/server.py +12 -12
- letta/services/job_manager.py +7 -12
- letta/services/tool_manager.py +17 -1
- letta/system.py +20 -0
- letta/utils.py +24 -1
- {letta_nightly-0.6.16.dev20250128104041.dist-info → letta_nightly-0.6.17.dev20250129174639.dist-info}/METADATA +4 -4
- {letta_nightly-0.6.16.dev20250128104041.dist-info → letta_nightly-0.6.17.dev20250129174639.dist-info}/RECORD +35 -31
- {letta_nightly-0.6.16.dev20250128104041.dist-info → letta_nightly-0.6.17.dev20250129174639.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.16.dev20250128104041.dist-info → letta_nightly-0.6.17.dev20250129174639.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.16.dev20250128104041.dist-info → letta_nightly-0.6.17.dev20250129174639.dist-info}/entry_points.txt +0 -0
letta/llm_api/openai.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import warnings
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import AsyncGenerator, List, Optional, Union
|
|
3
3
|
|
|
4
4
|
import requests
|
|
5
|
-
from openai import
|
|
5
|
+
from openai import AsyncOpenAI
|
|
6
6
|
|
|
7
7
|
from letta.llm_api.helpers import add_inner_thoughts_to_functions, convert_to_structured_output, make_post_request
|
|
8
8
|
from letta.local_llm.constants import INNER_THOUGHTS_KWARG, INNER_THOUGHTS_KWARG_DESCRIPTION, INNER_THOUGHTS_KWARG_DESCRIPTION_GO_FIRST
|
|
@@ -158,7 +158,7 @@ def build_openai_chat_completions_request(
|
|
|
158
158
|
return data
|
|
159
159
|
|
|
160
160
|
|
|
161
|
-
def openai_chat_completions_process_stream(
|
|
161
|
+
async def openai_chat_completions_process_stream(
|
|
162
162
|
url: str,
|
|
163
163
|
api_key: str,
|
|
164
164
|
chat_completion_request: ChatCompletionRequest,
|
|
@@ -229,9 +229,10 @@ def openai_chat_completions_process_stream(
|
|
|
229
229
|
stream_interface.stream_start()
|
|
230
230
|
|
|
231
231
|
n_chunks = 0 # approx == n_tokens
|
|
232
|
+
chunk_idx = 0
|
|
232
233
|
try:
|
|
233
|
-
for
|
|
234
|
-
|
|
234
|
+
async for chat_completion_chunk in openai_chat_completions_request_stream(
|
|
235
|
+
url=url, api_key=api_key, chat_completion_request=chat_completion_request
|
|
235
236
|
):
|
|
236
237
|
assert isinstance(chat_completion_chunk, ChatCompletionChunkResponse), type(chat_completion_chunk)
|
|
237
238
|
|
|
@@ -348,6 +349,7 @@ def openai_chat_completions_process_stream(
|
|
|
348
349
|
|
|
349
350
|
# increment chunk counter
|
|
350
351
|
n_chunks += 1
|
|
352
|
+
chunk_idx += 1
|
|
351
353
|
|
|
352
354
|
except Exception as e:
|
|
353
355
|
if stream_interface:
|
|
@@ -380,24 +382,24 @@ def openai_chat_completions_process_stream(
|
|
|
380
382
|
return chat_completion_response
|
|
381
383
|
|
|
382
384
|
|
|
383
|
-
def openai_chat_completions_request_stream(
|
|
385
|
+
async def openai_chat_completions_request_stream(
|
|
384
386
|
url: str,
|
|
385
387
|
api_key: str,
|
|
386
388
|
chat_completion_request: ChatCompletionRequest,
|
|
387
|
-
) ->
|
|
389
|
+
) -> AsyncGenerator[ChatCompletionChunkResponse, None]:
|
|
388
390
|
data = prepare_openai_payload(chat_completion_request)
|
|
389
391
|
data["stream"] = True
|
|
390
|
-
client =
|
|
392
|
+
client = AsyncOpenAI(
|
|
391
393
|
api_key=api_key,
|
|
392
394
|
base_url=url,
|
|
393
395
|
)
|
|
394
|
-
stream = client.chat.completions.create(**data)
|
|
395
|
-
for chunk in stream:
|
|
396
|
+
stream = await client.chat.completions.create(**data)
|
|
397
|
+
async for chunk in stream:
|
|
396
398
|
# TODO: Use the native OpenAI objects here?
|
|
397
399
|
yield ChatCompletionChunkResponse(**chunk.model_dump(exclude_none=True))
|
|
398
400
|
|
|
399
401
|
|
|
400
|
-
def openai_chat_completions_request(
|
|
402
|
+
async def openai_chat_completions_request(
|
|
401
403
|
url: str,
|
|
402
404
|
api_key: str,
|
|
403
405
|
chat_completion_request: ChatCompletionRequest,
|
|
@@ -410,8 +412,8 @@ def openai_chat_completions_request(
|
|
|
410
412
|
https://platform.openai.com/docs/guides/text-generation?lang=curl
|
|
411
413
|
"""
|
|
412
414
|
data = prepare_openai_payload(chat_completion_request)
|
|
413
|
-
client =
|
|
414
|
-
chat_completion = client.chat.completions.create(**data)
|
|
415
|
+
client = AsyncOpenAI(api_key=api_key, base_url=url)
|
|
416
|
+
chat_completion = await client.chat.completions.create(**data)
|
|
415
417
|
return ChatCompletionResponse(**chat_completion.model_dump())
|
|
416
418
|
|
|
417
419
|
|
letta/orm/agent.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, List, Optional
|
|
|
4
4
|
from sqlalchemy import JSON, Index, String
|
|
5
5
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
6
6
|
|
|
7
|
+
from letta.constants import MULTI_AGENT_TOOLS
|
|
7
8
|
from letta.orm.block import Block
|
|
8
9
|
from letta.orm.custom_columns import EmbeddingConfigColumn, LLMConfigColumn, ToolRulesColumn
|
|
9
10
|
from letta.orm.message import Message
|
|
@@ -15,7 +16,7 @@ from letta.schemas.agent import AgentType
|
|
|
15
16
|
from letta.schemas.embedding_config import EmbeddingConfig
|
|
16
17
|
from letta.schemas.llm_config import LLMConfig
|
|
17
18
|
from letta.schemas.memory import Memory
|
|
18
|
-
from letta.schemas.tool_rule import ToolRule
|
|
19
|
+
from letta.schemas.tool_rule import TerminalToolRule, ToolRule
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING:
|
|
21
22
|
from letta.orm.agents_tags import AgentsTags
|
|
@@ -114,6 +115,16 @@ class Agent(SqlalchemyBase, OrganizationMixin):
|
|
|
114
115
|
|
|
115
116
|
def to_pydantic(self) -> PydanticAgentState:
|
|
116
117
|
"""converts to the basic pydantic model counterpart"""
|
|
118
|
+
# add default rule for having send_message be a terminal tool
|
|
119
|
+
tool_rules = self.tool_rules
|
|
120
|
+
if not tool_rules:
|
|
121
|
+
tool_rules = [
|
|
122
|
+
TerminalToolRule(tool_name="send_message"),
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
for tool_name in MULTI_AGENT_TOOLS:
|
|
126
|
+
tool_rules.append(TerminalToolRule(tool_name=tool_name))
|
|
127
|
+
|
|
117
128
|
state = {
|
|
118
129
|
"id": self.id,
|
|
119
130
|
"organization_id": self.organization_id,
|
|
@@ -123,7 +134,7 @@ class Agent(SqlalchemyBase, OrganizationMixin):
|
|
|
123
134
|
"tools": self.tools,
|
|
124
135
|
"sources": [source.to_pydantic() for source in self.sources],
|
|
125
136
|
"tags": [t.tag for t in self.tags],
|
|
126
|
-
"tool_rules":
|
|
137
|
+
"tool_rules": tool_rules,
|
|
127
138
|
"system": self.system,
|
|
128
139
|
"agent_type": self.agent_type,
|
|
129
140
|
"llm_config": self.llm_config,
|
|
@@ -136,4 +147,5 @@ class Agent(SqlalchemyBase, OrganizationMixin):
|
|
|
136
147
|
"updated_at": self.updated_at,
|
|
137
148
|
"tool_exec_environment_variables": self.tool_exec_environment_variables,
|
|
138
149
|
}
|
|
150
|
+
|
|
139
151
|
return self.__pydantic_model__(**state)
|
letta/orm/job.py
CHANGED
|
@@ -9,7 +9,7 @@ from letta.orm.mixins import UserMixin
|
|
|
9
9
|
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
10
10
|
from letta.schemas.enums import JobStatus
|
|
11
11
|
from letta.schemas.job import Job as PydanticJob
|
|
12
|
-
from letta.schemas.
|
|
12
|
+
from letta.schemas.job import LettaRequestConfig
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
15
|
from letta.orm.job_messages import JobMessage
|
letta/orm/sqlalchemy_base.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from functools import wraps
|
|
4
|
+
from pprint import pformat
|
|
4
5
|
from typing import TYPE_CHECKING, List, Literal, Optional, Tuple, Union
|
|
5
6
|
|
|
6
7
|
from sqlalchemy import String, and_, func, or_, select
|
|
@@ -504,7 +505,14 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
|
|
|
504
505
|
model.metadata = self.metadata_
|
|
505
506
|
return model
|
|
506
507
|
|
|
507
|
-
def
|
|
508
|
-
"""
|
|
509
|
-
|
|
510
|
-
|
|
508
|
+
def pretty_print_columns(self) -> str:
|
|
509
|
+
"""
|
|
510
|
+
Pretty prints all columns of the current SQLAlchemy object along with their values.
|
|
511
|
+
"""
|
|
512
|
+
if not hasattr(self, "__table__") or not hasattr(self.__table__, "columns"):
|
|
513
|
+
raise NotImplementedError("This object does not have a '__table__.columns' attribute.")
|
|
514
|
+
|
|
515
|
+
# Iterate over the columns correctly
|
|
516
|
+
column_data = {column.name: getattr(self, column.name, None) for column in self.__table__.columns}
|
|
517
|
+
|
|
518
|
+
return pformat(column_data, indent=4, sort_dicts=True)
|
letta/schemas/job.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
-
from pydantic import Field
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
|
+
from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
|
|
6
7
|
from letta.orm.enums import JobType
|
|
7
8
|
from letta.schemas.enums import JobStatus
|
|
8
9
|
from letta.schemas.letta_base import OrmMetadataBase
|
|
@@ -38,3 +39,18 @@ class JobUpdate(JobBase):
|
|
|
38
39
|
|
|
39
40
|
class Config:
|
|
40
41
|
extra = "ignore" # Ignores extra fields
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class LettaRequestConfig(BaseModel):
|
|
45
|
+
use_assistant_message: bool = Field(
|
|
46
|
+
default=True,
|
|
47
|
+
description="Whether the server should parse specific tool call arguments (default `send_message`) as `AssistantMessage` objects.",
|
|
48
|
+
)
|
|
49
|
+
assistant_message_tool_name: str = Field(
|
|
50
|
+
default=DEFAULT_MESSAGE_TOOL,
|
|
51
|
+
description="The name of the designated message tool.",
|
|
52
|
+
)
|
|
53
|
+
assistant_message_tool_kwarg: str = Field(
|
|
54
|
+
default=DEFAULT_MESSAGE_TOOL_KWARG,
|
|
55
|
+
description="The name of the message argument in the designated message tool.",
|
|
56
|
+
)
|
letta/schemas/letta_request.py
CHANGED
|
@@ -6,8 +6,8 @@ from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
|
|
|
6
6
|
from letta.schemas.message import MessageCreate
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class
|
|
10
|
-
|
|
9
|
+
class LettaRequest(BaseModel):
|
|
10
|
+
messages: List[MessageCreate] = Field(..., description="The messages to be sent to the agent.")
|
|
11
11
|
use_assistant_message: bool = Field(
|
|
12
12
|
default=True,
|
|
13
13
|
description="Whether the server should parse specific tool call arguments (default `send_message`) as `AssistantMessage` objects.",
|
|
@@ -22,11 +22,6 @@ class LettaRequestConfig(BaseModel):
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class LettaRequest(BaseModel):
|
|
26
|
-
messages: List[MessageCreate] = Field(..., description="The messages to be sent to the agent.")
|
|
27
|
-
config: LettaRequestConfig = Field(default=LettaRequestConfig(), description="Configuration options for the LettaRequest.")
|
|
28
|
-
|
|
29
|
-
|
|
30
25
|
class LettaStreamingRequest(LettaRequest):
|
|
31
26
|
stream_tokens: bool = Field(
|
|
32
27
|
default=False,
|
letta/schemas/llm_config.py
CHANGED
|
@@ -88,6 +88,7 @@ class LLMConfig(BaseModel):
|
|
|
88
88
|
model_endpoint="https://api.openai.com/v1",
|
|
89
89
|
model_wrapper=None,
|
|
90
90
|
context_window=8192,
|
|
91
|
+
put_inner_thoughts_in_kwargs=True,
|
|
91
92
|
)
|
|
92
93
|
elif model_name == "gpt-4o-mini":
|
|
93
94
|
return cls(
|
|
@@ -97,6 +98,14 @@ class LLMConfig(BaseModel):
|
|
|
97
98
|
model_wrapper=None,
|
|
98
99
|
context_window=128000,
|
|
99
100
|
)
|
|
101
|
+
elif model_name == "gpt-4o":
|
|
102
|
+
return cls(
|
|
103
|
+
model="gpt-4o",
|
|
104
|
+
model_endpoint_type="openai",
|
|
105
|
+
model_endpoint="https://api.openai.com/v1",
|
|
106
|
+
model_wrapper=None,
|
|
107
|
+
context_window=128000,
|
|
108
|
+
)
|
|
100
109
|
elif model_name == "letta":
|
|
101
110
|
return cls(
|
|
102
111
|
model="memgpt-openai",
|
letta/schemas/message.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import copy
|
|
2
4
|
import json
|
|
3
5
|
import warnings
|
|
@@ -25,6 +27,7 @@ from letta.schemas.letta_message import (
|
|
|
25
27
|
ToolReturnMessage,
|
|
26
28
|
UserMessage,
|
|
27
29
|
)
|
|
30
|
+
from letta.system import unpack_message
|
|
28
31
|
from letta.utils import get_utc_time, is_utc_datetime, json_dumps
|
|
29
32
|
|
|
30
33
|
|
|
@@ -176,9 +179,47 @@ class Message(BaseMessage):
|
|
|
176
179
|
json_message["created_at"] = self.created_at.isoformat()
|
|
177
180
|
return json_message
|
|
178
181
|
|
|
182
|
+
@staticmethod
|
|
183
|
+
def to_letta_messages_from_list(
|
|
184
|
+
messages: List[Message],
|
|
185
|
+
use_assistant_message: bool = True,
|
|
186
|
+
assistant_message_tool_name: str = DEFAULT_MESSAGE_TOOL,
|
|
187
|
+
assistant_message_tool_kwarg: str = DEFAULT_MESSAGE_TOOL_KWARG,
|
|
188
|
+
) -> List[LettaMessage]:
|
|
189
|
+
if use_assistant_message:
|
|
190
|
+
message_ids_to_remove = []
|
|
191
|
+
assistant_messages_by_tool_call = {
|
|
192
|
+
tool_call.id: msg
|
|
193
|
+
for msg in messages
|
|
194
|
+
if msg.role == MessageRole.assistant and msg.tool_calls
|
|
195
|
+
for tool_call in msg.tool_calls
|
|
196
|
+
}
|
|
197
|
+
for message in messages:
|
|
198
|
+
if (
|
|
199
|
+
message.role == MessageRole.tool
|
|
200
|
+
and message.tool_call_id in assistant_messages_by_tool_call
|
|
201
|
+
and assistant_messages_by_tool_call[message.tool_call_id].tool_calls
|
|
202
|
+
and assistant_message_tool_name
|
|
203
|
+
in [tool_call.function.name for tool_call in assistant_messages_by_tool_call[message.tool_call_id].tool_calls]
|
|
204
|
+
):
|
|
205
|
+
message_ids_to_remove.append(message.id)
|
|
206
|
+
|
|
207
|
+
messages = [msg for msg in messages if msg.id not in message_ids_to_remove]
|
|
208
|
+
|
|
209
|
+
# Convert messages to LettaMessages
|
|
210
|
+
return [
|
|
211
|
+
msg
|
|
212
|
+
for m in messages
|
|
213
|
+
for msg in m.to_letta_message(
|
|
214
|
+
use_assistant_message=use_assistant_message,
|
|
215
|
+
assistant_message_tool_name=assistant_message_tool_name,
|
|
216
|
+
assistant_message_tool_kwarg=assistant_message_tool_kwarg,
|
|
217
|
+
)
|
|
218
|
+
]
|
|
219
|
+
|
|
179
220
|
def to_letta_message(
|
|
180
221
|
self,
|
|
181
|
-
|
|
222
|
+
use_assistant_message: bool = False,
|
|
182
223
|
assistant_message_tool_name: str = DEFAULT_MESSAGE_TOOL,
|
|
183
224
|
assistant_message_tool_kwarg: str = DEFAULT_MESSAGE_TOOL_KWARG,
|
|
184
225
|
) -> List[LettaMessage]:
|
|
@@ -201,7 +242,7 @@ class Message(BaseMessage):
|
|
|
201
242
|
for tool_call in self.tool_calls:
|
|
202
243
|
# If we're supporting using assistant message,
|
|
203
244
|
# then we want to treat certain function calls as a special case
|
|
204
|
-
if
|
|
245
|
+
if use_assistant_message and tool_call.function.name == assistant_message_tool_name:
|
|
205
246
|
# We need to unpack the actual message contents from the function call
|
|
206
247
|
try:
|
|
207
248
|
func_args = json.loads(tool_call.function.arguments)
|
|
@@ -264,11 +305,12 @@ class Message(BaseMessage):
|
|
|
264
305
|
elif self.role == MessageRole.user:
|
|
265
306
|
# This is type UserMessage
|
|
266
307
|
assert self.text is not None, self
|
|
308
|
+
message_str = unpack_message(self.text)
|
|
267
309
|
messages.append(
|
|
268
310
|
UserMessage(
|
|
269
311
|
id=self.id,
|
|
270
312
|
date=self.created_at,
|
|
271
|
-
content=self.text,
|
|
313
|
+
content=message_str or self.text,
|
|
272
314
|
)
|
|
273
315
|
)
|
|
274
316
|
elif self.role == MessageRole.system:
|
|
@@ -311,26 +353,13 @@ class Message(BaseMessage):
|
|
|
311
353
|
assert "tool_call_id" in openai_message_dict, openai_message_dict
|
|
312
354
|
|
|
313
355
|
# Convert from 'function' response to a 'tool' response
|
|
314
|
-
# NOTE: this does not conventionally include a tool_call_id, it's on the caster to provide it
|
|
315
|
-
message_args = dict(
|
|
316
|
-
user_id=user_id,
|
|
317
|
-
agent_id=agent_id,
|
|
318
|
-
model=model,
|
|
319
|
-
# standard fields expected in an OpenAI ChatCompletion message object
|
|
320
|
-
role=MessageRole.tool, # NOTE
|
|
321
|
-
text=openai_message_dict["content"],
|
|
322
|
-
name=openai_message_dict["name"] if "name" in openai_message_dict else None,
|
|
323
|
-
tool_calls=openai_message_dict["tool_calls"] if "tool_calls" in openai_message_dict else None,
|
|
324
|
-
tool_call_id=openai_message_dict["tool_call_id"] if "tool_call_id" in openai_message_dict else None,
|
|
325
|
-
created_at=created_at,
|
|
326
|
-
)
|
|
327
356
|
if id is not None:
|
|
328
357
|
return Message(
|
|
329
358
|
agent_id=agent_id,
|
|
330
359
|
model=model,
|
|
331
360
|
# standard fields expected in an OpenAI ChatCompletion message object
|
|
332
361
|
role=MessageRole.tool, # NOTE
|
|
333
|
-
content=[TextContent(text=openai_message_dict["content"])],
|
|
362
|
+
content=[TextContent(text=openai_message_dict["content"])] if openai_message_dict["content"] else [],
|
|
334
363
|
name=openai_message_dict["name"] if "name" in openai_message_dict else None,
|
|
335
364
|
tool_calls=openai_message_dict["tool_calls"] if "tool_calls" in openai_message_dict else None,
|
|
336
365
|
tool_call_id=openai_message_dict["tool_call_id"] if "tool_call_id" in openai_message_dict else None,
|
|
@@ -343,7 +372,7 @@ class Message(BaseMessage):
|
|
|
343
372
|
model=model,
|
|
344
373
|
# standard fields expected in an OpenAI ChatCompletion message object
|
|
345
374
|
role=MessageRole.tool, # NOTE
|
|
346
|
-
content=[TextContent(text=openai_message_dict["content"])],
|
|
375
|
+
content=[TextContent(text=openai_message_dict["content"])] if openai_message_dict["content"] else [],
|
|
347
376
|
name=openai_message_dict["name"] if "name" in openai_message_dict else None,
|
|
348
377
|
tool_calls=openai_message_dict["tool_calls"] if "tool_calls" in openai_message_dict else None,
|
|
349
378
|
tool_call_id=openai_message_dict["tool_call_id"] if "tool_call_id" in openai_message_dict else None,
|
|
@@ -375,7 +404,7 @@ class Message(BaseMessage):
|
|
|
375
404
|
model=model,
|
|
376
405
|
# standard fields expected in an OpenAI ChatCompletion message object
|
|
377
406
|
role=MessageRole(openai_message_dict["role"]),
|
|
378
|
-
content=[TextContent(text=openai_message_dict["content"])],
|
|
407
|
+
content=[TextContent(text=openai_message_dict["content"])] if openai_message_dict["content"] else [],
|
|
379
408
|
name=openai_message_dict["name"] if "name" in openai_message_dict else None,
|
|
380
409
|
tool_calls=tool_calls,
|
|
381
410
|
tool_call_id=None, # NOTE: None, since this field is only non-null for role=='tool'
|
|
@@ -388,7 +417,7 @@ class Message(BaseMessage):
|
|
|
388
417
|
model=model,
|
|
389
418
|
# standard fields expected in an OpenAI ChatCompletion message object
|
|
390
419
|
role=MessageRole(openai_message_dict["role"]),
|
|
391
|
-
content=[TextContent(text=openai_message_dict["content"])],
|
|
420
|
+
content=[TextContent(text=openai_message_dict["content"])] if openai_message_dict["content"] else [],
|
|
392
421
|
name=openai_message_dict["name"] if "name" in openai_message_dict else None,
|
|
393
422
|
tool_calls=tool_calls,
|
|
394
423
|
tool_call_id=None, # NOTE: None, since this field is only non-null for role=='tool'
|
|
@@ -420,7 +449,7 @@ class Message(BaseMessage):
|
|
|
420
449
|
model=model,
|
|
421
450
|
# standard fields expected in an OpenAI ChatCompletion message object
|
|
422
451
|
role=MessageRole(openai_message_dict["role"]),
|
|
423
|
-
content=[TextContent(text=openai_message_dict["content"])],
|
|
452
|
+
content=[TextContent(text=openai_message_dict["content"])] if openai_message_dict["content"] else [],
|
|
424
453
|
name=openai_message_dict["name"] if "name" in openai_message_dict else None,
|
|
425
454
|
tool_calls=tool_calls,
|
|
426
455
|
tool_call_id=openai_message_dict["tool_call_id"] if "tool_call_id" in openai_message_dict else None,
|
|
@@ -433,7 +462,7 @@ class Message(BaseMessage):
|
|
|
433
462
|
model=model,
|
|
434
463
|
# standard fields expected in an OpenAI ChatCompletion message object
|
|
435
464
|
role=MessageRole(openai_message_dict["role"]),
|
|
436
|
-
content=[TextContent(text=openai_message_dict["content"]
|
|
465
|
+
content=[TextContent(text=openai_message_dict["content"])] if openai_message_dict["content"] else [],
|
|
437
466
|
name=openai_message_dict["name"] if "name" in openai_message_dict else None,
|
|
438
467
|
tool_calls=tool_calls,
|
|
439
468
|
tool_call_id=openai_message_dict["tool_call_id"] if "tool_call_id" in openai_message_dict else None,
|
|
@@ -116,7 +116,7 @@ class MessageDelta(BaseModel):
|
|
|
116
116
|
|
|
117
117
|
content: Optional[str] = None
|
|
118
118
|
tool_calls: Optional[List[ToolCallDelta]] = None
|
|
119
|
-
|
|
119
|
+
role: Optional[str] = None
|
|
120
120
|
function_call: Optional[FunctionCallDelta] = None # Deprecated
|
|
121
121
|
|
|
122
122
|
|
|
@@ -132,7 +132,7 @@ class ChatCompletionChunkResponse(BaseModel):
|
|
|
132
132
|
|
|
133
133
|
id: str
|
|
134
134
|
choices: List[ChunkChoice]
|
|
135
|
-
created: datetime.datetime
|
|
135
|
+
created: Union[datetime.datetime, str]
|
|
136
136
|
model: str
|
|
137
137
|
# system_fingerprint: str # docs say this is mandatory, but in reality API returns None
|
|
138
138
|
system_fingerprint: Optional[str] = None
|
letta/schemas/run.py
CHANGED
|
@@ -3,8 +3,7 @@ from typing import Optional
|
|
|
3
3
|
from pydantic import Field
|
|
4
4
|
|
|
5
5
|
from letta.orm.enums import JobType
|
|
6
|
-
from letta.schemas.job import Job, JobBase
|
|
7
|
-
from letta.schemas.letta_request import LettaRequestConfig
|
|
6
|
+
from letta.schemas.job import Job, JobBase, LettaRequestConfig
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class RunBase(JobBase):
|
letta/server/rest_api/app.py
CHANGED
|
@@ -12,7 +12,7 @@ from starlette.middleware.base import BaseHTTPMiddleware
|
|
|
12
12
|
from starlette.middleware.cors import CORSMiddleware
|
|
13
13
|
|
|
14
14
|
from letta.__init__ import __version__
|
|
15
|
-
from letta.constants import ADMIN_PREFIX, API_PREFIX
|
|
15
|
+
from letta.constants import ADMIN_PREFIX, API_PREFIX, OPENAI_API_PREFIX
|
|
16
16
|
from letta.errors import BedrockPermissionError, LettaAgentNotFoundError, LettaUserNotFoundError
|
|
17
17
|
from letta.log import get_logger
|
|
18
18
|
from letta.orm.errors import DatabaseTimeoutError, ForeignKeyConstraintViolationError, NoResultFound, UniqueConstraintViolationError
|
|
@@ -22,6 +22,7 @@ from letta.server.constants import REST_DEFAULT_PORT
|
|
|
22
22
|
# NOTE(charles): these are extra routes that are not part of v1 but we still need to mount to pass tests
|
|
23
23
|
from letta.server.rest_api.auth.index import setup_auth_router # TODO: probably remove right?
|
|
24
24
|
from letta.server.rest_api.interface import StreamingServerInterface
|
|
25
|
+
from letta.server.rest_api.routers.openai.chat_completions.chat_completions import router as openai_chat_completions_router
|
|
25
26
|
|
|
26
27
|
# from letta.orm.utilities import get_db_session # TODO(ethan) reenable once we merge ORM
|
|
27
28
|
from letta.server.rest_api.routers.v1 import ROUTERS as v1_routes
|
|
@@ -241,6 +242,9 @@ def create_application() -> "FastAPI":
|
|
|
241
242
|
app.include_router(users_router, prefix=ADMIN_PREFIX)
|
|
242
243
|
app.include_router(organizations_router, prefix=ADMIN_PREFIX)
|
|
243
244
|
|
|
245
|
+
# openai
|
|
246
|
+
app.include_router(openai_chat_completions_router, prefix=OPENAI_API_PREFIX)
|
|
247
|
+
|
|
244
248
|
# /api/auth endpoints
|
|
245
249
|
app.include_router(setup_auth_router(server, interface, password), prefix=API_PREFIX)
|
|
246
250
|
|