lionagi 0.7.0__py3-none-any.whl → 0.7.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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