lionagi 0.17.11__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/libs/schema/minimal_yaml.py +98 -0
- lionagi/ln/__init__.py +3 -1
- lionagi/ln/concurrency/primitives.py +4 -4
- lionagi/ln/concurrency/task.py +1 -0
- lionagi/ln/types.py +32 -5
- lionagi/models/field_model.py +21 -4
- lionagi/models/hashable_model.py +2 -3
- lionagi/operations/ReAct/ReAct.py +475 -238
- lionagi/operations/ReAct/utils.py +3 -0
- lionagi/operations/act/act.py +206 -0
- lionagi/operations/builder.py +5 -7
- lionagi/operations/chat/chat.py +130 -114
- lionagi/operations/communicate/communicate.py +101 -42
- lionagi/operations/fields.py +380 -0
- lionagi/operations/flow.py +8 -10
- lionagi/operations/interpret/interpret.py +65 -20
- lionagi/operations/node.py +4 -4
- lionagi/operations/operate/operate.py +216 -108
- lionagi/{protocols/operatives → operations/operate}/operative.py +4 -5
- lionagi/{protocols/operatives → operations/operate}/step.py +34 -39
- lionagi/operations/parse/parse.py +170 -142
- lionagi/operations/select/select.py +79 -18
- lionagi/operations/select/utils.py +8 -2
- lionagi/operations/types.py +119 -23
- 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/log.py +3 -2
- 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 +29 -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 +31 -22
- lionagi/protocols/messages/instruction.py +280 -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 +1 -13
- lionagi/service/connections/__init__.py +3 -0
- lionagi/service/connections/endpoint.py +0 -8
- lionagi/service/connections/providers/claude_code_cli.py +3 -2
- lionagi/service/connections/providers/oai_.py +29 -94
- lionagi/service/connections/providers/ollama_.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 +63 -3
- lionagi/service/imodel.py +24 -20
- lionagi/service/third_party/claude_code.py +3 -3
- lionagi/service/third_party/openai_models.py +435 -0
- lionagi/service/token_calculator.py +1 -94
- lionagi/session/branch.py +190 -400
- lionagi/session/session.py +8 -99
- lionagi/tools/file/reader.py +2 -2
- lionagi/version.py +1 -1
- {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/METADATA +6 -6
- lionagi-0.18.1.dist-info/RECORD +164 -0
- 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/_act/act.py +0 -86
- 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/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/protocols/operatives/__init__.py +0 -2
- 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-0.17.11.dist-info/RECORD +0 -199
- /lionagi/operations/{_act → act}/__init__.py +0 -0
- {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/WHEEL +0 -0
- {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
6
6
|
|
|
7
|
+
from pydantic import JsonValue
|
|
8
|
+
|
|
9
|
+
from lionagi.ln.fuzzy import FuzzyMatchKeysParams
|
|
7
10
|
from lionagi.ln.fuzzy._fuzzy_validate import fuzzy_validate_mapping
|
|
8
|
-
from lionagi.
|
|
11
|
+
from lionagi.ln.types import Undefined
|
|
12
|
+
|
|
13
|
+
from ..types import ChatParam, ParseParam
|
|
9
14
|
|
|
10
15
|
if TYPE_CHECKING:
|
|
16
|
+
from lionagi.protocols.messages.instruction import Instruction
|
|
11
17
|
from lionagi.session.branch import Branch
|
|
12
18
|
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
def prepare_communicate_kw(
|
|
15
21
|
branch: "Branch",
|
|
16
22
|
instruction=None,
|
|
17
23
|
*,
|
|
@@ -29,7 +35,7 @@ async def communicate(
|
|
|
29
35
|
parse_model=None,
|
|
30
36
|
skip_validation=False,
|
|
31
37
|
images=None,
|
|
32
|
-
image_detail="auto",
|
|
38
|
+
image_detail: Literal["low", "high", "auto"] = "auto",
|
|
33
39
|
num_parse_retries=3,
|
|
34
40
|
fuzzy_match_kwargs=None,
|
|
35
41
|
clear_messages=False,
|
|
@@ -37,88 +43,141 @@ async def communicate(
|
|
|
37
43
|
include_token_usage_to_model: bool = False,
|
|
38
44
|
**kwargs,
|
|
39
45
|
):
|
|
46
|
+
# Handle deprecated parameters
|
|
40
47
|
if operative_model:
|
|
41
|
-
|
|
42
|
-
"operative_model is deprecated. Use response_format instead."
|
|
48
|
+
warnings.warn(
|
|
49
|
+
"Parameter 'operative_model' is deprecated. Use 'response_format' instead.",
|
|
50
|
+
DeprecationWarning,
|
|
51
|
+
stacklevel=2,
|
|
43
52
|
)
|
|
53
|
+
|
|
44
54
|
if (
|
|
45
55
|
(operative_model and response_format)
|
|
46
56
|
or (operative_model and request_model)
|
|
47
57
|
or (response_format and request_model)
|
|
48
58
|
):
|
|
49
59
|
raise ValueError(
|
|
50
|
-
"Cannot specify both operative_model and response_format"
|
|
51
|
-
"or operative_model and request_model as they are aliases"
|
|
60
|
+
"Cannot specify both operative_model and response_format "
|
|
61
|
+
"or operative_model and request_model as they are aliases "
|
|
52
62
|
"for the same parameter."
|
|
53
63
|
)
|
|
54
64
|
|
|
55
65
|
response_format = response_format or operative_model or request_model
|
|
56
|
-
|
|
57
66
|
imodel = imodel or chat_model or branch.chat_model
|
|
58
67
|
parse_model = parse_model or branch.parse_model
|
|
59
68
|
|
|
60
|
-
if clear_messages:
|
|
61
|
-
branch.msgs.clear_messages()
|
|
62
|
-
|
|
63
69
|
if num_parse_retries > 5:
|
|
64
|
-
|
|
65
|
-
f"
|
|
66
|
-
|
|
70
|
+
warnings.warn(
|
|
71
|
+
f"num_parse_retries={num_parse_retries} is high. Lowering to 5. Suggestion: <3",
|
|
72
|
+
UserWarning,
|
|
73
|
+
stacklevel=2,
|
|
67
74
|
)
|
|
68
75
|
num_parse_retries = 5
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
# Build contexts
|
|
78
|
+
chat_param = ChatParam(
|
|
72
79
|
guidance=guidance,
|
|
73
80
|
context=context,
|
|
74
|
-
sender=sender,
|
|
75
|
-
recipient=recipient,
|
|
81
|
+
sender=sender or branch.user or "user",
|
|
82
|
+
recipient=recipient or branch.id,
|
|
76
83
|
response_format=response_format,
|
|
77
84
|
progression=progression,
|
|
78
|
-
|
|
79
|
-
images=images,
|
|
85
|
+
tool_schemas=[],
|
|
86
|
+
images=images or [],
|
|
80
87
|
image_detail=image_detail,
|
|
81
|
-
plain_content=plain_content,
|
|
82
|
-
return_ins_res_message=True,
|
|
88
|
+
plain_content=plain_content or "",
|
|
83
89
|
include_token_usage_to_model=include_token_usage_to_model,
|
|
84
|
-
|
|
90
|
+
imodel=imodel,
|
|
91
|
+
imodel_kw=kwargs,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
parse_param = None
|
|
95
|
+
if response_format and not skip_validation:
|
|
96
|
+
from ..parse.parse import get_default_call
|
|
97
|
+
|
|
98
|
+
fuzzy_kw = fuzzy_match_kwargs or {}
|
|
99
|
+
handle_validation = fuzzy_kw.pop("handle_validation", "raise")
|
|
100
|
+
|
|
101
|
+
parse_param = ParseParam(
|
|
102
|
+
response_format=response_format,
|
|
103
|
+
fuzzy_match_params=(
|
|
104
|
+
FuzzyMatchKeysParams(**fuzzy_kw)
|
|
105
|
+
if fuzzy_kw
|
|
106
|
+
else FuzzyMatchKeysParams()
|
|
107
|
+
),
|
|
108
|
+
handle_validation=handle_validation,
|
|
109
|
+
alcall_params=get_default_call().with_updates(
|
|
110
|
+
retry_attempts=num_parse_retries
|
|
111
|
+
),
|
|
112
|
+
imodel=parse_model,
|
|
113
|
+
imodel_kw={},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
"instruction": instruction or "",
|
|
118
|
+
"chat_param": chat_param,
|
|
119
|
+
"parse_param": parse_param,
|
|
120
|
+
"clear_messages": clear_messages,
|
|
121
|
+
"skip_validation": skip_validation,
|
|
122
|
+
"request_fields": request_fields,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
async def communicate(
|
|
127
|
+
branch: "Branch",
|
|
128
|
+
instruction: "JsonValue | Instruction",
|
|
129
|
+
chat_param: ChatParam,
|
|
130
|
+
parse_param: ParseParam | None = None,
|
|
131
|
+
clear_messages: bool = False,
|
|
132
|
+
skip_validation: bool = False,
|
|
133
|
+
request_fields: dict | None = None,
|
|
134
|
+
) -> Any:
|
|
135
|
+
if clear_messages:
|
|
136
|
+
branch.msgs.clear_messages()
|
|
137
|
+
|
|
138
|
+
from ..chat.chat import chat
|
|
139
|
+
|
|
140
|
+
ins, res = await chat(
|
|
141
|
+
branch, instruction, chat_param, return_ins_res_message=True
|
|
85
142
|
)
|
|
143
|
+
|
|
86
144
|
branch.msgs.add_message(instruction=ins)
|
|
87
145
|
branch.msgs.add_message(assistant_response=res)
|
|
88
146
|
|
|
89
147
|
if skip_validation:
|
|
90
148
|
return res.response
|
|
91
149
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
150
|
+
# Handle response_format with parse
|
|
151
|
+
if parse_param and chat_param.response_format:
|
|
152
|
+
from lionagi.protocols.messages.assistant_response import (
|
|
153
|
+
AssistantResponse,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
from ..parse.parse import parse
|
|
98
157
|
|
|
99
158
|
try:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
request_type=response_format,
|
|
103
|
-
max_retries=num_parse_retries,
|
|
104
|
-
**parse_kwargs,
|
|
159
|
+
out, res2 = await parse(
|
|
160
|
+
branch, res.response, parse_param, return_res_message=True
|
|
105
161
|
)
|
|
162
|
+
if res2 and isinstance(res2, AssistantResponse):
|
|
163
|
+
res.metadata["original_model_response"] = res.model_response
|
|
164
|
+
# model_response is read-only property - update metadata instead
|
|
165
|
+
res.metadata["model_response"] = res2.model_response
|
|
166
|
+
return out
|
|
106
167
|
except ValueError as e:
|
|
107
168
|
# Re-raise with more context
|
|
108
|
-
logging.error(
|
|
109
|
-
f"Failed to parse response '{res.response}' into {response_format}: {e}"
|
|
110
|
-
)
|
|
111
169
|
raise ValueError(
|
|
112
|
-
f"Failed to parse model response into {response_format
|
|
170
|
+
f"Failed to parse model response into {chat_param.response_format}: {e}"
|
|
113
171
|
) from e
|
|
114
172
|
|
|
173
|
+
# Handle request_fields with fuzzy validation
|
|
115
174
|
if request_fields is not None:
|
|
116
175
|
_d = fuzzy_validate_mapping(
|
|
117
176
|
res.response,
|
|
118
177
|
request_fields,
|
|
119
178
|
handle_unmatched="force",
|
|
120
|
-
fill_value=
|
|
179
|
+
fill_value=Undefined,
|
|
121
180
|
)
|
|
122
|
-
return {k: v for k, v in _d.items() if v !=
|
|
181
|
+
return {k: v for k, v in _d.items() if v != Undefined}
|
|
123
182
|
|
|
124
183
|
return res.response
|
|
@@ -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
|
lionagi/operations/flow.py
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
|
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:
|
|
@@ -429,14 +427,14 @@ class DependencyAwareExecutor:
|
|
|
429
427
|
if hasattr(branch, "_message_manager") and hasattr(
|
|
430
428
|
primary_branch, "_message_manager"
|
|
431
429
|
):
|
|
432
|
-
branch._message_manager.
|
|
433
|
-
for msg in primary_branch._message_manager.
|
|
430
|
+
branch._message_manager.messages.clear()
|
|
431
|
+
for msg in primary_branch._message_manager.messages:
|
|
434
432
|
if hasattr(msg, "clone"):
|
|
435
|
-
branch._message_manager.
|
|
433
|
+
branch._message_manager.messages.append(
|
|
436
434
|
msg.clone()
|
|
437
435
|
)
|
|
438
436
|
else:
|
|
439
|
-
branch._message_manager.
|
|
437
|
+
branch._message_manager.messages.append(msg)
|
|
440
438
|
|
|
441
439
|
# Clear the pending flag
|
|
442
440
|
branch.metadata["pending_context_inheritance"] = False
|