lionagi 0.6.0__py3-none-any.whl → 0.7.0__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.
- lionagi/__init__.py +2 -0
- lionagi/libs/token_transform/__init__.py +0 -0
- lionagi/libs/token_transform/llmlingua.py +1 -0
- lionagi/libs/token_transform/perplexity.py +439 -0
- lionagi/libs/token_transform/synthlang.py +409 -0
- lionagi/operations/ReAct/ReAct.py +126 -0
- lionagi/operations/ReAct/utils.py +28 -0
- lionagi/operations/__init__.py +1 -9
- lionagi/operations/_act/act.py +73 -0
- lionagi/operations/chat/__init__.py +3 -0
- lionagi/operations/chat/chat.py +173 -0
- lionagi/operations/communicate/__init__.py +0 -0
- lionagi/operations/communicate/communicate.py +167 -0
- lionagi/operations/instruct/__init__.py +3 -0
- lionagi/operations/instruct/instruct.py +29 -0
- lionagi/operations/interpret/__init__.py +3 -0
- lionagi/operations/interpret/interpret.py +40 -0
- lionagi/operations/operate/__init__.py +3 -0
- lionagi/operations/operate/operate.py +189 -0
- lionagi/operations/parse/__init__.py +3 -0
- lionagi/operations/parse/parse.py +125 -0
- lionagi/operations/plan/plan.py +3 -3
- lionagi/operations/select/__init__.py +0 -4
- lionagi/operations/select/select.py +11 -30
- lionagi/operations/select/utils.py +13 -2
- lionagi/operations/translate/__init__.py +0 -0
- lionagi/operations/translate/translate.py +47 -0
- lionagi/operations/types.py +25 -3
- lionagi/operatives/action/function_calling.py +1 -1
- lionagi/operatives/action/manager.py +22 -26
- lionagi/operatives/action/tool.py +1 -1
- lionagi/operatives/strategies/__init__.py +3 -0
- lionagi/{operations → operatives}/strategies/params.py +18 -2
- lionagi/protocols/adapters/__init__.py +0 -0
- lionagi/protocols/adapters/adapter.py +95 -0
- lionagi/protocols/adapters/json_adapter.py +101 -0
- lionagi/protocols/adapters/pandas_/__init__.py +0 -0
- lionagi/protocols/adapters/pandas_/csv_adapter.py +50 -0
- lionagi/protocols/adapters/pandas_/excel_adapter.py +52 -0
- lionagi/protocols/adapters/pandas_/pd_dataframe_adapter.py +31 -0
- lionagi/protocols/adapters/pandas_/pd_series_adapter.py +17 -0
- lionagi/protocols/adapters/types.py +18 -0
- lionagi/protocols/generic/pile.py +22 -1
- lionagi/protocols/graph/node.py +17 -1
- lionagi/protocols/types.py +3 -3
- lionagi/service/__init__.py +1 -14
- lionagi/service/endpoints/base.py +1 -1
- lionagi/service/endpoints/rate_limited_processor.py +2 -1
- lionagi/service/manager.py +1 -1
- lionagi/service/types.py +18 -0
- lionagi/session/branch.py +1098 -929
- lionagi/version.py +1 -1
- {lionagi-0.6.0.dist-info → lionagi-0.7.0.dist-info}/METADATA +4 -4
- {lionagi-0.6.0.dist-info → lionagi-0.7.0.dist-info}/RECORD +66 -38
- lionagi/libs/compress/models.py +0 -66
- lionagi/libs/compress/utils.py +0 -69
- lionagi/operations/select/prompt.py +0 -5
- lionagi/protocols/_adapter.py +0 -224
- /lionagi/{libs/compress → operations/ReAct}/__init__.py +0 -0
- /lionagi/operations/{strategies → _act}/__init__.py +0 -0
- /lionagi/{operations → operatives}/strategies/base.py +0 -0
- /lionagi/{operations → operatives}/strategies/concurrent.py +0 -0
- /lionagi/{operations → operatives}/strategies/concurrent_chunk.py +0 -0
- /lionagi/{operations → operatives}/strategies/concurrent_sequential_chunk.py +0 -0
- /lionagi/{operations → operatives}/strategies/sequential.py +0 -0
- /lionagi/{operations → operatives}/strategies/sequential_chunk.py +0 -0
- /lionagi/{operations → operatives}/strategies/sequential_concurrent_chunk.py +0 -0
- /lionagi/{operations → operatives}/strategies/utils.py +0 -0
- {lionagi-0.6.0.dist-info → lionagi-0.7.0.dist-info}/WHEEL +0 -0
- {lionagi-0.6.0.dist-info → lionagi-0.7.0.dist-info}/licenses/LICENSE +0 -0
lionagi/session/branch.py
CHANGED
@@ -2,24 +2,20 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
-
import
|
6
|
-
from typing import Any, Literal
|
5
|
+
from enum import Enum
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal
|
7
7
|
|
8
8
|
import pandas as pd
|
9
9
|
from jinja2 import Template
|
10
10
|
from pydantic import BaseModel, Field, JsonValue, PrivateAttr
|
11
11
|
|
12
|
-
from lionagi.libs.validate.fuzzy_validate_mapping import fuzzy_validate_mapping
|
13
|
-
from lionagi.operatives.models.field_model import FieldModel
|
14
|
-
from lionagi.operatives.models.model_params import ModelParams
|
15
12
|
from lionagi.operatives.types import (
|
16
13
|
ActionManager,
|
17
|
-
|
18
|
-
FunctionCalling,
|
14
|
+
FieldModel,
|
19
15
|
FuncTool,
|
20
16
|
Instruct,
|
17
|
+
ModelParams,
|
21
18
|
Operative,
|
22
|
-
Step,
|
23
19
|
Tool,
|
24
20
|
ToolRef,
|
25
21
|
)
|
@@ -38,7 +34,6 @@ from lionagi.protocols.types import (
|
|
38
34
|
LogManagerConfig,
|
39
35
|
Mail,
|
40
36
|
Mailbox,
|
41
|
-
MessageFlag,
|
42
37
|
MessageManager,
|
43
38
|
MessageRole,
|
44
39
|
Package,
|
@@ -50,51 +45,67 @@ from lionagi.protocols.types import (
|
|
50
45
|
SenderRecipient,
|
51
46
|
System,
|
52
47
|
)
|
53
|
-
from lionagi.service import iModel, iModelManager
|
48
|
+
from lionagi.service.types import iModel, iModelManager
|
54
49
|
from lionagi.settings import Settings
|
55
|
-
from lionagi.utils import
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
)
|
50
|
+
from lionagi.utils import UNDEFINED, alcall, copy
|
51
|
+
|
52
|
+
if TYPE_CHECKING:
|
53
|
+
# Forward references for type checking (e.g., in operations or extended modules)
|
54
|
+
from lionagi.session.branch import Branch
|
61
55
|
|
62
56
|
__all__ = ("Branch",)
|
63
57
|
|
64
58
|
|
65
59
|
class Branch(Element, Communicatable, Relational):
|
66
|
-
"""
|
60
|
+
"""
|
61
|
+
Manages a conversation 'branch' with messages, tools, and iModels.
|
62
|
+
|
63
|
+
The `Branch` class serves as a high-level interface or orchestrator that:
|
64
|
+
- Handles message management (`MessageManager`).
|
65
|
+
- Registers and invokes tools/actions (`ActionManager`).
|
66
|
+
- Manages model instances (`iModelManager`).
|
67
|
+
- Logs activity (`LogManager`).
|
68
|
+
- Communicates via mailboxes (`Mailbox`).
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
70
|
+
**Key responsibilities**:
|
71
|
+
1. Storing and organizing messages, including system instructions, user instructions, and model responses.
|
72
|
+
2. Handling asynchronous or synchronous execution of LLM calls and tool invocations.
|
73
|
+
3. Providing a consistent interface for “operate,” “chat,” “communicate,” “parse,” etc.
|
72
74
|
|
73
75
|
Attributes:
|
74
76
|
user (SenderRecipient | None):
|
75
|
-
The user or
|
77
|
+
The user or "owner" of this branch (often tied to a session).
|
76
78
|
name (str | None):
|
77
79
|
A human-readable name for this branch.
|
78
80
|
mailbox (Mailbox):
|
79
|
-
A mailbox for sending and receiving `Package`
|
80
|
-
|
81
|
+
A mailbox for sending and receiving `Package` objects to/from other branches.
|
82
|
+
|
83
|
+
Note:
|
84
|
+
Actual implementations for chat, parse, operate, etc., are referenced
|
85
|
+
via lazy loading or modular imports. You typically won't need to
|
86
|
+
subclass `Branch`, but you can instantiate it and call the
|
87
|
+
associated methods for complex orchestrations.
|
81
88
|
"""
|
82
89
|
|
83
90
|
user: SenderRecipient | None = Field(
|
84
91
|
None,
|
85
92
|
description=(
|
86
|
-
"The user or sender of the branch,
|
87
|
-
"an external user identifier.
|
88
|
-
"
|
93
|
+
"The user or sender of the branch, often a session object or "
|
94
|
+
"an external user identifier. Not to be confused with the "
|
95
|
+
"LLM API's user parameter."
|
89
96
|
),
|
90
97
|
)
|
91
98
|
|
92
99
|
name: str | None = Field(
|
93
100
|
None,
|
94
|
-
description="A human
|
101
|
+
description="A human-readable name of the branch (optional).",
|
95
102
|
)
|
96
103
|
|
97
|
-
mailbox: Mailbox = Field(
|
104
|
+
mailbox: Mailbox = Field(
|
105
|
+
default_factory=Mailbox,
|
106
|
+
exclude=True,
|
107
|
+
description="Mailbox for cross-branch or external communication.",
|
108
|
+
)
|
98
109
|
|
99
110
|
_message_manager: MessageManager | None = PrivateAttr(None)
|
100
111
|
_action_manager: ActionManager | None = PrivateAttr(None)
|
@@ -109,52 +120,60 @@ class Branch(Element, Communicatable, Relational):
|
|
109
120
|
messages: Pile[RoledMessage] = None, # message manager kwargs
|
110
121
|
system: System | JsonValue = None,
|
111
122
|
system_sender: SenderRecipient = None,
|
112
|
-
chat_model: iModel = None, #
|
123
|
+
chat_model: iModel = None, # iModelManager kwargs
|
113
124
|
parse_model: iModel = None,
|
114
125
|
imodel: iModel = None, # deprecated, alias of chat_model
|
115
|
-
tools: FuncTool | list[FuncTool] = None, #
|
116
|
-
log_config: LogManagerConfig | dict = None, #
|
126
|
+
tools: FuncTool | list[FuncTool] = None, # ActionManager kwargs
|
127
|
+
log_config: LogManagerConfig | dict = None, # LogManager kwargs
|
117
128
|
system_datetime: bool | str = None,
|
118
129
|
system_template: Template | str = None,
|
119
130
|
system_template_context: dict = None,
|
120
131
|
logs: Pile[Log] = None,
|
121
132
|
**kwargs,
|
122
133
|
):
|
123
|
-
"""
|
134
|
+
"""
|
135
|
+
Initializes a `Branch` with references to managers and an optional mailbox.
|
124
136
|
|
125
137
|
Args:
|
126
138
|
user (SenderRecipient, optional):
|
127
|
-
The user or sender
|
139
|
+
The user or sender context for this branch.
|
128
140
|
name (str | None, optional):
|
129
141
|
A human-readable name for this branch.
|
130
142
|
messages (Pile[RoledMessage], optional):
|
131
|
-
Initial messages
|
143
|
+
Initial messages for seeding the MessageManager.
|
132
144
|
system (System | JsonValue, optional):
|
133
|
-
|
145
|
+
Optional system-level configuration or message for the LLM.
|
134
146
|
system_sender (SenderRecipient, optional):
|
135
|
-
Sender to
|
147
|
+
Sender to attribute to the system message if it is added.
|
136
148
|
chat_model (iModel, optional):
|
137
|
-
The chat
|
138
|
-
|
149
|
+
The primary "chat" iModel for conversation. If not provided,
|
150
|
+
a default from `Settings.iModel.CHAT` is used.
|
139
151
|
parse_model (iModel, optional):
|
140
|
-
The parse
|
152
|
+
The "parse" iModel for structured data parsing.
|
153
|
+
Defaults to `Settings.iModel.PARSE`.
|
141
154
|
imodel (iModel, optional):
|
142
|
-
Deprecated. Alias for chat_model
|
155
|
+
Deprecated. Alias for `chat_model`.
|
143
156
|
tools (FuncTool | list[FuncTool], optional):
|
144
|
-
Tools for the ActionManager.
|
157
|
+
Tools or a list of tools for the ActionManager.
|
145
158
|
log_config (LogManagerConfig | dict, optional):
|
146
|
-
Configuration for the LogManager.
|
159
|
+
Configuration dict or object for the LogManager.
|
147
160
|
system_datetime (bool | str, optional):
|
148
|
-
Whether to include timestamps in system messages
|
161
|
+
Whether to include timestamps in system messages (True/False)
|
162
|
+
or a string format for datetime.
|
149
163
|
system_template (Template | str, optional):
|
150
|
-
|
164
|
+
Optional Jinja2 template for system messages.
|
151
165
|
system_template_context (dict, optional):
|
152
|
-
Context
|
153
|
-
|
166
|
+
Context for rendering the system template.
|
167
|
+
logs (Pile[Log], optional):
|
168
|
+
Existing logs to seed the LogManager.
|
169
|
+
**kwargs:
|
170
|
+
Additional parameters passed to `Element` parent init.
|
154
171
|
"""
|
155
172
|
super().__init__(user=user, name=name, **kwargs)
|
156
173
|
|
174
|
+
# --- MessageManager ---
|
157
175
|
self._message_manager = MessageManager(messages=messages)
|
176
|
+
# If system instructions or templates are provided, add them
|
158
177
|
if any(
|
159
178
|
i is not None
|
160
179
|
for i in [system, system_sender, system_datetime, system_template]
|
@@ -183,641 +202,419 @@ class Branch(Element, Communicatable, Relational):
|
|
183
202
|
self._imodel_manager = iModelManager(
|
184
203
|
chat=chat_model, parse=parse_model
|
185
204
|
)
|
205
|
+
|
206
|
+
# --- ActionManager ---
|
186
207
|
self._action_manager = ActionManager(tools)
|
187
208
|
|
209
|
+
# --- LogManager ---
|
188
210
|
if log_config:
|
189
|
-
log_config
|
190
|
-
log_config
|
191
|
-
if isinstance(log_config, LogManagerConfig)
|
192
|
-
else LogManagerConfig(**log_config)
|
193
|
-
)
|
211
|
+
if isinstance(log_config, dict):
|
212
|
+
log_config = LogManagerConfig(**log_config)
|
194
213
|
self._log_manager = LogManager.from_config(log_config, logs=logs)
|
195
214
|
else:
|
196
215
|
self._log_manager = LogManager(**Settings.Config.LOG, logs=logs)
|
197
216
|
|
217
|
+
# -------------------------------------------------------------------------
|
218
|
+
# Properties to expose managers and core data
|
219
|
+
# -------------------------------------------------------------------------
|
198
220
|
@property
|
199
221
|
def system(self) -> System | None:
|
200
|
-
"""
|
222
|
+
"""The system message/configuration, if any."""
|
201
223
|
return self._message_manager.system
|
202
224
|
|
203
225
|
@property
|
204
226
|
def msgs(self) -> MessageManager:
|
205
|
-
"""
|
227
|
+
"""Returns the associated MessageManager."""
|
206
228
|
return self._message_manager
|
207
229
|
|
208
230
|
@property
|
209
231
|
def acts(self) -> ActionManager:
|
210
|
-
"""ActionManager
|
232
|
+
"""Returns the associated ActionManager for tool management."""
|
211
233
|
return self._action_manager
|
212
234
|
|
213
235
|
@property
|
214
236
|
def mdls(self) -> iModelManager:
|
215
|
-
"""
|
237
|
+
"""Returns the associated iModelManager."""
|
216
238
|
return self._imodel_manager
|
217
239
|
|
218
240
|
@property
|
219
241
|
def messages(self) -> Pile[RoledMessage]:
|
220
|
-
"""
|
242
|
+
"""Convenience property to retrieve all messages from MessageManager."""
|
221
243
|
return self._message_manager.messages
|
222
244
|
|
223
245
|
@property
|
224
246
|
def logs(self) -> Pile[Log]:
|
225
|
-
"""
|
247
|
+
"""Convenience property to retrieve all logs from the LogManager."""
|
226
248
|
return self._log_manager.logs
|
227
249
|
|
228
250
|
@property
|
229
251
|
def chat_model(self) -> iModel:
|
230
|
-
"""
|
252
|
+
"""
|
253
|
+
The primary "chat" model (`iModel`) used for conversational LLM calls.
|
254
|
+
"""
|
231
255
|
return self._imodel_manager.chat
|
232
256
|
|
233
257
|
@chat_model.setter
|
234
258
|
def chat_model(self, value: iModel) -> None:
|
235
|
-
"""
|
259
|
+
"""
|
260
|
+
Sets the primary "chat" model in the iModelManager.
|
236
261
|
|
237
262
|
Args:
|
238
|
-
value (iModel): The new chat model.
|
263
|
+
value (iModel): The new chat model to register.
|
239
264
|
"""
|
240
265
|
self._imodel_manager.register_imodel("chat", value)
|
241
266
|
|
242
267
|
@property
|
243
268
|
def parse_model(self) -> iModel:
|
244
|
-
"""
|
269
|
+
"""The "parse" model (`iModel`) used for structured data parsing."""
|
245
270
|
return self._imodel_manager.parse
|
246
271
|
|
247
272
|
@parse_model.setter
|
248
273
|
def parse_model(self, value: iModel) -> None:
|
249
|
-
"""
|
274
|
+
"""
|
275
|
+
Sets the "parse" model in the iModelManager.
|
250
276
|
|
251
277
|
Args:
|
252
|
-
value (iModel): The new parse model.
|
278
|
+
value (iModel): The new parse model to register.
|
253
279
|
"""
|
254
280
|
self._imodel_manager.register_imodel("parse", value)
|
255
281
|
|
256
282
|
@property
|
257
283
|
def tools(self) -> dict[str, Tool]:
|
258
|
-
"""
|
284
|
+
"""
|
285
|
+
All registered tools (actions) in the ActionManager,
|
286
|
+
keyed by their tool names or IDs.
|
287
|
+
"""
|
259
288
|
return self._action_manager.registry
|
260
289
|
|
290
|
+
# -------------------------------------------------------------------------
|
291
|
+
# Cloning
|
292
|
+
# -------------------------------------------------------------------------
|
261
293
|
async def aclone(self, sender: ID.Ref = None) -> "Branch":
|
262
|
-
"""
|
294
|
+
"""
|
295
|
+
Asynchronously clones this `Branch` with optional new sender ID.
|
263
296
|
|
264
297
|
Args:
|
265
298
|
sender (ID.Ref, optional):
|
266
|
-
If provided,
|
299
|
+
If provided, this ID is set as the sender for all cloned messages.
|
267
300
|
|
268
301
|
Returns:
|
269
|
-
Branch: A new branch instance
|
302
|
+
Branch: A new branch instance, containing cloned state.
|
270
303
|
"""
|
271
304
|
async with self.msgs.messages:
|
272
305
|
return self.clone(sender)
|
273
306
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
chat_model: iModel = None,
|
286
|
-
invoke_actions: bool = True,
|
287
|
-
tool_schemas: list[dict] = None,
|
288
|
-
images: list = None,
|
289
|
-
image_detail: Literal["low", "high", "auto"] = None,
|
290
|
-
parse_model: iModel = None,
|
291
|
-
skip_validation: bool = False,
|
292
|
-
tools: ToolRef = None,
|
293
|
-
operative: Operative = None,
|
294
|
-
response_format: type[
|
295
|
-
BaseModel
|
296
|
-
] = None, # alias of operative.request_type
|
297
|
-
return_operative: bool = False,
|
298
|
-
actions: bool = False,
|
299
|
-
reason: bool = False,
|
300
|
-
action_kwargs: dict = None,
|
301
|
-
field_models: list[FieldModel] = None,
|
302
|
-
exclude_fields: list | dict | None = None,
|
303
|
-
request_params: ModelParams = None,
|
304
|
-
request_param_kwargs: dict = None,
|
305
|
-
response_params: ModelParams = None,
|
306
|
-
response_param_kwargs: dict = None,
|
307
|
-
handle_validation: Literal[
|
308
|
-
"raise", "return_value", "return_none"
|
309
|
-
] = "return_value",
|
310
|
-
operative_model: type[BaseModel] = None,
|
311
|
-
request_model: type[BaseModel] = None,
|
312
|
-
**kwargs,
|
313
|
-
) -> list | BaseModel | None | dict | str:
|
314
|
-
"""Orchestrates an 'operate' flow with optional tool invocation.
|
307
|
+
def clone(self, sender: ID.Ref = None) -> "Branch":
|
308
|
+
"""
|
309
|
+
Clones this `Branch` synchronously, optionally updating the sender ID.
|
310
|
+
|
311
|
+
Args:
|
312
|
+
sender (ID.Ref, optional):
|
313
|
+
If provided, all messages in the clone will have this sender ID.
|
314
|
+
Otherwise, uses the current branch's ID.
|
315
|
+
|
316
|
+
Raises:
|
317
|
+
ValueError: If `sender` is not a valid ID.Ref.
|
315
318
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
+
Returns:
|
320
|
+
Branch: A new branch object with a copy of the messages, system info, etc.
|
321
|
+
"""
|
322
|
+
if sender is not None:
|
323
|
+
if not ID.is_id(sender):
|
324
|
+
raise ValueError(
|
325
|
+
f"Cannot clone Branch: '{sender}' is not a valid sender ID."
|
326
|
+
)
|
327
|
+
sender = ID.get_id(sender)
|
328
|
+
|
329
|
+
system = self.msgs.system.clone() if self.msgs.system else None
|
330
|
+
tools = (
|
331
|
+
list(self._action_manager.registry.values())
|
332
|
+
if self._action_manager.registry
|
333
|
+
else None
|
334
|
+
)
|
335
|
+
branch_clone = Branch(
|
336
|
+
system=system,
|
337
|
+
user=self.user,
|
338
|
+
messages=[msg.clone() for msg in self.msgs.messages],
|
339
|
+
tools=tools,
|
340
|
+
metadata={"clone_from": self},
|
341
|
+
)
|
342
|
+
for message in branch_clone.msgs.messages:
|
343
|
+
message.sender = sender or self.id
|
344
|
+
message.recipient = branch_clone.id
|
345
|
+
|
346
|
+
return branch_clone
|
347
|
+
|
348
|
+
# -------------------------------------------------------------------------
|
349
|
+
# Conversion / Serialization
|
350
|
+
# -------------------------------------------------------------------------
|
351
|
+
def to_df(self, *, progression: Progression = None) -> pd.DataFrame:
|
352
|
+
"""
|
353
|
+
Convert branch messages into a `pandas.DataFrame`.
|
319
354
|
|
320
355
|
Args:
|
321
|
-
instruct (Instruct):
|
322
|
-
The instruction containing context, guidance, etc.
|
323
|
-
sender (SenderRecipient, optional):
|
324
|
-
The sender of this operation.
|
325
|
-
recipient (SenderRecipient, optional):
|
326
|
-
The recipient of this operation.
|
327
356
|
progression (Progression, optional):
|
328
|
-
|
329
|
-
imodel (iModel, optional):
|
330
|
-
Deprecated, alias for chat_model.
|
331
|
-
chat_model (iModel, optional):
|
332
|
-
The chat model to invoke.
|
333
|
-
invoke_actions (bool, optional):
|
334
|
-
Whether to call requested tools (actions).
|
335
|
-
tool_schemas (list[dict], optional):
|
336
|
-
Overridden schemas for the tools to be used.
|
337
|
-
images (list, optional):
|
338
|
-
Additional images for the model context.
|
339
|
-
image_detail (Literal["low", "high", "auto"], optional):
|
340
|
-
The level of detail for images, if relevant.
|
341
|
-
parse_model (iModel, optional):
|
342
|
-
The parse model for validating or refining responses.
|
343
|
-
skip_validation (bool, optional):
|
344
|
-
If True, skip post-response validation steps.
|
345
|
-
tools (ToolRef, optional):
|
346
|
-
Specific tools to make available if `invoke_actions` is True.
|
347
|
-
operative (Operative, optional):
|
348
|
-
The operative describing how to handle the response.
|
349
|
-
response_format (type[BaseModel], optional):
|
350
|
-
An expected response schema (alias of `operative.request_type`).
|
351
|
-
fuzzy_match_kwargs (dict, optional):
|
352
|
-
Settings for fuzzy validation if used.
|
353
|
-
operative_kwargs (dict, optional):
|
354
|
-
Additional arguments for creating an Operative if none is given.
|
355
|
-
**kwargs: Additional arguments passed to the model invocation.
|
357
|
+
A custom message ordering. If `None`, uses the stored progression.
|
356
358
|
|
357
359
|
Returns:
|
358
|
-
|
359
|
-
The final parsed response, or an Operative object, or the
|
360
|
-
string/dict if skipping validation or no tools needed.
|
360
|
+
pd.DataFrame: Each row represents a message, with columns defined by MESSAGE_FIELDS.
|
361
361
|
"""
|
362
|
-
if
|
363
|
-
|
364
|
-
"operative_model is deprecated. Use response_format instead."
|
365
|
-
)
|
366
|
-
if (
|
367
|
-
(operative_model and response_format)
|
368
|
-
or (operative_model and request_model)
|
369
|
-
or (response_format and request_model)
|
370
|
-
):
|
371
|
-
raise ValueError(
|
372
|
-
"Cannot specify both operative_model and response_format"
|
373
|
-
"or operative_model and request_model as they are aliases"
|
374
|
-
"for the same parameter."
|
375
|
-
)
|
362
|
+
if progression is None:
|
363
|
+
progression = self.msgs.progression
|
376
364
|
|
377
|
-
|
378
|
-
|
379
|
-
|
365
|
+
msgs = [
|
366
|
+
self.msgs.messages[i]
|
367
|
+
for i in progression
|
368
|
+
if i in self.msgs.messages
|
369
|
+
]
|
370
|
+
p = Pile(collections=msgs)
|
371
|
+
return p.to_df(columns=MESSAGE_FIELDS)
|
380
372
|
|
381
|
-
|
382
|
-
|
373
|
+
# -------------------------------------------------------------------------
|
374
|
+
# Mailbox Send / Receive
|
375
|
+
# -------------------------------------------------------------------------
|
376
|
+
def send(
|
377
|
+
self,
|
378
|
+
recipient: IDType,
|
379
|
+
category: PackageCategory | None,
|
380
|
+
item: Any,
|
381
|
+
request_source: IDType | None = None,
|
382
|
+
) -> None:
|
383
|
+
"""
|
384
|
+
Sends a `Package` (wrapped in a `Mail` object) to a specified recipient.
|
383
385
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
386
|
+
Args:
|
387
|
+
recipient (IDType):
|
388
|
+
ID of the recipient branch or component.
|
389
|
+
category (PackageCategory | None):
|
390
|
+
The category/type of the package (e.g., 'message', 'tool', 'imodel').
|
391
|
+
item (Any):
|
392
|
+
The payload to send (e.g., a message, tool reference, model, etc.).
|
393
|
+
request_source (IDType | None):
|
394
|
+
The ID that prompted or requested this send operation (optional).
|
395
|
+
"""
|
396
|
+
package = Package(
|
397
|
+
category=category,
|
398
|
+
item=item,
|
399
|
+
request_source=request_source,
|
388
400
|
)
|
389
401
|
|
390
|
-
|
391
|
-
|
392
|
-
if actions:
|
393
|
-
instruct.actions = True
|
394
|
-
|
395
|
-
operative: Operative = Step.request_operative(
|
396
|
-
request_params=request_params,
|
397
|
-
reason=instruct.reason,
|
398
|
-
actions=instruct.actions,
|
399
|
-
exclude_fields=exclude_fields,
|
400
|
-
base_type=response_format,
|
401
|
-
field_models=field_models,
|
402
|
-
**(request_param_kwargs or {}),
|
403
|
-
)
|
404
|
-
if instruct.actions:
|
405
|
-
tools = tools or True
|
406
|
-
if invoke_actions and tools:
|
407
|
-
tool_schemas = self.acts.get_tool_schema(tools)
|
408
|
-
|
409
|
-
ins, res = await self.invoke_chat(
|
410
|
-
instruction=instruct.instruction,
|
411
|
-
guidance=instruct.guidance,
|
412
|
-
context=instruct.context,
|
413
|
-
sender=sender,
|
402
|
+
mail = Mail(
|
403
|
+
sender=self.id,
|
414
404
|
recipient=recipient,
|
415
|
-
|
416
|
-
progression=progression,
|
417
|
-
imodel=chat_model,
|
418
|
-
images=images,
|
419
|
-
image_detail=image_detail,
|
420
|
-
tool_schemas=tool_schemas,
|
421
|
-
**kwargs,
|
405
|
+
package=package,
|
422
406
|
)
|
423
|
-
self.
|
424
|
-
self.msgs.add_message(assistant_response=res)
|
425
|
-
|
426
|
-
operative.response_str_dict = res.response
|
427
|
-
if skip_validation:
|
428
|
-
if return_operative:
|
429
|
-
return operative
|
430
|
-
return operative.response_str_dict
|
431
|
-
|
432
|
-
response_model = operative.update_response_model(res.response)
|
433
|
-
|
434
|
-
if not isinstance(response_model, BaseModel):
|
435
|
-
response_model = await self.parse(
|
436
|
-
text=res.response,
|
437
|
-
request_type=operative.request_type,
|
438
|
-
max_retries=operative.max_retries,
|
439
|
-
handle_validation="return_value",
|
440
|
-
)
|
441
|
-
operative.response_model = operative.update_response_model(
|
442
|
-
response_model
|
443
|
-
)
|
407
|
+
self.mailbox.append_out(mail)
|
444
408
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
)
|
409
|
+
def receive(
|
410
|
+
self,
|
411
|
+
sender: IDType,
|
412
|
+
message: bool = False,
|
413
|
+
tool: bool = False,
|
414
|
+
imodel: bool = False,
|
415
|
+
) -> None:
|
416
|
+
"""
|
417
|
+
Retrieves and processes mail from a given sender according to the specified flags.
|
455
418
|
|
456
|
-
|
457
|
-
|
419
|
+
Args:
|
420
|
+
sender (IDType):
|
421
|
+
The ID of the mail sender.
|
422
|
+
message (bool):
|
423
|
+
If `True`, process packages categorized as "message".
|
424
|
+
tool (bool):
|
425
|
+
If `True`, process packages categorized as "tool".
|
426
|
+
imodel (bool):
|
427
|
+
If `True`, process packages categorized as "imodel".
|
458
428
|
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
)
|
467
|
-
operative = Step.respond_operative(
|
468
|
-
response_params=response_params,
|
469
|
-
operative=operative,
|
470
|
-
additional_data={"action_responses": action_response_models},
|
471
|
-
**(response_param_kwargs or {}),
|
472
|
-
)
|
473
|
-
return operative if return_operative else operative.response_model
|
429
|
+
Raises:
|
430
|
+
ValueError: If no mail exists from the specified sender,
|
431
|
+
or if a package is invalid for the chosen category.
|
432
|
+
"""
|
433
|
+
sender = ID.get_id(sender)
|
434
|
+
if sender not in self.mailbox.pending_ins.keys():
|
435
|
+
raise ValueError(f"No mail or package found from sender: {sender}")
|
474
436
|
|
475
|
-
|
476
|
-
self
|
477
|
-
|
478
|
-
|
479
|
-
"raise", "return_value", "return_none"
|
480
|
-
] = "return_value",
|
481
|
-
max_retries: int = 3,
|
482
|
-
request_type: type[BaseModel] = None,
|
483
|
-
operative: Operative = None,
|
484
|
-
similarity_algo="jaro_winkler",
|
485
|
-
similarity_threshold: float = 0.85,
|
486
|
-
fuzzy_match: bool = True,
|
487
|
-
handle_unmatched: Literal[
|
488
|
-
"ignore", "raise", "remove", "fill", "force"
|
489
|
-
] = "force",
|
490
|
-
fill_value: Any = None,
|
491
|
-
fill_mapping: dict[str, Any] | None = None,
|
492
|
-
strict: bool = False,
|
493
|
-
suppress_conversion_errors: bool = False,
|
494
|
-
):
|
495
|
-
"""Attempts to parse text into a structured Pydantic model.
|
437
|
+
skipped_requests = Progression()
|
438
|
+
while self.mailbox.pending_ins[sender]:
|
439
|
+
mail_id = self.mailbox.pending_ins[sender].popleft()
|
440
|
+
mail: Mail = self.mailbox.pile_[mail_id]
|
496
441
|
|
497
|
-
|
442
|
+
if mail.category == "message" and message:
|
443
|
+
if not isinstance(mail.package.item, RoledMessage):
|
444
|
+
raise ValueError(
|
445
|
+
"Invalid message package: The item must be a `RoledMessage`."
|
446
|
+
)
|
447
|
+
new_message = mail.package.item.clone()
|
448
|
+
new_message.sender = mail.sender
|
449
|
+
new_message.recipient = self.id
|
450
|
+
self.msgs.messages.include(new_message)
|
451
|
+
self.mailbox.pile_.pop(mail_id)
|
498
452
|
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
The Pydantic model to parse into.
|
507
|
-
operative (Operative, optional):
|
508
|
-
If provided, uses its model and max_retries setting.
|
509
|
-
similarity_algo (str):
|
510
|
-
The similarity algorithm for fuzzy field matching.
|
511
|
-
similarity_threshold (float):
|
512
|
-
A threshold for fuzzy matching (0.0 - 1.0).
|
513
|
-
fuzzy_match (bool):
|
514
|
-
If True, tries to match unrecognized keys to known ones.
|
515
|
-
handle_unmatched (Literal["ignore","raise","remove","fill","force"]):
|
516
|
-
How to handle unmatched fields.
|
517
|
-
fill_value (Any):
|
518
|
-
A default value used when fill is needed.
|
519
|
-
fill_mapping (dict[str, Any] | None):
|
520
|
-
A mapping from field -> fill value override.
|
521
|
-
strict (bool):
|
522
|
-
If True, raises errors on ambiguous fields or data types.
|
523
|
-
suppress_conversion_errors (bool):
|
524
|
-
If True, logs or ignores errors during data conversion.
|
453
|
+
elif mail.category == "tool" and tool:
|
454
|
+
if not isinstance(mail.package.item, Tool):
|
455
|
+
raise ValueError(
|
456
|
+
"Invalid tool package: The item must be a `Tool` instance."
|
457
|
+
)
|
458
|
+
self._action_manager.register_tools(mail.package.item)
|
459
|
+
self.mailbox.pile_.pop(mail_id)
|
525
460
|
|
526
|
-
|
527
|
-
|
528
|
-
The parsed model instance, or a dict/string/None depending
|
529
|
-
on the handling mode.
|
530
|
-
"""
|
531
|
-
_should_try = True
|
532
|
-
num_try = 0
|
533
|
-
response_model = text
|
534
|
-
if operative is not None:
|
535
|
-
max_retries = operative.max_retries
|
536
|
-
request_type = operative.request_type
|
537
|
-
|
538
|
-
while (
|
539
|
-
_should_try
|
540
|
-
and num_try < max_retries
|
541
|
-
and not isinstance(response_model, BaseModel)
|
542
|
-
):
|
543
|
-
num_try += 1
|
544
|
-
_, res = await self.invoke_chat(
|
545
|
-
instruction="reformat text into specified model",
|
546
|
-
guidane="follow the required response format, using the model schema as a guide",
|
547
|
-
context=[{"text_to_format": text}],
|
548
|
-
response_format=request_type,
|
549
|
-
sender=self.user,
|
550
|
-
recipient=self.id,
|
551
|
-
imodel=self.parse_model,
|
552
|
-
)
|
553
|
-
if operative is not None:
|
554
|
-
response_model = operative.update_response_model(res.response)
|
555
|
-
else:
|
556
|
-
response_model = fuzzy_validate_mapping(
|
557
|
-
res.response,
|
558
|
-
breakdown_pydantic_annotation(request_type),
|
559
|
-
similarity_algo=similarity_algo,
|
560
|
-
similarity_threshold=similarity_threshold,
|
561
|
-
fuzzy_match=fuzzy_match,
|
562
|
-
handle_unmatched=handle_unmatched,
|
563
|
-
fill_value=fill_value,
|
564
|
-
fill_mapping=fill_mapping,
|
565
|
-
strict=strict,
|
566
|
-
suppress_conversion_errors=suppress_conversion_errors,
|
567
|
-
)
|
568
|
-
response_model = request_type.model_validate(response_model)
|
569
|
-
|
570
|
-
if not isinstance(response_model, BaseModel):
|
571
|
-
match handle_validation:
|
572
|
-
case "return_value":
|
573
|
-
return response_model
|
574
|
-
case "return_none":
|
575
|
-
return None
|
576
|
-
case "raise":
|
461
|
+
elif mail.category == "imodel" and imodel:
|
462
|
+
if not isinstance(mail.package.item, iModel):
|
577
463
|
raise ValueError(
|
578
|
-
"
|
464
|
+
"Invalid iModel package: The item must be an `iModel` instance."
|
579
465
|
)
|
466
|
+
self._imodel_manager.register_imodel(
|
467
|
+
mail.package.item.name or "chat", mail.package.item
|
468
|
+
)
|
469
|
+
self.mailbox.pile_.pop(mail_id)
|
580
470
|
|
581
|
-
|
471
|
+
else:
|
472
|
+
# If the category doesn't match the flags or is unhandled
|
473
|
+
skipped_requests.append(mail)
|
582
474
|
|
583
|
-
|
475
|
+
# Requeue any skipped mail
|
476
|
+
self.mailbox.pending_ins[sender] = skipped_requests
|
477
|
+
if len(self.mailbox.pending_ins[sender]) == 0:
|
478
|
+
self.mailbox.pending_ins.pop(sender)
|
479
|
+
|
480
|
+
async def asend(
|
584
481
|
self,
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
recipient: SenderRecipient = None,
|
590
|
-
progression: ID.IDSeq = None,
|
591
|
-
request_model: type[BaseModel] | BaseModel | None = None,
|
592
|
-
response_format: type[BaseModel] = None,
|
593
|
-
request_fields: dict | list[str] = None,
|
594
|
-
imodel: iModel = None, # alias of chat_model
|
595
|
-
chat_model: iModel = None,
|
596
|
-
parse_model: iModel = None,
|
597
|
-
skip_validation: bool = False,
|
598
|
-
images: list = None,
|
599
|
-
image_detail: Literal["low", "high", "auto"] = None,
|
600
|
-
num_parse_retries: int = 3,
|
601
|
-
fuzzy_match_kwargs: dict = None,
|
602
|
-
clear_messages: bool = False,
|
603
|
-
operative_model: type[BaseModel] = None,
|
604
|
-
**kwargs,
|
482
|
+
recipient: IDType,
|
483
|
+
category: PackageCategory | None,
|
484
|
+
package: Any,
|
485
|
+
request_source: IDType | None = None,
|
605
486
|
):
|
606
|
-
"""
|
607
|
-
|
608
|
-
Sends messages to the model, optionally parses the response, and
|
609
|
-
can handle simpler field-level validation.
|
487
|
+
"""
|
488
|
+
Async version of `send()`.
|
610
489
|
|
611
490
|
Args:
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
The
|
620
|
-
recipient (SenderRecipient, optional):
|
621
|
-
The recipient of this message.
|
622
|
-
progression (ID.IDSeq, optional):
|
623
|
-
A custom progression of conversation messages.
|
624
|
-
request_model (type[BaseModel] | BaseModel | None, optional):
|
625
|
-
Model for structured responses.
|
626
|
-
response_format (type[BaseModel], optional):
|
627
|
-
Alias for request_model if both are not given simultaneously.
|
628
|
-
request_fields (dict | list[str], optional):
|
629
|
-
Simpler field-level mapping if no model is used.
|
630
|
-
imodel (iModel, optional):
|
631
|
-
Deprecated, alias for chat_model.
|
632
|
-
chat_model (iModel, optional):
|
633
|
-
Model used for the conversation.
|
634
|
-
parse_model (iModel, optional):
|
635
|
-
Model used for any parsing operation.
|
636
|
-
skip_validation (bool, optional):
|
637
|
-
If True, returns the raw response without further checks.
|
638
|
-
images (list, optional):
|
639
|
-
Additional images if relevant to the LLM context.
|
640
|
-
image_detail (Literal["low","high","auto"], optional):
|
641
|
-
Level of image detail if used.
|
642
|
-
num_parse_retries (int, optional):
|
643
|
-
Max times to retry parsing on failure (capped at 5).
|
644
|
-
fuzzy_match_kwargs (dict, optional):
|
645
|
-
Settings passed to the fuzzy validation function.
|
646
|
-
clear_messages (bool, optional):
|
647
|
-
If True, clears previously stored messages.
|
648
|
-
**kwargs:
|
649
|
-
Additional arguments for the LLM call.
|
650
|
-
|
651
|
-
Returns:
|
652
|
-
Any:
|
653
|
-
The raw string, a validated Pydantic model, or a dict
|
654
|
-
of requested fields, depending on the parameters.
|
491
|
+
recipient (IDType):
|
492
|
+
ID of the recipient branch or component.
|
493
|
+
category (PackageCategory | None):
|
494
|
+
The category/type of the package.
|
495
|
+
package (Any):
|
496
|
+
The item(s) to send (message/tool/model).
|
497
|
+
request_source (IDType | None):
|
498
|
+
The origin request ID (if any).
|
655
499
|
"""
|
656
|
-
|
657
|
-
|
658
|
-
"operative_model is deprecated. Use response_format instead."
|
659
|
-
)
|
660
|
-
if (
|
661
|
-
(operative_model and response_format)
|
662
|
-
or (operative_model and request_model)
|
663
|
-
or (response_format and request_model)
|
664
|
-
):
|
665
|
-
raise ValueError(
|
666
|
-
"Cannot specify both operative_model and response_format"
|
667
|
-
"or operative_model and request_model as they are aliases"
|
668
|
-
"for the same parameter."
|
669
|
-
)
|
670
|
-
|
671
|
-
response_format = response_format or operative_model or request_model
|
500
|
+
async with self.mailbox.pile_:
|
501
|
+
self.send(recipient, category, package, request_source)
|
672
502
|
|
673
|
-
|
674
|
-
|
503
|
+
async def areceive(
|
504
|
+
self,
|
505
|
+
sender: IDType,
|
506
|
+
message: bool = False,
|
507
|
+
tool: bool = False,
|
508
|
+
imodel: bool = False,
|
509
|
+
) -> None:
|
510
|
+
"""
|
511
|
+
Async version of `receive()`.
|
675
512
|
|
676
|
-
|
677
|
-
|
513
|
+
Args:
|
514
|
+
sender (IDType):
|
515
|
+
The ID of the mail sender.
|
516
|
+
message (bool):
|
517
|
+
If `True`, process packages categorized as "message".
|
518
|
+
tool (bool):
|
519
|
+
If `True`, process packages categorized as "tool".
|
520
|
+
imodel (bool):
|
521
|
+
If `True`, process packages categorized as "imodel".
|
522
|
+
"""
|
523
|
+
async with self.mailbox.pile_:
|
524
|
+
self.receive(sender, message, tool, imodel)
|
678
525
|
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
"times? lowering retry attempts to 5. Suggestion is under 3"
|
683
|
-
)
|
684
|
-
num_parse_retries = 5
|
526
|
+
def receive_all(self) -> None:
|
527
|
+
"""
|
528
|
+
Receives mail from all known senders without filtering.
|
685
529
|
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
sender=sender,
|
691
|
-
recipient=recipient,
|
692
|
-
response_format=response_format,
|
693
|
-
progression=progression,
|
694
|
-
imodel=imodel,
|
695
|
-
images=images,
|
696
|
-
image_detail=image_detail,
|
697
|
-
**kwargs,
|
698
|
-
)
|
699
|
-
self.msgs.add_message(instruction=ins)
|
700
|
-
self.msgs.add_message(assistant_response=res)
|
701
|
-
|
702
|
-
if skip_validation:
|
703
|
-
return res.response
|
704
|
-
|
705
|
-
if response_format is not None:
|
706
|
-
return await self.parse(
|
707
|
-
text=res.response,
|
708
|
-
request_type=response_format,
|
709
|
-
max_retries=num_parse_retries,
|
710
|
-
**(fuzzy_match_kwargs or {}),
|
711
|
-
)
|
530
|
+
(Duplicate method included in your snippet; you may unify or remove.)
|
531
|
+
"""
|
532
|
+
for key in self.mailbox.pending_ins:
|
533
|
+
self.receive(key)
|
712
534
|
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
535
|
+
# -------------------------------------------------------------------------
|
536
|
+
# Dictionary Conversion
|
537
|
+
# -------------------------------------------------------------------------
|
538
|
+
def to_dict(self):
|
539
|
+
"""
|
540
|
+
Serializes the branch to a Python dictionary, including:
|
541
|
+
- Messages
|
542
|
+
- Logs
|
543
|
+
- Chat/Parse models
|
544
|
+
- System message
|
545
|
+
- LogManager config
|
546
|
+
- Metadata
|
721
547
|
|
722
|
-
|
548
|
+
Returns:
|
549
|
+
dict: A dictionary representing the branch's internal state.
|
550
|
+
"""
|
551
|
+
meta = {}
|
552
|
+
if "clone_from" in self.metadata:
|
723
553
|
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
retry_timeout: float | None = None,
|
737
|
-
retry_timing: bool = False,
|
738
|
-
max_concurrent: int | None = None,
|
739
|
-
throttle_period: float | None = None,
|
740
|
-
flatten: bool = True,
|
741
|
-
dropna: bool = True,
|
742
|
-
unique_output: bool = False,
|
743
|
-
flatten_tuple_set: bool = False,
|
744
|
-
):
|
745
|
-
params = locals()
|
746
|
-
params.pop("self")
|
747
|
-
params.pop("action_request")
|
748
|
-
return await alcall(
|
749
|
-
action_request,
|
750
|
-
self._invoke_action,
|
751
|
-
**params,
|
554
|
+
# Provide some reference info about the source from which we cloned
|
555
|
+
meta["clone_from"] = {
|
556
|
+
"id": str(self.metadata["clone_from"].id),
|
557
|
+
"user": str(self.metadata["clone_from"].user),
|
558
|
+
"created_at": self.metadata["clone_from"].created_at,
|
559
|
+
"progression": [
|
560
|
+
str(i)
|
561
|
+
for i in self.metadata["clone_from"].msgs.progression
|
562
|
+
],
|
563
|
+
}
|
564
|
+
meta.update(
|
565
|
+
copy({k: v for k, v in self.metadata.items() if k != "clone_from"})
|
752
566
|
)
|
753
567
|
|
754
|
-
|
755
|
-
self
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
568
|
+
dict_ = super().to_dict()
|
569
|
+
dict_["messages"] = self.messages.to_dict()
|
570
|
+
dict_["logs"] = self.logs.to_dict()
|
571
|
+
dict_["chat_model"] = self.chat_model.to_dict()
|
572
|
+
dict_["parse_model"] = self.parse_model.to_dict()
|
573
|
+
if self.system:
|
574
|
+
dict_["system"] = self.system.to_dict()
|
575
|
+
dict_["log_config"] = self._log_manager._config.model_dump()
|
576
|
+
dict_["metadata"] = meta
|
577
|
+
return dict_
|
578
|
+
|
579
|
+
@classmethod
|
580
|
+
def from_dict(cls, data: dict):
|
581
|
+
"""
|
582
|
+
Creates a `Branch` instance from a serialized dictionary.
|
760
583
|
|
761
584
|
Args:
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
If True, logs errors instead of raising.
|
585
|
+
data (dict):
|
586
|
+
Must include (or optionally include) `messages`, `logs`,
|
587
|
+
`chat_model`, `parse_model`, `system`, and `log_config`.
|
766
588
|
|
767
589
|
Returns:
|
768
|
-
|
590
|
+
Branch: A new `Branch` instance based on the deserialized data.
|
769
591
|
"""
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
if action_request.keys() >= {"function", "arguments"}:
|
780
|
-
func = action_request["function"]
|
781
|
-
args = action_request["arguments"]
|
782
|
-
|
783
|
-
func_call: FunctionCalling = await self._action_manager.invoke(
|
784
|
-
action_request
|
785
|
-
)
|
786
|
-
if isinstance(func_call, FunctionCalling):
|
787
|
-
self._log_manager.log(Log.create(func_call))
|
788
|
-
|
789
|
-
if not isinstance(action_request, ActionRequest):
|
790
|
-
action_request = ActionRequest.create(
|
791
|
-
function=func,
|
792
|
-
arguments=args,
|
793
|
-
sender=self.id,
|
794
|
-
recipient=func_call.func_tool.id,
|
795
|
-
)
|
796
|
-
|
797
|
-
if action_request not in self.messages:
|
798
|
-
self.msgs.add_message(action_request=action_request)
|
799
|
-
|
800
|
-
self.msgs.add_message(
|
801
|
-
action_request=action_request,
|
802
|
-
action_output=func_call.response,
|
803
|
-
)
|
804
|
-
|
805
|
-
return ActionResponseModel(
|
806
|
-
function=action_request.function,
|
807
|
-
arguments=action_request.arguments,
|
808
|
-
output=func_call.response,
|
809
|
-
)
|
810
|
-
if isinstance(func_call, Log):
|
811
|
-
self._log_manager.log(func_call)
|
812
|
-
return None
|
592
|
+
dict_ = {
|
593
|
+
"messages": data.pop("messages", UNDEFINED),
|
594
|
+
"logs": data.pop("logs", UNDEFINED),
|
595
|
+
"chat_model": data.pop("chat_model", UNDEFINED),
|
596
|
+
"parse_model": data.pop("parse_model", UNDEFINED),
|
597
|
+
"system": data.pop("system", UNDEFINED),
|
598
|
+
"log_config": data.pop("log_config", UNDEFINED),
|
599
|
+
}
|
600
|
+
params = {}
|
813
601
|
|
814
|
-
|
815
|
-
|
816
|
-
|
602
|
+
# Merge in the rest of the data
|
603
|
+
for k, v in data.items():
|
604
|
+
# If the item is a dict with an 'id', we expand it
|
605
|
+
if isinstance(v, dict) and "id" in v:
|
606
|
+
params.update(v)
|
817
607
|
else:
|
818
|
-
|
608
|
+
params[k] = v
|
609
|
+
|
610
|
+
params.update(dict_)
|
611
|
+
# Remove placeholders (UNDEFINED) so we don't incorrectly assign them
|
612
|
+
return cls(**{k: v for k, v in params.items() if v is not UNDEFINED})
|
819
613
|
|
820
|
-
|
614
|
+
# -------------------------------------------------------------------------
|
615
|
+
# Asynchronous Operations (chat, parse, operate, etc.)
|
616
|
+
# -------------------------------------------------------------------------
|
617
|
+
async def chat(
|
821
618
|
self,
|
822
619
|
instruction=None,
|
823
620
|
guidance=None,
|
@@ -831,457 +628,829 @@ class Branch(Element, Communicatable, Relational):
|
|
831
628
|
tool_schemas=None,
|
832
629
|
images: list = None,
|
833
630
|
image_detail: Literal["low", "high", "auto"] = None,
|
631
|
+
plain_content: str = None,
|
834
632
|
**kwargs,
|
835
633
|
) -> tuple[Instruction, AssistantResponse]:
|
836
|
-
"""
|
634
|
+
"""
|
635
|
+
Invokes the chat model with the current conversation history.
|
837
636
|
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
637
|
+
**High-level flow**:
|
638
|
+
1. Construct a sequence of messages from the stored progression.
|
639
|
+
2. Integrate any pending action responses into the context.
|
640
|
+
3. Invoke the chat model with the combined messages.
|
641
|
+
4. Capture and return the final response as an `AssistantResponse`.
|
842
642
|
|
843
643
|
Args:
|
844
644
|
instruction (Any):
|
845
|
-
|
645
|
+
Main user instruction text or structured data.
|
846
646
|
guidance (Any):
|
847
|
-
Additional system or user guidance.
|
647
|
+
Additional system or user guidance text.
|
848
648
|
context (Any):
|
849
649
|
Context data to pass to the model.
|
850
650
|
sender (Any):
|
851
|
-
The user or entity sending this message.
|
651
|
+
The user or entity sending this message (defaults to `Branch.user`).
|
852
652
|
recipient (Any):
|
853
|
-
The
|
653
|
+
The recipient of this message (defaults to `self.id`).
|
854
654
|
request_fields (Any):
|
855
|
-
|
856
|
-
|
857
|
-
A
|
655
|
+
Partial field-level validation reference (rarely used).
|
656
|
+
response_format (type[BaseModel], optional):
|
657
|
+
A Pydantic model type for structured model responses.
|
858
658
|
progression (Any):
|
859
|
-
|
659
|
+
Custom ordering of messages in the conversation.
|
860
660
|
imodel (iModel, optional):
|
861
|
-
|
661
|
+
An override for the chat model to use. If not provided, uses `self.chat_model`.
|
862
662
|
tool_schemas (Any, optional):
|
863
|
-
Additional schemas
|
663
|
+
Additional schemas for tool invocation in function-calling.
|
864
664
|
images (list, optional):
|
865
|
-
Optional
|
866
|
-
image_detail (Literal["low","high","auto"], optional):
|
867
|
-
|
665
|
+
Optional images relevant to the model's context.
|
666
|
+
image_detail (Literal["low", "high", "auto"], optional):
|
667
|
+
Level of detail for image-based context (if relevant).
|
668
|
+
plain_content (str, optional):
|
669
|
+
Plain text content appended to the instruction.
|
868
670
|
**kwargs:
|
869
|
-
Additional
|
671
|
+
Additional parameters for the LLM invocation.
|
870
672
|
|
871
673
|
Returns:
|
872
674
|
tuple[Instruction, AssistantResponse]:
|
873
|
-
The
|
874
|
-
AssistantResponse from the model call.
|
675
|
+
The `Instruction` object and the final `AssistantResponse`.
|
875
676
|
"""
|
876
|
-
|
677
|
+
from lionagi.operations.chat.chat import chat
|
678
|
+
|
679
|
+
return await chat(
|
680
|
+
self,
|
877
681
|
instruction=instruction,
|
878
682
|
guidance=guidance,
|
879
683
|
context=context,
|
880
|
-
sender=sender
|
881
|
-
recipient=recipient
|
882
|
-
response_format=response_format,
|
684
|
+
sender=sender,
|
685
|
+
recipient=recipient,
|
883
686
|
request_fields=request_fields,
|
687
|
+
response_format=response_format,
|
688
|
+
progression=progression,
|
689
|
+
chat_model=imodel,
|
690
|
+
tool_schemas=tool_schemas,
|
884
691
|
images=images,
|
885
692
|
image_detail=image_detail,
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
progression = progression or self.msgs.progression
|
890
|
-
messages: list[RoledMessage] = [
|
891
|
-
self.msgs.messages[i] for i in progression
|
892
|
-
]
|
893
|
-
|
894
|
-
use_ins = None
|
895
|
-
_to_use = []
|
896
|
-
_action_responses: set[ActionResponse] = set()
|
897
|
-
|
898
|
-
for i in messages:
|
899
|
-
if isinstance(i, ActionResponse):
|
900
|
-
_action_responses.add(i)
|
901
|
-
if isinstance(i, AssistantResponse):
|
902
|
-
j = AssistantResponse(
|
903
|
-
role=i.role,
|
904
|
-
content=copy(i.content),
|
905
|
-
sender=i.sender,
|
906
|
-
recipient=i.recipient,
|
907
|
-
template=i.template,
|
908
|
-
)
|
909
|
-
_to_use.append(j)
|
910
|
-
if isinstance(i, Instruction):
|
911
|
-
j = Instruction(
|
912
|
-
role=i.role,
|
913
|
-
content=copy(i.content),
|
914
|
-
sender=i.sender,
|
915
|
-
recipient=i.recipient,
|
916
|
-
template=i.template,
|
917
|
-
)
|
918
|
-
j.tool_schemas = None
|
919
|
-
j.respond_schema_info = None
|
920
|
-
j.request_response_format = None
|
921
|
-
|
922
|
-
if _action_responses:
|
923
|
-
d_ = [k.content for k in _action_responses]
|
924
|
-
for z in d_:
|
925
|
-
if z not in j.context:
|
926
|
-
j.context.append(z)
|
927
|
-
|
928
|
-
_to_use.append(j)
|
929
|
-
_action_responses = set()
|
930
|
-
else:
|
931
|
-
_to_use.append(j)
|
932
|
-
|
933
|
-
messages = _to_use
|
934
|
-
if _action_responses:
|
935
|
-
j = ins.model_copy()
|
936
|
-
d_ = [k.content for k in _action_responses]
|
937
|
-
for z in d_:
|
938
|
-
if z not in j.context:
|
939
|
-
j.context.append(z)
|
940
|
-
use_ins = j
|
941
|
-
|
942
|
-
if messages and len(messages) > 1:
|
943
|
-
_msgs = [messages[0]]
|
944
|
-
|
945
|
-
for i in messages[1:]:
|
946
|
-
if isinstance(i, AssistantResponse):
|
947
|
-
if isinstance(_msgs[-1], AssistantResponse):
|
948
|
-
_msgs[-1].response = (
|
949
|
-
f"{_msgs[-1].response}\n\n{i.response}"
|
950
|
-
)
|
951
|
-
else:
|
952
|
-
_msgs.append(i)
|
953
|
-
else:
|
954
|
-
if isinstance(_msgs[-1], AssistantResponse):
|
955
|
-
_msgs.append(i)
|
956
|
-
messages = _msgs
|
957
|
-
|
958
|
-
if self.msgs.system and imodel.sequential_exchange:
|
959
|
-
messages = [msg for msg in messages if msg.role != "system"]
|
960
|
-
first_instruction = None
|
961
|
-
|
962
|
-
if len(messages) == 0:
|
963
|
-
first_instruction = ins.model_copy()
|
964
|
-
first_instruction.guidance = self.msgs.system.rendered + (
|
965
|
-
first_instruction.guidance or ""
|
966
|
-
)
|
967
|
-
messages.append(first_instruction)
|
968
|
-
elif len(messages) >= 1:
|
969
|
-
first_instruction = messages[0]
|
970
|
-
if not isinstance(first_instruction, Instruction):
|
971
|
-
raise ValueError(
|
972
|
-
"First message in progression must be an Instruction or System"
|
973
|
-
)
|
974
|
-
first_instruction = first_instruction.model_copy()
|
975
|
-
first_instruction.guidance = self.msgs.system.rendered + (
|
976
|
-
first_instruction.guidance or ""
|
977
|
-
)
|
978
|
-
messages[0] = first_instruction
|
979
|
-
messages.append(use_ins or ins)
|
980
|
-
|
981
|
-
else:
|
982
|
-
messages.append(use_ins or ins)
|
983
|
-
|
984
|
-
kwargs["messages"] = [i.chat_msg for i in messages]
|
985
|
-
imodel = imodel or self.chat_model
|
986
|
-
|
987
|
-
api_call = await imodel.invoke(**kwargs)
|
988
|
-
self._log_manager.log(Log.create(api_call))
|
989
|
-
|
990
|
-
res = AssistantResponse.create(
|
991
|
-
assistant_response=api_call.response,
|
992
|
-
sender=self.id,
|
993
|
-
recipient=self.user,
|
693
|
+
plain_content=plain_content,
|
694
|
+
**kwargs,
|
994
695
|
)
|
995
696
|
|
996
|
-
|
697
|
+
async def parse(
|
698
|
+
self,
|
699
|
+
text: str,
|
700
|
+
handle_validation: Literal[
|
701
|
+
"raise", "return_value", "return_none"
|
702
|
+
] = "return_value",
|
703
|
+
max_retries: int = 3,
|
704
|
+
request_type: type[BaseModel] = None,
|
705
|
+
operative: Operative = None,
|
706
|
+
similarity_algo="jaro_winkler",
|
707
|
+
similarity_threshold: float = 0.85,
|
708
|
+
fuzzy_match: bool = True,
|
709
|
+
handle_unmatched: Literal[
|
710
|
+
"ignore", "raise", "remove", "fill", "force"
|
711
|
+
] = "force",
|
712
|
+
fill_value: Any = None,
|
713
|
+
fill_mapping: dict[str, Any] | None = None,
|
714
|
+
strict: bool = False,
|
715
|
+
suppress_conversion_errors: bool = False,
|
716
|
+
response_format: type[BaseModel] = None,
|
717
|
+
):
|
718
|
+
"""
|
719
|
+
Attempts to parse text into a structured Pydantic model using parse model logic.
|
997
720
|
|
998
|
-
|
999
|
-
|
721
|
+
If fuzzy matching is enabled, tries to map partial or uncertain keys
|
722
|
+
to the known fields of the model. Retries are performed if initial parsing fails.
|
1000
723
|
|
1001
724
|
Args:
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
725
|
+
text (str):
|
726
|
+
The raw text to parse.
|
727
|
+
handle_validation (Literal["raise","return_value","return_none"]):
|
728
|
+
What to do if parsing fails (default: "return_value").
|
729
|
+
max_retries (int):
|
730
|
+
Number of times to retry parsing on failure (default: 3).
|
731
|
+
request_type (type[BaseModel], optional):
|
732
|
+
The Pydantic model to parse into.
|
733
|
+
operative (Operative, optional):
|
734
|
+
An `Operative` object with known request model and settings.
|
735
|
+
similarity_algo (str):
|
736
|
+
Algorithm name for fuzzy field matching.
|
737
|
+
similarity_threshold (float):
|
738
|
+
Threshold for matching (0.0 - 1.0).
|
739
|
+
fuzzy_match (bool):
|
740
|
+
Whether to attempt fuzzy matching for unmatched fields.
|
741
|
+
handle_unmatched (Literal["ignore","raise","remove","fill","force"]):
|
742
|
+
Policy for unrecognized fields (default: "force").
|
743
|
+
fill_value (Any):
|
744
|
+
Default placeholder for missing fields (if fill is used).
|
745
|
+
fill_mapping (dict[str, Any] | None):
|
746
|
+
A mapping of specific fields to fill values.
|
747
|
+
strict (bool):
|
748
|
+
If True, raises errors on ambiguous fields or data types.
|
749
|
+
suppress_conversion_errors (bool):
|
750
|
+
If True, logs or ignores conversion errors instead of raising.
|
1005
751
|
|
1006
752
|
Returns:
|
1007
|
-
|
753
|
+
BaseModel | dict | str | None:
|
754
|
+
Parsed model instance, or a fallback based on `handle_validation`.
|
1008
755
|
"""
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
756
|
+
from lionagi.operations.parse.parse import parse
|
757
|
+
|
758
|
+
return await parse(
|
759
|
+
self,
|
760
|
+
text=text,
|
761
|
+
handle_validation=handle_validation,
|
762
|
+
max_retries=max_retries,
|
763
|
+
request_type=request_type,
|
764
|
+
operative=operative,
|
765
|
+
similarity_algo=similarity_algo,
|
766
|
+
similarity_threshold=similarity_threshold,
|
767
|
+
fuzzy_match=fuzzy_match,
|
768
|
+
handle_unmatched=handle_unmatched,
|
769
|
+
fill_value=fill_value,
|
770
|
+
fill_mapping=fill_mapping,
|
771
|
+
strict=strict,
|
772
|
+
suppress_conversion_errors=suppress_conversion_errors,
|
773
|
+
response_format=response_format,
|
1021
774
|
)
|
1022
|
-
branch_clone = Branch(
|
1023
|
-
system=system,
|
1024
|
-
user=self.user,
|
1025
|
-
messages=[i.clone() for i in self.msgs.messages],
|
1026
|
-
tools=tools,
|
1027
|
-
metadata={"clone_from": self},
|
1028
|
-
)
|
1029
|
-
for message in branch_clone.msgs.messages:
|
1030
|
-
message.sender = sender or self.id
|
1031
|
-
message.recipient = branch_clone.id
|
1032
|
-
return branch_clone
|
1033
775
|
|
1034
|
-
def
|
1035
|
-
|
776
|
+
async def operate(
|
777
|
+
self,
|
778
|
+
*,
|
779
|
+
instruct: Instruct = None,
|
780
|
+
instruction: Instruction | JsonValue = None,
|
781
|
+
guidance: JsonValue = None,
|
782
|
+
context: JsonValue = None,
|
783
|
+
sender: SenderRecipient = None,
|
784
|
+
recipient: SenderRecipient = None,
|
785
|
+
progression: Progression = None,
|
786
|
+
imodel: iModel = None, # deprecated, alias of chat_model
|
787
|
+
chat_model: iModel = None,
|
788
|
+
invoke_actions: bool = True,
|
789
|
+
tool_schemas: list[dict] = None,
|
790
|
+
images: list = None,
|
791
|
+
image_detail: Literal["low", "high", "auto"] = None,
|
792
|
+
parse_model: iModel = None,
|
793
|
+
skip_validation: bool = False,
|
794
|
+
tools: ToolRef = None,
|
795
|
+
operative: Operative = None,
|
796
|
+
response_format: type[
|
797
|
+
BaseModel
|
798
|
+
] = None, # alias of operative.request_type
|
799
|
+
return_operative: bool = False,
|
800
|
+
actions: bool = False,
|
801
|
+
reason: bool = False,
|
802
|
+
action_kwargs: dict = None,
|
803
|
+
field_models: list[FieldModel] = None,
|
804
|
+
exclude_fields: list | dict | None = None,
|
805
|
+
request_params: ModelParams = None,
|
806
|
+
request_param_kwargs: dict = None,
|
807
|
+
response_params: ModelParams = None,
|
808
|
+
response_param_kwargs: dict = None,
|
809
|
+
handle_validation: Literal[
|
810
|
+
"raise", "return_value", "return_none"
|
811
|
+
] = "return_value",
|
812
|
+
operative_model: type[BaseModel] = None,
|
813
|
+
request_model: type[BaseModel] = None,
|
814
|
+
**kwargs,
|
815
|
+
) -> list | BaseModel | None | dict | str:
|
816
|
+
"""
|
817
|
+
Orchestrates an "operate" flow with optional tool invocation and
|
818
|
+
structured response validation.
|
819
|
+
|
820
|
+
**Workflow**:
|
821
|
+
1) Builds or updates an `Operative` object to specify how the LLM should respond.
|
822
|
+
2) Sends an instruction (`instruct`) or direct `instruction` text to `branch.chat()`.
|
823
|
+
3) Optionally validates/parses the result into a model or dictionary.
|
824
|
+
4) If `invoke_actions=True`, any requested tool calls are automatically invoked.
|
825
|
+
5) Returns either the final structure, raw response, or an updated `Operative`.
|
1036
826
|
|
1037
827
|
Args:
|
828
|
+
branch (Branch):
|
829
|
+
The active branch that orchestrates messages, models, and logs.
|
830
|
+
instruct (Instruct, optional):
|
831
|
+
Contains the instruction, guidance, context, etc. If not provided,
|
832
|
+
uses `instruction`, `guidance`, `context` directly.
|
833
|
+
instruction (Instruction | JsonValue, optional):
|
834
|
+
The main user instruction or content for the LLM.
|
835
|
+
guidance (JsonValue, optional):
|
836
|
+
Additional system or user instructions.
|
837
|
+
context (JsonValue, optional):
|
838
|
+
Extra context data.
|
839
|
+
sender (SenderRecipient, optional):
|
840
|
+
The sender ID for newly added messages.
|
841
|
+
recipient (SenderRecipient, optional):
|
842
|
+
The recipient ID for newly added messages.
|
1038
843
|
progression (Progression, optional):
|
1039
|
-
|
1040
|
-
|
844
|
+
Custom ordering of conversation messages.
|
845
|
+
imodel (iModel, deprecated):
|
846
|
+
Alias of `chat_model`.
|
847
|
+
chat_model (iModel, optional):
|
848
|
+
The LLM used for the main chat operation. Defaults to `branch.chat_model`.
|
849
|
+
invoke_actions (bool, optional):
|
850
|
+
If `True`, executes any requested tools found in the LLM's response.
|
851
|
+
tool_schemas (list[dict], optional):
|
852
|
+
Additional schema definitions for tool-based function-calling.
|
853
|
+
images (list, optional):
|
854
|
+
Optional images appended to the LLM context.
|
855
|
+
image_detail (Literal["low","high","auto"], optional):
|
856
|
+
The level of image detail, if relevant.
|
857
|
+
parse_model (iModel, optional):
|
858
|
+
Model used for deeper or specialized parsing, if needed.
|
859
|
+
skip_validation (bool, optional):
|
860
|
+
If `True`, bypasses final validation and returns raw text or partial structure.
|
861
|
+
tools (ToolRef, optional):
|
862
|
+
Tools to be registered or made available if `invoke_actions` is True.
|
863
|
+
operative (Operative, optional):
|
864
|
+
If provided, reuses an existing operative's config for parsing/validation.
|
865
|
+
response_format (type[BaseModel], optional):
|
866
|
+
Expected Pydantic model for the final response (alias for `operative.request_type`).
|
867
|
+
return_operative (bool, optional):
|
868
|
+
If `True`, returns the entire `Operative` object after processing
|
869
|
+
rather than the structured or raw output.
|
870
|
+
actions (bool, optional):
|
871
|
+
If `True`, signals that function-calling or "action" usage is expected.
|
872
|
+
reason (bool, optional):
|
873
|
+
If `True`, signals that the LLM should provide chain-of-thought or reasoning (where applicable).
|
874
|
+
action_kwargs (dict | None, optional):
|
875
|
+
Additional parameters for the `branch.act()` call if tools are invoked.
|
876
|
+
field_models (list[FieldModel] | None, optional):
|
877
|
+
Field-level definitions or overrides for the model schema.
|
878
|
+
exclude_fields (list|dict|None, optional):
|
879
|
+
Which fields to exclude from final validation or model building.
|
880
|
+
request_params (ModelParams | None, optional):
|
881
|
+
Extra config for building the request model in the operative.
|
882
|
+
request_param_kwargs (dict|None, optional):
|
883
|
+
Additional kwargs passed to the `ModelParams` constructor for the request.
|
884
|
+
response_params (ModelParams | None, optional):
|
885
|
+
Config for building the response model after actions.
|
886
|
+
response_param_kwargs (dict|None, optional):
|
887
|
+
Additional kwargs passed to the `ModelParams` constructor for the response.
|
888
|
+
handle_validation (Literal["raise","return_value","return_none"], optional):
|
889
|
+
How to handle parsing failures (default: "return_value").
|
890
|
+
operative_model (type[BaseModel], deprecated):
|
891
|
+
Alias for `response_format`.
|
892
|
+
request_model (type[BaseModel], optional):
|
893
|
+
Another alias for `response_format`.
|
894
|
+
**kwargs:
|
895
|
+
Additional keyword arguments passed to the LLM via `branch.chat()`.
|
1041
896
|
|
1042
897
|
Returns:
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
898
|
+
list | BaseModel | None | dict | str:
|
899
|
+
- The parsed or raw response from the LLM,
|
900
|
+
- `None` if validation fails and `handle_validation='return_none'`,
|
901
|
+
- or the entire `Operative` object if `return_operative=True`.
|
902
|
+
|
903
|
+
Raises:
|
904
|
+
ValueError:
|
905
|
+
- If both `operative_model` and `response_format` or `request_model` are given.
|
906
|
+
- If the LLM's response cannot be parsed into the expected format and `handle_validation='raise'`.
|
1046
907
|
"""
|
1047
|
-
|
1048
|
-
progression = self.msgs.progression
|
908
|
+
from lionagi.operations.operate.operate import operate
|
1049
909
|
|
1050
|
-
|
1051
|
-
self
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
910
|
+
return await operate(
|
911
|
+
self,
|
912
|
+
instruct=instruct,
|
913
|
+
instruction=instruction,
|
914
|
+
guidance=guidance,
|
915
|
+
context=context,
|
916
|
+
sender=sender,
|
917
|
+
recipient=recipient,
|
918
|
+
progression=progression,
|
919
|
+
chat_model=chat_model,
|
920
|
+
invoke_actions=invoke_actions,
|
921
|
+
tool_schemas=tool_schemas,
|
922
|
+
images=images,
|
923
|
+
image_detail=image_detail,
|
924
|
+
parse_model=parse_model,
|
925
|
+
skip_validation=skip_validation,
|
926
|
+
tools=tools,
|
927
|
+
operative=operative,
|
928
|
+
response_format=response_format,
|
929
|
+
return_operative=return_operative,
|
930
|
+
actions=actions,
|
931
|
+
reason=reason,
|
932
|
+
action_kwargs=action_kwargs,
|
933
|
+
field_models=field_models,
|
934
|
+
exclude_fields=exclude_fields,
|
935
|
+
request_params=request_params,
|
936
|
+
request_param_kwargs=request_param_kwargs,
|
937
|
+
response_params=response_params,
|
938
|
+
response_param_kwargs=response_param_kwargs,
|
939
|
+
handle_validation=handle_validation,
|
940
|
+
operative_model=operative_model,
|
941
|
+
request_model=request_model,
|
942
|
+
imodel=imodel,
|
943
|
+
**kwargs,
|
944
|
+
)
|
1057
945
|
|
1058
|
-
async def
|
1059
|
-
|
946
|
+
async def communicate(
|
947
|
+
self,
|
948
|
+
instruction: Instruction | JsonValue = None,
|
949
|
+
*,
|
950
|
+
guidance: JsonValue = None,
|
951
|
+
context: JsonValue = None,
|
952
|
+
plain_content: str = None,
|
953
|
+
sender: SenderRecipient = None,
|
954
|
+
recipient: SenderRecipient = None,
|
955
|
+
progression: ID.IDSeq = None,
|
956
|
+
request_model: type[BaseModel] | BaseModel | None = None,
|
957
|
+
response_format: type[BaseModel] = None,
|
958
|
+
request_fields: dict | list[str] = None,
|
959
|
+
imodel: iModel = None, # alias of chat_model
|
960
|
+
chat_model: iModel = None,
|
961
|
+
parse_model: iModel = None,
|
962
|
+
skip_validation: bool = False,
|
963
|
+
images: list = None,
|
964
|
+
image_detail: Literal["low", "high", "auto"] = None,
|
965
|
+
num_parse_retries: int = 3,
|
966
|
+
fuzzy_match_kwargs: dict = None,
|
967
|
+
clear_messages: bool = False,
|
968
|
+
operative_model: type[BaseModel] = None,
|
969
|
+
**kwargs,
|
970
|
+
):
|
971
|
+
"""
|
972
|
+
A simpler orchestration than `operate()`, typically without tool invocation.
|
1060
973
|
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
974
|
+
**Flow**:
|
975
|
+
1. Sends an instruction (or conversation) to the chat model.
|
976
|
+
2. Optionally parses the response into a structured model or fields.
|
977
|
+
3. Returns either the raw string, the parsed model, or a dict of fields.
|
1064
978
|
|
1065
979
|
Args:
|
1066
|
-
|
1067
|
-
The
|
980
|
+
instruction (Instruction | dict, optional):
|
981
|
+
The user's main query or data.
|
982
|
+
guidance (JsonValue, optional):
|
983
|
+
Additional instructions or context for the LLM.
|
984
|
+
context (JsonValue, optional):
|
985
|
+
Extra data or context.
|
986
|
+
plain_content (str, optional):
|
987
|
+
Plain text content appended to the instruction.
|
988
|
+
sender (SenderRecipient, optional):
|
989
|
+
Sender ID (defaults to `Branch.user`).
|
990
|
+
recipient (SenderRecipient, optional):
|
991
|
+
Recipient ID (defaults to `self.id`).
|
992
|
+
progression (ID.IDSeq, optional):
|
993
|
+
Custom ordering of messages.
|
994
|
+
request_model (type[BaseModel] | BaseModel | None, optional):
|
995
|
+
Model for validating or structuring the LLM's response.
|
996
|
+
response_format (type[BaseModel], optional):
|
997
|
+
Alias for `request_model`. If both are provided, raises ValueError.
|
998
|
+
request_fields (dict|list[str], optional):
|
999
|
+
If you only need certain fields from the LLM's response.
|
1000
|
+
imodel (iModel, optional):
|
1001
|
+
Deprecated alias for `chat_model`.
|
1002
|
+
chat_model (iModel, optional):
|
1003
|
+
An alternative to the default chat model.
|
1004
|
+
parse_model (iModel, optional):
|
1005
|
+
If parsing is needed, you can override the default parse model.
|
1006
|
+
skip_validation (bool, optional):
|
1007
|
+
If True, returns the raw response string unvalidated.
|
1008
|
+
images (list, optional):
|
1009
|
+
Any relevant images.
|
1010
|
+
image_detail (Literal["low","high","auto"], optional):
|
1011
|
+
Image detail level (if used).
|
1012
|
+
num_parse_retries (int, optional):
|
1013
|
+
Maximum parsing retries (capped at 5).
|
1014
|
+
fuzzy_match_kwargs (dict, optional):
|
1015
|
+
Additional settings for fuzzy field matching (if used).
|
1016
|
+
clear_messages (bool, optional):
|
1017
|
+
Whether to clear stored messages before sending.
|
1018
|
+
operative_model (type[BaseModel], optional):
|
1019
|
+
Deprecated, alias for `response_format`.
|
1068
1020
|
**kwargs:
|
1069
|
-
Additional arguments for
|
1021
|
+
Additional arguments for the underlying LLM call.
|
1070
1022
|
|
1071
1023
|
Returns:
|
1072
|
-
Any:
|
1024
|
+
Any:
|
1025
|
+
- Raw string (if `skip_validation=True`),
|
1026
|
+
- A validated Pydantic model,
|
1027
|
+
- A dict of the requested fields,
|
1028
|
+
- or `None` if parsing fails and `handle_validation='return_none'`.
|
1073
1029
|
"""
|
1074
|
-
|
1075
|
-
if any(i in config and config[i] for i in Instruct.reserved_kwargs):
|
1076
|
-
if "response_format" in config or "request_model" in config:
|
1077
|
-
return await self.operate(**config)
|
1078
|
-
for i in Instruct.reserved_kwargs:
|
1079
|
-
config.pop(i, None)
|
1030
|
+
from lionagi.operations.communicate.communicate import communicate
|
1080
1031
|
|
1081
|
-
return await
|
1032
|
+
return await communicate(
|
1033
|
+
self,
|
1034
|
+
instruction=instruction,
|
1035
|
+
guidance=guidance,
|
1036
|
+
context=context,
|
1037
|
+
plain_content=plain_content,
|
1038
|
+
sender=sender,
|
1039
|
+
recipient=recipient,
|
1040
|
+
progression=progression,
|
1041
|
+
request_model=request_model,
|
1042
|
+
response_format=response_format,
|
1043
|
+
request_fields=request_fields,
|
1044
|
+
chat_model=chat_model or imodel,
|
1045
|
+
parse_model=parse_model,
|
1046
|
+
skip_validation=skip_validation,
|
1047
|
+
images=images,
|
1048
|
+
image_detail=image_detail,
|
1049
|
+
num_parse_retries=num_parse_retries,
|
1050
|
+
fuzzy_match_kwargs=fuzzy_match_kwargs,
|
1051
|
+
clear_messages=clear_messages,
|
1052
|
+
operative_model=operative_model,
|
1053
|
+
**kwargs,
|
1054
|
+
)
|
1082
1055
|
|
1083
|
-
def
|
1056
|
+
async def _act(
|
1084
1057
|
self,
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
"""Sends a package (wrapped in Mail) to a specific recipient.
|
1058
|
+
action_request: ActionRequest | BaseModel | dict,
|
1059
|
+
suppress_errors: bool = False,
|
1060
|
+
) -> ActionResponse:
|
1061
|
+
"""
|
1062
|
+
Internal method to invoke a tool (action) asynchronously.
|
1091
1063
|
|
1092
1064
|
Args:
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
The ID that requested this send (if any).
|
1065
|
+
action_request (ActionRequest|BaseModel|dict):
|
1066
|
+
Must contain `function` and `arguments`.
|
1067
|
+
suppress_errors (bool, optional):
|
1068
|
+
If True, errors are logged instead of raised.
|
1069
|
+
|
1070
|
+
Returns:
|
1071
|
+
ActionResponse: Result of the tool invocation or `None` if suppressed.
|
1101
1072
|
"""
|
1102
|
-
|
1103
|
-
category=category,
|
1104
|
-
item=item,
|
1105
|
-
request_source=request_source,
|
1106
|
-
)
|
1073
|
+
from lionagi.operations._act.act import _act
|
1107
1074
|
|
1108
|
-
|
1109
|
-
sender=self.id,
|
1110
|
-
recipient=recipient,
|
1111
|
-
package=package,
|
1112
|
-
)
|
1113
|
-
self.mailbox.append_out(mail)
|
1075
|
+
return await _act(self, action_request, suppress_errors)
|
1114
1076
|
|
1115
|
-
def
|
1077
|
+
async def act(
|
1116
1078
|
self,
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1079
|
+
action_request: list | ActionRequest | BaseModel | dict,
|
1080
|
+
*,
|
1081
|
+
suppress_errors: bool = True,
|
1082
|
+
sanitize_input: bool = False,
|
1083
|
+
unique_input: bool = False,
|
1084
|
+
num_retries: int = 0,
|
1085
|
+
initial_delay: float = 0,
|
1086
|
+
retry_delay: float = 0,
|
1087
|
+
backoff_factor: float = 1,
|
1088
|
+
retry_default: Any = UNDEFINED,
|
1089
|
+
retry_timeout: float | None = None,
|
1090
|
+
retry_timing: bool = False,
|
1091
|
+
max_concurrent: int | None = None,
|
1092
|
+
throttle_period: float | None = None,
|
1093
|
+
flatten: bool = True,
|
1094
|
+
dropna: bool = True,
|
1095
|
+
unique_output: bool = False,
|
1096
|
+
flatten_tuple_set: bool = False,
|
1097
|
+
) -> list[ActionResponse] | ActionResponse | Any:
|
1098
|
+
"""
|
1099
|
+
Public, potentially batched, asynchronous interface to run one or multiple action requests.
|
1123
1100
|
|
1124
1101
|
Args:
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1102
|
+
action_request (list|ActionRequest|BaseModel|dict):
|
1103
|
+
A single or list of action requests, each requiring
|
1104
|
+
`function` and `arguments`.
|
1105
|
+
suppress_errors (bool):
|
1106
|
+
If True, log errors instead of raising exceptions.
|
1107
|
+
sanitize_input (bool):
|
1108
|
+
Reserved. Potentially sanitize the action arguments.
|
1109
|
+
unique_input (bool):
|
1110
|
+
Reserved. Filter out duplicate requests.
|
1111
|
+
num_retries (int):
|
1112
|
+
Number of times to retry on failure (default 0).
|
1113
|
+
initial_delay (float):
|
1114
|
+
Delay before first attempt (seconds).
|
1115
|
+
retry_delay (float):
|
1116
|
+
Base delay between retries.
|
1117
|
+
backoff_factor (float):
|
1118
|
+
Multiplier for the `retry_delay` after each attempt.
|
1119
|
+
retry_default (Any):
|
1120
|
+
Fallback value if all retries fail (if suppressing errors).
|
1121
|
+
retry_timeout (float|None):
|
1122
|
+
Overall timeout for all attempts (None = no limit).
|
1123
|
+
retry_timing (bool):
|
1124
|
+
If True, track time used for retries.
|
1125
|
+
max_concurrent (int|None):
|
1126
|
+
Maximum concurrent tasks (if batching).
|
1127
|
+
throttle_period (float|None):
|
1128
|
+
Minimum spacing (in seconds) between requests.
|
1129
|
+
flatten (bool):
|
1130
|
+
If a list of results is returned, flatten them if possible.
|
1131
|
+
dropna (bool):
|
1132
|
+
Remove `None` or invalid results from final output if True.
|
1133
|
+
unique_output (bool):
|
1134
|
+
Only return unique results if True.
|
1135
|
+
flatten_tuple_set (bool):
|
1136
|
+
Flatten nested tuples in results if True.
|
1133
1137
|
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
for the chosen processing options.
|
1138
|
+
Returns:
|
1139
|
+
Any:
|
1140
|
+
The result or results from the invoked tool(s).
|
1138
1141
|
"""
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
while self.mailbox.pending_ins[sender]:
|
1144
|
-
mail_id = self.mailbox.pending_ins[sender].popleft()
|
1145
|
-
mail: Mail = self.mailbox.pile_[mail_id]
|
1146
|
-
|
1147
|
-
if mail.category == "message" and message:
|
1148
|
-
if not isinstance(mail.package.item, RoledMessage):
|
1149
|
-
raise ValueError("Invalid message format")
|
1150
|
-
new_message = mail.package.item.clone()
|
1151
|
-
new_message.sender = mail.sender
|
1152
|
-
new_message.recipient = self.id
|
1153
|
-
self.msgs.messages.include(new_message)
|
1154
|
-
self.mailbox.pile_.pop(mail_id)
|
1155
|
-
|
1156
|
-
elif mail.category == "tool" and tool:
|
1157
|
-
if not isinstance(mail.package.item, Tool):
|
1158
|
-
raise ValueError("Invalid tools format")
|
1159
|
-
self._action_manager.register_tools(mail.package.item)
|
1160
|
-
self.mailbox.pile_.pop(mail_id)
|
1161
|
-
|
1162
|
-
elif mail.category == "imodel" and imodel:
|
1163
|
-
if not isinstance(mail.package.item, iModel):
|
1164
|
-
raise ValueError("Invalid iModel format")
|
1165
|
-
self._imodel_manager.register_imodel(
|
1166
|
-
imodel.name or "chat", mail.package.item
|
1167
|
-
)
|
1168
|
-
self.mailbox.pile_.pop(mail_id)
|
1142
|
+
params = locals()
|
1143
|
+
params.pop("self")
|
1144
|
+
params.pop("action_request")
|
1145
|
+
return await alcall(action_request, self._act, **params)
|
1169
1146
|
|
1170
|
-
|
1171
|
-
|
1147
|
+
async def translate(
|
1148
|
+
self,
|
1149
|
+
text: str,
|
1150
|
+
technique: Literal["SynthLang"] = "SynthLang",
|
1151
|
+
technique_kwargs: dict = None,
|
1152
|
+
compress: bool = False,
|
1153
|
+
chat_model: iModel = None,
|
1154
|
+
compress_model: iModel = None,
|
1155
|
+
compression_ratio: float = 0.2,
|
1156
|
+
compress_kwargs=None,
|
1157
|
+
verbose: bool = True,
|
1158
|
+
new_branch: bool = True,
|
1159
|
+
**kwargs,
|
1160
|
+
) -> str:
|
1161
|
+
"""
|
1162
|
+
An example "translate" operation that transforms text using a chosen technique
|
1163
|
+
(e.g., "SynthLang"). Optionally compresses text with a custom `compress_model`.
|
1172
1164
|
|
1173
|
-
|
1165
|
+
Args:
|
1166
|
+
text (str):
|
1167
|
+
The text to be translated or transformed.
|
1168
|
+
technique (Literal["SynthLang"]):
|
1169
|
+
The translation/transform technique (currently only "SynthLang").
|
1170
|
+
technique_kwargs (dict, optional):
|
1171
|
+
Additional parameters for the chosen technique.
|
1172
|
+
compress (bool):
|
1173
|
+
Whether to compress the resulting text further.
|
1174
|
+
chat_model (iModel, optional):
|
1175
|
+
A custom model for the translation step (defaults to self.chat_model).
|
1176
|
+
compress_model (iModel, optional):
|
1177
|
+
A separate model for compression (if `compress=True`).
|
1178
|
+
compression_ratio (float):
|
1179
|
+
Desired compression ratio if compressing text (0.0 - 1.0).
|
1180
|
+
compress_kwargs (dict, optional):
|
1181
|
+
Additional arguments for the compression step.
|
1182
|
+
verbose (bool):
|
1183
|
+
If True, prints debug/logging info.
|
1184
|
+
new_branch (bool):
|
1185
|
+
If True, performs the translation in a new branch context.
|
1186
|
+
**kwargs:
|
1187
|
+
Additional parameters passed through to the technique function.
|
1174
1188
|
|
1175
|
-
|
1176
|
-
|
1189
|
+
Returns:
|
1190
|
+
str: The transformed (and optionally compressed) text.
|
1191
|
+
"""
|
1192
|
+
from lionagi.operations.translate.translate import translate
|
1193
|
+
|
1194
|
+
return await translate(
|
1195
|
+
branch=self,
|
1196
|
+
text=text,
|
1197
|
+
technique=technique,
|
1198
|
+
technique_kwargs=technique_kwargs,
|
1199
|
+
compress=compress,
|
1200
|
+
chat_model=chat_model,
|
1201
|
+
compress_model=compress_model,
|
1202
|
+
compression_ratio=compression_ratio,
|
1203
|
+
compress_kwargs=compress_kwargs,
|
1204
|
+
verbose=verbose,
|
1205
|
+
new_branch=new_branch,
|
1206
|
+
**kwargs,
|
1207
|
+
)
|
1177
1208
|
|
1178
|
-
async def
|
1209
|
+
async def select(
|
1179
1210
|
self,
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1211
|
+
instruct: Instruct | dict[str, Any],
|
1212
|
+
choices: list[str] | type[Enum] | dict[str, Any],
|
1213
|
+
max_num_selections: int = 1,
|
1214
|
+
branch_kwargs: dict[str, Any] | None = None,
|
1215
|
+
verbose: bool = False,
|
1216
|
+
**kwargs: Any,
|
1184
1217
|
):
|
1185
|
-
"""
|
1218
|
+
"""
|
1219
|
+
Performs a selection operation from given choices using an LLM-driven approach.
|
1186
1220
|
|
1187
1221
|
Args:
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1222
|
+
instruct (Instruct|dict[str, Any]):
|
1223
|
+
The instruction model or dictionary for the LLM call.
|
1224
|
+
choices (list[str]|type[Enum]|dict[str,Any]):
|
1225
|
+
The set of options to choose from.
|
1226
|
+
max_num_selections (int):
|
1227
|
+
Maximum allowed selections (default = 1).
|
1228
|
+
branch_kwargs (dict[str, Any]|None):
|
1229
|
+
Extra arguments to create or configure a new branch if needed.
|
1230
|
+
verbose (bool):
|
1231
|
+
If True, prints debug info.
|
1232
|
+
**kwargs:
|
1233
|
+
Additional arguments for the underlying `operate()` call.
|
1234
|
+
|
1235
|
+
Returns:
|
1236
|
+
Any:
|
1237
|
+
A `SelectionModel` or similar that indicates the user's choice(s).
|
1192
1238
|
"""
|
1193
|
-
|
1194
|
-
|
1239
|
+
from lionagi.operations.select.select import select
|
1240
|
+
|
1241
|
+
return await select(
|
1242
|
+
branch=self,
|
1243
|
+
instruct=instruct,
|
1244
|
+
choices=choices,
|
1245
|
+
max_num_selections=max_num_selections,
|
1246
|
+
branch_kwargs=branch_kwargs,
|
1247
|
+
verbose=verbose,
|
1248
|
+
**kwargs,
|
1249
|
+
)
|
1195
1250
|
|
1196
|
-
async def
|
1251
|
+
async def compress(
|
1197
1252
|
self,
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1253
|
+
text: str,
|
1254
|
+
system_msg: str = None,
|
1255
|
+
target_ratio: float = 0.2,
|
1256
|
+
n_samples: int = 5,
|
1257
|
+
max_tokens_per_sample=80,
|
1258
|
+
verbose=True,
|
1259
|
+
) -> str:
|
1260
|
+
"""
|
1261
|
+
Uses the `chat_model`'s built-in compression routine to shorten text.
|
1204
1262
|
|
1205
1263
|
Args:
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1264
|
+
text (str):
|
1265
|
+
The text to compress.
|
1266
|
+
system_msg (str, optional):
|
1267
|
+
System-level instructions, appended to the prompt.
|
1268
|
+
target_ratio (float):
|
1269
|
+
Desired compression ratio (0.0-1.0).
|
1270
|
+
n_samples (int):
|
1271
|
+
How many compression attempts to combine or evaluate.
|
1272
|
+
max_tokens_per_sample (int):
|
1273
|
+
Max token count per sample chunk.
|
1274
|
+
verbose (bool):
|
1275
|
+
If True, logs or prints progress.
|
1276
|
+
|
1277
|
+
Returns:
|
1278
|
+
str: The compressed text.
|
1210
1279
|
"""
|
1211
|
-
|
1212
|
-
|
1280
|
+
return await self.chat_model.compress_text(
|
1281
|
+
text=text,
|
1282
|
+
system_msg=system_msg,
|
1283
|
+
target_ratio=target_ratio,
|
1284
|
+
n_samples=n_samples,
|
1285
|
+
max_tokens_per_sample=max_tokens_per_sample,
|
1286
|
+
verbose=verbose,
|
1287
|
+
)
|
1213
1288
|
|
1214
|
-
def
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1289
|
+
async def interpret(
|
1290
|
+
self,
|
1291
|
+
text: str,
|
1292
|
+
domain: str | None = None,
|
1293
|
+
style: str | None = None,
|
1294
|
+
**kwargs,
|
1295
|
+
) -> str:
|
1296
|
+
"""
|
1297
|
+
Interprets (rewrites) a user's raw input into a more formal or structured
|
1298
|
+
LLM prompt. This function can be seen as a "prompt translator," which
|
1299
|
+
ensures the user's original query is clarified or enhanced for better
|
1300
|
+
LLM responses.
|
1218
1301
|
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1302
|
+
The method calls `branch.communicate()` behind the scenes with a system prompt
|
1303
|
+
that instructs the LLM to rewrite the input. You can provide additional
|
1304
|
+
parameters in `**kwargs` (e.g., `parse_model`, `skip_validation`, etc.)
|
1305
|
+
if you want to shape how the rewriting is done.
|
1222
1306
|
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1307
|
+
Args:
|
1308
|
+
branch (Branch):
|
1309
|
+
The active branch context for messages, logging, etc.
|
1310
|
+
text (str):
|
1311
|
+
The raw user input or question that needs interpreting.
|
1312
|
+
domain (str | None, optional):
|
1313
|
+
Optional domain hint (e.g. "finance", "marketing", "devops").
|
1314
|
+
The LLM can use this hint to tailor its rewriting approach.
|
1315
|
+
style (str | None, optional):
|
1316
|
+
Optional style hint (e.g. "concise", "detailed").
|
1317
|
+
**kwargs:
|
1318
|
+
Additional arguments passed to `branch.communicate()`,
|
1319
|
+
such as `parse_model`, `skip_validation`, `temperature`, etc.
|
1320
|
+
|
1321
|
+
Returns:
|
1322
|
+
str:
|
1323
|
+
A refined or "improved" user prompt string, suitable for feeding
|
1324
|
+
back into the LLM as a clearer instruction.
|
1325
|
+
|
1326
|
+
Example:
|
1327
|
+
refined = await interpret(
|
1328
|
+
branch=my_branch, text="How do I do marketing analytics?",
|
1329
|
+
domain="marketing", style="detailed"
|
1330
|
+
)
|
1331
|
+
# refined might be "Explain step-by-step how to set up a marketing analytics
|
1332
|
+
# pipeline to track campaign performance..."
|
1333
|
+
"""
|
1334
|
+
from lionagi.operations.interpret.interpret import interpret
|
1335
|
+
|
1336
|
+
return await interpret(
|
1337
|
+
self, text=text, domain=domain, style=style, **kwargs
|
1234
1338
|
)
|
1235
1339
|
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1340
|
+
async def instruct(
|
1341
|
+
self,
|
1342
|
+
instruct: Instruct,
|
1343
|
+
/,
|
1344
|
+
**kwargs,
|
1345
|
+
):
|
1346
|
+
"""
|
1347
|
+
A convenience method that chooses between `operate()` and `communicate()`
|
1348
|
+
based on the contents of an `Instruct` object.
|
1245
1349
|
|
1246
|
-
|
1350
|
+
If the `Instruct` indicates tool usage or advanced response format,
|
1351
|
+
`operate()` is used. Otherwise, it defaults to `communicate()`.
|
1247
1352
|
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
"chat_model": data.pop("chat_model", UNDEFINED),
|
1254
|
-
"parse_model": data.pop("parse_model", UNDEFINED),
|
1255
|
-
"system": data.pop("system", UNDEFINED),
|
1256
|
-
"log_config": data.pop("log_config", UNDEFINED),
|
1257
|
-
}
|
1258
|
-
params = {}
|
1259
|
-
for k, v in data.items():
|
1260
|
-
if isinstance(v, dict) and "id" in v:
|
1261
|
-
params.update(v)
|
1262
|
-
else:
|
1263
|
-
params[k] = v
|
1353
|
+
Args:
|
1354
|
+
instruct (Instruct):
|
1355
|
+
An object containing `instruction`, `guidance`, `context`, etc.
|
1356
|
+
**kwargs:
|
1357
|
+
Additional args forwarded to `operate()` or `communicate()`.
|
1264
1358
|
|
1265
|
-
|
1266
|
-
|
1359
|
+
Returns:
|
1360
|
+
Any:
|
1361
|
+
The result of the underlying call (structured object, raw text, etc.).
|
1362
|
+
"""
|
1363
|
+
from lionagi.operations.instruct.instruct import instruct as _ins
|
1267
1364
|
|
1268
|
-
|
1269
|
-
"""Receives mail from all senders."""
|
1270
|
-
for key in self.mailbox.pending_ins:
|
1271
|
-
self.receive(key)
|
1365
|
+
return await _ins(self, instruct, **kwargs)
|
1272
1366
|
|
1273
|
-
def
|
1367
|
+
async def ReAct(
|
1274
1368
|
self,
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1369
|
+
instruct: Instruct | dict[str, Any],
|
1370
|
+
interpret: bool = False,
|
1371
|
+
tools: Any = None,
|
1372
|
+
tool_schemas: Any = None,
|
1373
|
+
response_format: type[BaseModel] = None,
|
1374
|
+
extension_allowed: bool = False,
|
1375
|
+
max_extensions: int = None,
|
1376
|
+
response_kwargs: dict | None = None,
|
1377
|
+
return_analysis: bool = False,
|
1378
|
+
analysis_model: iModel | None = None,
|
1379
|
+
**kwargs,
|
1380
|
+
):
|
1381
|
+
"""
|
1382
|
+
Performs a multi-step "ReAct" flow (inspired by the ReAct paradigm in LLM usage),
|
1383
|
+
which may include:
|
1384
|
+
1) Optionally interpreting the user's original instructions via `branch.interpret()`.
|
1385
|
+
2) Generating chain-of-thought analysis or reasoning using a specialized schema (`ReActAnalysis`).
|
1386
|
+
3) Optionally expanding the conversation multiple times if the analysis indicates more steps (extensions).
|
1387
|
+
4) Producing a final answer by invoking the branch's `instruct()` method.
|
1388
|
+
|
1389
|
+
Args:
|
1390
|
+
branch (Branch):
|
1391
|
+
The active branch context that orchestrates messages, models, and actions.
|
1392
|
+
instruct (Instruct | dict[str, Any]):
|
1393
|
+
The user's instruction object or a dict with equivalent keys.
|
1394
|
+
interpret (bool, optional):
|
1395
|
+
If `True`, first interprets (`branch.interpret`) the instructions to refine them
|
1396
|
+
before proceeding. Defaults to `False`.
|
1397
|
+
tools (Any, optional):
|
1398
|
+
Tools to be made available for the ReAct process. If omitted or `None`,
|
1399
|
+
and if no `tool_schemas` are provided, it defaults to `True` (all tools).
|
1400
|
+
tool_schemas (Any, optional):
|
1401
|
+
Additional or custom schemas for tools if function calling is needed.
|
1402
|
+
response_format (type[BaseModel], optional):
|
1403
|
+
The final schema for the user-facing output after the ReAct expansions.
|
1404
|
+
If `None`, the output is raw text or an unstructured response.
|
1405
|
+
extension_allowed (bool, optional):
|
1406
|
+
Whether to allow multiple expansions if the analysis indicates more steps.
|
1407
|
+
Defaults to `False`.
|
1408
|
+
max_extensions (int | None, optional):
|
1409
|
+
The max number of expansions if `extension_allowed` is `True`.
|
1410
|
+
If omitted, no upper limit is enforced (other than logic).
|
1411
|
+
response_kwargs (dict | None, optional):
|
1412
|
+
Extra kwargs passed into the final `_instruct()` call that produces the
|
1413
|
+
final output. Defaults to `None`.
|
1414
|
+
return_analysis (bool, optional):
|
1415
|
+
If `True`, returns both the final output and the list of analysis objects
|
1416
|
+
produced throughout expansions. Defaults to `False`.
|
1417
|
+
analysis_model (iModel | None, optional):
|
1418
|
+
A custom LLM model for generating the ReAct analysis steps. If `None`,
|
1419
|
+
uses the branch's default `chat_model`.
|
1420
|
+
**kwargs:
|
1421
|
+
Additional keyword arguments passed into the initial `branch.operate()` call.
|
1422
|
+
|
1423
|
+
Returns:
|
1424
|
+
Any | tuple[Any, list]:
|
1425
|
+
- If `return_analysis=False`, returns only the final output (which may be
|
1426
|
+
a raw string, dict, or structured model depending on `response_format`).
|
1427
|
+
- If `return_analysis=True`, returns a tuple of (final_output, list_of_analyses).
|
1428
|
+
The list_of_analyses is a sequence of the intermediate or extended
|
1429
|
+
ReActAnalysis objects.
|
1430
|
+
|
1431
|
+
Notes:
|
1432
|
+
- If `max_extensions` is greater than 5, a warning is logged, and it is set to 5.
|
1433
|
+
- If `interpret=True`, the user instruction is replaced by the interpreted
|
1434
|
+
string before proceeding.
|
1435
|
+
- The expansions loop continues until either `analysis.extension_needed` is `False`
|
1436
|
+
or `extensions` (the remaining allowed expansions) is `0`.
|
1437
|
+
"""
|
1438
|
+
from lionagi.operations.ReAct.ReAct import ReAct
|
1439
|
+
|
1440
|
+
return await ReAct(
|
1441
|
+
self,
|
1442
|
+
instruct,
|
1443
|
+
interpret=interpret,
|
1444
|
+
tools=tools,
|
1445
|
+
tool_schemas=tool_schemas,
|
1446
|
+
response_format=response_format,
|
1447
|
+
extension_allowed=extension_allowed,
|
1448
|
+
max_extensions=max_extensions,
|
1449
|
+
response_kwargs=response_kwargs,
|
1450
|
+
return_analysis=return_analysis,
|
1451
|
+
analysis_model=analysis_model,
|
1452
|
+
**kwargs,
|
1453
|
+
)
|
1285
1454
|
|
1286
1455
|
|
1287
1456
|
# File: lionagi/session/branch.py
|