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.
Files changed (95) hide show
  1. lionagi/__init__.py +0 -7
  2. lionagi/_types.py +0 -1
  3. lionagi/libs/fields/__init__.py +36 -0
  4. lionagi/libs/fields/action.py +190 -0
  5. lionagi/libs/fields/file.py +135 -0
  6. lionagi/{operatives/instruct → libs/fields}/instruct.py +9 -9
  7. lionagi/{operatives/instruct → libs/fields}/reason.py +28 -49
  8. lionagi/libs/schema/function_to_schema.py +1 -1
  9. lionagi/models/__init__.py +19 -0
  10. lionagi/models/hashable_model.py +24 -0
  11. lionagi/{operatives/models → models}/operable_model.py +2 -1
  12. lionagi/{operatives/models → models}/schema_model.py +1 -1
  13. lionagi/operations/ReAct/ReAct.py +2 -3
  14. lionagi/operations/_act/act.py +2 -7
  15. lionagi/operations/brainstorm/brainstorm.py +1 -1
  16. lionagi/operations/instruct/instruct.py +1 -1
  17. lionagi/operations/operate/operate.py +6 -5
  18. lionagi/operations/parse/parse.py +1 -1
  19. lionagi/operations/plan/plan.py +1 -1
  20. lionagi/operations/select/select.py +1 -1
  21. lionagi/operations/utils.py +1 -1
  22. lionagi/{operatives → protocols}/action/function_calling.py +4 -1
  23. lionagi/{operatives → protocols}/action/manager.py +2 -9
  24. lionagi/{operatives → protocols}/action/tool.py +1 -6
  25. lionagi/{operatives → protocols}/forms/base.py +8 -2
  26. lionagi/{operatives → protocols}/forms/flow.py +6 -1
  27. lionagi/{operatives → protocols}/forms/form.py +6 -1
  28. lionagi/{operatives → protocols}/forms/report.py +7 -3
  29. lionagi/protocols/generic/pile.py +9 -5
  30. lionagi/protocols/graph/node.py +8 -4
  31. lionagi/protocols/mail/exchange.py +0 -6
  32. lionagi/protocols/mail/mail.py +0 -6
  33. lionagi/protocols/mail/mailbox.py +0 -5
  34. lionagi/protocols/mail/manager.py +0 -5
  35. lionagi/protocols/mail/package.py +0 -6
  36. lionagi/protocols/messages/action_request.py +0 -6
  37. lionagi/protocols/messages/action_response.py +0 -5
  38. lionagi/protocols/messages/assistant_response.py +0 -4
  39. lionagi/protocols/messages/base.py +0 -6
  40. lionagi/protocols/messages/instruction.py +0 -5
  41. lionagi/protocols/messages/manager.py +0 -6
  42. lionagi/protocols/messages/message.py +0 -5
  43. lionagi/protocols/messages/system.py +0 -5
  44. lionagi/{operatives → protocols/operatives}/operative.py +1 -3
  45. lionagi/{operatives → protocols/operatives}/step.py +5 -7
  46. lionagi/protocols/types.py +36 -21
  47. lionagi/service/endpoints/base.py +1 -1
  48. lionagi/service/endpoints/rate_limited_processor.py +1 -1
  49. lionagi/service/providers/ollama_/chat_completions.py +2 -2
  50. lionagi/session/branch.py +9 -10
  51. lionagi/session/session.py +24 -8
  52. lionagi/tools/base.py +1 -1
  53. lionagi/tools/file/reader.py +1 -1
  54. lionagi/utils.py +0 -22
  55. lionagi/version.py +1 -1
  56. {lionagi-0.9.19.dist-info → lionagi-0.10.0.dist-info}/METADATA +1 -1
  57. {lionagi-0.9.19.dist-info → lionagi-0.10.0.dist-info}/RECORD +75 -90
  58. lionagi/operatives/action/request_response_model.py +0 -121
  59. lionagi/operatives/action/utils.py +0 -147
  60. lionagi/operatives/instruct/__init__.py +0 -3
  61. lionagi/operatives/instruct/base.py +0 -81
  62. lionagi/operatives/instruct/instruct_collection.py +0 -52
  63. lionagi/operatives/instruct/node.py +0 -13
  64. lionagi/operatives/instruct/prompts.py +0 -51
  65. lionagi/operatives/manager.py +0 -9
  66. lionagi/operatives/models/__init__.py +0 -3
  67. lionagi/operatives/strategies/__init__.py +0 -3
  68. lionagi/operatives/strategies/base.py +0 -56
  69. lionagi/operatives/strategies/concurrent.py +0 -75
  70. lionagi/operatives/strategies/concurrent_chunk.py +0 -46
  71. lionagi/operatives/strategies/concurrent_sequential_chunk.py +0 -108
  72. lionagi/operatives/strategies/params.py +0 -151
  73. lionagi/operatives/strategies/sequential.py +0 -27
  74. lionagi/operatives/strategies/sequential_chunk.py +0 -93
  75. lionagi/operatives/strategies/sequential_concurrent_chunk.py +0 -105
  76. lionagi/operatives/strategies/utils.py +0 -52
  77. lionagi/operatives/types.py +0 -72
  78. /lionagi/{protocols/adapters → adapters}/__init__.py +0 -0
  79. /lionagi/{protocols/adapters → adapters}/adapter.py +0 -0
  80. /lionagi/{protocols/adapters → adapters}/json_adapter.py +0 -0
  81. /lionagi/{protocols/adapters → adapters}/pandas_/__init__.py +0 -0
  82. /lionagi/{protocols/adapters → adapters}/pandas_/csv_adapter.py +0 -0
  83. /lionagi/{protocols/adapters → adapters}/pandas_/excel_adapter.py +0 -0
  84. /lionagi/{protocols/adapters → adapters}/pandas_/pd_dataframe_adapter.py +0 -0
  85. /lionagi/{protocols/adapters → adapters}/pandas_/pd_series_adapter.py +0 -0
  86. /lionagi/{protocols/adapters → adapters}/toml_adapter.py +0 -0
  87. /lionagi/{protocols/adapters → adapters}/types.py +0 -0
  88. /lionagi/{operatives/models → models}/field_model.py +0 -0
  89. /lionagi/{operatives/models → models}/model_params.py +0 -0
  90. /lionagi/{operatives/models → models}/note.py +0 -0
  91. /lionagi/{operatives → protocols/action}/__init__.py +0 -0
  92. /lionagi/{operatives/action → protocols/forms}/__init__.py +0 -0
  93. /lionagi/{operatives/forms → protocols/operatives}/__init__.py +0 -0
  94. {lionagi-0.9.19.dist-info → lionagi-0.10.0.dist-info}/WHEEL +0 -0
  95. {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,3 +0,0 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
@@ -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
- )
@@ -1,9 +0,0 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
-
6
- class OperativeManager:
7
-
8
- def __init__(self):
9
- self.registry = {}
@@ -1,3 +0,0 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
@@ -1,3 +0,0 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
@@ -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