lionagi 0.7.0__py3-none-any.whl → 0.7.2__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.
Files changed (43) hide show
  1. lionagi/operations/ReAct/ReAct.py +2 -2
  2. lionagi/operations/_act/act.py +10 -3
  3. lionagi/operations/communicate/communicate.py +0 -59
  4. lionagi/operations/interpret/interpret.py +1 -2
  5. lionagi/operations/operate/operate.py +10 -5
  6. lionagi/operations/parse/parse.py +0 -36
  7. lionagi/operations/plan/plan.py +3 -3
  8. lionagi/operatives/action/manager.py +105 -82
  9. lionagi/operatives/action/request_response_model.py +31 -0
  10. lionagi/operatives/action/tool.py +50 -20
  11. lionagi/protocols/_concepts.py +1 -1
  12. lionagi/protocols/adapters/adapter.py +25 -0
  13. lionagi/protocols/adapters/json_adapter.py +107 -27
  14. lionagi/protocols/adapters/pandas_/csv_adapter.py +55 -11
  15. lionagi/protocols/adapters/pandas_/excel_adapter.py +52 -10
  16. lionagi/protocols/adapters/pandas_/pd_dataframe_adapter.py +54 -4
  17. lionagi/protocols/adapters/pandas_/pd_series_adapter.py +40 -0
  18. lionagi/protocols/generic/element.py +1 -1
  19. lionagi/protocols/generic/pile.py +5 -8
  20. lionagi/protocols/graph/edge.py +1 -1
  21. lionagi/protocols/graph/graph.py +16 -8
  22. lionagi/protocols/graph/node.py +1 -1
  23. lionagi/protocols/mail/exchange.py +126 -15
  24. lionagi/protocols/mail/mail.py +33 -0
  25. lionagi/protocols/mail/mailbox.py +62 -0
  26. lionagi/protocols/mail/manager.py +97 -41
  27. lionagi/protocols/mail/package.py +57 -3
  28. lionagi/protocols/messages/action_request.py +77 -26
  29. lionagi/protocols/messages/action_response.py +55 -26
  30. lionagi/protocols/messages/assistant_response.py +50 -15
  31. lionagi/protocols/messages/base.py +36 -0
  32. lionagi/protocols/messages/instruction.py +175 -145
  33. lionagi/protocols/messages/manager.py +152 -56
  34. lionagi/protocols/messages/message.py +61 -25
  35. lionagi/protocols/messages/system.py +54 -19
  36. lionagi/service/imodel.py +24 -0
  37. lionagi/session/branch.py +40 -32
  38. lionagi/utils.py +1 -0
  39. lionagi/version.py +1 -1
  40. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/METADATA +1 -1
  41. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/RECORD +43 -43
  42. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/WHEEL +0 -0
  43. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/licenses/LICENSE +0 -0
@@ -2,6 +2,11 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ """
6
+ Defines the `Instruction` class, representing user commands or instructions
7
+ sent to the system. Supports optional context, images, and schema requests.
8
+ """
9
+
5
10
  from typing import Any, Literal, override
6
11
 
7
12
  from pydantic import BaseModel, JsonValue, field_serializer
@@ -14,13 +19,14 @@ from .message import RoledMessage, SenderRecipient
14
19
 
15
20
  def prepare_request_response_format(request_fields: dict) -> str:
16
21
  """
17
- Prepare a standardized format for request responses.
22
+ Creates a mandated JSON code block for the response
23
+ based on requested fields.
18
24
 
19
25
  Args:
20
- request_fields: Dictionary of fields to include in response
26
+ request_fields: Dictionary of fields for the response format.
21
27
 
22
28
  Returns:
23
- str: Formatted response template
29
+ str: A string instructing the user to return valid JSON.
24
30
  """
25
31
  return (
26
32
  "**MUST RETURN JSON-PARSEABLE RESPONSE ENCLOSED BY JSON CODE BLOCKS."
@@ -28,35 +34,36 @@ def prepare_request_response_format(request_fields: dict) -> str:
28
34
  ).strip()
29
35
 
30
36
 
31
- def format_image_item(idx: str, x: str, /) -> dict[str, Any]:
37
+ def format_image_item(idx: str, detail: str) -> dict[str, Any]:
32
38
  """
33
- Create an image_url dictionary for content formatting.
39
+ Wrap image data in a standard dictionary format.
34
40
 
35
41
  Args:
36
- idx: Base64 encoded image data
37
- x: Image detail level
42
+ idx: A base64 image ID or URL reference.
43
+ detail: The image detail level.
38
44
 
39
45
  Returns:
40
- dict: Formatted image item
46
+ dict: A dictionary describing the image.
41
47
  """
42
48
  return {
43
49
  "type": "image_url",
44
50
  "image_url": {
45
51
  "url": f"data:image/jpeg;base64,{idx}",
46
- "detail": x,
52
+ "detail": detail,
47
53
  },
48
54
  }
49
55
 
50
56
 
51
57
  def format_text_item(item: Any) -> str:
52
58
  """
53
- Format a text item or list of items into a string.
59
+ Turn a single item (or dict) into a string. If multiple items,
60
+ combine them line by line.
54
61
 
55
62
  Args:
56
- item: Text item(s) to format
63
+ item: Any item, possibly a list/dict with text data.
57
64
 
58
65
  Returns:
59
- str: Formatted text
66
+ str: Concatenated text lines.
60
67
  """
61
68
  msg = ""
62
69
  item = [item] if not isinstance(item, list) else item
@@ -73,13 +80,14 @@ def format_text_item(item: Any) -> str:
73
80
 
74
81
  def format_text_content(content: dict) -> str:
75
82
  """
76
- Format dictionary content into a structured text format.
83
+ Convert a dictionary with keys like 'guidance', 'instruction', 'context', etc.
84
+ into a readable text block.
77
85
 
78
86
  Args:
79
- content: Dictionary containing content sections
87
+ content (dict): The content dictionary.
80
88
 
81
89
  Returns:
82
- str: Formatted text content
90
+ str: A textual summary.
83
91
  """
84
92
  if "plain_content" in content and isinstance(
85
93
  content["plain_content"], str
@@ -140,17 +148,17 @@ def format_image_content(
140
148
  text_content: str,
141
149
  images: list,
142
150
  image_detail: Literal["low", "high", "auto"],
143
- ) -> dict[str, Any]:
151
+ ) -> list[dict[str, Any]]:
144
152
  """
145
- Format text content with images for message content.
153
+ Merge textual content with a list of image dictionaries for consumption.
146
154
 
147
155
  Args:
148
- text_content: The text content to format
149
- images: List of images to include
150
- image_detail: Level of detail for images
156
+ text_content (str): The textual portion
157
+ images (list): A list of base64 or references
158
+ image_detail (Literal["low","high","auto"]): How detailed the images are
151
159
 
152
160
  Returns:
153
- dict: Formatted content with text and images
161
+ list[dict[str,Any]]: A combined structure of text + image dicts.
154
162
  """
155
163
  content = [{"type": "text", "text": text_content}]
156
164
  content.extend(format_image_item(i, image_detail) for i in images)
@@ -169,24 +177,34 @@ def prepare_instruction_content(
169
177
  tool_schemas: dict | None = None,
170
178
  ) -> dict:
171
179
  """
172
- Prepare the content for an instruction message.
180
+ Combine various pieces (instruction, guidance, context, etc.) into
181
+ a single dictionary describing the user's instruction.
173
182
 
174
183
  Args:
175
- guidance: Optional guidance text
176
- instruction: Main instruction content
177
- context: Additional context information
178
- request_fields: Fields to request in response
179
- plain_content: Plain text content
180
- request_model: Pydantic model for structured requests
181
- images: Images to include
182
- image_detail: Level of detail for images
183
- tool_schemas: Tool schemas to include
184
+ guidance (str | None):
185
+ Optional guiding text.
186
+ instruction (str | None):
187
+ Main instruction or command to be executed.
188
+ context (str | dict | list | None):
189
+ Additional context about the environment or previous steps.
190
+ request_fields (dict | list[str] | None):
191
+ If the user requests certain fields in the response.
192
+ plain_content (str | None):
193
+ A raw plain text fallback.
194
+ request_model (BaseModel | None):
195
+ If there's a pydantic model for the request schema.
196
+ images (str | list | None):
197
+ Optional images, base64-coded or references.
198
+ image_detail (str | None):
199
+ The detail level for images ("low", "high", "auto").
200
+ tool_schemas (dict | None):
201
+ Extra data describing available tools.
184
202
 
185
203
  Returns:
186
- Note: Prepared instruction content
204
+ dict: The combined instruction content.
187
205
 
188
206
  Raises:
189
- ValueError: If both request_fields and request_model are provided
207
+ ValueError: If request_fields and request_model are both given.
190
208
  """
191
209
  if request_fields and request_model:
192
210
  raise ValueError(
@@ -227,10 +245,15 @@ def prepare_instruction_content(
227
245
  if plain_content:
228
246
  out_["plain_content"] = plain_content
229
247
 
248
+ # remove keys with None/UNDEFINED
230
249
  return {k: v for k, v in out_.items() if v not in [None, UNDEFINED]}
231
250
 
232
251
 
233
252
  class Instruction(RoledMessage):
253
+ """
254
+ A user-facing message that conveys commands or tasks. It supports
255
+ optional images, tool references, and schema-based requests.
256
+ """
234
257
 
235
258
  @classmethod
236
259
  def create(
@@ -247,9 +270,44 @@ class Instruction(RoledMessage):
247
270
  image_detail: Literal["low", "high", "auto"] = None,
248
271
  request_model: BaseModel | type[BaseModel] = None,
249
272
  response_format: BaseModel | type[BaseModel] = None,
250
- tool_schemas: dict = None,
251
- ):
273
+ tool_schemas: list[dict] = None,
274
+ ) -> "Instruction":
275
+ """
276
+ Construct a new Instruction.
277
+
278
+ Args:
279
+ instruction (JsonValue, optional):
280
+ The main user instruction.
281
+ context (JsonValue, optional):
282
+ Additional context or environment info.
283
+ guidance (JsonValue, optional):
284
+ Guidance or disclaimers for the instruction.
285
+ images (list, optional):
286
+ A set of images relevant to the instruction.
287
+ request_fields (JsonValue, optional):
288
+ The fields the user wants in the assistant's response.
289
+ plain_content (JsonValue, optional):
290
+ A raw plain text fallback.
291
+ image_detail ("low"|"high"|"auto", optional):
292
+ The detail level for included images.
293
+ request_model (BaseModel|type[BaseModel], optional):
294
+ A Pydantic schema for the request.
295
+ response_format (BaseModel|type[BaseModel], optional):
296
+ Alias for request_model.
297
+ tool_schemas (list[dict] | dict, optional):
298
+ Extra tool reference data.
299
+ sender (SenderRecipient, optional):
300
+ The sender role or ID.
301
+ recipient (SenderRecipient, optional):
302
+ The recipient role or ID.
303
+
304
+ Returns:
305
+ Instruction: A newly created instruction object.
252
306
 
307
+ Raises:
308
+ ValueError: If more than one of `request_fields`, `request_model`,
309
+ or `response_format` is passed at once.
310
+ """
253
311
  if (
254
312
  sum(
255
313
  bool(i)
@@ -281,162 +339,123 @@ class Instruction(RoledMessage):
281
339
 
282
340
  @property
283
341
  def guidance(self) -> str | None:
284
- """Get the guidance content of the instruction."""
285
342
  return self.content.get("guidance", None)
286
343
 
287
344
  @guidance.setter
288
345
  def guidance(self, guidance: str) -> None:
289
- """Set the guidance content of the instruction."""
290
346
  if guidance is None:
291
347
  self.content.pop("guidance", None)
292
- return
293
-
294
- if not isinstance(guidance, str):
295
- guidance = str(guidance)
296
- self.content["guidance"] = guidance
348
+ else:
349
+ self.content["guidance"] = str(guidance)
297
350
 
298
351
  @property
299
352
  def instruction(self) -> JsonValue | None:
300
- """Get the main instruction content."""
301
353
  if "plain_content" in self.content:
302
354
  return self.content["plain_content"]
303
- else:
304
- return self.content.get("instruction", None)
355
+ return self.content.get("instruction", None)
305
356
 
306
357
  @instruction.setter
307
- def instruction(self, instruction: JsonValue) -> None:
308
- """Set the main instruction content."""
309
- if instruction is None:
358
+ def instruction(self, val: JsonValue) -> None:
359
+ if val is None:
310
360
  self.content.pop("instruction", None)
311
- return
312
-
313
- self.content["instruction"] = instruction
361
+ else:
362
+ self.content["instruction"] = val
314
363
 
315
364
  @property
316
365
  def context(self) -> JsonValue | None:
317
- """Get the context of the instruction."""
318
366
  return self.content.get("context", None)
319
367
 
320
368
  @context.setter
321
- def context(self, context: JsonValue) -> None:
322
- """Set the context of the instruction."""
323
- if context is None:
369
+ def context(self, ctx: JsonValue) -> None:
370
+ if ctx is None:
324
371
  self.content["context"] = []
325
- return
326
-
327
- if not isinstance(context, list):
328
- context = [context]
329
- self.content["context"] = context
372
+ else:
373
+ self.content["context"] = (
374
+ list(ctx) if isinstance(ctx, list) else [ctx]
375
+ )
330
376
 
331
377
  @property
332
378
  def tool_schemas(self) -> JsonValue | None:
333
- """Get the schemas of the tools in the instruction."""
334
379
  return self.content.get("tool_schemas", None)
335
380
 
336
381
  @tool_schemas.setter
337
- def tool_schemas(self, tool_schemas: dict) -> None:
338
- """Set the schemas of the tools in the instruction."""
339
- if not tool_schemas:
382
+ def tool_schemas(self, val: list[dict] | dict) -> None:
383
+ if not val:
340
384
  self.content.pop("tool_schemas", None)
341
385
  return
342
-
343
- self.content["tool_schemas"] = tool_schemas
386
+ self.content["tool_schemas"] = val
344
387
 
345
388
  @property
346
389
  def plain_content(self) -> str | None:
347
- """Get the plain text content of the instruction."""
348
390
  return self.content.get("plain_content", None)
349
391
 
350
392
  @plain_content.setter
351
- def plain_content(self, plain_content: str) -> None:
352
- """Set the plain text content of the instruction."""
353
- self.content["plain_content"] = plain_content
393
+ def plain_content(self, pc: str) -> None:
394
+ self.content["plain_content"] = pc
354
395
 
355
396
  @property
356
397
  def image_detail(self) -> Literal["low", "high", "auto"] | None:
357
- """Get the image detail level of the instruction."""
358
398
  return self.content.get("image_detail", None)
359
399
 
360
400
  @image_detail.setter
361
- def image_detail(
362
- self, image_detail: Literal["low", "high", "auto"]
363
- ) -> None:
364
- """Set the image detail level of the instruction."""
365
- self.content["image_detail"] = image_detail
401
+ def image_detail(self, detail: Literal["low", "high", "auto"]) -> None:
402
+ self.content["image_detail"] = detail
366
403
 
367
404
  @property
368
405
  def images(self) -> list:
369
- """Get the images associated with the instruction."""
370
406
  return self.content.get("images", [])
371
407
 
372
408
  @images.setter
373
- def images(self, images: list) -> None:
374
- """Set the images associated with the instruction."""
375
- if not isinstance(images, list):
376
- images = [images]
377
- self.content["images"] = images
409
+ def images(self, imgs: list) -> None:
410
+ self.content["images"] = imgs if isinstance(imgs, list) else [imgs]
378
411
 
379
412
  @property
380
413
  def request_fields(self) -> dict | None:
381
- """Get the requested fields in the instruction."""
382
414
  return self.content.get("request_fields", None)
383
415
 
384
416
  @request_fields.setter
385
- def request_fields(self, request_fields: dict) -> None:
386
- """Set the requested fields in the instruction."""
387
- self.content["request_fields"] = request_fields
417
+ def request_fields(self, fields: dict) -> None:
418
+ self.content["request_fields"] = fields
388
419
  self.content["request_response_format"] = (
389
- prepare_request_response_format(request_fields)
420
+ prepare_request_response_format(fields)
390
421
  )
391
422
 
392
423
  @property
393
424
  def response_format(self) -> type[BaseModel] | None:
394
- """Get the request model of the instruction."""
395
425
  return self.content.get("request_model", None)
396
426
 
397
427
  @response_format.setter
398
- def response_format(self, request_model: type[BaseModel]) -> None:
399
- """
400
- Set the request model of the instruction.
401
-
402
- This also updates request fields and context based on the model.
403
- """
404
- if isinstance(request_model, BaseModel):
405
- self.content["request_model"] = type(request_model)
428
+ def response_format(self, model: type[BaseModel]) -> None:
429
+ if isinstance(model, BaseModel):
430
+ self.content["request_model"] = type(model)
406
431
  else:
407
- self.content["request_model"] = request_model
432
+ self.content["request_model"] = model
408
433
 
409
434
  self.request_fields = {}
410
- self.extend_context(
411
- respond_schema_info=request_model.model_json_schema()
412
- )
413
- self.request_fields = breakdown_pydantic_annotation(request_model)
435
+ self.extend_context(respond_schema_info=model.model_json_schema())
436
+ self.request_fields = breakdown_pydantic_annotation(model)
414
437
 
415
438
  @property
416
439
  def respond_schema_info(self) -> dict | None:
417
- """Get the response schema information."""
418
440
  return self.content.get("respond_schema_info", None)
419
441
 
420
442
  @respond_schema_info.setter
421
- def respond_schema_info(self, respond_schema_info: dict) -> None:
422
- """Set the response schema information."""
423
- if respond_schema_info is None:
443
+ def respond_schema_info(self, info: dict) -> None:
444
+ if info is None:
424
445
  self.content.pop("respond_schema_info", None)
425
446
  else:
426
- self.content["respond_schema_info"] = respond_schema_info
447
+ self.content["respond_schema_info"] = info
427
448
 
428
449
  @property
429
450
  def request_response_format(self) -> str | None:
430
- """Get the request response format."""
431
451
  return self.content.get("request_response_format", None)
432
452
 
433
453
  @request_response_format.setter
434
- def request_response_format(self, request_response_format: str) -> None:
435
- """Set the request response format."""
436
- if not request_response_format:
454
+ def request_response_format(self, val: str) -> None:
455
+ if not val:
437
456
  self.content.pop("request_response_format", None)
438
457
  else:
439
- self.content["request_response_format"] = request_response_format
458
+ self.content["request_response_format"] = val
440
459
 
441
460
  def extend_images(
442
461
  self,
@@ -444,38 +463,34 @@ class Instruction(RoledMessage):
444
463
  image_detail: Literal["low", "high", "auto"] = None,
445
464
  ) -> None:
446
465
  """
447
- Add new images to the instruction.
466
+ Append images to the existing list.
448
467
 
449
468
  Args:
450
- images: New images to add
451
- image_detail: Optional new image detail level
469
+ images: The new images to add, a single or multiple.
470
+ image_detail: If provided, updates the image detail field.
452
471
  """
453
- images = images if isinstance(images, list) else [images]
454
- _ima: list = self.content.get("images", [])
455
- _ima.extend(images)
456
- self.images = _ima
457
-
472
+ arr: list = self.images
473
+ arr.extend(images if isinstance(images, list) else [images])
474
+ self.images = arr
458
475
  if image_detail:
459
476
  self.image_detail = image_detail
460
477
 
461
478
  def extend_context(self, *args, **kwargs) -> None:
462
479
  """
463
- Add new context to the instruction.
480
+ Append additional context to the existing context array.
464
481
 
465
482
  Args:
466
- *args: Positional arguments to add to context
467
- **kwargs: Keyword arguments to add to context
483
+ *args: Positional args are appended as list items.
484
+ **kwargs: Key-value pairs are appended as separate dict items.
468
485
  """
469
- context: list = self.content.get("context", [])
470
-
486
+ ctx: list = self.context or []
471
487
  if args:
472
- context.extend(args)
488
+ ctx.extend(args)
473
489
  if kwargs:
474
- kwargs = copy(kwargs)
475
- for k, v in kwargs.items():
476
- context.append({k: v})
477
-
478
- self.context = context
490
+ kw = copy(kwargs)
491
+ for k, v in kw.items():
492
+ ctx.append({k: v})
493
+ self.context = ctx
479
494
 
480
495
  def update(
481
496
  self,
@@ -494,22 +509,23 @@ class Instruction(RoledMessage):
494
509
  recipient: SenderRecipient = None,
495
510
  ):
496
511
  """
497
- Update multiple aspects of the instruction.
512
+ Batch-update this Instruction.
498
513
 
499
514
  Args:
500
- *args: Positional arguments for context update
501
- guidance: New guidance content
502
- instruction: New instruction content
503
- request_fields: New request fields
504
- plain_content: New plain text content
505
- request_model: New request model
506
- images: New images to add
507
- image_detail: New image detail level
508
- tool_schemas: New tool schemas
509
- **kwargs: Additional keyword arguments for context update
515
+ guidance (JsonValue): New guidance text.
516
+ instruction (JsonValue): Main user instruction.
517
+ request_fields (JsonValue): Updated request fields.
518
+ plain_content (JsonValue): Plain text fallback.
519
+ request_model (BaseModel|type[BaseModel]): Pydantic schema model.
520
+ response_format (BaseModel|type[BaseModel]): Alias for request_model.
521
+ images (list|str): Additional images to add.
522
+ image_detail ("low"|"high"|"auto"): Image detail level.
523
+ tool_schemas (dict): New tool schemas.
524
+ sender (SenderRecipient): New sender.
525
+ recipient (SenderRecipient): New recipient.
510
526
 
511
527
  Raises:
512
- ValueError: If both request_model and request_fields are provided
528
+ ValueError: If request_model and request_fields are both set.
513
529
  """
514
530
  if response_format and request_model:
515
531
  raise ValueError(
@@ -559,8 +575,14 @@ class Instruction(RoledMessage):
559
575
 
560
576
  @override
561
577
  @property
562
- def rendered(self) -> dict[str, Any]:
563
- """Format the content of the instruction."""
578
+ def rendered(self) -> Any:
579
+ """
580
+ Convert content into a text or combined text+image structure.
581
+
582
+ Returns:
583
+ If no images are included, returns a single text block.
584
+ Otherwise returns an array of text + image dicts.
585
+ """
564
586
  content = copy(self.content)
565
587
  text_content = format_text_content(content)
566
588
  if "images" not in content:
@@ -575,8 +597,16 @@ class Instruction(RoledMessage):
575
597
 
576
598
  @field_serializer("content")
577
599
  def _serialize_content(self, values) -> dict:
578
- """Serialize the content of the instruction."""
600
+ """
601
+ Remove certain ephemeral fields before saving.
602
+
603
+ Returns:
604
+ dict: The sanitized content dictionary.
605
+ """
579
606
  values.pop("request_model", None)
580
607
  values.pop("request_fields", None)
581
608
 
582
609
  return values
610
+
611
+
612
+ # File: lionagi/protocols/messages/instruction.py