lionagi 0.17.11__py3-none-any.whl → 0.18.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/libs/schema/minimal_yaml.py +98 -0
- lionagi/ln/types.py +32 -5
- lionagi/models/field_model.py +9 -0
- lionagi/operations/ReAct/ReAct.py +474 -237
- lionagi/operations/ReAct/utils.py +3 -0
- lionagi/operations/act/act.py +206 -0
- lionagi/operations/chat/chat.py +130 -114
- lionagi/operations/communicate/communicate.py +101 -42
- lionagi/operations/flow.py +4 -4
- lionagi/operations/interpret/interpret.py +65 -20
- lionagi/operations/operate/operate.py +212 -106
- lionagi/operations/parse/parse.py +170 -142
- lionagi/operations/select/select.py +78 -17
- lionagi/operations/select/utils.py +1 -1
- lionagi/operations/types.py +119 -23
- lionagi/protocols/generic/log.py +3 -2
- lionagi/protocols/messages/__init__.py +27 -0
- lionagi/protocols/messages/action_request.py +86 -184
- lionagi/protocols/messages/action_response.py +73 -131
- lionagi/protocols/messages/assistant_response.py +130 -159
- lionagi/protocols/messages/base.py +26 -18
- lionagi/protocols/messages/instruction.py +281 -625
- lionagi/protocols/messages/manager.py +112 -62
- lionagi/protocols/messages/message.py +87 -197
- lionagi/protocols/messages/system.py +52 -123
- lionagi/protocols/types.py +0 -2
- lionagi/service/connections/endpoint.py +0 -8
- lionagi/service/connections/providers/oai_.py +29 -94
- lionagi/service/connections/providers/ollama_.py +3 -2
- lionagi/service/hooks/hooked_event.py +2 -2
- lionagi/service/third_party/claude_code.py +3 -2
- lionagi/service/third_party/openai_models.py +433 -0
- lionagi/session/branch.py +170 -178
- lionagi/session/session.py +3 -9
- lionagi/tools/file/reader.py +2 -2
- lionagi/version.py +1 -1
- {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/METADATA +1 -2
- {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/RECORD +41 -49
- lionagi/operations/_act/act.py +0 -86
- lionagi/protocols/messages/templates/README.md +0 -28
- lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
- lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
- lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
- lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
- lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
- lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
- lionagi/service/connections/providers/types.py +0 -28
- lionagi/service/third_party/openai_model_names.py +0 -198
- lionagi/service/types.py +0 -58
- /lionagi/operations/{_act → act}/__init__.py +0 -0
- {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/WHEEL +0 -0
- {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,7 +3,6 @@
|
|
3
3
|
|
4
4
|
from typing import Any, Literal
|
5
5
|
|
6
|
-
from jinja2 import Template
|
7
6
|
from pydantic import BaseModel, JsonValue
|
8
7
|
|
9
8
|
from .._concepts import Manager
|
@@ -88,6 +87,7 @@ class MessageManager(Manager):
|
|
88
87
|
*,
|
89
88
|
instruction: JsonValue = None,
|
90
89
|
context: JsonValue = None,
|
90
|
+
handle_context: Literal["extend", "replace"] = "extend",
|
91
91
|
guidance: JsonValue = None,
|
92
92
|
images: list = None,
|
93
93
|
request_fields: JsonValue = None,
|
@@ -105,17 +105,49 @@ class MessageManager(Manager):
|
|
105
105
|
If `instruction` is an existing Instruction, it is updated in place.
|
106
106
|
Otherwise, a new instance is created.
|
107
107
|
"""
|
108
|
-
|
108
|
+
raw_params = {
|
109
109
|
k: v
|
110
110
|
for k, v in locals().items()
|
111
111
|
if k != "instruction" and v is not None
|
112
112
|
}
|
113
113
|
|
114
|
+
handle_ctx = raw_params.get("handle_context", "extend")
|
115
|
+
|
114
116
|
if isinstance(instruction, Instruction):
|
117
|
+
params = {
|
118
|
+
k: v for k, v in raw_params.items() if k != "handle_context"
|
119
|
+
}
|
120
|
+
ctx_value = params.pop("context", None)
|
121
|
+
if ctx_value is not None:
|
122
|
+
if isinstance(ctx_value, list):
|
123
|
+
ctx_list = list(ctx_value)
|
124
|
+
else:
|
125
|
+
ctx_list = [ctx_value]
|
126
|
+
if handle_ctx == "extend":
|
127
|
+
merged = list(instruction.content.prompt_context)
|
128
|
+
merged.extend(ctx_list)
|
129
|
+
params["context"] = merged
|
130
|
+
else:
|
131
|
+
params["context"] = list(ctx_list)
|
132
|
+
# Always replace in from_dict since we've already done the merge logic
|
133
|
+
params["handle_context"] = "replace"
|
115
134
|
instruction.update(**params)
|
116
135
|
return instruction
|
117
136
|
else:
|
118
|
-
|
137
|
+
# Build content dict for Instruction
|
138
|
+
content_dict = {
|
139
|
+
k: v
|
140
|
+
for k, v in raw_params.items()
|
141
|
+
if k not in ["sender", "recipient"]
|
142
|
+
}
|
143
|
+
content_dict["handle_context"] = handle_ctx
|
144
|
+
if instruction is not None:
|
145
|
+
content_dict["instruction"] = instruction
|
146
|
+
return Instruction(
|
147
|
+
content=content_dict,
|
148
|
+
sender=raw_params.get("sender"),
|
149
|
+
recipient=raw_params.get("recipient"),
|
150
|
+
)
|
119
151
|
|
120
152
|
@staticmethod
|
121
153
|
def create_assistant_response(
|
@@ -123,8 +155,6 @@ class MessageManager(Manager):
|
|
123
155
|
sender: Any = None,
|
124
156
|
recipient: Any = None,
|
125
157
|
assistant_response: AssistantResponse | Any = None,
|
126
|
-
template: Template | str = None,
|
127
|
-
template_context: dict[str, Any] = None,
|
128
158
|
) -> AssistantResponse:
|
129
159
|
"""
|
130
160
|
Build or update an `AssistantResponse`. If `assistant_response` is an
|
@@ -133,17 +163,23 @@ class MessageManager(Manager):
|
|
133
163
|
params = {
|
134
164
|
k: v
|
135
165
|
for k, v in locals().items()
|
136
|
-
if k
|
166
|
+
if k != "assistant_response" and v is not None
|
137
167
|
}
|
138
|
-
t_ctx = template_context or {}
|
139
|
-
params.update(t_ctx)
|
140
168
|
|
141
169
|
if isinstance(assistant_response, AssistantResponse):
|
142
170
|
assistant_response.update(**params)
|
143
171
|
return assistant_response
|
144
172
|
|
145
|
-
|
146
|
-
|
173
|
+
# Create new AssistantResponse
|
174
|
+
content_dict = (
|
175
|
+
{"assistant_response": assistant_response}
|
176
|
+
if assistant_response
|
177
|
+
else {}
|
178
|
+
)
|
179
|
+
return AssistantResponse(
|
180
|
+
content=content_dict,
|
181
|
+
sender=params.get("sender"),
|
182
|
+
recipient=params.get("recipient"),
|
147
183
|
)
|
148
184
|
|
149
185
|
@staticmethod
|
@@ -154,8 +190,6 @@ class MessageManager(Manager):
|
|
154
190
|
function: str = None,
|
155
191
|
arguments: dict[str, Any] = None,
|
156
192
|
action_request: ActionRequest | None = None,
|
157
|
-
template: Template | str = None,
|
158
|
-
template_context: dict[str, Any] = None,
|
159
193
|
) -> ActionRequest:
|
160
194
|
"""
|
161
195
|
Build or update an ActionRequest.
|
@@ -166,25 +200,31 @@ class MessageManager(Manager):
|
|
166
200
|
function: Function name for the request.
|
167
201
|
arguments: Arguments for the function.
|
168
202
|
action_request: Possibly existing ActionRequest to update.
|
169
|
-
template: Optional jinja template.
|
170
|
-
template_context: Extra context for the template.
|
171
203
|
|
172
204
|
Returns:
|
173
205
|
ActionRequest: The new or updated request object.
|
174
206
|
"""
|
175
207
|
params = {
|
176
|
-
|
177
|
-
|
178
|
-
"
|
179
|
-
"arguments": arguments,
|
180
|
-
"template": template,
|
208
|
+
k: v
|
209
|
+
for k, v in locals().items()
|
210
|
+
if k != "action_request" and v is not None
|
181
211
|
}
|
182
|
-
params.update(template_context or {})
|
183
212
|
|
184
213
|
if isinstance(action_request, ActionRequest):
|
185
214
|
action_request.update(**params)
|
186
215
|
return action_request
|
187
|
-
|
216
|
+
|
217
|
+
# Create new ActionRequest
|
218
|
+
content_dict = {}
|
219
|
+
if function:
|
220
|
+
content_dict["function"] = function
|
221
|
+
if arguments:
|
222
|
+
content_dict["arguments"] = arguments
|
223
|
+
return ActionRequest(
|
224
|
+
content=content_dict,
|
225
|
+
sender=params.get("sender"),
|
226
|
+
recipient=params.get("recipient"),
|
227
|
+
)
|
188
228
|
|
189
229
|
@staticmethod
|
190
230
|
def create_action_response(
|
@@ -217,17 +257,27 @@ class MessageManager(Manager):
|
|
217
257
|
raise ValueError(
|
218
258
|
"Error: please provide a corresponding action request for an action response."
|
219
259
|
)
|
220
|
-
params = {
|
221
|
-
"action_request": action_request,
|
222
|
-
"output": action_output,
|
223
|
-
"sender": sender,
|
224
|
-
"recipient": recipient,
|
225
|
-
}
|
226
260
|
if isinstance(action_response, ActionResponse):
|
227
|
-
action_response.update(
|
261
|
+
action_response.update(
|
262
|
+
output=action_output, sender=sender, recipient=recipient
|
263
|
+
)
|
228
264
|
return action_response
|
229
265
|
|
230
|
-
|
266
|
+
# Create new ActionResponse
|
267
|
+
content_dict = {
|
268
|
+
"function": action_request.content.function,
|
269
|
+
"arguments": action_request.content.arguments,
|
270
|
+
"output": action_output,
|
271
|
+
"action_request_id": str(action_request.id),
|
272
|
+
}
|
273
|
+
response = ActionResponse(
|
274
|
+
content=content_dict, sender=sender, recipient=recipient
|
275
|
+
)
|
276
|
+
|
277
|
+
# Update the request to reference this response
|
278
|
+
action_request.content.action_response_id = str(response.id)
|
279
|
+
|
280
|
+
return response
|
231
281
|
|
232
282
|
@staticmethod
|
233
283
|
def create_system(
|
@@ -236,28 +286,33 @@ class MessageManager(Manager):
|
|
236
286
|
system_datetime: bool | str = None,
|
237
287
|
sender: Any = None,
|
238
288
|
recipient: Any = None,
|
239
|
-
template: Template | str = None,
|
240
|
-
template_context: dict[str, Any] = None,
|
241
289
|
) -> System:
|
242
290
|
"""
|
243
291
|
Create or update a `System` message. If `system` is an instance, update.
|
244
292
|
Otherwise, create a new System message.
|
245
293
|
"""
|
246
294
|
params = {
|
247
|
-
|
248
|
-
|
249
|
-
"
|
250
|
-
"template": template,
|
251
|
-
**(template_context or {}),
|
295
|
+
k: v
|
296
|
+
for k, v in locals().items()
|
297
|
+
if k != "system" and v is not None
|
252
298
|
}
|
299
|
+
|
253
300
|
if isinstance(system, System):
|
254
301
|
system.update(**params)
|
255
302
|
return system
|
256
303
|
|
304
|
+
# Create new System message
|
305
|
+
content_dict = {}
|
257
306
|
if system:
|
258
|
-
|
259
|
-
|
260
|
-
|
307
|
+
content_dict["system_message"] = system
|
308
|
+
if system_datetime is not None:
|
309
|
+
content_dict["system_datetime"] = system_datetime
|
310
|
+
|
311
|
+
return System(
|
312
|
+
content=content_dict if content_dict else None,
|
313
|
+
sender=params.get("sender"),
|
314
|
+
recipient=params.get("recipient"),
|
315
|
+
)
|
261
316
|
|
262
317
|
def add_message(
|
263
318
|
self,
|
@@ -265,12 +320,11 @@ class MessageManager(Manager):
|
|
265
320
|
# common
|
266
321
|
sender: SenderRecipient = None,
|
267
322
|
recipient: SenderRecipient = None,
|
268
|
-
template: Template | str = None,
|
269
|
-
template_context: dict[str, Any] = None,
|
270
323
|
metadata: dict[str, Any] = None,
|
271
324
|
# instruction
|
272
325
|
instruction: JsonValue = None,
|
273
326
|
context: JsonValue = None,
|
327
|
+
handle_context: Literal["extend", "replace"] = "extend",
|
274
328
|
guidance: JsonValue = None,
|
275
329
|
request_fields: JsonValue = None,
|
276
330
|
plain_content: JsonValue = None,
|
@@ -299,18 +353,13 @@ class MessageManager(Manager):
|
|
299
353
|
- ActionRequest / ActionResponse
|
300
354
|
"""
|
301
355
|
_msg = None
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
action_request,
|
310
|
-
)
|
311
|
-
)
|
312
|
-
> 1
|
313
|
-
):
|
356
|
+
# When creating ActionResponse, both action_request and action_output are needed
|
357
|
+
# So don't count action_request as a message type when action_output is present
|
358
|
+
message_types = [instruction, assistant_response, system]
|
359
|
+
if action_request and not action_output:
|
360
|
+
message_types.append(action_request)
|
361
|
+
|
362
|
+
if sum(bool(x) for x in message_types) > 1:
|
314
363
|
raise ValueError("Only one message type can be added at a time.")
|
315
364
|
|
316
365
|
if system:
|
@@ -319,8 +368,6 @@ class MessageManager(Manager):
|
|
319
368
|
system_datetime=system_datetime,
|
320
369
|
sender=sender,
|
321
370
|
recipient=recipient,
|
322
|
-
template=template,
|
323
|
-
template_context=template_context,
|
324
371
|
)
|
325
372
|
self.set_system(_msg)
|
326
373
|
|
@@ -333,15 +380,15 @@ class MessageManager(Manager):
|
|
333
380
|
recipient=recipient,
|
334
381
|
)
|
335
382
|
|
336
|
-
elif action_request or (
|
383
|
+
elif action_request or (
|
384
|
+
action_function and action_arguments is not None
|
385
|
+
):
|
337
386
|
_msg = self.create_action_request(
|
338
387
|
sender=sender,
|
339
388
|
recipient=recipient,
|
340
389
|
function=action_function,
|
341
390
|
arguments=action_arguments,
|
342
391
|
action_request=action_request,
|
343
|
-
template=template,
|
344
|
-
template_context=template_context,
|
345
392
|
)
|
346
393
|
|
347
394
|
elif assistant_response:
|
@@ -349,14 +396,13 @@ class MessageManager(Manager):
|
|
349
396
|
sender=sender,
|
350
397
|
recipient=recipient,
|
351
398
|
assistant_response=assistant_response,
|
352
|
-
template=template,
|
353
|
-
template_context=template_context,
|
354
399
|
)
|
355
400
|
|
356
401
|
else:
|
357
402
|
_msg = self.create_instruction(
|
358
403
|
instruction=instruction,
|
359
404
|
context=context,
|
405
|
+
handle_context=handle_context,
|
360
406
|
guidance=guidance,
|
361
407
|
images=images,
|
362
408
|
request_fields=request_fields,
|
@@ -466,7 +512,9 @@ class MessageManager(Manager):
|
|
466
512
|
Convenience method to strip 'tool_schemas' from the most recent Instruction.
|
467
513
|
"""
|
468
514
|
if self.last_instruction:
|
469
|
-
self.messages[
|
515
|
+
self.messages[
|
516
|
+
self.last_instruction.id
|
517
|
+
].content.tool_schemas.clear()
|
470
518
|
|
471
519
|
def concat_recent_action_responses_to_instruction(
|
472
520
|
self, instruction: Instruction
|
@@ -477,7 +525,9 @@ class MessageManager(Manager):
|
|
477
525
|
"""
|
478
526
|
for i in reversed(list(self.messages.progression)):
|
479
527
|
if isinstance(self.messages[i], ActionResponse):
|
480
|
-
instruction.
|
528
|
+
instruction.content.prompt_context.append(
|
529
|
+
self.messages[i].content
|
530
|
+
)
|
481
531
|
else:
|
482
532
|
break
|
483
533
|
|
@@ -1,252 +1,142 @@
|
|
1
1
|
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
3
3
|
|
4
|
-
from
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import Any, ClassVar
|
5
6
|
|
6
|
-
from
|
7
|
-
from typing import Any
|
7
|
+
from pydantic import field_serializer, field_validator
|
8
8
|
|
9
|
-
from
|
10
|
-
from pydantic import Field, PrivateAttr, field_serializer
|
11
|
-
|
12
|
-
from lionagi import ln
|
9
|
+
from lionagi.ln.types import DataClass
|
13
10
|
|
14
11
|
from .._concepts import Sendable
|
15
|
-
from ..generic.element import Element, IDType
|
16
|
-
from ..generic.log import Log
|
17
12
|
from ..graph.node import Node
|
18
13
|
from .base import (
|
19
|
-
MessageFlag,
|
20
14
|
MessageRole,
|
21
15
|
SenderRecipient,
|
16
|
+
serialize_sender_recipient,
|
22
17
|
validate_sender_recipient,
|
23
18
|
)
|
24
19
|
|
25
|
-
template_path = Path(__file__).parent / "templates"
|
26
|
-
jinja_env = Environment(loader=FileSystemLoader(template_path))
|
27
|
-
|
28
|
-
__all__ = ("RoledMessage",)
|
29
20
|
|
21
|
+
@dataclass(slots=True)
|
22
|
+
class MessageContent(DataClass):
|
23
|
+
"""A base class for message content structures."""
|
30
24
|
|
31
|
-
|
32
|
-
"""
|
33
|
-
A base class for all messages that have a `role` and carry structured
|
34
|
-
`content`. Subclasses might be `Instruction`, `ActionRequest`, etc.
|
35
|
-
"""
|
25
|
+
_none_as_sentinel: ClassVar[bool] = True
|
36
26
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
27
|
+
@property
|
28
|
+
def rendered(self) -> str:
|
29
|
+
"""Render the content as a string."""
|
30
|
+
raise NotImplementedError(
|
31
|
+
"Subclasses must implement rendered property."
|
32
|
+
)
|
41
33
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
34
|
+
@classmethod
|
35
|
+
def from_dict(cls, data: dict[str, Any]) -> "MessageContent":
|
36
|
+
"""Create an instance from a dictionary."""
|
37
|
+
raise NotImplementedError(
|
38
|
+
"Subclasses must implement from_dict method."
|
39
|
+
)
|
46
40
|
|
47
|
-
_flag: MessageFlag | None = PrivateAttr(None)
|
48
41
|
|
49
|
-
|
42
|
+
class RoledMessage(Node, Sendable):
|
43
|
+
"""Base class for all messages with a role and structured content.
|
50
44
|
|
51
|
-
|
52
|
-
|
53
|
-
title="Sender",
|
54
|
-
description="The ID of the sender node or a role.",
|
55
|
-
)
|
45
|
+
Subclasses must provide a concrete MessageContent type.
|
46
|
+
"""
|
56
47
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
)
|
48
|
+
role: MessageRole = MessageRole.UNSET
|
49
|
+
content: MessageContent
|
50
|
+
sender: SenderRecipient | None = MessageRole.UNSET
|
51
|
+
recipient: SenderRecipient | None = MessageRole.UNSET
|
62
52
|
|
63
53
|
@field_serializer("sender", "recipient")
|
64
54
|
def _serialize_sender_recipient(self, value: SenderRecipient) -> str:
|
65
|
-
|
66
|
-
return value.value
|
67
|
-
if isinstance(value, str):
|
68
|
-
return value
|
69
|
-
if isinstance(value, Element):
|
70
|
-
return str(value.id)
|
71
|
-
if isinstance(value, IDType):
|
72
|
-
return str(value)
|
73
|
-
return str(value)
|
55
|
+
return serialize_sender_recipient(value)
|
74
56
|
|
75
|
-
@
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
Returns:
|
82
|
-
list[dict[str,Any]] | None: If no images found, None.
|
83
|
-
"""
|
84
|
-
msg_ = self.chat_msg
|
85
|
-
if isinstance(msg_, dict) and isinstance(msg_["content"], list):
|
86
|
-
return [i for i in msg_["content"] if i["type"] == "image_url"]
|
87
|
-
return None
|
57
|
+
@field_validator("sender", "recipient")
|
58
|
+
def _validate_sender_recipient(cls, v):
|
59
|
+
if v is None:
|
60
|
+
return None
|
61
|
+
return validate_sender_recipient(v)
|
88
62
|
|
89
63
|
@property
|
90
64
|
def chat_msg(self) -> dict[str, Any] | None:
|
91
|
-
"""
|
92
|
-
A dictionary representation typically used in chat-based contexts.
|
93
|
-
|
94
|
-
Returns:
|
95
|
-
dict: `{"role": <role>, "content": <rendered content>}`
|
96
|
-
"""
|
65
|
+
"""A dictionary representation typically used in chat-based contexts."""
|
97
66
|
try:
|
98
|
-
|
67
|
+
role_str = (
|
68
|
+
self.role.value
|
69
|
+
if isinstance(self.role, MessageRole)
|
70
|
+
else str(self.role)
|
71
|
+
)
|
72
|
+
return {"role": role_str, "content": self.rendered}
|
99
73
|
except Exception:
|
100
74
|
return None
|
101
75
|
|
102
76
|
@property
|
103
77
|
def rendered(self) -> str:
|
104
|
-
"""
|
105
|
-
Attempt to format the message with a Jinja template (if provided).
|
106
|
-
If no template, fallback to JSON.
|
78
|
+
"""Render the message content as a string.
|
107
79
|
|
108
|
-
|
109
|
-
str: The final formatted string.
|
80
|
+
Delegates to the content's rendered property.
|
110
81
|
"""
|
111
|
-
|
112
|
-
if isinstance(self.template, str):
|
113
|
-
return self.template.format(**self.content)
|
114
|
-
if isinstance(self.template, Template):
|
115
|
-
return self.template.render(**self.content)
|
116
|
-
except Exception:
|
117
|
-
return ln.json_dumps(
|
118
|
-
self.content,
|
119
|
-
pretty=True,
|
120
|
-
sort_keys=True,
|
121
|
-
append_newline=True,
|
122
|
-
deterministic_sets=True,
|
123
|
-
decimal_as_float=True,
|
124
|
-
)
|
82
|
+
return self.content.rendered
|
125
83
|
|
126
|
-
@
|
127
|
-
def
|
128
|
-
|
84
|
+
@field_validator("role", mode="before")
|
85
|
+
def _validate_role(cls, v):
|
86
|
+
if isinstance(v, str):
|
87
|
+
return MessageRole(v)
|
88
|
+
if isinstance(v, MessageRole):
|
89
|
+
return v
|
90
|
+
return MessageRole.UNSET
|
129
91
|
|
130
|
-
|
131
|
-
|
132
|
-
"""
|
133
|
-
Deserialize a dictionary into a RoledMessage or subclass.
|
92
|
+
def update(self, sender=None, recipient=None, **kw):
|
93
|
+
"""Update message fields.
|
134
94
|
|
135
95
|
Args:
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
RoledMessage: A newly constructed instance.
|
96
|
+
sender: New sender role or ID.
|
97
|
+
recipient: New recipient role or ID.
|
98
|
+
**kw: Content updates to apply via from_dict() reconstruction.
|
140
99
|
"""
|
141
|
-
|
142
|
-
self
|
143
|
-
|
144
|
-
)
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
100
|
+
if sender:
|
101
|
+
self.sender = validate_sender_recipient(sender)
|
102
|
+
if recipient:
|
103
|
+
self.recipient = validate_sender_recipient(recipient)
|
104
|
+
if kw:
|
105
|
+
_dict = self.content.to_dict()
|
106
|
+
_dict.update(kw)
|
107
|
+
self.content = type(self.content).from_dict(_dict)
|
149
108
|
|
150
|
-
def
|
151
|
-
"""
|
152
|
-
Check if this message is flagged as a clone.
|
109
|
+
def clone(self) -> "RoledMessage":
|
110
|
+
"""Create a clone with a new ID but reference to original.
|
153
111
|
|
154
112
|
Returns:
|
155
|
-
|
113
|
+
A new message instance with a new ID and deep-copied content,
|
114
|
+
with a reference to the original message in metadata.
|
156
115
|
"""
|
157
|
-
|
116
|
+
# Create a new instance from dict, excluding frozen fields (id, created_at)
|
117
|
+
# This allows new id and created_at to be generated
|
118
|
+
data = self.to_dict()
|
119
|
+
original_id = data.pop("id")
|
120
|
+
data.pop("created_at") # Let new created_at be generated
|
158
121
|
|
159
|
-
|
160
|
-
|
161
|
-
Create a shallow copy of this message, possibly resetting the role.
|
122
|
+
# Create new instance
|
123
|
+
cloned = type(self).from_dict(data)
|
162
124
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
Returns:
|
167
|
-
RoledMessage: The new cloned message.
|
168
|
-
"""
|
169
|
-
instance = self.__class__(
|
170
|
-
content=self.content,
|
171
|
-
role=self.role if keep_role else MessageRole.UNSET,
|
172
|
-
metadata={"clone_from": self},
|
173
|
-
)
|
174
|
-
instance._flag = MessageFlag.MESSAGE_CLONE
|
175
|
-
return instance
|
176
|
-
|
177
|
-
def to_log(self) -> Log:
|
178
|
-
"""
|
179
|
-
Convert this message into a `Log`, preserving all current fields.
|
125
|
+
# Store reference to original in metadata
|
126
|
+
cloned.metadata["clone_from"] = str(original_id)
|
180
127
|
|
181
|
-
|
182
|
-
Log: An immutable log entry derived from this message.
|
183
|
-
"""
|
184
|
-
return Log.create(self)
|
185
|
-
|
186
|
-
@field_serializer("role")
|
187
|
-
def _serialize_role(self, value: MessageRole):
|
188
|
-
if isinstance(value, MessageRole):
|
189
|
-
return value.value
|
190
|
-
return str(value)
|
191
|
-
|
192
|
-
@field_serializer("metadata")
|
193
|
-
def _serialize_metadata(self, value: dict):
|
194
|
-
if "clone_from" in value:
|
195
|
-
origin_obj: RoledMessage = value.pop("clone_from")
|
196
|
-
origin_info = origin_obj.to_dict()
|
197
|
-
value["clone_from_info"] = {
|
198
|
-
"clone_from_info": {
|
199
|
-
"original_id": origin_info["id"],
|
200
|
-
"original_created_at": origin_info["created_at"],
|
201
|
-
"original_sender": origin_info["sender"],
|
202
|
-
"original_recipient": origin_info["recipient"],
|
203
|
-
"original_lion_class": origin_info["metadata"][
|
204
|
-
"lion_class"
|
205
|
-
],
|
206
|
-
"original_role": origin_info["role"],
|
207
|
-
}
|
208
|
-
}
|
209
|
-
return value
|
210
|
-
|
211
|
-
@field_serializer("template")
|
212
|
-
def _serialize_template(self, value: Template | str):
|
213
|
-
# We do not store or transmit the raw Template object.
|
214
|
-
if isinstance(value, Template):
|
215
|
-
return None
|
216
|
-
return value
|
128
|
+
return cloned
|
217
129
|
|
218
|
-
|
130
|
+
@property
|
131
|
+
def image_content(self) -> list[dict[str, Any]] | None:
|
219
132
|
"""
|
220
|
-
|
221
|
-
|
222
|
-
Args:
|
223
|
-
sender (SenderRecipient):
|
224
|
-
New sender or role.
|
225
|
-
recipient (SenderRecipient):
|
226
|
-
New recipient or role.
|
227
|
-
template (Template | str):
|
228
|
-
New jinja Template or format string.
|
229
|
-
**kwargs:
|
230
|
-
Additional content to merge into self.content.
|
133
|
+
Extract structured image data from the message content if it is
|
134
|
+
represented as a chat message array.
|
231
135
|
"""
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
if kwargs:
|
237
|
-
self.content.update(kwargs)
|
238
|
-
if template:
|
239
|
-
if not isinstance(template, Template | str):
|
240
|
-
raise ValueError("Template must be a Jinja2 Template or str")
|
241
|
-
self.template = template
|
242
|
-
|
243
|
-
def __str__(self) -> str:
|
244
|
-
content_preview = (
|
245
|
-
f"{str(self.content)[:75]}..."
|
246
|
-
if len(str(self.content)) > 75
|
247
|
-
else str(self.content)
|
248
|
-
)
|
249
|
-
return f"Message(role={self.role}, sender={self.sender}, content='{content_preview}')"
|
136
|
+
msg_ = self.chat_msg
|
137
|
+
if isinstance(msg_, dict) and isinstance(msg_["content"], list):
|
138
|
+
return [i for i in msg_["content"] if i["type"] == "image_url"]
|
139
|
+
return None
|
250
140
|
|
251
141
|
|
252
142
|
# File: lionagi/protocols/messages/message.py
|