lionagi 0.18.0__py3-none-any.whl → 0.18.1__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/_errors.py +0 -5
- lionagi/fields.py +83 -0
- lionagi/ln/__init__.py +3 -1
- lionagi/ln/concurrency/primitives.py +4 -4
- lionagi/ln/concurrency/task.py +1 -0
- lionagi/models/field_model.py +12 -4
- lionagi/models/hashable_model.py +2 -3
- lionagi/operations/ReAct/ReAct.py +1 -1
- lionagi/operations/act/act.py +3 -3
- lionagi/operations/builder.py +5 -7
- lionagi/operations/fields.py +380 -0
- lionagi/operations/flow.py +4 -6
- lionagi/operations/node.py +4 -4
- lionagi/operations/operate/operate.py +9 -7
- lionagi/{protocols/operatives → operations/operate}/operative.py +4 -5
- lionagi/{protocols/operatives → operations/operate}/step.py +34 -39
- lionagi/operations/select/select.py +1 -1
- lionagi/operations/select/utils.py +7 -1
- lionagi/operations/types.py +1 -1
- lionagi/protocols/action/manager.py +5 -6
- lionagi/protocols/contracts.py +2 -2
- lionagi/protocols/generic/__init__.py +22 -0
- lionagi/protocols/generic/element.py +36 -127
- lionagi/protocols/generic/pile.py +9 -10
- lionagi/protocols/generic/progression.py +23 -22
- lionagi/protocols/graph/edge.py +6 -5
- lionagi/protocols/ids.py +6 -49
- lionagi/protocols/messages/__init__.py +3 -1
- lionagi/protocols/messages/base.py +7 -6
- lionagi/protocols/messages/instruction.py +0 -1
- lionagi/protocols/types.py +1 -11
- lionagi/service/connections/__init__.py +3 -0
- lionagi/service/connections/providers/claude_code_cli.py +3 -2
- lionagi/service/hooks/_types.py +1 -1
- lionagi/service/hooks/_utils.py +1 -1
- lionagi/service/hooks/hook_event.py +3 -8
- lionagi/service/hooks/hook_registry.py +5 -5
- lionagi/service/hooks/hooked_event.py +61 -1
- lionagi/service/imodel.py +24 -20
- lionagi/service/third_party/claude_code.py +1 -2
- lionagi/service/third_party/openai_models.py +24 -22
- lionagi/service/token_calculator.py +1 -94
- lionagi/session/branch.py +26 -228
- lionagi/session/session.py +5 -90
- lionagi/version.py +1 -1
- {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/METADATA +6 -5
- {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/RECORD +49 -76
- lionagi/fields/__init__.py +0 -47
- lionagi/fields/action.py +0 -188
- lionagi/fields/base.py +0 -153
- lionagi/fields/code.py +0 -239
- lionagi/fields/file.py +0 -234
- lionagi/fields/instruct.py +0 -135
- lionagi/fields/reason.py +0 -55
- lionagi/fields/research.py +0 -52
- lionagi/operations/brainstorm/__init__.py +0 -2
- lionagi/operations/brainstorm/brainstorm.py +0 -498
- lionagi/operations/brainstorm/prompt.py +0 -11
- lionagi/operations/instruct/__init__.py +0 -2
- lionagi/operations/instruct/instruct.py +0 -28
- lionagi/operations/plan/__init__.py +0 -6
- lionagi/operations/plan/plan.py +0 -386
- lionagi/operations/plan/prompt.py +0 -25
- lionagi/operations/utils.py +0 -45
- lionagi/protocols/forms/__init__.py +0 -2
- lionagi/protocols/forms/base.py +0 -85
- lionagi/protocols/forms/flow.py +0 -79
- lionagi/protocols/forms/form.py +0 -86
- lionagi/protocols/forms/report.py +0 -48
- lionagi/protocols/mail/__init__.py +0 -2
- lionagi/protocols/mail/exchange.py +0 -220
- lionagi/protocols/mail/mail.py +0 -51
- lionagi/protocols/mail/mailbox.py +0 -103
- lionagi/protocols/mail/manager.py +0 -218
- lionagi/protocols/mail/package.py +0 -101
- lionagi/protocols/operatives/__init__.py +0 -2
- {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/WHEEL +0 -0
- {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/licenses/LICENSE +0 -0
lionagi/_errors.py
CHANGED
|
@@ -13,7 +13,6 @@ __all__ = (
|
|
|
13
13
|
"ObservationError",
|
|
14
14
|
"ResourceError",
|
|
15
15
|
"RateLimitError",
|
|
16
|
-
"IDError",
|
|
17
16
|
"RelationError",
|
|
18
17
|
"OperationError",
|
|
19
18
|
"ExecutionError",
|
|
@@ -127,10 +126,6 @@ class RateLimitError(LionError):
|
|
|
127
126
|
object.__setattr__(self, "retry_after", retry_after)
|
|
128
127
|
|
|
129
128
|
|
|
130
|
-
class IDError(LionError):
|
|
131
|
-
pass
|
|
132
|
-
|
|
133
|
-
|
|
134
129
|
class RelationError(LionError):
|
|
135
130
|
pass
|
|
136
131
|
|
lionagi/fields.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from .operations.fields import (
|
|
5
|
+
ActionRequestModel,
|
|
6
|
+
ActionResponseModel,
|
|
7
|
+
Instruct,
|
|
8
|
+
Reason,
|
|
9
|
+
get_default_field,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
_lazy_imports = {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def __getattr__(name: str):
|
|
16
|
+
if name in _lazy_imports:
|
|
17
|
+
return _lazy_imports[name]
|
|
18
|
+
|
|
19
|
+
if name == "ActionRequestModel":
|
|
20
|
+
from .operations.fields import ActionRequestModel
|
|
21
|
+
|
|
22
|
+
_lazy_imports[name] = ActionRequestModel
|
|
23
|
+
return ActionRequestModel
|
|
24
|
+
|
|
25
|
+
if name == "ActionResponseModel":
|
|
26
|
+
from .operations.fields import ActionResponseModel
|
|
27
|
+
|
|
28
|
+
_lazy_imports[name] = ActionResponseModel
|
|
29
|
+
return ActionResponseModel
|
|
30
|
+
|
|
31
|
+
if name == "Instruct":
|
|
32
|
+
from .operations.fields import Instruct
|
|
33
|
+
|
|
34
|
+
_lazy_imports[name] = Instruct
|
|
35
|
+
return Instruct
|
|
36
|
+
|
|
37
|
+
if name == "Reason":
|
|
38
|
+
from .operations.fields import Reason
|
|
39
|
+
|
|
40
|
+
_lazy_imports[name] = Reason
|
|
41
|
+
return Reason
|
|
42
|
+
|
|
43
|
+
from .operations.fields import get_default_field
|
|
44
|
+
|
|
45
|
+
if name == "get_default_field":
|
|
46
|
+
|
|
47
|
+
_lazy_imports[name] = get_default_field
|
|
48
|
+
return get_default_field
|
|
49
|
+
|
|
50
|
+
if name == "ACTION_REQUESTS_FIELD":
|
|
51
|
+
return get_default_field("action_requests")
|
|
52
|
+
|
|
53
|
+
if name == "ACTION_RESPONSES_FIELD":
|
|
54
|
+
return get_default_field("action_responses")
|
|
55
|
+
|
|
56
|
+
if name == "ACTION_REQUIRED_FIELD":
|
|
57
|
+
return get_default_field("action_required")
|
|
58
|
+
|
|
59
|
+
if name == "INSTRUCT_FIELD":
|
|
60
|
+
return get_default_field("instruct")
|
|
61
|
+
|
|
62
|
+
if name == "LIST_INSTRUCT_FIELD_MODEL":
|
|
63
|
+
return get_default_field("instruct", listable=True)
|
|
64
|
+
|
|
65
|
+
if name == "REASON_FIELD":
|
|
66
|
+
return get_default_field("reason")
|
|
67
|
+
|
|
68
|
+
raise AttributeError(f"module {__name__} has no attribute {name}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
__all__ = (
|
|
72
|
+
"ACTION_REQUESTS_FIELD",
|
|
73
|
+
"ACTION_RESPONSES_FIELD",
|
|
74
|
+
"ACTION_REQUIRED_FIELD",
|
|
75
|
+
"INSTRUCT_FIELD",
|
|
76
|
+
"LIST_INSTRUCT_FIELD_MODEL",
|
|
77
|
+
"REASON_FIELD",
|
|
78
|
+
"ActionRequestModel",
|
|
79
|
+
"ActionResponseModel",
|
|
80
|
+
"Instruct",
|
|
81
|
+
"Reason",
|
|
82
|
+
"get_default_field",
|
|
83
|
+
)
|
lionagi/ln/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from ._async_call import alcall, bcall
|
|
1
|
+
from ._async_call import AlcallParams, BcallParams, alcall, bcall
|
|
2
2
|
from ._hash import hash_dict
|
|
3
3
|
from ._json_dump import (
|
|
4
4
|
get_orjson_default,
|
|
@@ -80,4 +80,6 @@ __all__ = (
|
|
|
80
80
|
"not_sentinel",
|
|
81
81
|
"to_dict",
|
|
82
82
|
"fuzzy_validate_mapping",
|
|
83
|
+
"AlcallParams",
|
|
84
|
+
"BcallParams",
|
|
83
85
|
)
|
|
@@ -142,15 +142,15 @@ class CapacityLimiter:
|
|
|
142
142
|
"""Get the number of currently available tokens."""
|
|
143
143
|
return self._lim.available_tokens
|
|
144
144
|
|
|
145
|
-
def acquire_on_behalf_of(self, borrower: object) -> None:
|
|
146
|
-
"""
|
|
145
|
+
async def acquire_on_behalf_of(self, borrower: object) -> None:
|
|
146
|
+
"""Asynchronously acquire capacity on behalf of another object.
|
|
147
147
|
|
|
148
148
|
For resource pooling where the acquirer differs from the releaser.
|
|
149
149
|
|
|
150
150
|
Args:
|
|
151
151
|
borrower: Object that will be responsible for releasing.
|
|
152
152
|
"""
|
|
153
|
-
self._lim.acquire_on_behalf_of(borrower)
|
|
153
|
+
await self._lim.acquire_on_behalf_of(borrower)
|
|
154
154
|
|
|
155
155
|
def release_on_behalf_of(self, borrower: object) -> None:
|
|
156
156
|
"""Release capacity that was acquired on behalf of an object.
|
|
@@ -326,6 +326,6 @@ class Condition:
|
|
|
326
326
|
"""Wake up all tasks waiting on this condition."""
|
|
327
327
|
self._condition.notify_all()
|
|
328
328
|
|
|
329
|
-
def statistics(self) -> anyio.
|
|
329
|
+
def statistics(self) -> anyio.ConditionStatistics:
|
|
330
330
|
"""Return statistics about waiting tasks."""
|
|
331
331
|
return self._condition.statistics()
|
lionagi/ln/concurrency/task.py
CHANGED
lionagi/models/field_model.py
CHANGED
|
@@ -84,7 +84,7 @@ class FieldModel(Params):
|
|
|
84
84
|
base_type: type[Any]
|
|
85
85
|
metadata: tuple[Meta, ...]
|
|
86
86
|
|
|
87
|
-
def __init__(self, **kwargs: Any) -> None:
|
|
87
|
+
def __init__(self, base_type: type[Any] = None, **kwargs: Any) -> None:
|
|
88
88
|
"""Initialize FieldModel with legacy compatibility.
|
|
89
89
|
|
|
90
90
|
Handles backward compatibility by converting old-style kwargs to the new
|
|
@@ -94,6 +94,8 @@ class FieldModel(Params):
|
|
|
94
94
|
**kwargs: Arbitrary keyword arguments, including legacy ones
|
|
95
95
|
"""
|
|
96
96
|
# Convert legacy kwargs to proper format
|
|
97
|
+
if base_type is not None:
|
|
98
|
+
kwargs["base_type"] = base_type
|
|
97
99
|
converted = self._convert_kwargs_to_params(**kwargs)
|
|
98
100
|
|
|
99
101
|
# Set fields directly and validate
|
|
@@ -538,7 +540,7 @@ class FieldModel(Params):
|
|
|
538
540
|
field_info = PydanticField(**field_kwargs)
|
|
539
541
|
|
|
540
542
|
# Set the annotation from base_type for backward compatibility
|
|
541
|
-
field_info.annotation = self.
|
|
543
|
+
field_info.annotation = self.annotation
|
|
542
544
|
|
|
543
545
|
return field_info
|
|
544
546
|
|
|
@@ -745,8 +747,14 @@ class FieldModel(Params):
|
|
|
745
747
|
|
|
746
748
|
@property
|
|
747
749
|
def annotation(self) -> type[Any]:
|
|
748
|
-
|
|
749
|
-
|
|
750
|
+
if self._is_sentinel(self.base_type):
|
|
751
|
+
return Any
|
|
752
|
+
t_ = self.base_type
|
|
753
|
+
if self.is_listable:
|
|
754
|
+
t_ = list[t_]
|
|
755
|
+
if self.is_nullable:
|
|
756
|
+
t_ = t_ | None
|
|
757
|
+
return t_
|
|
750
758
|
|
|
751
759
|
def to_dict(self) -> dict[str, Any]:
|
|
752
760
|
"""Convert field model to dictionary for backward compatibility.
|
lionagi/models/hashable_model.py
CHANGED
|
@@ -94,12 +94,11 @@ class HashableModel(BaseModel):
|
|
|
94
94
|
def _get_default_hashable_serializer():
|
|
95
95
|
global _DEFAULT_HASHABLE_SERIALIZER
|
|
96
96
|
if _DEFAULT_HASHABLE_SERIALIZER is None:
|
|
97
|
-
from lionagi.protocols.ids import Element
|
|
97
|
+
from lionagi.protocols.ids import Element
|
|
98
98
|
|
|
99
99
|
_DEFAULT_HASHABLE_SERIALIZER = ln.get_orjson_default(
|
|
100
|
-
order=[
|
|
100
|
+
order=[Element, BaseModel],
|
|
101
101
|
additional={
|
|
102
|
-
IDType: lambda o: str(o),
|
|
103
102
|
Element: lambda o: o.to_dict(),
|
|
104
103
|
BaseModel: lambda o: o.model_dump(mode="json"),
|
|
105
104
|
},
|
|
@@ -9,7 +9,6 @@ from pydantic import BaseModel
|
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
12
|
-
from lionagi.fields.instruct import Instruct
|
|
13
12
|
from lionagi.libs.schema.as_readable import as_readable
|
|
14
13
|
from lionagi.libs.validate.common_field_validators import (
|
|
15
14
|
validate_model_to_type,
|
|
@@ -18,6 +17,7 @@ from lionagi.ln.fuzzy import FuzzyMatchKeysParams
|
|
|
18
17
|
from lionagi.models.field_model import FieldModel
|
|
19
18
|
from lionagi.service.imodel import iModel
|
|
20
19
|
|
|
20
|
+
from ..fields import Instruct
|
|
21
21
|
from ..types import (
|
|
22
22
|
ActionParam,
|
|
23
23
|
ChatParam,
|
lionagi/operations/act/act.py
CHANGED
|
@@ -6,10 +6,10 @@ from typing import TYPE_CHECKING, Literal
|
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel
|
|
8
8
|
|
|
9
|
-
from lionagi.
|
|
10
|
-
from lionagi.
|
|
11
|
-
from lionagi.protocols.types import ActionRequest, ActionResponse
|
|
9
|
+
from lionagi.ln import AlcallParams
|
|
10
|
+
from lionagi.protocols.messages import ActionRequest, ActionResponse
|
|
12
11
|
|
|
12
|
+
from ..fields import ActionResponseModel
|
|
13
13
|
from ..types import ActionParam
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
lionagi/operations/builder.py
CHANGED
|
@@ -46,9 +46,9 @@ class OperationGraphBuilder:
|
|
|
46
46
|
>>> result = await session.flow(graph)
|
|
47
47
|
>>>
|
|
48
48
|
>>> # Expand based on results
|
|
49
|
-
>>> if hasattr(result, '
|
|
49
|
+
>>> if hasattr(result, 'instruct_model'):
|
|
50
50
|
... builder.expand_from_result(
|
|
51
|
-
... result.
|
|
51
|
+
... result.instruct_model,
|
|
52
52
|
... source_node_id=builder.last_operation_id,
|
|
53
53
|
... operation="instruct"
|
|
54
54
|
... )
|
|
@@ -150,7 +150,7 @@ class OperationGraphBuilder:
|
|
|
150
150
|
based on results.
|
|
151
151
|
|
|
152
152
|
Args:
|
|
153
|
-
items: Items from result to expand (e.g.,
|
|
153
|
+
items: Items from result to expand (e.g., instruct_model)
|
|
154
154
|
source_node_id: ID of node that produced these items
|
|
155
155
|
operation: Operation to apply to each item
|
|
156
156
|
strategy: How to organize the expanded operations
|
|
@@ -253,11 +253,9 @@ class OperationGraphBuilder:
|
|
|
253
253
|
if not sources:
|
|
254
254
|
raise ValueError("No source nodes for aggregation")
|
|
255
255
|
|
|
256
|
-
# Add aggregation metadata - convert
|
|
256
|
+
# Add aggregation metadata - convert UUID to strings for JSON serialization
|
|
257
257
|
agg_params = {
|
|
258
|
-
"aggregation_sources": [
|
|
259
|
-
str(s) for s in sources
|
|
260
|
-
], # Convert IDType to strings
|
|
258
|
+
"aggregation_sources": [str(s) for s in sources],
|
|
261
259
|
"aggregation_count": len(sources),
|
|
262
260
|
**parameters,
|
|
263
261
|
}
|
|
@@ -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
|