lionagi 0.18.0__py3-none-any.whl → 0.18.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 (93) hide show
  1. lionagi/__init__.py +102 -59
  2. lionagi/_errors.py +0 -5
  3. lionagi/adapters/spec_adapters/__init__.py +9 -0
  4. lionagi/adapters/spec_adapters/_protocol.py +236 -0
  5. lionagi/adapters/spec_adapters/pydantic_field.py +158 -0
  6. lionagi/fields.py +83 -0
  7. lionagi/ln/__init__.py +3 -1
  8. lionagi/ln/_async_call.py +2 -2
  9. lionagi/ln/concurrency/primitives.py +4 -4
  10. lionagi/ln/concurrency/task.py +1 -0
  11. lionagi/ln/fuzzy/_fuzzy_match.py +2 -2
  12. lionagi/ln/types/__init__.py +51 -0
  13. lionagi/ln/types/_sentinel.py +154 -0
  14. lionagi/ln/{types.py → types/base.py} +108 -168
  15. lionagi/ln/types/operable.py +221 -0
  16. lionagi/ln/types/spec.py +441 -0
  17. lionagi/models/field_model.py +69 -7
  18. lionagi/models/hashable_model.py +2 -3
  19. lionagi/models/model_params.py +4 -3
  20. lionagi/operations/ReAct/ReAct.py +1 -1
  21. lionagi/operations/act/act.py +3 -3
  22. lionagi/operations/builder.py +5 -7
  23. lionagi/operations/fields.py +380 -0
  24. lionagi/operations/flow.py +4 -6
  25. lionagi/operations/node.py +4 -4
  26. lionagi/operations/operate/operate.py +123 -89
  27. lionagi/operations/operate/operative.py +198 -0
  28. lionagi/operations/operate/step.py +203 -0
  29. lionagi/operations/select/select.py +1 -1
  30. lionagi/operations/select/utils.py +7 -1
  31. lionagi/operations/types.py +7 -7
  32. lionagi/protocols/action/manager.py +5 -6
  33. lionagi/protocols/contracts.py +2 -2
  34. lionagi/protocols/generic/__init__.py +22 -0
  35. lionagi/protocols/generic/element.py +36 -127
  36. lionagi/protocols/generic/pile.py +9 -10
  37. lionagi/protocols/generic/progression.py +23 -22
  38. lionagi/protocols/graph/edge.py +6 -5
  39. lionagi/protocols/ids.py +6 -49
  40. lionagi/protocols/messages/__init__.py +3 -1
  41. lionagi/protocols/messages/base.py +7 -6
  42. lionagi/protocols/messages/instruction.py +0 -1
  43. lionagi/protocols/messages/message.py +2 -2
  44. lionagi/protocols/types.py +1 -11
  45. lionagi/service/connections/__init__.py +3 -0
  46. lionagi/service/connections/providers/claude_code_cli.py +3 -2
  47. lionagi/service/hooks/_types.py +1 -1
  48. lionagi/service/hooks/_utils.py +1 -1
  49. lionagi/service/hooks/hook_event.py +3 -8
  50. lionagi/service/hooks/hook_registry.py +5 -5
  51. lionagi/service/hooks/hooked_event.py +61 -1
  52. lionagi/service/imodel.py +24 -20
  53. lionagi/service/third_party/claude_code.py +1 -2
  54. lionagi/service/third_party/openai_models.py +24 -22
  55. lionagi/service/token_calculator.py +1 -94
  56. lionagi/session/branch.py +26 -228
  57. lionagi/session/session.py +5 -90
  58. lionagi/version.py +1 -1
  59. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/METADATA +6 -5
  60. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/RECORD +62 -82
  61. lionagi/fields/__init__.py +0 -47
  62. lionagi/fields/action.py +0 -188
  63. lionagi/fields/base.py +0 -153
  64. lionagi/fields/code.py +0 -239
  65. lionagi/fields/file.py +0 -234
  66. lionagi/fields/instruct.py +0 -135
  67. lionagi/fields/reason.py +0 -55
  68. lionagi/fields/research.py +0 -52
  69. lionagi/operations/brainstorm/__init__.py +0 -2
  70. lionagi/operations/brainstorm/brainstorm.py +0 -498
  71. lionagi/operations/brainstorm/prompt.py +0 -11
  72. lionagi/operations/instruct/__init__.py +0 -2
  73. lionagi/operations/instruct/instruct.py +0 -28
  74. lionagi/operations/plan/__init__.py +0 -6
  75. lionagi/operations/plan/plan.py +0 -386
  76. lionagi/operations/plan/prompt.py +0 -25
  77. lionagi/operations/utils.py +0 -45
  78. lionagi/protocols/forms/__init__.py +0 -2
  79. lionagi/protocols/forms/base.py +0 -85
  80. lionagi/protocols/forms/flow.py +0 -79
  81. lionagi/protocols/forms/form.py +0 -86
  82. lionagi/protocols/forms/report.py +0 -48
  83. lionagi/protocols/mail/__init__.py +0 -2
  84. lionagi/protocols/mail/exchange.py +0 -220
  85. lionagi/protocols/mail/mail.py +0 -51
  86. lionagi/protocols/mail/mailbox.py +0 -103
  87. lionagi/protocols/mail/manager.py +0 -218
  88. lionagi/protocols/mail/package.py +0 -101
  89. lionagi/protocols/operatives/__init__.py +0 -2
  90. lionagi/protocols/operatives/operative.py +0 -362
  91. lionagi/protocols/operatives/step.py +0 -227
  92. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/WHEEL +0 -0
  93. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,380 @@
1
+ import re
2
+ from typing import Any, Literal
3
+
4
+ from pydantic import BaseModel, Field, JsonValue, field_validator
5
+
6
+ from lionagi.ln import extract_json, to_dict, to_list
7
+ from lionagi.ln.types import Unset
8
+ from lionagi.models import HashableModel
9
+
10
+ _DEFAULT_FIELDS = {}
11
+
12
+
13
+ class Instruct(HashableModel):
14
+ """Model for defining instruction parameters and execution requirements."""
15
+
16
+ instruction: str | None = Field(
17
+ None,
18
+ description=(
19
+ "A clear, actionable task definition. Specify:\n"
20
+ "1) The primary goal or objective\n"
21
+ "2) Key success criteria or constraints\n"
22
+ "\n"
23
+ "Guidelines:\n"
24
+ "- Start with a direct action verb (e.g., 'Analyze', 'Generate', 'Create')\n"
25
+ "- Include scope, boundaries, or constraints\n"
26
+ "- Provide success criteria if relevant\n"
27
+ "- For complex tasks, break them into logical steps"
28
+ ),
29
+ )
30
+
31
+ guidance: JsonValue | None = Field(
32
+ None,
33
+ description=(
34
+ "Strategic direction and constraints for executing the task. "
35
+ "Include:\n"
36
+ "1) Preferred methods or frameworks\n"
37
+ "2) Quality benchmarks (e.g., speed, clarity)\n"
38
+ "3) Resource or environmental constraints\n"
39
+ "4) Relevant compliance or standards\n"
40
+ "Use None if no special guidance."
41
+ ),
42
+ )
43
+
44
+ context: JsonValue | None = Field(
45
+ None,
46
+ description=(
47
+ "Background information and current-state data needed for the task. "
48
+ "Should be:\n"
49
+ "1) Directly relevant\n"
50
+ "2) Sufficient to perform the task\n"
51
+ "3) Free of extraneous detail\n"
52
+ "Include environment, prior outcomes, system states, or dependencies. "
53
+ "Use None if no additional context is needed."
54
+ ),
55
+ )
56
+
57
+ reason: bool | None = Field(
58
+ None,
59
+ description=(
60
+ "Include a thoughtful explanation of decisions, trade-offs, "
61
+ "and insights. Encourage deeper introspection on why certain "
62
+ "choices were made, potential alternatives, and how confidence "
63
+ "was shaped. If not needed, set to None."
64
+ ),
65
+ )
66
+ actions: bool | None = Field(
67
+ None,
68
+ description=(
69
+ "Controls execution mode. "
70
+ "True: Execute specified actions. "
71
+ "False: Analysis/recommendations only. "
72
+ "None: Contextual execution."
73
+ ),
74
+ )
75
+
76
+ action_strategy: Literal["sequential", "concurrent"] | None = Field(
77
+ None,
78
+ description="Action strategy to use for executing actions. Default "
79
+ "is 'concurrent'. Only provide for if actions are enabled.",
80
+ )
81
+
82
+ @field_validator("instruction", "guidance", "context", mode="before")
83
+ def _validate_instruction(cls, v):
84
+ from lionagi.libs.validate.common_field_validators import (
85
+ validate_nullable_jsonvalue_field,
86
+ )
87
+
88
+ return validate_nullable_jsonvalue_field(cls, v)
89
+
90
+ @field_validator("reason", "actions", mode="before")
91
+ def _validate_reason(cls, v):
92
+ from lionagi.libs.validate.common_field_validators import (
93
+ validate_boolean_field,
94
+ )
95
+
96
+ return validate_boolean_field(cls, v)
97
+
98
+ @field_validator("action_strategy", mode="before")
99
+ def _validate_action_strategy(cls, v):
100
+ if v not in ["batch", "sequential", "concurrent"]:
101
+ return "concurrent"
102
+ return v
103
+
104
+
105
+ class Reason(HashableModel):
106
+ title: str | None = None
107
+ content: str | None = None
108
+ confidence_score: float | None = Field(
109
+ None,
110
+ title="Confidence Score",
111
+ description=(
112
+ "Numeric confidence score (0.0 to 1.0, up to three decimals) indicating "
113
+ "how well you've met user expectations. Use this guide:\n"
114
+ " • 1.0: Highly confident\n"
115
+ " • 0.8-1.0: Reasonably sure\n"
116
+ " • 0.5-0.8: Re-check, refine or backtrack\n"
117
+ " • 0.0-0.5: Off track, stop"
118
+ ),
119
+ )
120
+
121
+ @field_validator("confidence_score", mode="before")
122
+ def _validate_confidence(cls, v):
123
+ if v is None:
124
+ return None
125
+ try:
126
+ from lionagi.libs.validate.to_num import to_num
127
+
128
+ return to_num(
129
+ v,
130
+ upper_bound=1,
131
+ lower_bound=0,
132
+ num_type=float,
133
+ precision=3,
134
+ )
135
+ except Exception:
136
+ return -1
137
+
138
+
139
+ class ActionRequestModel(HashableModel):
140
+ """
141
+ Captures a single action request, typically from a user or system message.
142
+ Includes the name of the function and the arguments to be passed.
143
+ """
144
+
145
+ function: str | None = Field(
146
+ None,
147
+ title="Function",
148
+ description=(
149
+ "Name of the function to call from the provided `tool_schemas`. "
150
+ "If no `tool_schemas` exist, set to None or leave blank. "
151
+ "Never invent new function names outside what's given."
152
+ ),
153
+ examples=["multiply", "create_user"],
154
+ )
155
+ arguments: dict[str, Any] | None = Field(
156
+ None,
157
+ title="Arguments",
158
+ description=(
159
+ "Dictionary of arguments for the chosen function. "
160
+ "Use only argument names/types defined in `tool_schemas`. "
161
+ "Never introduce extra argument names."
162
+ ),
163
+ )
164
+
165
+ @field_validator("arguments", mode="before")
166
+ def validate_arguments(cls, value: Any) -> dict[str, Any]:
167
+ """
168
+ Coerce arguments into a dictionary if possible, recursively.
169
+
170
+ Raises:
171
+ ValueError if the data can't be coerced.
172
+ """
173
+ return to_dict(
174
+ value,
175
+ fuzzy_parse=True,
176
+ recursive=True,
177
+ recursive_python_only=False,
178
+ )
179
+
180
+ @field_validator("function", mode="before")
181
+ def validate_function(cls, value: str) -> str:
182
+ """
183
+ Ensure the function name is a valid non-empty string (if provided).
184
+ """
185
+ from lionagi.libs.validate.common_field_validators import (
186
+ validate_nullable_string_field,
187
+ )
188
+
189
+ return validate_nullable_string_field(cls, value, "function", False)
190
+
191
+ @classmethod
192
+ def create(cls, content: str):
193
+ """
194
+ Attempt to parse a string (usually from a conversation or JSON) into
195
+ one or more ActionRequestModel instances.
196
+
197
+ If no valid structure is found, returns an empty list.
198
+ """
199
+
200
+ def parse_action_request(content: str | dict) -> list[dict]:
201
+
202
+ json_blocks = []
203
+
204
+ if isinstance(content, BaseModel):
205
+ json_blocks = [content.model_dump()]
206
+
207
+ elif isinstance(content, str):
208
+ json_blocks = extract_json(content, fuzzy_parse=True)
209
+ if not json_blocks:
210
+ pattern2 = r"```python\s*(.*?)\s*```"
211
+ _d = re.findall(pattern2, content, re.DOTALL)
212
+ json_blocks = [
213
+ extract_json(match, fuzzy_parse=True) for match in _d
214
+ ]
215
+ json_blocks = to_list(json_blocks, dropna=True)
216
+
217
+ print(json_blocks)
218
+
219
+ elif content and isinstance(content, dict):
220
+ json_blocks = [content]
221
+
222
+ if json_blocks and not isinstance(json_blocks, list):
223
+ json_blocks = [json_blocks]
224
+
225
+ out = []
226
+
227
+ for i in json_blocks:
228
+ j = {}
229
+ if isinstance(i, dict):
230
+ if "function" in i and isinstance(i["function"], dict):
231
+ if "name" in i["function"]:
232
+ i["function"] = i["function"]["name"]
233
+ for k, v in i.items():
234
+ k = (
235
+ k.replace("action_", "")
236
+ .replace("recipient_", "")
237
+ .replace("s", "")
238
+ )
239
+ if k in ["name", "function", "recipient"]:
240
+ j["function"] = v
241
+ elif k in ["parameter", "argument", "arg", "param"]:
242
+ j["arguments"] = to_dict(
243
+ v,
244
+ str_type="json",
245
+ fuzzy_parse=True,
246
+ suppress=True,
247
+ )
248
+ if (
249
+ j
250
+ and all(key in j for key in ["function", "arguments"])
251
+ and j["arguments"]
252
+ ):
253
+ out.append(j)
254
+
255
+ return out
256
+
257
+ try:
258
+ ctx = parse_action_request(content)
259
+ if ctx:
260
+ return [cls.model_validate(i) for i in ctx]
261
+ return []
262
+ except Exception:
263
+ return []
264
+
265
+
266
+ class ActionResponseModel(HashableModel):
267
+ """
268
+ Encapsulates a function's output after being called. Typically
269
+ references the original function name, arguments, and the result.
270
+ """
271
+
272
+ function: str = Field(default_factory=str, title="Function")
273
+ arguments: dict[str, Any] = Field(default_factory=dict)
274
+ output: Any = None
275
+
276
+
277
+ def get_default_field(
278
+ kind: Literal[
279
+ "action_requests",
280
+ "action_responses",
281
+ "action_required",
282
+ "instruct",
283
+ "reason",
284
+ ],
285
+ default: Any = Unset,
286
+ nullable: bool = True,
287
+ listable: bool = None,
288
+ ):
289
+ global _DEFAULT_FIELDS
290
+ key = (kind, str(default), nullable, listable)
291
+ if key not in _DEFAULT_FIELDS:
292
+ _DEFAULT_FIELDS[key] = _get_default_fields(
293
+ kind, default=default, nullable=nullable, listable=listable
294
+ )
295
+ return _DEFAULT_FIELDS[key]
296
+
297
+
298
+ def _get_default_fields(
299
+ kind: Literal[
300
+ "action_requests",
301
+ "action_responses",
302
+ "action_required",
303
+ "instruct",
304
+ "reason",
305
+ ],
306
+ default: Any = Unset,
307
+ nullable: bool = True,
308
+ listable: bool = None,
309
+ ):
310
+ from lionagi.models.field_model import FieldModel
311
+
312
+ fm = None
313
+
314
+ match kind:
315
+
316
+ case "instruct":
317
+ fm = FieldModel(Instruct, name="instruct_model")
318
+
319
+ case "action_required":
320
+ from lionagi.libs.validate.common_field_validators import (
321
+ validate_boolean_field,
322
+ )
323
+
324
+ fm = FieldModel(
325
+ bool,
326
+ name="action_required",
327
+ validator=lambda cls, v: validate_boolean_field(cls, v, False),
328
+ description=(
329
+ "Whether this step strictly requires performing actions. "
330
+ "If true, the requests in `action_requests` must be fulfilled, "
331
+ "assuming `tool_schemas` are available. "
332
+ "If false or no `tool_schemas` exist, actions are optional."
333
+ ),
334
+ )
335
+
336
+ case "action_requests":
337
+ fm = FieldModel(
338
+ ActionRequestModel,
339
+ name="action_requests",
340
+ listable=True,
341
+ description=(
342
+ "List of actions to be executed when `action_required` is true. "
343
+ "Each action must align with the available `tool_schemas`. "
344
+ "Leave empty if no actions are needed."
345
+ ),
346
+ )
347
+
348
+ case "action_responses":
349
+ fm = FieldModel(
350
+ ActionResponseModel, name="action_responses", listable=True
351
+ )
352
+
353
+ case "reason":
354
+ fm = FieldModel(Reason, name="reason")
355
+
356
+ case _:
357
+ raise ValueError(f"Unknown default field kind: {kind}")
358
+
359
+ if listable is not None:
360
+ if listable and not fm.is_listable:
361
+ fm = fm.as_listable()
362
+ else:
363
+ fm = fm.with_metadata("listable", False)
364
+
365
+ if nullable:
366
+ fm = fm.as_nullable()
367
+ default = None
368
+
369
+ if fm.is_listable and default is Unset:
370
+ default = list
371
+
372
+ if default is not Unset:
373
+ fm = fm.with_default(default)
374
+
375
+ if fm.is_listable:
376
+ fm = fm.with_validator(
377
+ lambda cls, x: to_list(x, dropna=True, flatten=True, unique=True)
378
+ )
379
+
380
+ return fm
@@ -10,8 +10,9 @@ using Events for synchronization and CapacityLimiter for concurrency control.
10
10
 
11
11
  import os
12
12
  from typing import TYPE_CHECKING, Any
13
+ from uuid import UUID
13
14
 
14
- from lionagi.ln._async_call import AlcallParams
15
+ from lionagi.ln import AlcallParams
15
16
  from lionagi.ln.concurrency import CapacityLimiter, ConcurrencyEvent
16
17
  from lionagi.operations.node import Operation
17
18
  from lionagi.protocols.types import EventStatus
@@ -164,13 +165,11 @@ class DependencyAwareExecutor:
164
165
  # Add to session branches collection directly
165
166
  # Check if this is a real branch (not a mock)
166
167
  try:
167
- from lionagi.protocols.types import IDType
168
-
169
168
  # Try to validate the ID
170
169
  if hasattr(branch_clone, "id"):
171
170
  branch_id = branch_clone.id
172
171
  # Only add to collections if it's a valid ID
173
- if isinstance(branch_id, (str, IDType)) or (
172
+ if isinstance(branch_id, (str, UUID)) or (
174
173
  hasattr(branch_id, "__str__")
175
174
  and not hasattr(branch_id, "_mock_name")
176
175
  ):
@@ -334,7 +333,7 @@ class DependencyAwareExecutor:
334
333
 
335
334
  # Wait for ALL sources (sources are now strings from builder.py)
336
335
  for source_id_str in sources:
337
- # Convert string back to IDType for lookup
336
+ # Convert string back to UUID for lookup
338
337
  # Check all operations to find matching ID
339
338
  for op_id in self.completion_events.keys():
340
339
  if str(op_id) == source_id_str:
@@ -367,7 +366,6 @@ class DependencyAwareExecutor:
367
366
  result, (str, int, float, bool)
368
367
  ):
369
368
  result = to_dict(result, recursive=True)
370
- # Use string representation of IDType for JSON serialization
371
369
  pred_context[f"{str(pred.id)}_result"] = result
372
370
 
373
371
  if "context" not in operation.parameters:
@@ -6,7 +6,7 @@ from uuid import UUID
6
6
  from anyio import get_cancelled_exc_class
7
7
  from pydantic import BaseModel, Field
8
8
 
9
- from lionagi.protocols.types import ID, Event, EventStatus, IDType, Node
9
+ from lionagi.protocols.types import ID, Event, EventStatus, Node
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from lionagi.session.branch import Branch
@@ -37,12 +37,12 @@ class Operation(Node, Event):
37
37
  )
38
38
 
39
39
  @property
40
- def branch_id(self) -> IDType | None:
40
+ def branch_id(self) -> UUID | None:
41
41
  if a := self.metadata.get("branch_id"):
42
42
  return ID.get_id(a)
43
43
 
44
44
  @branch_id.setter
45
- def branch_id(self, value: str | UUID | IDType | None):
45
+ def branch_id(self, value: str | UUID | None):
46
46
  if value is None:
47
47
  self.metadata.pop("branch_id", None)
48
48
  else:
@@ -54,7 +54,7 @@ class Operation(Node, Event):
54
54
  return ID.get_id(a)
55
55
 
56
56
  @graph_id.setter
57
- def graph_id(self, value: str | UUID | IDType | None):
57
+ def graph_id(self, value: str | UUID | None):
58
58
  if value is None:
59
59
  self.metadata.pop("graph_id", None)
60
60
  else: