lionagi 0.9.19__py3-none-any.whl → 0.10.0__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/__init__.py +0 -7
- lionagi/_types.py +0 -1
- lionagi/libs/fields/__init__.py +36 -0
- lionagi/libs/fields/action.py +190 -0
- lionagi/libs/fields/file.py +135 -0
- lionagi/{operatives/instruct → libs/fields}/instruct.py +9 -9
- lionagi/{operatives/instruct → libs/fields}/reason.py +28 -49
- lionagi/libs/schema/function_to_schema.py +1 -1
- lionagi/models/__init__.py +19 -0
- lionagi/models/hashable_model.py +24 -0
- lionagi/{operatives/models → models}/operable_model.py +2 -1
- lionagi/{operatives/models → models}/schema_model.py +1 -1
- lionagi/operations/ReAct/ReAct.py +2 -3
- lionagi/operations/_act/act.py +2 -7
- lionagi/operations/brainstorm/brainstorm.py +1 -1
- lionagi/operations/instruct/instruct.py +1 -1
- lionagi/operations/operate/operate.py +6 -5
- lionagi/operations/parse/parse.py +1 -1
- lionagi/operations/plan/plan.py +1 -1
- lionagi/operations/select/select.py +1 -1
- lionagi/operations/utils.py +1 -1
- lionagi/{operatives → protocols}/action/function_calling.py +4 -1
- lionagi/{operatives → protocols}/action/manager.py +2 -9
- lionagi/{operatives → protocols}/action/tool.py +1 -6
- lionagi/{operatives → protocols}/forms/base.py +8 -2
- lionagi/{operatives → protocols}/forms/flow.py +6 -1
- lionagi/{operatives → protocols}/forms/form.py +6 -1
- lionagi/{operatives → protocols}/forms/report.py +7 -3
- lionagi/protocols/generic/pile.py +9 -5
- lionagi/protocols/graph/node.py +8 -4
- lionagi/protocols/mail/exchange.py +0 -6
- lionagi/protocols/mail/mail.py +0 -6
- lionagi/protocols/mail/mailbox.py +0 -5
- lionagi/protocols/mail/manager.py +0 -5
- lionagi/protocols/mail/package.py +0 -6
- lionagi/protocols/messages/action_request.py +0 -6
- lionagi/protocols/messages/action_response.py +0 -5
- lionagi/protocols/messages/assistant_response.py +0 -4
- lionagi/protocols/messages/base.py +0 -6
- lionagi/protocols/messages/instruction.py +0 -5
- lionagi/protocols/messages/manager.py +0 -6
- lionagi/protocols/messages/message.py +0 -5
- lionagi/protocols/messages/system.py +0 -5
- lionagi/{operatives → protocols/operatives}/operative.py +1 -3
- lionagi/{operatives → protocols/operatives}/step.py +5 -7
- lionagi/protocols/types.py +36 -21
- lionagi/service/endpoints/base.py +1 -1
- lionagi/service/endpoints/rate_limited_processor.py +1 -1
- lionagi/service/providers/ollama_/chat_completions.py +2 -2
- lionagi/session/branch.py +9 -10
- lionagi/session/session.py +24 -8
- lionagi/tools/base.py +1 -1
- lionagi/tools/file/reader.py +1 -1
- lionagi/utils.py +0 -22
- lionagi/version.py +1 -1
- {lionagi-0.9.19.dist-info → lionagi-0.10.0.dist-info}/METADATA +1 -1
- {lionagi-0.9.19.dist-info → lionagi-0.10.0.dist-info}/RECORD +75 -90
- lionagi/operatives/action/request_response_model.py +0 -121
- lionagi/operatives/action/utils.py +0 -147
- lionagi/operatives/instruct/__init__.py +0 -3
- lionagi/operatives/instruct/base.py +0 -81
- lionagi/operatives/instruct/instruct_collection.py +0 -52
- lionagi/operatives/instruct/node.py +0 -13
- lionagi/operatives/instruct/prompts.py +0 -51
- lionagi/operatives/manager.py +0 -9
- lionagi/operatives/models/__init__.py +0 -3
- lionagi/operatives/strategies/__init__.py +0 -3
- lionagi/operatives/strategies/base.py +0 -56
- lionagi/operatives/strategies/concurrent.py +0 -75
- lionagi/operatives/strategies/concurrent_chunk.py +0 -46
- lionagi/operatives/strategies/concurrent_sequential_chunk.py +0 -108
- lionagi/operatives/strategies/params.py +0 -151
- lionagi/operatives/strategies/sequential.py +0 -27
- lionagi/operatives/strategies/sequential_chunk.py +0 -93
- lionagi/operatives/strategies/sequential_concurrent_chunk.py +0 -105
- lionagi/operatives/strategies/utils.py +0 -52
- lionagi/operatives/types.py +0 -72
- /lionagi/{protocols/adapters → adapters}/__init__.py +0 -0
- /lionagi/{protocols/adapters → adapters}/adapter.py +0 -0
- /lionagi/{protocols/adapters → adapters}/json_adapter.py +0 -0
- /lionagi/{protocols/adapters → adapters}/pandas_/__init__.py +0 -0
- /lionagi/{protocols/adapters → adapters}/pandas_/csv_adapter.py +0 -0
- /lionagi/{protocols/adapters → adapters}/pandas_/excel_adapter.py +0 -0
- /lionagi/{protocols/adapters → adapters}/pandas_/pd_dataframe_adapter.py +0 -0
- /lionagi/{protocols/adapters → adapters}/pandas_/pd_series_adapter.py +0 -0
- /lionagi/{protocols/adapters → adapters}/toml_adapter.py +0 -0
- /lionagi/{protocols/adapters → adapters}/types.py +0 -0
- /lionagi/{operatives/models → models}/field_model.py +0 -0
- /lionagi/{operatives/models → models}/model_params.py +0 -0
- /lionagi/{operatives/models → models}/note.py +0 -0
- /lionagi/{operatives → protocols/action}/__init__.py +0 -0
- /lionagi/{operatives/action → protocols/forms}/__init__.py +0 -0
- /lionagi/{operatives/forms → protocols/operatives}/__init__.py +0 -0
- {lionagi-0.9.19.dist-info → lionagi-0.10.0.dist-info}/WHEEL +0 -0
- {lionagi-0.9.19.dist-info → lionagi-0.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,147 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
import re
|
6
|
-
from typing import Any
|
7
|
-
|
8
|
-
from pydantic import BaseModel
|
9
|
-
|
10
|
-
from lionagi.libs.validate.common_field_validators import (
|
11
|
-
validate_boolean_field,
|
12
|
-
validate_nullable_string_field,
|
13
|
-
)
|
14
|
-
from lionagi.utils import to_dict, to_json, to_list
|
15
|
-
|
16
|
-
from ..models.field_model import FieldModel
|
17
|
-
|
18
|
-
function_field_description = (
|
19
|
-
"Name of the function to call from the provided `tool_schemas`. "
|
20
|
-
"If no `tool_schemas` exist, set to None or leave blank. "
|
21
|
-
"Never invent new function names outside what's given."
|
22
|
-
)
|
23
|
-
|
24
|
-
arguments_field_description = (
|
25
|
-
"Dictionary of arguments for the chosen function. "
|
26
|
-
"Use only argument names/types defined in `tool_schemas`. "
|
27
|
-
"Never introduce extra argument names."
|
28
|
-
)
|
29
|
-
|
30
|
-
action_required_field_description = (
|
31
|
-
"Whether this step strictly requires performing actions. "
|
32
|
-
"If true, the requests in `action_requests` must be fulfilled, "
|
33
|
-
"assuming `tool_schemas` are available. "
|
34
|
-
"If false or no `tool_schemas` exist, actions are optional."
|
35
|
-
)
|
36
|
-
|
37
|
-
action_requests_field_description = (
|
38
|
-
"List of actions to be executed when `action_required` is true. "
|
39
|
-
"Each action must align with the available `tool_schemas`. "
|
40
|
-
"Leave empty if no actions are needed."
|
41
|
-
)
|
42
|
-
|
43
|
-
|
44
|
-
__all__ = (
|
45
|
-
"FUNCTION_FIELD",
|
46
|
-
"ARGUMENTS_FIELD",
|
47
|
-
"ACTION_REQUIRED_FIELD",
|
48
|
-
"parse_action_request",
|
49
|
-
)
|
50
|
-
|
51
|
-
|
52
|
-
def parse_action_request(content: str | dict) -> list[dict]:
|
53
|
-
|
54
|
-
json_blocks = []
|
55
|
-
|
56
|
-
if isinstance(content, BaseModel):
|
57
|
-
json_blocks = [content.model_dump()]
|
58
|
-
|
59
|
-
elif isinstance(content, str):
|
60
|
-
json_blocks = to_json(content, fuzzy_parse=True)
|
61
|
-
if not json_blocks:
|
62
|
-
pattern2 = r"```python\s*(.*?)\s*```"
|
63
|
-
_d = re.findall(pattern2, content, re.DOTALL)
|
64
|
-
json_blocks = [to_json(match, fuzzy_parse=True) for match in _d]
|
65
|
-
json_blocks = to_list(json_blocks, dropna=True)
|
66
|
-
|
67
|
-
print(json_blocks)
|
68
|
-
|
69
|
-
elif content and isinstance(content, dict):
|
70
|
-
json_blocks = [content]
|
71
|
-
|
72
|
-
if json_blocks and not isinstance(json_blocks, list):
|
73
|
-
json_blocks = [json_blocks]
|
74
|
-
|
75
|
-
out = []
|
76
|
-
|
77
|
-
for i in json_blocks:
|
78
|
-
j = {}
|
79
|
-
if isinstance(i, dict):
|
80
|
-
if "function" in i and isinstance(i["function"], dict):
|
81
|
-
if "name" in i["function"]:
|
82
|
-
i["function"] = i["function"]["name"]
|
83
|
-
for k, v in i.items():
|
84
|
-
k = (
|
85
|
-
k.replace("action_", "")
|
86
|
-
.replace("recipient_", "")
|
87
|
-
.replace("s", "")
|
88
|
-
)
|
89
|
-
if k in ["name", "function", "recipient"]:
|
90
|
-
j["function"] = v
|
91
|
-
elif k in ["parameter", "argument", "arg", "param"]:
|
92
|
-
j["arguments"] = to_dict(
|
93
|
-
v, str_type="json", fuzzy_parse=True, suppress=True
|
94
|
-
)
|
95
|
-
if (
|
96
|
-
j
|
97
|
-
and all(key in j for key in ["function", "arguments"])
|
98
|
-
and j["arguments"]
|
99
|
-
):
|
100
|
-
out.append(j)
|
101
|
-
|
102
|
-
return out
|
103
|
-
|
104
|
-
|
105
|
-
def validate_function_name(cls, value: Any) -> str | None:
|
106
|
-
return validate_nullable_string_field(cls, value, strict=False)
|
107
|
-
|
108
|
-
|
109
|
-
def validate_arguments(cls, value: Any) -> dict:
|
110
|
-
return to_dict(
|
111
|
-
value,
|
112
|
-
fuzzy_parse=True,
|
113
|
-
suppress=True,
|
114
|
-
recursive=True,
|
115
|
-
)
|
116
|
-
|
117
|
-
|
118
|
-
FUNCTION_FIELD = FieldModel(
|
119
|
-
name="function",
|
120
|
-
default=None,
|
121
|
-
annotation=str | None,
|
122
|
-
title="Function",
|
123
|
-
description=function_field_description,
|
124
|
-
examples=["add", "multiply", "divide"],
|
125
|
-
validator=validate_function_name,
|
126
|
-
)
|
127
|
-
|
128
|
-
ARGUMENTS_FIELD = FieldModel(
|
129
|
-
name="arguments",
|
130
|
-
annotation=dict | None,
|
131
|
-
default_factory=dict,
|
132
|
-
title="Action Arguments",
|
133
|
-
description=arguments_field_description,
|
134
|
-
examples=[{"num1": 1, "num2": 2}, {"x": "hello", "y": "world"}],
|
135
|
-
validator=validate_arguments,
|
136
|
-
validator_kwargs={"mode": "before"},
|
137
|
-
)
|
138
|
-
|
139
|
-
ACTION_REQUIRED_FIELD = FieldModel(
|
140
|
-
name="action_required",
|
141
|
-
annotation=bool,
|
142
|
-
default=False,
|
143
|
-
title="Action Required",
|
144
|
-
description=action_required_field_description,
|
145
|
-
validator=lambda cls, v: validate_boolean_field(cls, v, False),
|
146
|
-
validator_kwargs={"mode": "before"},
|
147
|
-
)
|
@@ -1,81 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
"""deprecated"""
|
6
|
-
|
7
|
-
from pydantic import JsonValue
|
8
|
-
|
9
|
-
from lionagi.libs.validate.common_field_validators import (
|
10
|
-
validate_boolean_field,
|
11
|
-
validate_nullable_jsonvalue_field,
|
12
|
-
)
|
13
|
-
|
14
|
-
from ..models.field_model import FieldModel
|
15
|
-
from .prompts import (
|
16
|
-
actions_field_description,
|
17
|
-
context_field_description,
|
18
|
-
guidance_field_description,
|
19
|
-
instruction_field_description,
|
20
|
-
reason_field_description,
|
21
|
-
)
|
22
|
-
|
23
|
-
__all__ = (
|
24
|
-
"INSTRUCTION_FIELD",
|
25
|
-
"GUIDANCE_FIELD",
|
26
|
-
"CONTEXT_FIELD",
|
27
|
-
"REASON_FIELD",
|
28
|
-
"ACTIONS_FIELD",
|
29
|
-
)
|
30
|
-
|
31
|
-
|
32
|
-
# Field Models
|
33
|
-
INSTRUCTION_FIELD = FieldModel(
|
34
|
-
name="instruction",
|
35
|
-
annotation=JsonValue | None,
|
36
|
-
default=None,
|
37
|
-
title="Primary Instruction",
|
38
|
-
description=instruction_field_description,
|
39
|
-
validator=validate_nullable_jsonvalue_field,
|
40
|
-
validator_kwargs={"mode": "before"},
|
41
|
-
)
|
42
|
-
|
43
|
-
GUIDANCE_FIELD = FieldModel(
|
44
|
-
name="guidance",
|
45
|
-
annotation=JsonValue | None,
|
46
|
-
default=None,
|
47
|
-
title="Execution Guidance",
|
48
|
-
description=guidance_field_description,
|
49
|
-
)
|
50
|
-
|
51
|
-
CONTEXT_FIELD = FieldModel(
|
52
|
-
name="context",
|
53
|
-
annotation=JsonValue | None,
|
54
|
-
default=None,
|
55
|
-
title="Task Context",
|
56
|
-
description=context_field_description,
|
57
|
-
)
|
58
|
-
|
59
|
-
REASON_FIELD = FieldModel(
|
60
|
-
name="reason",
|
61
|
-
annotation=bool,
|
62
|
-
default=False,
|
63
|
-
title="Include Reasoning",
|
64
|
-
description=reason_field_description,
|
65
|
-
validator=lambda cls, value: validate_boolean_field(
|
66
|
-
cls, value, default=False
|
67
|
-
),
|
68
|
-
validator_kwargs={"mode": "before"},
|
69
|
-
)
|
70
|
-
|
71
|
-
ACTIONS_FIELD = FieldModel(
|
72
|
-
name="actions",
|
73
|
-
annotation=bool,
|
74
|
-
default=False,
|
75
|
-
title="Require Actions",
|
76
|
-
description=actions_field_description,
|
77
|
-
validator=lambda cls, value: validate_boolean_field(
|
78
|
-
cls, value, default=False
|
79
|
-
),
|
80
|
-
validator_kwargs={"mode": "before"},
|
81
|
-
)
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from functools import lru_cache
|
6
|
-
|
7
|
-
from pydantic import BaseModel
|
8
|
-
|
9
|
-
from ..models.model_params import ModelParams
|
10
|
-
from .instruct import INSTRUCT_FIELD, Instruct
|
11
|
-
from .node import InstructNode
|
12
|
-
|
13
|
-
__all__ = ("InstructCollection",)
|
14
|
-
|
15
|
-
|
16
|
-
class InstructCollection(BaseModel):
|
17
|
-
|
18
|
-
@property
|
19
|
-
def instruct_models(self) -> list[Instruct]:
|
20
|
-
fields = []
|
21
|
-
for field in self.model_fields:
|
22
|
-
if field.startswith("instruct_"):
|
23
|
-
fields.append(field)
|
24
|
-
return [getattr(self, field) for field in fields]
|
25
|
-
|
26
|
-
@classmethod
|
27
|
-
def create_model_params(
|
28
|
-
cls, num_instructs: int = 3, **kwargs
|
29
|
-
) -> ModelParams:
|
30
|
-
return create_instruct_collection_model_params(
|
31
|
-
cls, num_instructs, **kwargs
|
32
|
-
)
|
33
|
-
|
34
|
-
def to_instruct_nodes(self) -> list[InstructNode]:
|
35
|
-
return [
|
36
|
-
InstructNode(instruct=instruct)
|
37
|
-
for instruct in self.instruct_models
|
38
|
-
]
|
39
|
-
|
40
|
-
|
41
|
-
@lru_cache
|
42
|
-
def create_instruct_collection_model_params(
|
43
|
-
base_type: type[BaseModel], num_instructs: int = 3, **kwargs
|
44
|
-
) -> ModelParams:
|
45
|
-
model_params = ModelParams(**kwargs)
|
46
|
-
for i in range(num_instructs):
|
47
|
-
field = INSTRUCT_FIELD.model_copy()
|
48
|
-
field.name = f"instruct_{i}"
|
49
|
-
model_params.field_models.append(field)
|
50
|
-
|
51
|
-
model_params.base_type = base_type
|
52
|
-
return model_params
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from lionagi.protocols.types import Node
|
6
|
-
|
7
|
-
from .instruct import INSTRUCT_FIELD, Instruct
|
8
|
-
|
9
|
-
__all__ = ("InstructNode",)
|
10
|
-
|
11
|
-
|
12
|
-
class InstructNode(Node):
|
13
|
-
instruct: Instruct | None = INSTRUCT_FIELD.field_info
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
"""deprecated"""
|
6
|
-
|
7
|
-
instruction_field_description = (
|
8
|
-
"A clear, actionable task definition. Specify:\n"
|
9
|
-
"1) The primary goal or objective\n"
|
10
|
-
"2) Key success criteria or constraints\n"
|
11
|
-
"\n"
|
12
|
-
"Guidelines:\n"
|
13
|
-
"- Start with a direct action verb (e.g., 'Analyze', 'Generate', 'Create')\n"
|
14
|
-
"- Include scope, boundaries, or constraints\n"
|
15
|
-
"- Provide success criteria if relevant\n"
|
16
|
-
"- For complex tasks, break them into logical steps"
|
17
|
-
)
|
18
|
-
|
19
|
-
guidance_field_description = (
|
20
|
-
"Strategic direction and constraints for executing the task. "
|
21
|
-
"Include:\n"
|
22
|
-
"1) Preferred methods or frameworks\n"
|
23
|
-
"2) Quality benchmarks (e.g., speed, clarity)\n"
|
24
|
-
"3) Resource or environmental constraints\n"
|
25
|
-
"4) Relevant compliance or standards\n"
|
26
|
-
"Use None if no special guidance."
|
27
|
-
)
|
28
|
-
|
29
|
-
context_field_description = (
|
30
|
-
"Background information and current-state data needed for the task. "
|
31
|
-
"Should be:\n"
|
32
|
-
"1) Directly relevant\n"
|
33
|
-
"2) Sufficient to perform the task\n"
|
34
|
-
"3) Free of extraneous detail\n"
|
35
|
-
"Include environment, prior outcomes, system states, or dependencies. "
|
36
|
-
"Use None if no additional context is needed."
|
37
|
-
)
|
38
|
-
|
39
|
-
reason_field_description = (
|
40
|
-
"Include a thoughtful explanation of decisions, trade-offs, "
|
41
|
-
"and insights. Encourage deeper introspection on why certain "
|
42
|
-
"choices were made, potential alternatives, and how confidence "
|
43
|
-
"was shaped. If not needed, set to None."
|
44
|
-
)
|
45
|
-
|
46
|
-
actions_field_description = (
|
47
|
-
"Controls execution mode. "
|
48
|
-
"True: Execute specified actions. "
|
49
|
-
"False: Analysis/recommendations only. "
|
50
|
-
"None: Contextual execution."
|
51
|
-
)
|
lionagi/operatives/manager.py
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from typing import ClassVar
|
6
|
-
|
7
|
-
from pydantic import BaseModel, model_validator
|
8
|
-
|
9
|
-
from lionagi.operatives.instruct.instruct import Instruct, InstructResponse
|
10
|
-
from lionagi.session.session import Branch, Session
|
11
|
-
|
12
|
-
from .params import StrategyParams
|
13
|
-
|
14
|
-
|
15
|
-
class StrategyExecutor(BaseModel):
|
16
|
-
"""Base class for different execution strategies.
|
17
|
-
|
18
|
-
Each concrete executor should implement `execute()` to run instructions
|
19
|
-
according to a specific strategy (e.g., sequential, concurrent, chunked).
|
20
|
-
"""
|
21
|
-
|
22
|
-
session: Session = None
|
23
|
-
branch: Branch = None
|
24
|
-
execute_branch: Branch = None
|
25
|
-
params: StrategyParams = None
|
26
|
-
params_cls: ClassVar[type[StrategyParams]] = StrategyParams
|
27
|
-
|
28
|
-
@model_validator(mode="before")
|
29
|
-
def validate_execution_config(cls, values: dict) -> dict:
|
30
|
-
params_cls = values.get("params_cls", cls.params_cls)
|
31
|
-
params = values.get("params", None)
|
32
|
-
|
33
|
-
if isinstance(params, dict):
|
34
|
-
params = params_cls(**params)
|
35
|
-
if not isinstance(params, StrategyParams):
|
36
|
-
params = params_cls(**values)
|
37
|
-
|
38
|
-
session: Session = values.get("session", params.session)
|
39
|
-
branch = values.get("branch", params.branch)
|
40
|
-
execute_branch = values.get("execute_branch", None) or session.split(
|
41
|
-
branch
|
42
|
-
)
|
43
|
-
|
44
|
-
return {
|
45
|
-
"params": params,
|
46
|
-
"session": session,
|
47
|
-
"branch": branch,
|
48
|
-
"execute_branch": execute_branch,
|
49
|
-
"params_cls": params_cls,
|
50
|
-
}
|
51
|
-
|
52
|
-
async def execute(
|
53
|
-
self, res
|
54
|
-
) -> tuple[list[Instruct], list[InstructResponse]]:
|
55
|
-
"""Run the instructions based on the strategy implemented by subclasses."""
|
56
|
-
raise NotImplementedError
|
@@ -1,75 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from lionagi.operatives.instruct.instruct import Instruct, InstructResponse
|
6
|
-
from lionagi.session.session import Branch
|
7
|
-
from lionagi.utils import alcall, to_list
|
8
|
-
|
9
|
-
from .base import StrategyExecutor
|
10
|
-
|
11
|
-
|
12
|
-
class ConcurrentExecutor(StrategyExecutor):
|
13
|
-
"""Executor for concurrent instruction processing.
|
14
|
-
|
15
|
-
Runs each instruction in parallel without chunking.
|
16
|
-
"""
|
17
|
-
|
18
|
-
async def execute_instruct(
|
19
|
-
self, ins: Instruct, branch: Branch, auto_run: bool, **kwargs
|
20
|
-
):
|
21
|
-
async def run(ins_):
|
22
|
-
if self.params.verbose:
|
23
|
-
print(f"\n-----Running sub-instruction-----\n{ins_.msg}")
|
24
|
-
b_ = self.session.split(branch)
|
25
|
-
return await self.execute_instruct(ins_, b_, False, **kwargs)
|
26
|
-
|
27
|
-
config = {**ins.model_dump(), **kwargs}
|
28
|
-
res = await branch.instruct(**config)
|
29
|
-
branch.msgs.logger.dump()
|
30
|
-
instructs = (
|
31
|
-
res.instruct_models if hasattr(res, "instruct_models") else []
|
32
|
-
)
|
33
|
-
|
34
|
-
if auto_run and instructs:
|
35
|
-
ress = await alcall(instructs, run)
|
36
|
-
response_ = []
|
37
|
-
for r in ress:
|
38
|
-
if isinstance(r, list):
|
39
|
-
response_.extend(r)
|
40
|
-
else:
|
41
|
-
response_.append(r)
|
42
|
-
response_.insert(0, res)
|
43
|
-
return response_
|
44
|
-
return res
|
45
|
-
|
46
|
-
async def instruct_concurrent_single(
|
47
|
-
self, ins_: Instruct, execute_branch: Branch
|
48
|
-
) -> InstructResponse:
|
49
|
-
if self.params.verbose:
|
50
|
-
print(f"\n-----Running instruction-----\n{ins_.msg}")
|
51
|
-
b_ = self.session.split(execute_branch)
|
52
|
-
response = await self.execute_instruct(
|
53
|
-
ins_, b_, self.params.auto_run, **self.params.execute_kwargs
|
54
|
-
)
|
55
|
-
return InstructResponse(instruct=ins_, response=response)
|
56
|
-
|
57
|
-
async def execute(
|
58
|
-
self, res
|
59
|
-
) -> tuple[list[Instruct], list[InstructResponse]]:
|
60
|
-
async with self.session.branches:
|
61
|
-
response_ = []
|
62
|
-
instructs = []
|
63
|
-
if hasattr(res, "instruct_models"):
|
64
|
-
instructs: list[Instruct] = res.instruct_models
|
65
|
-
ress = await alcall(
|
66
|
-
instructs,
|
67
|
-
self.instruct_concurrent_single,
|
68
|
-
execute_branch=self.execute_branch,
|
69
|
-
)
|
70
|
-
ress = to_list(ress, dropna=True, flatten=True)
|
71
|
-
response_ = [r for r in ress if not isinstance(r, (str, dict))]
|
72
|
-
response_ = to_list(
|
73
|
-
response_, unique=True, dropna=True, flatten=True
|
74
|
-
)
|
75
|
-
return instructs, response_
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from typing import ClassVar
|
6
|
-
|
7
|
-
from lionagi.operatives.instruct.instruct import Instruct, InstructResponse
|
8
|
-
from lionagi.utils import bcall, to_list
|
9
|
-
|
10
|
-
from .concurrent import ConcurrentExecutor
|
11
|
-
from .params import ChunkStrategyParams
|
12
|
-
|
13
|
-
|
14
|
-
class ConcurrentChunkExecutor(ConcurrentExecutor):
|
15
|
-
"""Executor for concurrent chunked instruction processing.
|
16
|
-
|
17
|
-
Breaks instructions into chunks and executes each chunk concurrently.
|
18
|
-
"""
|
19
|
-
|
20
|
-
params: ChunkStrategyParams | None = None
|
21
|
-
params_cls: ClassVar[type[ChunkStrategyParams]] = ChunkStrategyParams
|
22
|
-
|
23
|
-
async def execute(
|
24
|
-
self, res
|
25
|
-
) -> tuple[list[Instruct], list[InstructResponse]]:
|
26
|
-
async with self.session.branches:
|
27
|
-
instructs = []
|
28
|
-
response_ = []
|
29
|
-
if hasattr(res, "instruct_models"):
|
30
|
-
instructs: list[Instruct] = res.instruct_models
|
31
|
-
ress = []
|
32
|
-
async for chunk_result in await bcall(
|
33
|
-
instructs,
|
34
|
-
self.instruct_concurrent_single,
|
35
|
-
execute_branch=self.execute_branch,
|
36
|
-
batch_size=self.params.chunk_size,
|
37
|
-
**self.params.rcall_params.to_dict(),
|
38
|
-
):
|
39
|
-
ress.extend(chunk_result)
|
40
|
-
|
41
|
-
ress = to_list(ress, dropna=True, flatten=True)
|
42
|
-
response_ = [r for r in ress if not isinstance(r, (str, dict))]
|
43
|
-
response_ = to_list(
|
44
|
-
response_, unique=True, dropna=True, flatten=True
|
45
|
-
)
|
46
|
-
return instructs, response_
|
@@ -1,108 +0,0 @@
|
|
1
|
-
# Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
4
|
-
|
5
|
-
from pydantic import model_validator
|
6
|
-
|
7
|
-
from lionagi.operatives.instruct.instruct import Instruct, InstructResponse
|
8
|
-
from lionagi.session.session import Branch, Session
|
9
|
-
from lionagi.utils import alcall
|
10
|
-
|
11
|
-
from .base import StrategyExecutor
|
12
|
-
from .params import HybridStrategyParams
|
13
|
-
|
14
|
-
|
15
|
-
class ConcurrentSequentialChunkExecutor(StrategyExecutor):
|
16
|
-
"""Concurrent-sequential chunked executor:
|
17
|
-
1. Splits instructions into chunks
|
18
|
-
2. Processes chunks concurrently
|
19
|
-
3. Processes each instruction within a chunk sequentially.
|
20
|
-
"""
|
21
|
-
|
22
|
-
params: HybridStrategyParams
|
23
|
-
session: Session
|
24
|
-
branch: Branch
|
25
|
-
|
26
|
-
@model_validator(mode="before")
|
27
|
-
def validate_execution_config(cls, values: dict) -> dict:
|
28
|
-
params = values.get("params", None)
|
29
|
-
if params is None:
|
30
|
-
params = HybridStrategyParams(**values)
|
31
|
-
|
32
|
-
if (
|
33
|
-
params.outer_mode != "concurrent"
|
34
|
-
or params.inner_mode != "sequential"
|
35
|
-
):
|
36
|
-
raise ValueError(
|
37
|
-
"Requires outer_mode='concurrent' and inner_mode='sequential'"
|
38
|
-
)
|
39
|
-
|
40
|
-
session = values.get("session", params.session)
|
41
|
-
if not session:
|
42
|
-
raise ValueError("Session is required")
|
43
|
-
branch = values.get("branch", params.branch)
|
44
|
-
if isinstance(branch, Branch):
|
45
|
-
if branch not in session.branches:
|
46
|
-
session.branches.include(branch)
|
47
|
-
elif not branch:
|
48
|
-
branch = session.new_branch()
|
49
|
-
elif isinstance(branch, str):
|
50
|
-
branch = session.split(branch)
|
51
|
-
else:
|
52
|
-
raise ValueError("Invalid branch type")
|
53
|
-
|
54
|
-
return {"params": params, "session": session, "branch": branch}
|
55
|
-
|
56
|
-
async def _execute_single(
|
57
|
-
self, ins_: Instruct, idx: int, ttl: int
|
58
|
-
) -> InstructResponse:
|
59
|
-
if self.params.verbose:
|
60
|
-
msg_ = (
|
61
|
-
(ins_.instruction[:100] + "...")
|
62
|
-
if len(ins_.instruction) > 100
|
63
|
-
else ins_.instruction
|
64
|
-
)
|
65
|
-
print(
|
66
|
-
f"\n-----Executing Instruct {idx}/{ttl}-----\n{msg_}"
|
67
|
-
if ttl
|
68
|
-
else f"\n-----Executing Instruct {idx}-----\n{msg_}"
|
69
|
-
)
|
70
|
-
|
71
|
-
res = await self.branch.instruct(ins_, **self.params.execute_kwargs)
|
72
|
-
return InstructResponse(instruct=ins_, response=res)
|
73
|
-
|
74
|
-
async def _execute_chunk(
|
75
|
-
self, chunk: list[tuple[Instruct, int, int]]
|
76
|
-
) -> list[InstructResponse]:
|
77
|
-
# Each chunk sequential
|
78
|
-
branch = self.session.split(self.branch)
|
79
|
-
responses = []
|
80
|
-
for ins_, idx, ttl in chunk:
|
81
|
-
r = await self._execute_single(ins_, idx, ttl)
|
82
|
-
responses.append(r)
|
83
|
-
return responses
|
84
|
-
|
85
|
-
async def execute(self) -> list[InstructResponse]:
|
86
|
-
instructions = self.params.instruct
|
87
|
-
chunk_size = self.params.chunk_size
|
88
|
-
total = len(instructions)
|
89
|
-
|
90
|
-
if total <= chunk_size:
|
91
|
-
chunk = [(ins, i + 1, total) for i, ins in enumerate(instructions)]
|
92
|
-
return await self._execute_chunk(chunk)
|
93
|
-
|
94
|
-
chunks = []
|
95
|
-
for start in range(0, total, chunk_size):
|
96
|
-
end = min(start + chunk_size, total)
|
97
|
-
chunk = [
|
98
|
-
(instructions[i], i + 1, total) for i in range(start, end)
|
99
|
-
]
|
100
|
-
chunks.append(chunk)
|
101
|
-
|
102
|
-
chunk_responses = await alcall(
|
103
|
-
chunks, self._execute_chunk, max_workers=self.params.max_workers
|
104
|
-
)
|
105
|
-
responses = []
|
106
|
-
for c in chunk_responses:
|
107
|
-
responses.extend(c)
|
108
|
-
return responses
|