letta-nightly 0.6.45.dev20250329104117__py3-none-any.whl → 0.6.46.dev20250330050944__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 +25 -8
- letta/agents/base_agent.py +6 -5
- letta/agents/letta_agent.py +323 -0
- letta/agents/voice_agent.py +4 -3
- letta/client/client.py +2 -0
- letta/dynamic_multi_agent.py +5 -5
- letta/errors.py +20 -0
- letta/helpers/tool_execution_helper.py +1 -1
- letta/helpers/tool_rule_solver.py +1 -1
- letta/llm_api/anthropic.py +2 -0
- letta/llm_api/anthropic_client.py +153 -167
- letta/llm_api/google_ai_client.py +112 -29
- letta/llm_api/llm_api_tools.py +5 -0
- letta/llm_api/llm_client.py +6 -7
- letta/llm_api/llm_client_base.py +38 -17
- letta/llm_api/openai.py +2 -0
- letta/orm/group.py +2 -5
- letta/round_robin_multi_agent.py +18 -7
- letta/schemas/group.py +6 -0
- letta/schemas/message.py +23 -14
- letta/schemas/openai/chat_completion_request.py +6 -1
- letta/schemas/providers.py +3 -3
- letta/serialize_schemas/marshmallow_agent.py +34 -10
- letta/serialize_schemas/pydantic_agent_schema.py +23 -3
- letta/server/rest_api/app.py +9 -0
- letta/server/rest_api/interface.py +25 -2
- letta/server/rest_api/optimistic_json_parser.py +1 -1
- letta/server/rest_api/routers/v1/agents.py +57 -23
- letta/server/rest_api/routers/v1/groups.py +72 -49
- letta/server/rest_api/routers/v1/sources.py +1 -0
- letta/server/rest_api/utils.py +0 -1
- letta/server/server.py +73 -80
- letta/server/startup.sh +1 -1
- letta/services/agent_manager.py +7 -0
- letta/services/group_manager.py +87 -29
- letta/services/message_manager.py +5 -0
- letta/services/tool_executor/async_tool_execution_sandbox.py +397 -0
- letta/services/tool_executor/tool_execution_manager.py +27 -0
- letta/services/{tool_execution_sandbox.py → tool_executor/tool_execution_sandbox.py} +40 -12
- letta/services/tool_executor/tool_executor.py +23 -6
- letta/settings.py +17 -1
- letta/supervisor_multi_agent.py +3 -1
- {letta_nightly-0.6.45.dev20250329104117.dist-info → letta_nightly-0.6.46.dev20250330050944.dist-info}/METADATA +1 -1
- {letta_nightly-0.6.45.dev20250329104117.dist-info → letta_nightly-0.6.46.dev20250330050944.dist-info}/RECORD +48 -46
- {letta_nightly-0.6.45.dev20250329104117.dist-info → letta_nightly-0.6.46.dev20250330050944.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.45.dev20250329104117.dist-info → letta_nightly-0.6.46.dev20250330050944.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.45.dev20250329104117.dist-info → letta_nightly-0.6.46.dev20250330050944.dist-info}/entry_points.txt +0 -0
letta/server/server.py
CHANGED
|
@@ -90,7 +90,7 @@ from letta.services.provider_manager import ProviderManager
|
|
|
90
90
|
from letta.services.sandbox_config_manager import SandboxConfigManager
|
|
91
91
|
from letta.services.source_manager import SourceManager
|
|
92
92
|
from letta.services.step_manager import StepManager
|
|
93
|
-
from letta.services.tool_execution_sandbox import ToolExecutionSandbox
|
|
93
|
+
from letta.services.tool_executor.tool_execution_sandbox import ToolExecutionSandbox
|
|
94
94
|
from letta.services.tool_manager import ToolManager
|
|
95
95
|
from letta.services.user_manager import UserManager
|
|
96
96
|
from letta.settings import model_settings, settings, tool_settings
|
|
@@ -367,6 +367,9 @@ class SyncServer(Server):
|
|
|
367
367
|
def load_multi_agent(
|
|
368
368
|
self, group: Group, actor: User, interface: Union[AgentInterface, None] = None, agent_state: Optional[AgentState] = None
|
|
369
369
|
) -> Agent:
|
|
370
|
+
if len(group.agent_ids) == 0:
|
|
371
|
+
raise ValueError("Empty group: group must have at least one agent")
|
|
372
|
+
|
|
370
373
|
match group.manager_type:
|
|
371
374
|
case ManagerType.round_robin:
|
|
372
375
|
agent_state = agent_state or self.agent_manager.get_agent_by_id(agent_id=group.agent_ids[0], actor=actor)
|
|
@@ -862,6 +865,7 @@ class SyncServer(Server):
|
|
|
862
865
|
after: Optional[str] = None,
|
|
863
866
|
before: Optional[str] = None,
|
|
864
867
|
limit: Optional[int] = 100,
|
|
868
|
+
group_id: Optional[str] = None,
|
|
865
869
|
reverse: Optional[bool] = False,
|
|
866
870
|
return_message_object: bool = True,
|
|
867
871
|
use_assistant_message: bool = True,
|
|
@@ -879,6 +883,7 @@ class SyncServer(Server):
|
|
|
879
883
|
before=before,
|
|
880
884
|
limit=limit,
|
|
881
885
|
ascending=not reverse,
|
|
886
|
+
group_id=group_id,
|
|
882
887
|
)
|
|
883
888
|
|
|
884
889
|
if not return_message_object:
|
|
@@ -1591,88 +1596,76 @@ class SyncServer(Server):
|
|
|
1591
1596
|
) -> Union[StreamingResponse, LettaResponse]:
|
|
1592
1597
|
include_final_message = True
|
|
1593
1598
|
if not stream_steps and stream_tokens:
|
|
1594
|
-
raise
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
supports_token_streaming
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
warnings.warn(
|
|
1607
|
-
f"Token streaming is only supported for models with type {' or '.join(supports_token_streaming)} in the model_endpoint: agent has endpoint type {llm_config.model_endpoint_type} and {llm_config.model_endpoint}. Setting stream_tokens to False."
|
|
1608
|
-
)
|
|
1609
|
-
stream_tokens = False
|
|
1610
|
-
|
|
1611
|
-
# Create a new interface per request
|
|
1612
|
-
letta_multi_agent.interface = StreamingServerInterface(
|
|
1613
|
-
use_assistant_message=use_assistant_message,
|
|
1614
|
-
assistant_message_tool_name=assistant_message_tool_name,
|
|
1615
|
-
assistant_message_tool_kwarg=assistant_message_tool_kwarg,
|
|
1616
|
-
inner_thoughts_in_kwargs=(
|
|
1617
|
-
llm_config.put_inner_thoughts_in_kwargs if llm_config.put_inner_thoughts_in_kwargs is not None else False
|
|
1618
|
-
),
|
|
1599
|
+
raise ValueError("stream_steps must be 'true' if stream_tokens is 'true'")
|
|
1600
|
+
|
|
1601
|
+
group = self.group_manager.retrieve_group(group_id=group_id, actor=actor)
|
|
1602
|
+
letta_multi_agent = self.load_multi_agent(group=group, actor=actor)
|
|
1603
|
+
|
|
1604
|
+
llm_config = letta_multi_agent.agent_state.llm_config
|
|
1605
|
+
supports_token_streaming = ["openai", "anthropic", "deepseek"]
|
|
1606
|
+
if stream_tokens and (
|
|
1607
|
+
llm_config.model_endpoint_type not in supports_token_streaming or "inference.memgpt.ai" in llm_config.model_endpoint
|
|
1608
|
+
):
|
|
1609
|
+
warnings.warn(
|
|
1610
|
+
f"Token streaming is only supported for models with type {' or '.join(supports_token_streaming)} in the model_endpoint: agent has endpoint type {llm_config.model_endpoint_type} and {llm_config.model_endpoint}. Setting stream_tokens to False."
|
|
1619
1611
|
)
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1612
|
+
stream_tokens = False
|
|
1613
|
+
|
|
1614
|
+
# Create a new interface per request
|
|
1615
|
+
letta_multi_agent.interface = StreamingServerInterface(
|
|
1616
|
+
use_assistant_message=use_assistant_message,
|
|
1617
|
+
assistant_message_tool_name=assistant_message_tool_name,
|
|
1618
|
+
assistant_message_tool_kwarg=assistant_message_tool_kwarg,
|
|
1619
|
+
inner_thoughts_in_kwargs=(
|
|
1620
|
+
llm_config.put_inner_thoughts_in_kwargs if llm_config.put_inner_thoughts_in_kwargs is not None else False
|
|
1621
|
+
),
|
|
1622
|
+
)
|
|
1623
|
+
streaming_interface = letta_multi_agent.interface
|
|
1624
|
+
if not isinstance(streaming_interface, StreamingServerInterface):
|
|
1625
|
+
raise ValueError(f"Agent has wrong type of interface: {type(streaming_interface)}")
|
|
1626
|
+
streaming_interface.streaming_mode = stream_tokens
|
|
1627
|
+
streaming_interface.streaming_chat_completion_mode = chat_completion_mode
|
|
1628
|
+
if metadata and hasattr(streaming_interface, "metadata"):
|
|
1629
|
+
streaming_interface.metadata = metadata
|
|
1630
|
+
|
|
1631
|
+
streaming_interface.stream_start()
|
|
1632
|
+
task = asyncio.create_task(
|
|
1633
|
+
asyncio.to_thread(
|
|
1634
|
+
letta_multi_agent.step,
|
|
1635
|
+
messages=messages,
|
|
1636
|
+
chaining=self.chaining,
|
|
1637
|
+
max_chaining_steps=self.max_chaining_steps,
|
|
1636
1638
|
)
|
|
1639
|
+
)
|
|
1637
1640
|
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
else:
|
|
1650
|
-
# buffer the stream, then return the list
|
|
1651
|
-
generated_stream = []
|
|
1652
|
-
async for message in streaming_interface.get_generator():
|
|
1653
|
-
assert (
|
|
1654
|
-
isinstance(message, LettaMessage)
|
|
1655
|
-
or isinstance(message, LegacyLettaMessage)
|
|
1656
|
-
or isinstance(message, MessageStreamStatus)
|
|
1657
|
-
), type(message)
|
|
1658
|
-
generated_stream.append(message)
|
|
1659
|
-
if message == MessageStreamStatus.done:
|
|
1660
|
-
break
|
|
1641
|
+
if stream_steps:
|
|
1642
|
+
# return a stream
|
|
1643
|
+
return StreamingResponse(
|
|
1644
|
+
sse_async_generator(
|
|
1645
|
+
streaming_interface.get_generator(),
|
|
1646
|
+
usage_task=task,
|
|
1647
|
+
finish_message=include_final_message,
|
|
1648
|
+
),
|
|
1649
|
+
media_type="text/event-stream",
|
|
1650
|
+
)
|
|
1661
1651
|
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1652
|
+
else:
|
|
1653
|
+
# buffer the stream, then return the list
|
|
1654
|
+
generated_stream = []
|
|
1655
|
+
async for message in streaming_interface.get_generator():
|
|
1656
|
+
assert (
|
|
1657
|
+
isinstance(message, LettaMessage) or isinstance(message, LegacyLettaMessage) or isinstance(message, MessageStreamStatus)
|
|
1658
|
+
), type(message)
|
|
1659
|
+
generated_stream.append(message)
|
|
1660
|
+
if message == MessageStreamStatus.done:
|
|
1661
|
+
break
|
|
1665
1662
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
# TODO: eventually update the interface to use `Message` and `MessageChunk` (new) inside the deque instead
|
|
1670
|
-
return LettaResponse(messages=filtered_stream, usage=usage)
|
|
1671
|
-
except HTTPException:
|
|
1672
|
-
raise
|
|
1673
|
-
except Exception as e:
|
|
1674
|
-
print(e)
|
|
1675
|
-
import traceback
|
|
1663
|
+
# Get rid of the stream status messages
|
|
1664
|
+
filtered_stream = [d for d in generated_stream if not isinstance(d, MessageStreamStatus)]
|
|
1665
|
+
usage = await task
|
|
1676
1666
|
|
|
1677
|
-
|
|
1678
|
-
|
|
1667
|
+
# By default the stream will be messages of type LettaMessage or LettaLegacyMessage
|
|
1668
|
+
# If we want to convert these to Message, we can use the attached IDs
|
|
1669
|
+
# NOTE: we will need to de-duplicate the Messsage IDs though (since Assistant->Inner+Func_Call)
|
|
1670
|
+
# TODO: eventually update the interface to use `Message` and `MessageChunk` (new) inside the deque instead
|
|
1671
|
+
return LettaResponse(messages=filtered_stream, usage=usage)
|
letta/server/startup.sh
CHANGED
letta/services/agent_manager.py
CHANGED
|
@@ -773,6 +773,13 @@ class AgentManager:
|
|
|
773
773
|
|
|
774
774
|
return agent_state
|
|
775
775
|
|
|
776
|
+
@enforce_types
|
|
777
|
+
def refresh_memory(self, agent_state: PydanticAgentState, actor: PydanticUser) -> PydanticAgentState:
|
|
778
|
+
agent_state.memory.blocks = self.block_manager.get_all_blocks_by_ids(
|
|
779
|
+
block_ids=[b.id for b in agent_state.memory.blocks], actor=actor
|
|
780
|
+
)
|
|
781
|
+
return agent_state
|
|
782
|
+
|
|
776
783
|
# ======================================================================================================================
|
|
777
784
|
# Source Management
|
|
778
785
|
# ======================================================================================================================
|
letta/services/group_manager.py
CHANGED
|
@@ -7,7 +7,9 @@ from letta.orm.errors import NoResultFound
|
|
|
7
7
|
from letta.orm.group import Group as GroupModel
|
|
8
8
|
from letta.orm.message import Message as MessageModel
|
|
9
9
|
from letta.schemas.group import Group as PydanticGroup
|
|
10
|
-
from letta.schemas.group import GroupCreate, ManagerType
|
|
10
|
+
from letta.schemas.group import GroupCreate, GroupUpdate, ManagerType
|
|
11
|
+
from letta.schemas.letta_message import LettaMessage
|
|
12
|
+
from letta.schemas.message import Message as PydanticMessage
|
|
11
13
|
from letta.schemas.user import User as PydanticUser
|
|
12
14
|
from letta.utils import enforce_types
|
|
13
15
|
|
|
@@ -22,12 +24,12 @@ class GroupManager:
|
|
|
22
24
|
@enforce_types
|
|
23
25
|
def list_groups(
|
|
24
26
|
self,
|
|
27
|
+
actor: PydanticUser,
|
|
25
28
|
project_id: Optional[str] = None,
|
|
26
29
|
manager_type: Optional[ManagerType] = None,
|
|
27
30
|
before: Optional[str] = None,
|
|
28
31
|
after: Optional[str] = None,
|
|
29
32
|
limit: Optional[int] = 50,
|
|
30
|
-
actor: PydanticUser = None,
|
|
31
33
|
) -> list[PydanticGroup]:
|
|
32
34
|
with self.session_maker() as session:
|
|
33
35
|
filters = {"organization_id": actor.organization_id}
|
|
@@ -56,26 +58,65 @@ class GroupManager:
|
|
|
56
58
|
new_group = GroupModel()
|
|
57
59
|
new_group.organization_id = actor.organization_id
|
|
58
60
|
new_group.description = group.description
|
|
61
|
+
|
|
62
|
+
match group.manager_config.manager_type:
|
|
63
|
+
case ManagerType.round_robin:
|
|
64
|
+
new_group.manager_type = ManagerType.round_robin
|
|
65
|
+
new_group.max_turns = group.manager_config.max_turns
|
|
66
|
+
case ManagerType.dynamic:
|
|
67
|
+
new_group.manager_type = ManagerType.dynamic
|
|
68
|
+
new_group.manager_agent_id = group.manager_config.manager_agent_id
|
|
69
|
+
new_group.max_turns = group.manager_config.max_turns
|
|
70
|
+
new_group.termination_token = group.manager_config.termination_token
|
|
71
|
+
case ManagerType.supervisor:
|
|
72
|
+
new_group.manager_type = ManagerType.supervisor
|
|
73
|
+
new_group.manager_agent_id = group.manager_config.manager_agent_id
|
|
74
|
+
case _:
|
|
75
|
+
raise ValueError(f"Unsupported manager type: {group.manager_config.manager_type}")
|
|
76
|
+
|
|
59
77
|
self._process_agent_relationship(session=session, group=new_group, agent_ids=group.agent_ids, allow_partial=False)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
|
|
79
|
+
new_group.create(session, actor=actor)
|
|
80
|
+
return new_group.to_pydantic()
|
|
81
|
+
|
|
82
|
+
@enforce_types
|
|
83
|
+
def modify_group(self, group_id: str, group_update: GroupUpdate, actor: PydanticUser) -> PydanticGroup:
|
|
84
|
+
with self.session_maker() as session:
|
|
85
|
+
group = GroupModel.read(db_session=session, identifier=group_id, actor=actor)
|
|
86
|
+
|
|
87
|
+
max_turns = None
|
|
88
|
+
termination_token = None
|
|
89
|
+
manager_agent_id = None
|
|
90
|
+
if group_update.manager_config:
|
|
91
|
+
if group_update.manager_config.manager_type != group.manager_type:
|
|
92
|
+
raise ValueError(f"Cannot change group pattern after creation")
|
|
93
|
+
match group_update.manager_config.manager_type:
|
|
64
94
|
case ManagerType.round_robin:
|
|
65
|
-
|
|
66
|
-
new_group.max_turns = group.manager_config.max_turns
|
|
95
|
+
max_turns = group_update.manager_config.max_turns
|
|
67
96
|
case ManagerType.dynamic:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
new_group.termination_token = group.manager_config.termination_token
|
|
97
|
+
manager_agent_id = group_update.manager_config.manager_agent_id
|
|
98
|
+
max_turns = group_update.manager_config.max_turns
|
|
99
|
+
termination_token = group_update.manager_config.termination_token
|
|
72
100
|
case ManagerType.supervisor:
|
|
73
|
-
|
|
74
|
-
new_group.manager_agent_id = group.manager_config.manager_agent_id
|
|
101
|
+
manager_agent_id = group_update.manager_config.manager_agent_id
|
|
75
102
|
case _:
|
|
76
|
-
raise ValueError(f"Unsupported manager type: {
|
|
77
|
-
|
|
78
|
-
|
|
103
|
+
raise ValueError(f"Unsupported manager type: {group_update.manager_config.manager_type}")
|
|
104
|
+
|
|
105
|
+
if max_turns:
|
|
106
|
+
group.max_turns = max_turns
|
|
107
|
+
if termination_token:
|
|
108
|
+
group.termination_token = termination_token
|
|
109
|
+
if manager_agent_id:
|
|
110
|
+
group.manager_agent_id = manager_agent_id
|
|
111
|
+
if group_update.description:
|
|
112
|
+
group.description = group_update.description
|
|
113
|
+
if group_update.agent_ids:
|
|
114
|
+
self._process_agent_relationship(
|
|
115
|
+
session=session, group=group, agent_ids=group_update.agent_ids, allow_partial=False, replace=True
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
group.update(session, actor=actor)
|
|
119
|
+
return group.to_pydantic()
|
|
79
120
|
|
|
80
121
|
@enforce_types
|
|
81
122
|
def delete_group(self, group_id: str, actor: PydanticUser) -> None:
|
|
@@ -87,23 +128,19 @@ class GroupManager:
|
|
|
87
128
|
@enforce_types
|
|
88
129
|
def list_group_messages(
|
|
89
130
|
self,
|
|
131
|
+
actor: PydanticUser,
|
|
90
132
|
group_id: Optional[str] = None,
|
|
91
133
|
before: Optional[str] = None,
|
|
92
134
|
after: Optional[str] = None,
|
|
93
135
|
limit: Optional[int] = 50,
|
|
94
|
-
actor: PydanticUser = None,
|
|
95
136
|
use_assistant_message: bool = True,
|
|
96
137
|
assistant_message_tool_name: str = "send_message",
|
|
97
138
|
assistant_message_tool_kwarg: str = "message",
|
|
98
|
-
) -> list[
|
|
139
|
+
) -> list[LettaMessage]:
|
|
99
140
|
with self.session_maker() as session:
|
|
100
|
-
group = GroupModel.read(db_session=session, identifier=group_id, actor=actor)
|
|
101
|
-
agent_id = group.manager_agent_id if group.manager_agent_id else group.agent_ids[0]
|
|
102
|
-
|
|
103
141
|
filters = {
|
|
104
142
|
"organization_id": actor.organization_id,
|
|
105
143
|
"group_id": group_id,
|
|
106
|
-
"agent_id": agent_id,
|
|
107
144
|
}
|
|
108
145
|
messages = MessageModel.list(
|
|
109
146
|
db_session=session,
|
|
@@ -114,21 +151,39 @@ class GroupManager:
|
|
|
114
151
|
)
|
|
115
152
|
|
|
116
153
|
messages = PydanticMessage.to_letta_messages_from_list(
|
|
117
|
-
messages=messages,
|
|
154
|
+
messages=[msg.to_pydantic() for msg in messages],
|
|
118
155
|
use_assistant_message=use_assistant_message,
|
|
119
156
|
assistant_message_tool_name=assistant_message_tool_name,
|
|
120
157
|
assistant_message_tool_kwarg=assistant_message_tool_kwarg,
|
|
121
158
|
)
|
|
122
159
|
|
|
160
|
+
# TODO: filter messages to return a clean conversation history
|
|
161
|
+
|
|
123
162
|
return messages
|
|
124
163
|
|
|
164
|
+
@enforce_types
|
|
165
|
+
def reset_messages(self, group_id: str, actor: PydanticUser) -> None:
|
|
166
|
+
with self.session_maker() as session:
|
|
167
|
+
# Ensure group is loadable by user
|
|
168
|
+
group = GroupModel.read(db_session=session, identifier=group_id, actor=actor)
|
|
169
|
+
|
|
170
|
+
# Delete all messages in the group
|
|
171
|
+
session.query(MessageModel).filter(
|
|
172
|
+
MessageModel.organization_id == actor.organization_id, MessageModel.group_id == group_id
|
|
173
|
+
).delete(synchronize_session=False)
|
|
174
|
+
|
|
175
|
+
session.commit()
|
|
176
|
+
|
|
125
177
|
def _process_agent_relationship(self, session: Session, group: GroupModel, agent_ids: List[str], allow_partial=False, replace=True):
|
|
126
|
-
current_relationship = getattr(group, "agents", [])
|
|
127
178
|
if not agent_ids:
|
|
128
179
|
if replace:
|
|
129
180
|
setattr(group, "agents", [])
|
|
181
|
+
setattr(group, "agent_ids", [])
|
|
130
182
|
return
|
|
131
183
|
|
|
184
|
+
if group.manager_type == ManagerType.dynamic and len(agent_ids) != len(set(agent_ids)):
|
|
185
|
+
raise ValueError("Duplicate agent ids found in list")
|
|
186
|
+
|
|
132
187
|
# Retrieve models for the provided IDs
|
|
133
188
|
found_items = session.query(AgentModel).filter(AgentModel.id.in_(agent_ids)).all()
|
|
134
189
|
|
|
@@ -137,11 +192,14 @@ class GroupManager:
|
|
|
137
192
|
missing = set(agent_ids) - {item.id for item in found_items}
|
|
138
193
|
raise NoResultFound(f"Items not found in agents: {missing}")
|
|
139
194
|
|
|
195
|
+
if group.manager_type == ManagerType.dynamic:
|
|
196
|
+
names = [item.name for item in found_items]
|
|
197
|
+
if len(names) != len(set(names)):
|
|
198
|
+
raise ValueError("Duplicate agent names found in the provided agent IDs.")
|
|
199
|
+
|
|
140
200
|
if replace:
|
|
141
201
|
# Replace the relationship
|
|
142
202
|
setattr(group, "agents", found_items)
|
|
203
|
+
setattr(group, "agent_ids", agent_ids)
|
|
143
204
|
else:
|
|
144
|
-
|
|
145
|
-
current_ids = {item.id for item in current_relationship}
|
|
146
|
-
new_items = [item for item in found_items if item.id not in current_ids]
|
|
147
|
-
current_relationship.extend(new_items)
|
|
205
|
+
raise ValueError("Extend relationship is not supported for groups.")
|
|
@@ -264,6 +264,7 @@ class MessageManager:
|
|
|
264
264
|
roles: Optional[Sequence[MessageRole]] = None,
|
|
265
265
|
limit: Optional[int] = 50,
|
|
266
266
|
ascending: bool = True,
|
|
267
|
+
group_id: Optional[str] = None,
|
|
267
268
|
) -> List[PydanticMessage]:
|
|
268
269
|
"""
|
|
269
270
|
Most performant query to list messages for an agent by directly querying the Message table.
|
|
@@ -296,6 +297,10 @@ class MessageManager:
|
|
|
296
297
|
# Build a query that directly filters the Message table by agent_id.
|
|
297
298
|
query = session.query(MessageModel).filter(MessageModel.agent_id == agent_id)
|
|
298
299
|
|
|
300
|
+
# If group_id is provided, filter messages by group_id.
|
|
301
|
+
if group_id:
|
|
302
|
+
query = query.filter(MessageModel.group_id == group_id)
|
|
303
|
+
|
|
299
304
|
# If query_text is provided, filter messages using subquery.
|
|
300
305
|
if query_text:
|
|
301
306
|
content_element = func.json_array_elements(MessageModel.content).alias("content_element")
|