lionagi 0.8.5__py3-none-any.whl → 0.8.7__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 +6 -0
- lionagi/operations/ReAct/ReAct.py +40 -12
- lionagi/operations/ReAct/utils.py +72 -8
- lionagi/operations/_act/act.py +8 -0
- lionagi/operations/interpret/interpret.py +39 -8
- lionagi/operations/operate/operate.py +7 -1
- lionagi/session/branch.py +54 -6
- lionagi/tools/__init__.py +0 -0
- lionagi/tools/base.py +12 -0
- lionagi/tools/reader.py +244 -0
- lionagi/tools/types.py +3 -0
- lionagi/version.py +1 -1
- {lionagi-0.8.5.dist-info → lionagi-0.8.7.dist-info}/METADATA +73 -246
- {lionagi-0.8.5.dist-info → lionagi-0.8.7.dist-info}/RECORD +16 -12
- {lionagi-0.8.5.dist-info → lionagi-0.8.7.dist-info}/WHEEL +0 -0
- {lionagi-0.8.5.dist-info → lionagi-0.8.7.dist-info}/licenses/LICENSE +0 -0
lionagi/__init__.py
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
+
import logging
|
6
|
+
|
5
7
|
from pydantic import BaseModel, Field
|
6
8
|
|
7
9
|
from . import _types as types
|
@@ -13,6 +15,9 @@ from .version import __version__
|
|
13
15
|
|
14
16
|
LiteiModel = iModel
|
15
17
|
|
18
|
+
logger = logging.getLogger(__name__)
|
19
|
+
logger.setLevel(logging.INFO)
|
20
|
+
|
16
21
|
__all__ = (
|
17
22
|
"Session",
|
18
23
|
"Branch",
|
@@ -24,4 +29,5 @@ __all__ = (
|
|
24
29
|
"__version__",
|
25
30
|
"BaseModel",
|
26
31
|
"Field",
|
32
|
+
"logger",
|
27
33
|
)
|
@@ -28,11 +28,12 @@ async def ReAct(
|
|
28
28
|
tools: Any = None,
|
29
29
|
tool_schemas: Any = None,
|
30
30
|
response_format: type[BaseModel] | BaseModel = None,
|
31
|
-
extension_allowed: bool =
|
32
|
-
max_extensions: int | None =
|
31
|
+
extension_allowed: bool = True,
|
32
|
+
max_extensions: int | None = 3,
|
33
33
|
response_kwargs: dict | None = None,
|
34
34
|
return_analysis: bool = False,
|
35
35
|
analysis_model: iModel | None = None,
|
36
|
+
verbose_analysis: bool = False,
|
36
37
|
**kwargs,
|
37
38
|
):
|
38
39
|
# If no tools or tool schemas are provided, default to "all tools"
|
@@ -53,6 +54,8 @@ async def ReAct(
|
|
53
54
|
sample_writing=interpret_sample,
|
54
55
|
**(interpret_kwargs or {}),
|
55
56
|
)
|
57
|
+
if verbose_analysis:
|
58
|
+
print(f"Interpreted instruction: {instruction_str}")
|
56
59
|
|
57
60
|
# Convert Instruct to dict if necessary
|
58
61
|
instruct_dict = (
|
@@ -60,10 +63,13 @@ async def ReAct(
|
|
60
63
|
if isinstance(instruct, Instruct)
|
61
64
|
else dict(instruct)
|
62
65
|
)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
66
|
+
|
67
|
+
# Overwrite "instruction" with the interpreted prompt (if any) plus a note about expansions
|
68
|
+
max_ext_info = f"\nIf needed, you can do up to {max_extensions or 0 if extension_allowed else 0} expansions."
|
69
|
+
instruct_dict["instruction"] = (
|
70
|
+
instruction_str
|
71
|
+
or (instruct_dict.get("instruction") or "") # in case it's missing
|
72
|
+
) + max_ext_info
|
67
73
|
|
68
74
|
# Prepare a copy of user-provided kwargs for the first operate call
|
69
75
|
kwargs_for_operate = copy(kwargs)
|
@@ -81,13 +87,23 @@ async def ReAct(
|
|
81
87
|
)
|
82
88
|
analyses = [analysis]
|
83
89
|
|
90
|
+
# If verbose, show round #1 analysis
|
91
|
+
if verbose_analysis:
|
92
|
+
print(
|
93
|
+
f"ReAct Round #1 Analysis:\n {analysis.model_dump_json(indent=2)}",
|
94
|
+
)
|
95
|
+
|
84
96
|
# Validate and clamp max_extensions if needed
|
85
|
-
if max_extensions and max_extensions >
|
86
|
-
logging.warning(
|
87
|
-
|
97
|
+
if max_extensions and max_extensions > 100:
|
98
|
+
logging.warning(
|
99
|
+
"max_extensions should not exceed 100; defaulting to 100."
|
100
|
+
)
|
101
|
+
max_extensions = 100
|
88
102
|
|
89
103
|
# Step 2: Possibly loop through expansions if extension_needed
|
90
104
|
extensions = max_extensions
|
105
|
+
round_count = 1
|
106
|
+
|
91
107
|
while (
|
92
108
|
extension_allowed
|
93
109
|
and analysis.extension_needed
|
@@ -103,16 +119,28 @@ async def ReAct(
|
|
103
119
|
extensions=extensions
|
104
120
|
)
|
105
121
|
|
122
|
+
operate_kwargs = copy(kwargs)
|
123
|
+
operate_kwargs["actions"] = True
|
124
|
+
operate_kwargs["reason"] = True
|
125
|
+
operate_kwargs["response_format"] = ReActAnalysis
|
126
|
+
operate_kwargs["action_strategy"] = analysis.action_strategy
|
127
|
+
if analysis.action_batch_size:
|
128
|
+
operate_kwargs["action_batch_size"] = analysis.action_batch_size
|
129
|
+
|
106
130
|
analysis = await branch.operate(
|
107
131
|
instruction=new_instruction,
|
108
|
-
response_format=ReActAnalysis,
|
109
132
|
tools=tools,
|
110
133
|
tool_schemas=tool_schemas,
|
111
|
-
|
112
|
-
actions=True,
|
134
|
+
**operate_kwargs,
|
113
135
|
)
|
114
136
|
analyses.append(analysis)
|
137
|
+
round_count += 1
|
115
138
|
|
139
|
+
# If verbose, show round analysis
|
140
|
+
if verbose_analysis:
|
141
|
+
print(
|
142
|
+
f"ReAct Round #{round_count} Analysis:\n {analysis.model_dump_json(indent=2)}",
|
143
|
+
)
|
116
144
|
if extensions:
|
117
145
|
extensions -= 1
|
118
146
|
|
@@ -2,27 +2,91 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
-
from typing import ClassVar
|
5
|
+
from typing import ClassVar, Literal
|
6
6
|
|
7
7
|
from pydantic import BaseModel, Field
|
8
8
|
|
9
9
|
|
10
|
+
class PlannedAction(BaseModel):
|
11
|
+
"""
|
12
|
+
Short descriptor for an upcoming action/tool invocation the LLM wants to perform.
|
13
|
+
The model can hold multiple actions in a single round if needed.
|
14
|
+
"""
|
15
|
+
|
16
|
+
action_type: str = Field(
|
17
|
+
...,
|
18
|
+
description="The name or type of tool/action to invoke (e.g., 'search_exa', 'reader_tool').",
|
19
|
+
)
|
20
|
+
description: str = Field(
|
21
|
+
...,
|
22
|
+
description="A short explanation of why or what is intended to achieve with this action.",
|
23
|
+
)
|
24
|
+
|
25
|
+
|
10
26
|
class ReActAnalysis(BaseModel):
|
27
|
+
"""
|
28
|
+
Captures the ReAct chain-of-thought output each round:
|
29
|
+
1) The LLM's 'analysis' (reasoning),
|
30
|
+
2) A list of planned actions to perform before finalizing,
|
31
|
+
3) Indication whether more expansions/rounds are needed,
|
32
|
+
4) Additional tuning knobs: how to handle validation, how to execute actions, etc.
|
33
|
+
"""
|
11
34
|
|
35
|
+
# Standard ReAct strings for controlling expansions:
|
12
36
|
FIRST_EXT_PROMPT: ClassVar[str] = (
|
13
|
-
"You
|
37
|
+
"You can perform multiple reason-action steps for accuracy. "
|
38
|
+
"If you are not ready to finalize, set extension_needed to True. "
|
39
|
+
"You have up to {extensions} expansions. Please continue."
|
14
40
|
)
|
15
|
-
|
16
41
|
CONTINUE_EXT_PROMPT: ClassVar[str] = (
|
17
|
-
"
|
42
|
+
"Another round is available. You may do multiple actions if needed. "
|
43
|
+
"You have up to {extensions} expansions. Please continue."
|
18
44
|
)
|
19
|
-
|
20
45
|
ANSWER_PROMPT: ClassVar[str] = (
|
21
|
-
"
|
46
|
+
"Given your reasoning and actions, please now provide the final answer "
|
47
|
+
"to the user's request:\n\n{instruction}"
|
48
|
+
)
|
49
|
+
|
50
|
+
analysis: str = Field(
|
51
|
+
...,
|
52
|
+
description="Free-form reasoning or chain-of-thought summary. Must be consistent with the plan.",
|
53
|
+
)
|
54
|
+
|
55
|
+
planned_actions: list[PlannedAction] = Field(
|
56
|
+
default_factory=list,
|
57
|
+
description=(
|
58
|
+
"One or more short descriptors of the tool calls or operations "
|
59
|
+
"the LLM wants to perform this round. For example, read the doc, "
|
60
|
+
"then run a search."
|
61
|
+
),
|
22
62
|
)
|
23
63
|
|
24
|
-
analysis: str
|
25
64
|
extension_needed: bool = Field(
|
26
65
|
False,
|
27
|
-
description="Set
|
66
|
+
description="Set True if more expansions are needed. If False, final answer is next.",
|
67
|
+
)
|
68
|
+
|
69
|
+
milestone: str | None = Field(
|
70
|
+
None,
|
71
|
+
description=(
|
72
|
+
"A sub-goal or mini-checkpoint to reach before finalizing. "
|
73
|
+
"E.g. 'Validate results from search_exa, then summarize outcomes.'"
|
74
|
+
),
|
75
|
+
)
|
76
|
+
|
77
|
+
action_strategy: Literal["sequential", "concurrent", "batch"] = Field(
|
78
|
+
"concurrent",
|
79
|
+
description=(
|
80
|
+
"Specifies how to invoke the planned actions:\n"
|
81
|
+
"'sequential' => Each action is run in order, \n"
|
82
|
+
"'concurrent' => All actions run in parallel, \n"
|
83
|
+
"'batch' => Divide actions into async batches of N (if reasonable)."
|
84
|
+
),
|
85
|
+
)
|
86
|
+
|
87
|
+
action_batch_size: int | None = Field(
|
88
|
+
None,
|
89
|
+
description=(
|
90
|
+
"provide if and only if action_strategy is 'batch', this specifies the number of actions to run in parallel per batch."
|
91
|
+
),
|
28
92
|
)
|
lionagi/operations/_act/act.py
CHANGED
@@ -18,6 +18,7 @@ async def _act(
|
|
18
18
|
branch: "Branch",
|
19
19
|
action_request: BaseModel | dict,
|
20
20
|
suppress_errors: bool = False,
|
21
|
+
verbose_action: bool = False,
|
21
22
|
) -> "ActionResponseModel":
|
22
23
|
|
23
24
|
_request = {}
|
@@ -35,6 +36,11 @@ async def _act(
|
|
35
36
|
|
36
37
|
try:
|
37
38
|
func_call = await branch._action_manager.invoke(_request)
|
39
|
+
if verbose_action:
|
40
|
+
print(
|
41
|
+
f"Action {_request['function']} invoked, status: {func_call.status}."
|
42
|
+
)
|
43
|
+
|
38
44
|
except Exception as e:
|
39
45
|
content = {
|
40
46
|
"error": str(e),
|
@@ -43,6 +49,8 @@ async def _act(
|
|
43
49
|
"branch": str(branch.id),
|
44
50
|
}
|
45
51
|
branch._log_manager.log(Log(content=content))
|
52
|
+
if verbose_action:
|
53
|
+
print(f"Action {_request['function']} failed, error: {str(e)}.")
|
46
54
|
if suppress_errors:
|
47
55
|
logging.error(
|
48
56
|
f"Error invoking action '{_request['function']}': {e}"
|
@@ -16,15 +16,46 @@ async def interpret(
|
|
16
16
|
sample_writing: str | None = None,
|
17
17
|
**kwargs,
|
18
18
|
) -> str:
|
19
|
-
instruction =
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
instruction = """
|
20
|
+
You are given a user's raw instruction or question. Your task is to rewrite it into a clearer, more structured prompt for an LLM or system, making any implicit or missing details explicit.
|
21
|
+
|
22
|
+
Follow these guidelines:
|
23
|
+
|
24
|
+
1. **Dissect the user's request**:
|
25
|
+
- If the user references a local file, note it clearly (e.g., "paper_file_path": "…").
|
26
|
+
- If the user might need external references or up-to-date data, mention that possibility.
|
27
|
+
- If the user's question is ambiguous, propose clarifications.
|
28
|
+
|
29
|
+
2. **Be explicit about the user's final objective**:
|
30
|
+
- For example, if the user wants a comparison with other works, add that as a bullet point or sub-question.
|
31
|
+
- If the user wants a summary plus code snippet, highlight that in your structured prompt.
|
32
|
+
|
33
|
+
3. **Do NOT produce final system actions**:
|
34
|
+
- You're not calling any tools directly here; only rewriting the user query to reflect potential next steps.
|
35
|
+
- If the user's request might require searching or doc reading, note it as an *option*, e.g. "Potential tool usage: {search, partial doc read}."
|
36
|
+
|
37
|
+
4. **Return only the improved user prompt**:
|
38
|
+
- The final output should be a single text block or short JSON specifying the clarified user request.
|
39
|
+
- Keep it concise yet thorough.
|
40
|
+
|
41
|
+
For instance, if the user's original text is:
|
42
|
+
"Please read my local PDF on RL and compare it to the newest research methods from exa or perplexity."
|
43
|
+
|
44
|
+
A re-written version might be:
|
45
|
+
"**Task**:
|
46
|
+
- Summarize the local PDF (paper_file_path: 'myRLpaper.pdf').
|
47
|
+
- Compare its approach with recent reinforcement learning research found via exa/perplexity searches.
|
48
|
+
**Potential Tool Usage**:
|
49
|
+
- Doc reading (reader_tool)
|
50
|
+
- External search (search_exa, search_perplexity)
|
51
|
+
**Output**:
|
52
|
+
- A structured summary + comparative analysis."
|
53
|
+
|
54
|
+
Now, apply this rewriting to the input below. Return only the re-written prompt.
|
55
|
+
"""
|
24
56
|
guidance = (
|
25
57
|
f"Domain hint: {domain or 'general'}. "
|
26
58
|
f"Desired style: {style or 'concise'}. "
|
27
|
-
"You can add or clarify context if needed."
|
28
59
|
)
|
29
60
|
if sample_writing:
|
30
61
|
guidance += f" Sample writing: {sample_writing}"
|
@@ -32,11 +63,11 @@ async def interpret(
|
|
32
63
|
context = [f"User input: {text}"]
|
33
64
|
|
34
65
|
# Default temperature if none provided
|
35
|
-
kwargs["temperature"] = kwargs.get("temperature", 0.1)
|
36
66
|
kwargs["guidance"] = guidance + "\n" + kwargs.get("guidance", "")
|
67
|
+
kwargs["instruction"] = instruction + "\n" + kwargs.get("instruction", "")
|
68
|
+
kwargs["temperature"] = kwargs.get("temperature", 0.1)
|
37
69
|
|
38
70
|
refined_prompt = await branch.chat(
|
39
|
-
instruction=instruction,
|
40
71
|
context=context,
|
41
72
|
**kwargs,
|
42
73
|
)
|
@@ -50,6 +50,8 @@ async def operate(
|
|
50
50
|
action_strategy: Literal[
|
51
51
|
"sequential", "concurrent", "batch"
|
52
52
|
] = "concurrent",
|
53
|
+
action_batch_size: int = None,
|
54
|
+
verbose_action: bool = False,
|
53
55
|
field_models: list[FieldModel] = None,
|
54
56
|
exclude_fields: list | dict | None = None,
|
55
57
|
request_params: ModelParams = None,
|
@@ -189,9 +191,13 @@ async def operate(
|
|
189
191
|
if instruct.action_strategy
|
190
192
|
else action_kwargs.get("strategy", "concurrent")
|
191
193
|
)
|
194
|
+
if action_batch_size:
|
195
|
+
action_kwargs["batch_size"] = action_batch_size
|
192
196
|
|
193
197
|
action_response_models = await branch.act(
|
194
|
-
response_model.action_requests,
|
198
|
+
response_model.action_requests,
|
199
|
+
verbose_action=verbose_action,
|
200
|
+
**action_kwargs,
|
195
201
|
)
|
196
202
|
# Possibly refine the operative with the tool outputs
|
197
203
|
operative = Step.respond_operative(
|
lionagi/session/branch.py
CHANGED
@@ -48,6 +48,7 @@ from lionagi.protocols.types import (
|
|
48
48
|
from lionagi.service.endpoints.base import EndPoint
|
49
49
|
from lionagi.service.types import iModel, iModelManager
|
50
50
|
from lionagi.settings import Settings
|
51
|
+
from lionagi.tools.base import LionTool
|
51
52
|
from lionagi.utils import UNDEFINED, alcall, bcall, copy
|
52
53
|
|
53
54
|
if TYPE_CHECKING:
|
@@ -205,7 +206,9 @@ class Branch(Element, Communicatable, Relational):
|
|
205
206
|
)
|
206
207
|
|
207
208
|
# --- ActionManager ---
|
208
|
-
self._action_manager = ActionManager(
|
209
|
+
self._action_manager = ActionManager()
|
210
|
+
if tools:
|
211
|
+
self.register_tools(tools)
|
209
212
|
|
210
213
|
# --- LogManager ---
|
211
214
|
if log_config:
|
@@ -346,19 +349,28 @@ class Branch(Element, Communicatable, Relational):
|
|
346
349
|
|
347
350
|
return branch_clone
|
348
351
|
|
352
|
+
def _register_tool(self, tools: FuncTool | LionTool, update: bool = False):
|
353
|
+
if isinstance(tools, type) and issubclass(tools, LionTool):
|
354
|
+
tools = tools()
|
355
|
+
if isinstance(tools, LionTool):
|
356
|
+
tools = tools.to_tool()
|
357
|
+
self._action_manager.register_tool(tools, update=update)
|
358
|
+
|
349
359
|
def register_tools(
|
350
|
-
self, tools: FuncTool | list[FuncTool], update: bool = False
|
360
|
+
self, tools: FuncTool | list[FuncTool] | LionTool, update: bool = False
|
351
361
|
):
|
352
362
|
"""
|
353
363
|
Registers one or more tools in the ActionManager.
|
354
364
|
|
355
365
|
Args:
|
356
|
-
tools (FuncTool | list[FuncTool]):
|
366
|
+
tools (FuncTool | list[FuncTool] | LionTool):
|
357
367
|
A single tool or a list of tools to register.
|
358
368
|
update (bool, optional):
|
359
369
|
If `True`, updates existing tools with the same name.
|
360
370
|
"""
|
361
|
-
|
371
|
+
tools = [tools] if not isinstance(tools, list) else tools
|
372
|
+
for tool in tools:
|
373
|
+
self._register_tool(tool, update=update)
|
362
374
|
|
363
375
|
# -------------------------------------------------------------------------
|
364
376
|
# Conversion / Serialization
|
@@ -892,6 +904,11 @@ class Branch(Element, Communicatable, Relational):
|
|
892
904
|
actions: bool = False,
|
893
905
|
reason: bool = False,
|
894
906
|
action_kwargs: dict = None,
|
907
|
+
action_strategy: Literal[
|
908
|
+
"sequential", "concurrent", "batch"
|
909
|
+
] = "concurrent",
|
910
|
+
action_batch_size: int = None,
|
911
|
+
verbose_action: bool = False,
|
895
912
|
field_models: list[FieldModel] = None,
|
896
913
|
exclude_fields: list | dict | None = None,
|
897
914
|
request_params: ModelParams = None,
|
@@ -966,6 +983,12 @@ class Branch(Element, Communicatable, Relational):
|
|
966
983
|
If `True`, signals that the LLM should provide chain-of-thought or reasoning (where applicable).
|
967
984
|
action_kwargs (dict | None, optional):
|
968
985
|
Additional parameters for the `branch.act()` call if tools are invoked.
|
986
|
+
action_strategy (Literal["sequential","concurrent","batch"], optional):
|
987
|
+
The strategy for invoking tools (default: "concurrent").
|
988
|
+
action_batch_size (int, optional):
|
989
|
+
The batch size for concurrent tool invocation if `action_strategy="batch"`.
|
990
|
+
verbose_action (bool, optional):
|
991
|
+
If `True`, logs detailed information about tool invocation.
|
969
992
|
field_models (list[FieldModel] | None, optional):
|
970
993
|
Field-level definitions or overrides for the model schema.
|
971
994
|
exclude_fields (list|dict|None, optional):
|
@@ -1023,6 +1046,9 @@ class Branch(Element, Communicatable, Relational):
|
|
1023
1046
|
actions=actions,
|
1024
1047
|
reason=reason,
|
1025
1048
|
action_kwargs=action_kwargs,
|
1049
|
+
action_strategy=action_strategy,
|
1050
|
+
action_batch_size=action_batch_size,
|
1051
|
+
verbose_action=verbose_action,
|
1026
1052
|
field_models=field_models,
|
1027
1053
|
exclude_fields=exclude_fields,
|
1028
1054
|
request_params=request_params,
|
@@ -1150,6 +1176,7 @@ class Branch(Element, Communicatable, Relational):
|
|
1150
1176
|
self,
|
1151
1177
|
action_request: ActionRequest | BaseModel | dict,
|
1152
1178
|
suppress_errors: bool = False,
|
1179
|
+
verbose_action: bool = False,
|
1153
1180
|
) -> ActionResponse:
|
1154
1181
|
"""
|
1155
1182
|
Internal method to invoke a tool (action) asynchronously.
|
@@ -1165,13 +1192,19 @@ class Branch(Element, Communicatable, Relational):
|
|
1165
1192
|
"""
|
1166
1193
|
from lionagi.operations._act.act import _act
|
1167
1194
|
|
1168
|
-
return await _act(
|
1195
|
+
return await _act(
|
1196
|
+
branch=self,
|
1197
|
+
action_request=action_request,
|
1198
|
+
suppress_errors=suppress_errors,
|
1199
|
+
verbose_action=verbose_action,
|
1200
|
+
)
|
1169
1201
|
|
1170
1202
|
async def act(
|
1171
1203
|
self,
|
1172
1204
|
action_request: list | ActionRequest | BaseModel | dict,
|
1173
1205
|
*,
|
1174
1206
|
strategy: Literal["concurrent", "sequential", "batch"] = "concurrent",
|
1207
|
+
verbose_action: bool = False,
|
1175
1208
|
batch_size: int = None,
|
1176
1209
|
suppress_errors: bool = True,
|
1177
1210
|
sanitize_input: bool = False,
|
@@ -1197,6 +1230,10 @@ class Branch(Element, Communicatable, Relational):
|
|
1197
1230
|
action_request (list|ActionRequest|BaseModel|dict):
|
1198
1231
|
A single or list of action requests, each requiring
|
1199
1232
|
`function` and `arguments`.
|
1233
|
+
strategy (Literal["concurrent","sequential","batch"]):
|
1234
|
+
The execution strategy to use.
|
1235
|
+
verbose_action (bool):
|
1236
|
+
If True, log detailed information about the action.
|
1200
1237
|
suppress_errors (bool):
|
1201
1238
|
If True, log errors instead of raising exceptions.
|
1202
1239
|
sanitize_input (bool):
|
@@ -1243,6 +1280,7 @@ class Branch(Element, Communicatable, Relational):
|
|
1243
1280
|
case "concurrent":
|
1244
1281
|
return await self._concurrent_act(
|
1245
1282
|
action_request,
|
1283
|
+
verbose_action=verbose_action,
|
1246
1284
|
suppress_errors=suppress_errors,
|
1247
1285
|
sanitize_input=sanitize_input,
|
1248
1286
|
unique_input=unique_input,
|
@@ -1263,11 +1301,13 @@ class Branch(Element, Communicatable, Relational):
|
|
1263
1301
|
case "sequential":
|
1264
1302
|
return await self._sequential_act(
|
1265
1303
|
action_request,
|
1304
|
+
verbose_action=verbose_action,
|
1266
1305
|
suppress_errors=suppress_errors,
|
1267
1306
|
)
|
1268
1307
|
case "batch":
|
1269
1308
|
return await self._batch_act(
|
1270
1309
|
action_request,
|
1310
|
+
verbose_action=verbose_action,
|
1271
1311
|
batch_size=batch_size or 1,
|
1272
1312
|
max_concurrent=max_concurrent,
|
1273
1313
|
suppress_errors=suppress_errors,
|
@@ -1298,6 +1338,7 @@ class Branch(Element, Communicatable, Relational):
|
|
1298
1338
|
self,
|
1299
1339
|
action_request: ActionRequest | BaseModel | dict,
|
1300
1340
|
suppress_errors: bool = True,
|
1341
|
+
verbose_action: bool = False,
|
1301
1342
|
) -> list:
|
1302
1343
|
action_request = (
|
1303
1344
|
action_request
|
@@ -1307,7 +1348,11 @@ class Branch(Element, Communicatable, Relational):
|
|
1307
1348
|
results = []
|
1308
1349
|
for req in action_request:
|
1309
1350
|
results.append(
|
1310
|
-
await self._act(
|
1351
|
+
await self._act(
|
1352
|
+
req,
|
1353
|
+
verbose_action=verbose_action,
|
1354
|
+
suppress_errors=suppress_errors,
|
1355
|
+
)
|
1311
1356
|
)
|
1312
1357
|
return results
|
1313
1358
|
|
@@ -1560,6 +1605,7 @@ class Branch(Element, Communicatable, Relational):
|
|
1560
1605
|
response_kwargs: dict | None = None,
|
1561
1606
|
return_analysis: bool = False,
|
1562
1607
|
analysis_model: iModel | None = None,
|
1608
|
+
verbose: bool = False,
|
1563
1609
|
**kwargs,
|
1564
1610
|
):
|
1565
1611
|
"""
|
@@ -1646,6 +1692,8 @@ class Branch(Element, Communicatable, Relational):
|
|
1646
1692
|
response_kwargs=response_kwargs,
|
1647
1693
|
return_analysis=return_analysis,
|
1648
1694
|
analysis_model=analysis_model,
|
1695
|
+
verbose_action=verbose,
|
1696
|
+
verbose_analysis=verbose,
|
1649
1697
|
**kwargs,
|
1650
1698
|
)
|
1651
1699
|
|
File without changes
|
lionagi/tools/base.py
ADDED
lionagi/tools/reader.py
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
import tempfile
|
2
|
+
from enum import Enum
|
3
|
+
|
4
|
+
from pydantic import BaseModel, Field, field_validator
|
5
|
+
|
6
|
+
from lionagi.operatives.action.tool import Tool
|
7
|
+
from lionagi.utils import to_num
|
8
|
+
|
9
|
+
from .base import LionTool
|
10
|
+
|
11
|
+
|
12
|
+
class ReaderAction(str, Enum):
|
13
|
+
"""
|
14
|
+
This enumeration indicates the *type* of action the LLM wants to perform.
|
15
|
+
- 'open': Convert a file/URL to text and store it internally for partial reads
|
16
|
+
- 'read': Return a partial slice of the already-opened doc
|
17
|
+
"""
|
18
|
+
|
19
|
+
open = "open"
|
20
|
+
read = "read"
|
21
|
+
|
22
|
+
|
23
|
+
class ReaderRequest(BaseModel):
|
24
|
+
"""
|
25
|
+
The request model for the 'ReaderTool'.
|
26
|
+
It indicates:
|
27
|
+
- whether we are 'open'-ing a doc or 'read'-ing from a doc
|
28
|
+
- which file/URL we want to open (if action='open')
|
29
|
+
- which doc_id and offsets we want to read (if action='read')
|
30
|
+
"""
|
31
|
+
|
32
|
+
action: ReaderAction = Field(
|
33
|
+
...,
|
34
|
+
description=(
|
35
|
+
"Action to perform. Must be one of: "
|
36
|
+
"- 'open': Convert a file/URL to text and store it internally for partial reads. "
|
37
|
+
"- 'read': Return a partial slice of the already-opened doc."
|
38
|
+
),
|
39
|
+
)
|
40
|
+
|
41
|
+
path_or_url: str | None = Field(
|
42
|
+
None,
|
43
|
+
description=(
|
44
|
+
"Local file path or remote URL to open. This field is REQUIRED if action='open'. "
|
45
|
+
"If action='read', leave it None."
|
46
|
+
),
|
47
|
+
)
|
48
|
+
|
49
|
+
doc_id: str | None = Field(
|
50
|
+
None,
|
51
|
+
description=(
|
52
|
+
"Unique ID referencing a previously opened document. "
|
53
|
+
"This field is REQUIRED if action='read'. If action='open', leave it None."
|
54
|
+
),
|
55
|
+
)
|
56
|
+
|
57
|
+
start_offset: int | None = Field(
|
58
|
+
None,
|
59
|
+
description=(
|
60
|
+
"Character start offset in the doc for partial reading. "
|
61
|
+
"If omitted or None, defaults to 0. Only used if action='read'."
|
62
|
+
),
|
63
|
+
)
|
64
|
+
|
65
|
+
end_offset: int | None = Field(
|
66
|
+
None,
|
67
|
+
description=(
|
68
|
+
"Character end offset in the doc for partial reading. "
|
69
|
+
"If omitted or None, we read until the document's end. Only used if action='read'."
|
70
|
+
),
|
71
|
+
)
|
72
|
+
|
73
|
+
@field_validator("start_offset", "end_offset", mode="before")
|
74
|
+
def _validate_offsets(cls, v):
|
75
|
+
try:
|
76
|
+
return to_num(v, num_type=int)
|
77
|
+
except ValueError:
|
78
|
+
return None
|
79
|
+
|
80
|
+
|
81
|
+
class DocumentInfo(BaseModel):
|
82
|
+
"""
|
83
|
+
Returned info when we 'open' a doc.
|
84
|
+
doc_id: The unique string to reference this doc in subsequent 'read' calls
|
85
|
+
length: The total character length of the converted text
|
86
|
+
"""
|
87
|
+
|
88
|
+
doc_id: str
|
89
|
+
length: int | None = None
|
90
|
+
|
91
|
+
|
92
|
+
class PartialChunk(BaseModel):
|
93
|
+
"""
|
94
|
+
Represents a partial slice of text from [start_offset..end_offset).
|
95
|
+
"""
|
96
|
+
|
97
|
+
start_offset: int | None = None
|
98
|
+
end_offset: int | None = None
|
99
|
+
content: str | None = None
|
100
|
+
|
101
|
+
|
102
|
+
class ReaderResponse(BaseModel):
|
103
|
+
"""
|
104
|
+
The response from the 'ReaderTool'.
|
105
|
+
- If action='open' succeeded, doc_info is filled (doc_id & length).
|
106
|
+
- If action='read' succeeded, chunk is filled (the partial text).
|
107
|
+
- If failure occurs, success=False & error hold details.
|
108
|
+
"""
|
109
|
+
|
110
|
+
success: bool = Field(
|
111
|
+
...,
|
112
|
+
description=(
|
113
|
+
"Indicates if the requested action was performed successfully."
|
114
|
+
),
|
115
|
+
)
|
116
|
+
error: str | None = Field(
|
117
|
+
None,
|
118
|
+
description=("Describes any error that occurred, if success=False."),
|
119
|
+
)
|
120
|
+
doc_info: DocumentInfo | None = Field(
|
121
|
+
None,
|
122
|
+
description=(
|
123
|
+
"Populated only if action='open' succeeded, letting the LLM know doc_id & total length."
|
124
|
+
),
|
125
|
+
)
|
126
|
+
chunk: PartialChunk | None = Field(
|
127
|
+
None,
|
128
|
+
description=(
|
129
|
+
"Populated only if action='read' succeeded, providing the partial slice of text."
|
130
|
+
),
|
131
|
+
)
|
132
|
+
|
133
|
+
|
134
|
+
class ReaderTool(LionTool):
|
135
|
+
"""
|
136
|
+
A single tool that the LLM can call with ReaderRequest to either:
|
137
|
+
- open a doc (File/URL) -> returns doc_id, doc length
|
138
|
+
- read partial text from doc -> returns chunk
|
139
|
+
"""
|
140
|
+
|
141
|
+
is_lion_system_tool = True
|
142
|
+
system_tool_name = "reader_tool"
|
143
|
+
|
144
|
+
from lionagi.libs.package.imports import check_import
|
145
|
+
|
146
|
+
DocumentConverter = check_import(
|
147
|
+
"docling",
|
148
|
+
module_name="document_converter",
|
149
|
+
import_name="DocumentConverter",
|
150
|
+
)
|
151
|
+
|
152
|
+
def __init__(self):
|
153
|
+
super().__init__()
|
154
|
+
self.converter = ReaderTool.DocumentConverter()
|
155
|
+
self.documents = {} # doc_id -> (temp_file_path, doc_length)
|
156
|
+
self._tool = None
|
157
|
+
|
158
|
+
def handle_request(self, request: ReaderRequest) -> ReaderResponse:
|
159
|
+
"""
|
160
|
+
A function that takes ReaderRequest to either:
|
161
|
+
- open a doc (File/URL) -> returns doc_id, doc length
|
162
|
+
- read partial text from doc -> returns chunk
|
163
|
+
"""
|
164
|
+
if isinstance(request, dict):
|
165
|
+
request = ReaderRequest(**request)
|
166
|
+
if request.action == "open":
|
167
|
+
return self._open_doc(request.path_or_url)
|
168
|
+
elif request.action == "read":
|
169
|
+
return self._read_doc(
|
170
|
+
request.doc_id, request.start_offset, request.end_offset
|
171
|
+
)
|
172
|
+
else:
|
173
|
+
return ReaderResponse(success=False, error="Unknown action type")
|
174
|
+
|
175
|
+
def _open_doc(self, source: str) -> ReaderResponse:
|
176
|
+
try:
|
177
|
+
result = self.converter.convert(source)
|
178
|
+
text = result.document.export_to_markdown()
|
179
|
+
except Exception as e:
|
180
|
+
return ReaderResponse(
|
181
|
+
success=False, error=f"Conversion error: {str(e)}"
|
182
|
+
)
|
183
|
+
|
184
|
+
doc_id = f"DOC_{abs(hash(source))}"
|
185
|
+
temp_file = tempfile.NamedTemporaryFile(
|
186
|
+
delete=False, mode="w", encoding="utf-8"
|
187
|
+
)
|
188
|
+
temp_file.write(text)
|
189
|
+
doc_len = len(text)
|
190
|
+
temp_file.close()
|
191
|
+
|
192
|
+
# store info
|
193
|
+
self.documents[doc_id] = (temp_file.name, doc_len)
|
194
|
+
|
195
|
+
return ReaderResponse(
|
196
|
+
success=True, doc_info=DocumentInfo(doc_id=doc_id, length=doc_len)
|
197
|
+
)
|
198
|
+
|
199
|
+
def _read_doc(self, doc_id: str, start: int, end: int) -> ReaderResponse:
|
200
|
+
if doc_id not in self.documents:
|
201
|
+
return ReaderResponse(
|
202
|
+
success=False, error="doc_id not found in memory"
|
203
|
+
)
|
204
|
+
|
205
|
+
path, length = self.documents[doc_id]
|
206
|
+
# clamp offsets
|
207
|
+
s = max(0, start if start is not None else 0)
|
208
|
+
e = min(length, end if end is not None else length)
|
209
|
+
|
210
|
+
try:
|
211
|
+
with open(path, encoding="utf-8") as f:
|
212
|
+
f.seek(s)
|
213
|
+
content = f.read(e - s)
|
214
|
+
except Exception as ex:
|
215
|
+
return ReaderResponse(
|
216
|
+
success=False, error=f"Read error: {str(ex)}"
|
217
|
+
)
|
218
|
+
|
219
|
+
return ReaderResponse(
|
220
|
+
success=True,
|
221
|
+
chunk=PartialChunk(start_offset=s, end_offset=e, content=content),
|
222
|
+
)
|
223
|
+
|
224
|
+
def to_tool(self):
|
225
|
+
if self._tool is None:
|
226
|
+
|
227
|
+
def reader_tool(**kwargs):
|
228
|
+
"""
|
229
|
+
A function that takes ReaderRequest to either:
|
230
|
+
- open a doc (File/URL) -> returns doc_id, doc length
|
231
|
+
- read partial text from doc -> returns chunk
|
232
|
+
"""
|
233
|
+
return self.handle_request(
|
234
|
+
ReaderRequest(**kwargs)
|
235
|
+
).model_dump()
|
236
|
+
|
237
|
+
if self.system_tool_name != "reader_tool":
|
238
|
+
reader_tool.__name__ = self.system_tool_name
|
239
|
+
|
240
|
+
self._tool = Tool(
|
241
|
+
func_callable=reader_tool,
|
242
|
+
request_options=ReaderRequest,
|
243
|
+
)
|
244
|
+
return self._tool
|
lionagi/tools/types.py
ADDED
lionagi/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.8.
|
1
|
+
__version__ = "0.8.7"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lionagi
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.7
|
4
4
|
Summary: An Intelligence Operating System.
|
5
5
|
Author-email: HaiyangLi <quantocean.li@gmail.com>
|
6
6
|
License: Apache License
|
@@ -235,310 +235,134 @@ Description-Content-Type: text/markdown
|
|
235
235
|
|
236
236
|
[Documentation](https://lion-agi.github.io/lionagi/) | [Discord](https://discord.gg/aqSJ2v46vu) | [PyPI](https://pypi.org/project/lionagi/) | [Roadmap](https://trello.com/b/3seomsrI/lionagi)
|
237
237
|
|
238
|
-
# LION
|
239
|
-
### Language InterOperable Network - The Future of Controlled AI Operations
|
238
|
+
# LION - Language InterOperable Network
|
240
239
|
|
241
|
-
|
240
|
+
## An Intelligence Operating System
|
242
241
|
|
243
|
-
|
242
|
+
LionAGI is a robust framework for orchestrating multi-step AI operations with precise control. Bring together multiple models, advanced ReAct reasoning, tool integrations, and custom validations in a single coherent pipeline.
|
244
243
|
|
245
|
-
|
246
|
-
|
247
|
-
LION is designed to be:
|
248
|
-
- 🔒 **Controlled**: Built-in safety mechanisms and verification
|
249
|
-
- 🎯 **Precise**: Exact control over AI behaviors
|
250
|
-
- 🔧 **Flexible**: Build any workflow you need
|
251
|
-
- 🚀 **Efficient**: Minimal dependencies, maximum performance
|
244
|
+
## Why LionAGI?
|
252
245
|
|
246
|
+
- **Structured**: LLM interactions are validated and typed (via Pydantic).
|
247
|
+
- **Expandable**: Integrate multiple providers (OpenAI, Anthropic, Perplexity, custom) with minimal friction.
|
248
|
+
- **Controlled**: Built-in safety checks, concurrency strategies, and advanced multi-step flows—like ReAct with verbose outputs.
|
249
|
+
- **Transparent**: Real-time logging, message introspection, and easy debugging of tool usage.
|
253
250
|
|
254
251
|
|
255
252
|
## Installation
|
256
253
|
|
257
|
-
|
258
|
-
|
259
|
-
```bash
|
260
|
-
uv pip install lionagi
|
254
|
+
```
|
255
|
+
pip install lionagi
|
261
256
|
```
|
262
257
|
|
263
258
|
Dependencies:
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
259
|
+
• litellm
|
260
|
+
• jinja2
|
261
|
+
• pandas
|
262
|
+
• pillow
|
263
|
+
• python-dotenv
|
270
264
|
|
271
265
|
## Quick Start
|
272
|
-
|
273
266
|
```python
|
274
|
-
from lionagi import
|
267
|
+
from lionagi import Branch, iModel
|
275
268
|
|
276
|
-
#
|
277
|
-
gpt4o = iModel(provider="openai",
|
269
|
+
# Pick a model
|
270
|
+
gpt4o = iModel(provider="openai", model="gpt-4o")
|
278
271
|
|
272
|
+
# Create a Branch (conversation context)
|
279
273
|
hunter = Branch(
|
280
|
-
system="you are a hilarious dragon hunter who responds in 10 words rhymes",
|
281
|
-
|
274
|
+
system="you are a hilarious dragon hunter who responds in 10 words rhymes.",
|
275
|
+
chat_model=gpt4o,
|
282
276
|
)
|
283
277
|
|
284
|
-
#
|
285
|
-
|
278
|
+
# Communicate asynchronously
|
279
|
+
response = await hunter.communicate("I am a dragon")
|
280
|
+
print(response)
|
286
281
|
```
|
287
282
|
|
288
283
|
```
|
289
284
|
You claim to be a dragon, oh what a braggin'!
|
290
285
|
```
|
286
|
+
### Structured Responses
|
291
287
|
|
292
|
-
|
293
|
-
|
294
|
-
### 1. Model Agnostic Structured Output
|
295
|
-
|
296
|
-
LION provides a unified interface for interacting with any AI model, regardless of the underlying architecture. This allows you to easily switch between models without changing your code.
|
288
|
+
Use Pydantic to keep outputs structured:
|
297
289
|
|
298
290
|
```python
|
299
291
|
from pydantic import BaseModel
|
300
292
|
|
301
293
|
class Joke(BaseModel):
|
302
|
-
|
294
|
+
joke: str
|
303
295
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
max_tokens=100, # max_tokens is required for anthropic models
|
296
|
+
res = await hunter.communicate(
|
297
|
+
"Tell me a short dragon joke",
|
298
|
+
response_format=Joke
|
308
299
|
)
|
309
|
-
|
310
|
-
response = await hunter.communicate(
|
311
|
-
instruction="I am a dragon",
|
312
|
-
response_format=Joke, # structured output in given pydantic model
|
313
|
-
clear_messages=True, # refresh the conversation
|
314
|
-
imodel=sonnet, # use sonnet model, which doesn't support structured output
|
315
|
-
)
|
316
|
-
|
317
300
|
print(type(response))
|
318
301
|
print(response.joke)
|
319
302
|
```
|
320
|
-
|
321
303
|
```
|
322
304
|
<class '__main__.Joke'>
|
323
|
-
|
305
|
+
With fiery claws, dragons hide their laughter flaws!
|
324
306
|
```
|
325
307
|
|
308
|
+
### ReAct and Tools
|
326
309
|
|
327
|
-
|
310
|
+
LionAGI supports advanced multi-step reasoning with ReAct. Tools let the LLM invoke external actions:
|
328
311
|
|
329
312
|
```python
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
imodel=pplx_small, # use perplexity model
|
313
|
+
from lionagi.tools.types import ReaderTool
|
314
|
+
|
315
|
+
branch = Branch(chat_model=gpt4o, tools=ReaderTool)
|
316
|
+
result = await branch.ReAct(
|
317
|
+
instruct={
|
318
|
+
"instruction": "Summarize my PDF and compare with relevant papers.",
|
319
|
+
"context": {"paper_file_path": "/path/to/paper.pdf"},
|
320
|
+
},
|
321
|
+
extension_allowed=True, # allow multi-round expansions
|
322
|
+
max_extensions=5,
|
323
|
+
verbose=True, # see step-by-step chain-of-thought
|
342
324
|
)
|
343
|
-
|
344
|
-
print(b)
|
345
|
-
```
|
346
|
-
|
347
|
-
```
|
348
|
-
A well-behaved dragon is one that's calm and bright,
|
349
|
-
No stress or fear, just a peaceful night.
|
350
|
-
It's active, not lethargic, with a happy face,
|
351
|
-
And behaviors like digging, not a frantic pace.
|
352
|
-
It's social, friendly, and never a fright,
|
353
|
-
Just a gentle soul, shining with delight
|
354
|
-
```
|
355
|
-
|
356
|
-
```python
|
357
|
-
hunter.msgs.last_response.model_response
|
358
|
-
```
|
359
|
-
|
360
|
-
```
|
361
|
-
{'id': '1be10f4c-0936-4050-ab48-91bd86ab11a5',
|
362
|
-
'model': 'llama-3.1-sonar-small-128k-online',
|
363
|
-
'object': 'chat.completion',
|
364
|
-
'created': 1734369700,
|
365
|
-
'choices': [{'index': 0,
|
366
|
-
'message': {'role': 'assistant',
|
367
|
-
'content': "A well-behaved dragon is one that's calm and bright,\nNo stress or fear, just a peaceful night.\nIt's active, not lethargic, with a happy face,\nAnd behaviors like digging, not a frantic pace.\nIt's social, friendly, and never a fright,\nJust a gentle soul, shining with delight"},
|
368
|
-
'finish_reason': 'stop',
|
369
|
-
'delta': {'role': 'assistant', 'content': ''}}],
|
370
|
-
'usage': {'prompt_tokens': 40, 'completion_tokens': 69, 'total_tokens': 109},
|
371
|
-
'citations': [{'url': 'https://dragonsdiet.com/blogs/dragon-care/15-bearded-dragon-behaviors-and-what-they-could-mean'},
|
372
|
-
{'url': 'https://masterbraeokk.tripod.com/dragons/behavior.html'},
|
373
|
-
{'url': 'https://files.eric.ed.gov/fulltext/ED247607.pdf'},
|
374
|
-
{'url': 'https://www.travelchinaguide.com/intro/social_customs/zodiac/dragon/five-elements.htm'},
|
375
|
-
{'url': 'https://www.travelchinaguide.com/intro/social_customs/zodiac/dragon/'}]}
|
325
|
+
print(result)
|
376
326
|
```
|
377
327
|
|
328
|
+
The LLM can now open the PDF, read in slices, fetch references, and produce a final structured summary.
|
378
329
|
|
379
|
-
###
|
380
|
-
|
330
|
+
### Observability & Debugging
|
331
|
+
- Inspect messages:
|
381
332
|
```python
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
class Reason(BaseModel):
|
386
|
-
reason: str
|
387
|
-
confidence_score: float
|
388
|
-
|
389
|
-
class Thought(BaseModel):
|
390
|
-
thought: str
|
391
|
-
|
392
|
-
class Analysis(BaseModel):
|
393
|
-
thought: list[Thought] = Field(
|
394
|
-
default_factory=list,
|
395
|
-
description="concise Chain of thoughts from you, 3 step, each in 8 words"
|
396
|
-
)
|
397
|
-
analysis: str = Field(
|
398
|
-
...,
|
399
|
-
description="Final analysis of the dragon's psyche in 20 words",
|
400
|
-
)
|
401
|
-
reason: list[Reason] = Field(
|
402
|
-
default_factory=list,
|
403
|
-
description="Concise Reasoning behind the analysis, 3 support, each in 8 words"
|
404
|
-
)
|
405
|
-
|
406
|
-
context1 = "I am a dragon, I think therefore I am, I suffer from shiny objects syndrome"
|
407
|
-
context2 = "I like food and poetry, I use uv sometimes, it's cool but I am not familiar with pip"
|
408
|
-
|
409
|
-
async def analyze(context) -> Analysis:
|
410
|
-
psychologist = Branch(
|
411
|
-
system="you are a renowned dragon psychologist",
|
412
|
-
imodel=gpt4o,
|
413
|
-
)
|
414
|
-
return await psychologist.communicate(
|
415
|
-
instruction="analyze the dragon's psyche using chain of thoughts",
|
416
|
-
guidance="think step by step, reason with logic",
|
417
|
-
context=context,
|
418
|
-
response_format=Analysis,
|
419
|
-
)
|
420
|
-
|
333
|
+
df = branch.to_df()
|
334
|
+
print(df.tail())
|
421
335
|
```
|
336
|
+
- Action logs show each tool call, arguments, and outcomes.
|
337
|
+
- Verbose ReAct provides chain-of-thought analysis (helpful for debugging multi-step flows).
|
422
338
|
|
423
|
-
|
424
|
-
result1 = await analyze(context1)
|
425
|
-
|
426
|
-
print("\nThoughts:")
|
427
|
-
for i in result1.thought:
|
428
|
-
print(i.thought)
|
429
|
-
|
430
|
-
print("\nAnalysis:")
|
431
|
-
print(result1.analysis)
|
432
|
-
|
433
|
-
print("\nReasoning:")
|
434
|
-
for i in result1.reason:
|
435
|
-
print(i.reason)
|
436
|
-
```
|
437
|
-
|
438
|
-
```
|
439
|
-
|
440
|
-
Thoughts:
|
441
|
-
Dragons are attracted to shiny objects naturally.
|
442
|
-
This suggests a strong affinity for hoarding.
|
443
|
-
Reflects the dragon's inherent desire for possession.
|
444
|
-
|
445
|
-
Analysis:
|
446
|
-
The dragon demonstrates a compulsive hoarding behavior linked to attraction for shiny objects.
|
447
|
-
|
448
|
-
Reasoning:
|
449
|
-
Shiny objects trigger instinctual hoarding behavior.
|
450
|
-
Possession indicates a symbol of power and security.
|
451
|
-
Hoarding is reinforced by evolutionary survival mechanisms.
|
452
|
-
```
|
339
|
+
### Example: Multi-Model Orchestration
|
453
340
|
|
454
341
|
```python
|
455
|
-
|
342
|
+
from lionagi import Branch, iModel
|
456
343
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
344
|
+
gpt4o = iModel(provider="openai", model="gpt-4o")
|
345
|
+
sonnet = iModel(
|
346
|
+
provider="anthropic",
|
347
|
+
model="claude-3-5-sonnet-20241022",
|
348
|
+
max_tokens=1000, # max_tokens is required for anthropic models
|
349
|
+
)
|
463
350
|
|
464
|
-
|
465
|
-
|
466
|
-
|
351
|
+
branch = Branch(chat_model=gpt4o)
|
352
|
+
# Switch mid-flow
|
353
|
+
analysis = await branch.communicate("Analyze these stats", imodel=sonnet)
|
467
354
|
```
|
468
355
|
|
469
|
-
|
470
|
-
Thoughts:
|
471
|
-
Dragon enjoys both food and poetry regularly.
|
472
|
-
Dragon uses uv light with frequent interest.
|
473
|
-
Dragon is unfamiliar and not comfortable with pip.
|
474
|
-
|
475
|
-
Analysis:
|
476
|
-
The dragon is curious and exploratory, yet selectively cautious about unfamiliar methodologies.
|
477
|
-
|
478
|
-
Reasoning:
|
479
|
-
Preference for food and poetry suggests curiosity.
|
480
|
-
Frequent uv light use indicates exploratory nature.
|
481
|
-
Discomfort with pip usage shows selective caution.
|
482
|
-
```
|
356
|
+
Seamlessly route to different models in the same workflow.
|
483
357
|
|
358
|
+
## Community & Contributing
|
484
359
|
|
360
|
+
We welcome issues, ideas, and pull requests:
|
361
|
+
- Discord: Join to chat or get help
|
362
|
+
- Issues / PRs: GitHub
|
485
363
|
|
486
|
-
|
487
|
-
|
488
|
-
Below is an example of what you can build with LION. Note that these are sample implementations - LION provides the building blocks, you create the workflows that fit your needs.
|
489
|
-
|
490
|
-
```mermaid
|
491
|
-
sequenceDiagram
|
492
|
-
autonumber
|
493
|
-
participant Client
|
494
|
-
participant Orchestrator
|
495
|
-
participant ResearchAgent
|
496
|
-
participant AnalysisAgent
|
497
|
-
participant ValidationAgent
|
498
|
-
participant Tools
|
499
|
-
|
500
|
-
Client->>+Orchestrator: Submit Complex Task
|
501
|
-
Note over Orchestrator: Task Analysis & Planning
|
502
|
-
|
503
|
-
%% Research Phase
|
504
|
-
Orchestrator->>+ResearchAgent: Delegate Research
|
505
|
-
activate ResearchAgent
|
506
|
-
ResearchAgent->>Tools: Access Data Sources
|
507
|
-
Tools-->>ResearchAgent: Raw Data
|
508
|
-
ResearchAgent-->>-Orchestrator: Research Results
|
509
|
-
deactivate ResearchAgent
|
510
|
-
|
511
|
-
%% Analysis Phase
|
512
|
-
Orchestrator->>+AnalysisAgent: Process Data
|
513
|
-
activate AnalysisAgent
|
514
|
-
AnalysisAgent->>Tools: Apply Models
|
515
|
-
Tools-->>AnalysisAgent: Analysis Results
|
516
|
-
AnalysisAgent-->>-Orchestrator: Processed Insights
|
517
|
-
deactivate AnalysisAgent
|
518
|
-
|
519
|
-
%% Validation Phase
|
520
|
-
Orchestrator->>+ValidationAgent: Verify Results
|
521
|
-
activate ValidationAgent
|
522
|
-
ValidationAgent->>Tools: Apply Safety Checks
|
523
|
-
Tools-->>ValidationAgent: Validation Status
|
524
|
-
ValidationAgent-->>-Orchestrator: Verified Results
|
525
|
-
deactivate ValidationAgent
|
526
|
-
|
527
|
-
Orchestrator-->>-Client: Return Validated Output
|
364
|
+
### Citation
|
528
365
|
```
|
529
|
-
|
530
|
-
|
531
|
-
## 🤝 Contributing
|
532
|
-
|
533
|
-
Join our [Discord community](https://discord.gg/aqSJ2v46vu) to:
|
534
|
-
- Share ideas
|
535
|
-
- Report issues
|
536
|
-
- Contribute code
|
537
|
-
- Learn from others
|
538
|
-
|
539
|
-
## 📚 Citation
|
540
|
-
|
541
|
-
```bibtex
|
542
366
|
@software{Li_LionAGI_2023,
|
543
367
|
author = {Haiyang Li},
|
544
368
|
month = {12},
|
@@ -547,3 +371,6 @@ Join our [Discord community](https://discord.gg/aqSJ2v46vu) to:
|
|
547
371
|
url = {https://github.com/lion-agi/lionagi},
|
548
372
|
}
|
549
373
|
```
|
374
|
+
|
375
|
+
**🦁 LionAGI**
|
376
|
+
> Because real AI orchestration demands more than a single prompt. Try it out and discover the next evolution in structured, multi-model, safe AI.
|
@@ -1,10 +1,10 @@
|
|
1
|
-
lionagi/__init__.py,sha256
|
1
|
+
lionagi/__init__.py,sha256=TWvUzgX_HgXO6xoShWT3kY8BNaZDOc8JIOu59PFqN2M,672
|
2
2
|
lionagi/_class_registry.py,sha256=dutMsw-FQNqVV5gGH-NEIv90uBkSr8fERJ_x3swbb-s,3112
|
3
3
|
lionagi/_errors.py,sha256=wNKdnVQvE_CHEstK7htrrj334RA_vbGcIds-3pUiRkc,455
|
4
4
|
lionagi/_types.py,sha256=9g7iytvSj3UjZxD-jL06_fxuNfgZyWT3Qnp0XYp1wQU,63
|
5
5
|
lionagi/settings.py,sha256=k9zRJXv57TveyfHO3Vr9VGiKrSwlRUUVKt5zf6v9RU4,1627
|
6
6
|
lionagi/utils.py,sha256=QbF4E1PG-BaRcEVH3kJIYCJVNq-oRNoTxjda5k8NYW4,73177
|
7
|
-
lionagi/version.py,sha256=
|
7
|
+
lionagi/version.py,sha256=vTwvdJOZi8jZb9U-Em7-d50qNDNPS2z51IXqRoojeNM,22
|
8
8
|
lionagi/libs/__init__.py,sha256=v8vNyJVIVj8_Oz9RJdVe6ZKUQMYTgDh1VQpnr1KdLaw,112
|
9
9
|
lionagi/libs/parse.py,sha256=tpEbmIRGuHhLCJlUlm6fjmqm_Z6XJLAXGNFHNuk422I,1011
|
10
10
|
lionagi/libs/file/__init__.py,sha256=v8vNyJVIVj8_Oz9RJdVe6ZKUQMYTgDh1VQpnr1KdLaw,112
|
@@ -48,11 +48,11 @@ lionagi/operations/__init__.py,sha256=O7nV0tedpUe7_OlUWmCcduGPFtqtzWZcR_SIOnjLsr
|
|
48
48
|
lionagi/operations/manager.py,sha256=H7UY86PIxvxKdzJY9YVsWyJcqlwLWhVyvm4sYePH_uY,565
|
49
49
|
lionagi/operations/types.py,sha256=LIa68xcyKLVafof-DSFwKtSkneuYPFqrtGyClohYI6o,704
|
50
50
|
lionagi/operations/utils.py,sha256=Twy6L_UFt9JqJFRYuKKTKVZIXsePidNl5ipcYcCbesI,1220
|
51
|
-
lionagi/operations/ReAct/ReAct.py,sha256=
|
51
|
+
lionagi/operations/ReAct/ReAct.py,sha256=_PwoP3RgazGsAaDEOWEABATpBujI7e5OQhbc9AyIA1o,5082
|
52
52
|
lionagi/operations/ReAct/__init__.py,sha256=v8vNyJVIVj8_Oz9RJdVe6ZKUQMYTgDh1VQpnr1KdLaw,112
|
53
|
-
lionagi/operations/ReAct/utils.py,sha256=
|
53
|
+
lionagi/operations/ReAct/utils.py,sha256=uWPZC1aJVAPvJweAgr3NdXpYszeagN5OnJIkUdrSvlw,3228
|
54
54
|
lionagi/operations/_act/__init__.py,sha256=v8vNyJVIVj8_Oz9RJdVe6ZKUQMYTgDh1VQpnr1KdLaw,112
|
55
|
-
lionagi/operations/_act/act.py,sha256=
|
55
|
+
lionagi/operations/_act/act.py,sha256=FWK8vXiccBZI_sIQcEBw8Cn6slMooZkfmkmxBaYA4kw,2739
|
56
56
|
lionagi/operations/brainstorm/__init__.py,sha256=v8vNyJVIVj8_Oz9RJdVe6ZKUQMYTgDh1VQpnr1KdLaw,112
|
57
57
|
lionagi/operations/brainstorm/brainstorm.py,sha256=OwByrh6E-rTU_u6fDNTwWOlkJ4ycYJB9ZF-x-HYOs8I,17222
|
58
58
|
lionagi/operations/brainstorm/prompt.py,sha256=f-Eh6pO606dT2TrX9BFv_einRDpYwFi6Gep9Strd1cM,610
|
@@ -63,9 +63,9 @@ lionagi/operations/communicate/communicate.py,sha256=1PzBpzgATcAdBog0EUxtj5_uJGi
|
|
63
63
|
lionagi/operations/instruct/__init__.py,sha256=v8vNyJVIVj8_Oz9RJdVe6ZKUQMYTgDh1VQpnr1KdLaw,112
|
64
64
|
lionagi/operations/instruct/instruct.py,sha256=CYICzWvmgALtSPIetfF3csx6WDsCuiu4NRRPL56UA2g,795
|
65
65
|
lionagi/operations/interpret/__init__.py,sha256=v8vNyJVIVj8_Oz9RJdVe6ZKUQMYTgDh1VQpnr1KdLaw,112
|
66
|
-
lionagi/operations/interpret/interpret.py,sha256=
|
66
|
+
lionagi/operations/interpret/interpret.py,sha256=Mtg65jletgMZAZ08kNLgjbC_y9C3l2xw67fHpHmBesg,2905
|
67
67
|
lionagi/operations/operate/__init__.py,sha256=v8vNyJVIVj8_Oz9RJdVe6ZKUQMYTgDh1VQpnr1KdLaw,112
|
68
|
-
lionagi/operations/operate/operate.py,sha256=
|
68
|
+
lionagi/operations/operate/operate.py,sha256=eHf1wopNm04hOAg1NMcrn6nz4acofUENi0R4r8FQMMs,7218
|
69
69
|
lionagi/operations/parse/__init__.py,sha256=v8vNyJVIVj8_Oz9RJdVe6ZKUQMYTgDh1VQpnr1KdLaw,112
|
70
70
|
lionagi/operations/parse/parse.py,sha256=LpF6LVAvCVoE8n63BkhSxXSHYgSx7CNkN7yXUwaNpQo,3003
|
71
71
|
lionagi/operations/plan/__init__.py,sha256=AFkAmOJBTqPlYuqFRRn7rCvIw3CGh9XXH_22cNWbfig,156
|
@@ -188,9 +188,13 @@ lionagi/service/providers/perplexity_/__init__.py,sha256=v8vNyJVIVj8_Oz9RJdVe6ZK
|
|
188
188
|
lionagi/service/providers/perplexity_/chat_completions.py,sha256=jhE-KHWRX6yYEeKWLMCKLgK3bQzieSv2viqQWDP8q0Q,1197
|
189
189
|
lionagi/service/providers/perplexity_/models.py,sha256=gXH4XGkhZ4aFxvMSDTlHq9Rz1mhu3aTENXAtE-BIr6U,4866
|
190
190
|
lionagi/session/__init__.py,sha256=v8vNyJVIVj8_Oz9RJdVe6ZKUQMYTgDh1VQpnr1KdLaw,112
|
191
|
-
lionagi/session/branch.py,sha256=
|
191
|
+
lionagi/session/branch.py,sha256=EH1JhOe1ZGlCVXpf0znz_xAA3GibNIGFATwLAyxiCK0,67835
|
192
192
|
lionagi/session/session.py,sha256=po6C7PnM0iu_ISHUo4PBzzQ61HFOgcsAUfPoO--eLak,8987
|
193
|
-
lionagi
|
194
|
-
lionagi
|
195
|
-
lionagi
|
196
|
-
lionagi
|
193
|
+
lionagi/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
194
|
+
lionagi/tools/base.py,sha256=ffaIcLF_uwEphCkP_wsa3UfkqVenML3HpsnR5kRCTtA,236
|
195
|
+
lionagi/tools/reader.py,sha256=jYWgEfyQZ7TR5-cW7ceGhz6b47eWlSS0bAvbCnHh1-I,7504
|
196
|
+
lionagi/tools/types.py,sha256=_OWzoTHTcqNwPs3OGrPkpO9m_vHDCxVDL-FN-t6ZD60,58
|
197
|
+
lionagi-0.8.7.dist-info/METADATA,sha256=-mm0T59W8n5E3bk73nVaqEsxn6bYCrgEIYNZ389h-Tw,18008
|
198
|
+
lionagi-0.8.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
199
|
+
lionagi-0.8.7.dist-info/licenses/LICENSE,sha256=VXFWsdoN5AAknBCgFqQNgPWYx7OPp-PFEP961zGdOjc,11288
|
200
|
+
lionagi-0.8.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|