lionagi 0.6.1__py3-none-any.whl → 0.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lionagi/libs/token_transform/__init__.py +0 -0
- lionagi/libs/token_transform/llmlingua.py +1 -0
- lionagi/libs/token_transform/perplexity.py +439 -0
- lionagi/libs/token_transform/synthlang.py +409 -0
- lionagi/operations/ReAct/ReAct.py +126 -0
- lionagi/operations/ReAct/utils.py +28 -0
- lionagi/operations/__init__.py +1 -9
- lionagi/operations/_act/act.py +73 -0
- lionagi/operations/chat/__init__.py +3 -0
- lionagi/operations/chat/chat.py +173 -0
- lionagi/operations/communicate/__init__.py +0 -0
- lionagi/operations/communicate/communicate.py +108 -0
- lionagi/operations/instruct/__init__.py +3 -0
- lionagi/operations/instruct/instruct.py +29 -0
- lionagi/operations/interpret/__init__.py +3 -0
- lionagi/operations/interpret/interpret.py +39 -0
- lionagi/operations/operate/__init__.py +3 -0
- lionagi/operations/operate/operate.py +194 -0
- lionagi/operations/parse/__init__.py +3 -0
- lionagi/operations/parse/parse.py +89 -0
- lionagi/operations/plan/plan.py +3 -3
- lionagi/operations/select/__init__.py +0 -4
- lionagi/operations/select/select.py +11 -30
- lionagi/operations/select/utils.py +13 -2
- lionagi/operations/translate/__init__.py +0 -0
- lionagi/operations/translate/translate.py +47 -0
- lionagi/operations/types.py +16 -0
- lionagi/operatives/action/manager.py +115 -93
- lionagi/operatives/action/request_response_model.py +31 -0
- lionagi/operatives/action/tool.py +50 -20
- lionagi/operatives/strategies/__init__.py +3 -0
- lionagi/protocols/_concepts.py +1 -1
- lionagi/protocols/adapters/adapter.py +25 -0
- lionagi/protocols/adapters/json_adapter.py +107 -27
- lionagi/protocols/adapters/pandas_/csv_adapter.py +55 -11
- lionagi/protocols/adapters/pandas_/excel_adapter.py +52 -10
- lionagi/protocols/adapters/pandas_/pd_dataframe_adapter.py +54 -4
- lionagi/protocols/adapters/pandas_/pd_series_adapter.py +40 -0
- lionagi/protocols/generic/element.py +1 -1
- lionagi/protocols/generic/pile.py +5 -8
- lionagi/protocols/graph/edge.py +1 -1
- lionagi/protocols/graph/graph.py +16 -8
- lionagi/protocols/graph/node.py +1 -1
- lionagi/protocols/mail/exchange.py +126 -15
- lionagi/protocols/mail/mail.py +33 -0
- lionagi/protocols/mail/mailbox.py +62 -0
- lionagi/protocols/mail/manager.py +97 -41
- lionagi/protocols/mail/package.py +57 -3
- lionagi/protocols/messages/action_request.py +77 -26
- lionagi/protocols/messages/action_response.py +55 -26
- lionagi/protocols/messages/assistant_response.py +50 -15
- lionagi/protocols/messages/base.py +36 -0
- lionagi/protocols/messages/instruction.py +175 -145
- lionagi/protocols/messages/manager.py +152 -56
- lionagi/protocols/messages/message.py +61 -25
- lionagi/protocols/messages/system.py +54 -19
- lionagi/service/imodel.py +24 -0
- lionagi/session/branch.py +1116 -939
- lionagi/utils.py +1 -0
- lionagi/version.py +1 -1
- {lionagi-0.6.1.dist-info → lionagi-0.7.1.dist-info}/METADATA +1 -1
- {lionagi-0.6.1.dist-info → lionagi-0.7.1.dist-info}/RECORD +75 -56
- lionagi/libs/compress/models.py +0 -66
- lionagi/libs/compress/utils.py +0 -69
- lionagi/operations/select/prompt.py +0 -5
- /lionagi/{libs/compress → operations/ReAct}/__init__.py +0 -0
- /lionagi/operations/{strategies → _act}/__init__.py +0 -0
- /lionagi/{operations → operatives}/strategies/base.py +0 -0
- /lionagi/{operations → operatives}/strategies/concurrent.py +0 -0
- /lionagi/{operations → operatives}/strategies/concurrent_chunk.py +0 -0
- /lionagi/{operations → operatives}/strategies/concurrent_sequential_chunk.py +0 -0
- /lionagi/{operations → operatives}/strategies/params.py +0 -0
- /lionagi/{operations → operatives}/strategies/sequential.py +0 -0
- /lionagi/{operations → operatives}/strategies/sequential_chunk.py +0 -0
- /lionagi/{operations → operatives}/strategies/sequential_concurrent_chunk.py +0 -0
- /lionagi/{operations → operatives}/strategies/utils.py +0 -0
- {lionagi-0.6.1.dist-info → lionagi-0.7.1.dist-info}/WHEEL +0 -0
- {lionagi-0.6.1.dist-info → lionagi-0.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,173 @@
|
|
1
|
+
# Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
6
|
+
|
7
|
+
from pydantic import BaseModel
|
8
|
+
|
9
|
+
from lionagi.protocols.types import (
|
10
|
+
ActionResponse,
|
11
|
+
AssistantResponse,
|
12
|
+
Instruction,
|
13
|
+
Log,
|
14
|
+
RoledMessage,
|
15
|
+
)
|
16
|
+
from lionagi.service.imodel import iModel
|
17
|
+
from lionagi.utils import copy
|
18
|
+
|
19
|
+
if TYPE_CHECKING:
|
20
|
+
from lionagi.session.branch import Branch
|
21
|
+
|
22
|
+
|
23
|
+
async def chat(
|
24
|
+
branch: "Branch",
|
25
|
+
instruction=None,
|
26
|
+
guidance=None,
|
27
|
+
context=None,
|
28
|
+
sender=None,
|
29
|
+
recipient=None,
|
30
|
+
request_fields=None,
|
31
|
+
response_format: type[BaseModel] = None,
|
32
|
+
progression=None,
|
33
|
+
imodel: iModel = None,
|
34
|
+
tool_schemas=None,
|
35
|
+
images: list = None,
|
36
|
+
image_detail: Literal["low", "high", "auto"] = None,
|
37
|
+
plain_content: str = None,
|
38
|
+
return_ins_res_message: bool = False,
|
39
|
+
**kwargs,
|
40
|
+
) -> tuple[Instruction, AssistantResponse]:
|
41
|
+
ins: Instruction = branch.msgs.create_instruction(
|
42
|
+
instruction=instruction,
|
43
|
+
guidance=guidance,
|
44
|
+
context=context,
|
45
|
+
sender=sender or branch.user or "user",
|
46
|
+
recipient=recipient or branch.id,
|
47
|
+
response_format=response_format,
|
48
|
+
request_fields=request_fields,
|
49
|
+
images=images,
|
50
|
+
image_detail=image_detail,
|
51
|
+
tool_schemas=tool_schemas,
|
52
|
+
plain_content=plain_content,
|
53
|
+
)
|
54
|
+
|
55
|
+
progression = progression or branch.msgs.progression
|
56
|
+
messages: list[RoledMessage] = [
|
57
|
+
branch.msgs.messages[i] for i in progression
|
58
|
+
]
|
59
|
+
|
60
|
+
use_ins = None
|
61
|
+
_to_use = []
|
62
|
+
_action_responses: set[ActionResponse] = set()
|
63
|
+
|
64
|
+
for i in messages:
|
65
|
+
if isinstance(i, ActionResponse):
|
66
|
+
_action_responses.add(i)
|
67
|
+
if isinstance(i, AssistantResponse):
|
68
|
+
j = AssistantResponse(
|
69
|
+
role=i.role,
|
70
|
+
content=copy(i.content),
|
71
|
+
sender=i.sender,
|
72
|
+
recipient=i.recipient,
|
73
|
+
template=i.template,
|
74
|
+
)
|
75
|
+
_to_use.append(j)
|
76
|
+
if isinstance(i, Instruction):
|
77
|
+
j = Instruction(
|
78
|
+
role=i.role,
|
79
|
+
content=copy(i.content),
|
80
|
+
sender=i.sender,
|
81
|
+
recipient=i.recipient,
|
82
|
+
template=i.template,
|
83
|
+
)
|
84
|
+
j.tool_schemas = None
|
85
|
+
j.respond_schema_info = None
|
86
|
+
j.request_response_format = None
|
87
|
+
|
88
|
+
if _action_responses:
|
89
|
+
d_ = [k.content for k in _action_responses]
|
90
|
+
for z in d_:
|
91
|
+
if z not in j.context:
|
92
|
+
j.context.append(z)
|
93
|
+
|
94
|
+
_to_use.append(j)
|
95
|
+
_action_responses = set()
|
96
|
+
else:
|
97
|
+
_to_use.append(j)
|
98
|
+
|
99
|
+
messages = _to_use
|
100
|
+
if _action_responses:
|
101
|
+
j = ins.model_copy()
|
102
|
+
d_ = [k.content for k in _action_responses]
|
103
|
+
for z in d_:
|
104
|
+
if z not in j.context:
|
105
|
+
j.context.append(z)
|
106
|
+
use_ins = j
|
107
|
+
|
108
|
+
if messages and len(messages) > 1:
|
109
|
+
_msgs = [messages[0]]
|
110
|
+
|
111
|
+
for i in messages[1:]:
|
112
|
+
if isinstance(i, AssistantResponse):
|
113
|
+
if isinstance(_msgs[-1], AssistantResponse):
|
114
|
+
_msgs[-1].response = (
|
115
|
+
f"{_msgs[-1].response}\n\n{i.response}"
|
116
|
+
)
|
117
|
+
else:
|
118
|
+
_msgs.append(i)
|
119
|
+
else:
|
120
|
+
if isinstance(_msgs[-1], AssistantResponse):
|
121
|
+
_msgs.append(i)
|
122
|
+
messages = _msgs
|
123
|
+
|
124
|
+
imodel = imodel or branch.chat_model
|
125
|
+
if branch.msgs.system and imodel.sequential_exchange:
|
126
|
+
messages = [msg for msg in messages if msg.role != "system"]
|
127
|
+
first_instruction = None
|
128
|
+
|
129
|
+
if len(messages) == 0:
|
130
|
+
first_instruction = ins.model_copy()
|
131
|
+
first_instruction.guidance = branch.msgs.system.rendered + (
|
132
|
+
first_instruction.guidance or ""
|
133
|
+
)
|
134
|
+
messages.append(first_instruction)
|
135
|
+
elif len(messages) >= 1:
|
136
|
+
first_instruction = messages[0]
|
137
|
+
if not isinstance(first_instruction, Instruction):
|
138
|
+
raise ValueError(
|
139
|
+
"First message in progression must be an Instruction or System"
|
140
|
+
)
|
141
|
+
first_instruction = first_instruction.model_copy()
|
142
|
+
first_instruction.guidance = branch.msgs.system.rendered + (
|
143
|
+
first_instruction.guidance or ""
|
144
|
+
)
|
145
|
+
messages[0] = first_instruction
|
146
|
+
messages.append(use_ins or ins)
|
147
|
+
|
148
|
+
else:
|
149
|
+
messages.append(use_ins or ins)
|
150
|
+
|
151
|
+
kwargs["messages"] = [i.chat_msg for i in messages]
|
152
|
+
imodel = imodel or branch.chat_model
|
153
|
+
|
154
|
+
api_call = None
|
155
|
+
if kwargs.get("stream", None) is True:
|
156
|
+
api_call = await imodel.stream(**kwargs)
|
157
|
+
else:
|
158
|
+
api_call = await imodel.invoke(**kwargs)
|
159
|
+
|
160
|
+
branch._log_manager.log(Log.create(api_call))
|
161
|
+
|
162
|
+
if return_ins_res_message:
|
163
|
+
# Wrap result in `AssistantResponse` and return
|
164
|
+
return ins, AssistantResponse.create(
|
165
|
+
assistant_response=api_call.response,
|
166
|
+
sender=branch.id,
|
167
|
+
recipient=branch.user,
|
168
|
+
)
|
169
|
+
return AssistantResponse.create(
|
170
|
+
assistant_response=api_call.response,
|
171
|
+
sender=branch.id,
|
172
|
+
recipient=branch.user,
|
173
|
+
).response
|
File without changes
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import TYPE_CHECKING
|
7
|
+
|
8
|
+
from lionagi.libs.validate.fuzzy_validate_mapping import fuzzy_validate_mapping
|
9
|
+
from lionagi.utils import UNDEFINED
|
10
|
+
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from lionagi.session.branch import Branch
|
13
|
+
|
14
|
+
|
15
|
+
async def communicate(
|
16
|
+
branch: "Branch",
|
17
|
+
instruction=None,
|
18
|
+
*,
|
19
|
+
guidance=None,
|
20
|
+
context=None,
|
21
|
+
plain_content=None,
|
22
|
+
sender=None,
|
23
|
+
recipient=None,
|
24
|
+
progression=None,
|
25
|
+
request_model=None,
|
26
|
+
response_format=None,
|
27
|
+
request_fields=None,
|
28
|
+
imodel=None,
|
29
|
+
chat_model=None,
|
30
|
+
parse_model=None,
|
31
|
+
skip_validation=False,
|
32
|
+
images=None,
|
33
|
+
image_detail="auto",
|
34
|
+
num_parse_retries=3,
|
35
|
+
fuzzy_match_kwargs=None,
|
36
|
+
clear_messages=False,
|
37
|
+
operative_model=None,
|
38
|
+
**kwargs,
|
39
|
+
):
|
40
|
+
if operative_model:
|
41
|
+
logging.warning(
|
42
|
+
"operative_model is deprecated. Use response_format instead."
|
43
|
+
)
|
44
|
+
if (
|
45
|
+
(operative_model and response_format)
|
46
|
+
or (operative_model and request_model)
|
47
|
+
or (response_format and request_model)
|
48
|
+
):
|
49
|
+
raise ValueError(
|
50
|
+
"Cannot specify both operative_model and response_format"
|
51
|
+
"or operative_model and request_model as they are aliases"
|
52
|
+
"for the same parameter."
|
53
|
+
)
|
54
|
+
|
55
|
+
response_format = response_format or operative_model or request_model
|
56
|
+
|
57
|
+
imodel = imodel or chat_model or branch.chat_model
|
58
|
+
parse_model = parse_model or branch.parse_model
|
59
|
+
|
60
|
+
if clear_messages:
|
61
|
+
branch.msgs.clear_messages()
|
62
|
+
|
63
|
+
if num_parse_retries > 5:
|
64
|
+
logging.warning(
|
65
|
+
f"Are you sure you want to retry {num_parse_retries} "
|
66
|
+
"times? lowering retry attempts to 5. Suggestion is under 3"
|
67
|
+
)
|
68
|
+
num_parse_retries = 5
|
69
|
+
|
70
|
+
ins, res = await branch.chat(
|
71
|
+
instruction=instruction,
|
72
|
+
guidance=guidance,
|
73
|
+
context=context,
|
74
|
+
sender=sender,
|
75
|
+
recipient=recipient,
|
76
|
+
response_format=response_format,
|
77
|
+
progression=progression,
|
78
|
+
imodel=imodel,
|
79
|
+
images=images,
|
80
|
+
image_detail=image_detail,
|
81
|
+
plain_content=plain_content,
|
82
|
+
return_ins_res_message=True,
|
83
|
+
**kwargs,
|
84
|
+
)
|
85
|
+
branch.msgs.add_message(instruction=ins)
|
86
|
+
branch.msgs.add_message(assistant_response=res)
|
87
|
+
|
88
|
+
if skip_validation:
|
89
|
+
return res.response
|
90
|
+
|
91
|
+
if response_format is not None:
|
92
|
+
return await branch.parse(
|
93
|
+
text=res.response,
|
94
|
+
request_type=response_format,
|
95
|
+
max_retries=num_parse_retries,
|
96
|
+
**(fuzzy_match_kwargs or {}),
|
97
|
+
)
|
98
|
+
|
99
|
+
if request_fields is not None:
|
100
|
+
_d = fuzzy_validate_mapping(
|
101
|
+
res.response,
|
102
|
+
request_fields,
|
103
|
+
handle_unmatched="force",
|
104
|
+
fill_value=UNDEFINED,
|
105
|
+
)
|
106
|
+
return {k: v for k, v in _d.items() if v != UNDEFINED}
|
107
|
+
|
108
|
+
return res.response
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING, Any
|
6
|
+
|
7
|
+
from lionagi.operatives.types import Instruct
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from lionagi.session.branch import Branch
|
11
|
+
|
12
|
+
|
13
|
+
async def instruct(
|
14
|
+
branch: "Branch",
|
15
|
+
instruct: Instruct,
|
16
|
+
/,
|
17
|
+
**kwargs,
|
18
|
+
) -> Any:
|
19
|
+
config = {
|
20
|
+
**(instruct.to_dict() if isinstance(instruct, Instruct) else instruct),
|
21
|
+
**kwargs,
|
22
|
+
}
|
23
|
+
if any(i in config and config[i] for i in Instruct.reserved_kwargs):
|
24
|
+
if "response_format" in config or "request_model" in config:
|
25
|
+
return await branch.operate(**config)
|
26
|
+
for i in Instruct.reserved_kwargs:
|
27
|
+
config.pop(i, None)
|
28
|
+
|
29
|
+
return await branch.communicate(**config)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from lionagi.session.branch import Branch
|
9
|
+
|
10
|
+
|
11
|
+
async def interpret(
|
12
|
+
branch: "Branch",
|
13
|
+
text: str,
|
14
|
+
domain: str | None = None,
|
15
|
+
style: str | None = None,
|
16
|
+
**kwargs,
|
17
|
+
) -> str:
|
18
|
+
instruction = (
|
19
|
+
"Rewrite the following user input into a clear, structured prompt or "
|
20
|
+
"query for an LLM, ensuring any implicit details are made explicit. "
|
21
|
+
"Return only the improved user prompt."
|
22
|
+
)
|
23
|
+
guidance = (
|
24
|
+
f"Domain hint: {domain or 'general'}. "
|
25
|
+
f"Desired style: {style or 'concise'}. "
|
26
|
+
"You can add or clarify context if needed."
|
27
|
+
)
|
28
|
+
context = [f"User input: {text}"]
|
29
|
+
|
30
|
+
# Default temperature if none provided
|
31
|
+
kwargs["temperature"] = kwargs.get("temperature", 0.1)
|
32
|
+
|
33
|
+
refined_prompt = await branch.chat(
|
34
|
+
instruction=instruction,
|
35
|
+
guidance=guidance,
|
36
|
+
context=context,
|
37
|
+
**kwargs,
|
38
|
+
)
|
39
|
+
return str(refined_prompt)
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import TYPE_CHECKING, Literal
|
7
|
+
|
8
|
+
from pydantic import BaseModel, JsonValue
|
9
|
+
|
10
|
+
from lionagi.operatives.types import (
|
11
|
+
FieldModel,
|
12
|
+
Instruct,
|
13
|
+
ModelParams,
|
14
|
+
Operative,
|
15
|
+
Step,
|
16
|
+
ToolRef,
|
17
|
+
)
|
18
|
+
from lionagi.protocols.types import Instruction, Progression, SenderRecipient
|
19
|
+
from lionagi.service.imodel import iModel
|
20
|
+
|
21
|
+
if TYPE_CHECKING:
|
22
|
+
from lionagi.session.branch import Branch
|
23
|
+
|
24
|
+
|
25
|
+
async def operate(
|
26
|
+
branch: "Branch",
|
27
|
+
*,
|
28
|
+
instruct: Instruct = None,
|
29
|
+
instruction: Instruction | JsonValue = None,
|
30
|
+
guidance: JsonValue = None,
|
31
|
+
context: JsonValue = None,
|
32
|
+
sender: SenderRecipient = None,
|
33
|
+
recipient: SenderRecipient = None,
|
34
|
+
progression: Progression = None,
|
35
|
+
imodel: iModel = None, # deprecated, alias of chat_model
|
36
|
+
chat_model: iModel = None,
|
37
|
+
invoke_actions: bool = True,
|
38
|
+
tool_schemas: list[dict] = None,
|
39
|
+
images: list = None,
|
40
|
+
image_detail: Literal["low", "high", "auto"] = None,
|
41
|
+
parse_model: iModel = None,
|
42
|
+
skip_validation: bool = False,
|
43
|
+
tools: ToolRef = None,
|
44
|
+
operative: Operative = None,
|
45
|
+
response_format: type[BaseModel] = None, # alias of operative.request_type
|
46
|
+
return_operative: bool = False,
|
47
|
+
actions: bool = False,
|
48
|
+
reason: bool = False,
|
49
|
+
action_kwargs: dict = None,
|
50
|
+
field_models: list[FieldModel] = None,
|
51
|
+
exclude_fields: list | dict | None = None,
|
52
|
+
request_params: ModelParams = None,
|
53
|
+
request_param_kwargs: dict = None,
|
54
|
+
response_params: ModelParams = None,
|
55
|
+
response_param_kwargs: dict = None,
|
56
|
+
handle_validation: Literal[
|
57
|
+
"raise", "return_value", "return_none"
|
58
|
+
] = "return_value",
|
59
|
+
operative_model: type[BaseModel] = None,
|
60
|
+
request_model: type[BaseModel] = None,
|
61
|
+
**kwargs,
|
62
|
+
) -> list | BaseModel | None | dict | str:
|
63
|
+
if operative_model:
|
64
|
+
logging.warning(
|
65
|
+
"`operative_model` is deprecated. Use `response_format` instead."
|
66
|
+
)
|
67
|
+
if (
|
68
|
+
(operative_model and response_format)
|
69
|
+
or (operative_model and request_model)
|
70
|
+
or (response_format and request_model)
|
71
|
+
):
|
72
|
+
raise ValueError(
|
73
|
+
"Cannot specify both `operative_model` and `response_format` (or `request_model`) "
|
74
|
+
"as they are aliases of each other."
|
75
|
+
)
|
76
|
+
|
77
|
+
# Use the final chosen format
|
78
|
+
response_format = response_format or operative_model or request_model
|
79
|
+
|
80
|
+
# Decide which chat model to use
|
81
|
+
chat_model = chat_model or imodel or branch.chat_model
|
82
|
+
parse_model = parse_model or chat_model
|
83
|
+
|
84
|
+
# Convert dict-based instructions to Instruct if needed
|
85
|
+
if isinstance(instruct, dict):
|
86
|
+
instruct = Instruct(**instruct)
|
87
|
+
|
88
|
+
# Or create a new Instruct if not provided
|
89
|
+
instruct = instruct or Instruct(
|
90
|
+
instruction=instruction,
|
91
|
+
guidance=guidance,
|
92
|
+
context=context,
|
93
|
+
)
|
94
|
+
|
95
|
+
# If reason or actions are requested, apply them to instruct
|
96
|
+
if reason:
|
97
|
+
instruct.reason = True
|
98
|
+
if actions:
|
99
|
+
instruct.actions = True
|
100
|
+
|
101
|
+
# 1) Create or update the Operative
|
102
|
+
operative = Step.request_operative(
|
103
|
+
request_params=request_params,
|
104
|
+
reason=instruct.reason,
|
105
|
+
actions=instruct.actions,
|
106
|
+
exclude_fields=exclude_fields,
|
107
|
+
base_type=response_format,
|
108
|
+
field_models=field_models,
|
109
|
+
**(request_param_kwargs or {}),
|
110
|
+
)
|
111
|
+
|
112
|
+
# If the instruction signals actions, ensure tools are provided
|
113
|
+
if instruct.actions:
|
114
|
+
tools = tools or True
|
115
|
+
|
116
|
+
# If we want to auto-invoke tools, fetch or generate the schemas
|
117
|
+
if invoke_actions and tools:
|
118
|
+
tool_schemas = tool_schemas or branch.acts.get_tool_schema(tools=tools)
|
119
|
+
|
120
|
+
# 2) Send the instruction to the chat model
|
121
|
+
ins, res = await branch.chat(
|
122
|
+
instruction=instruct.instruction,
|
123
|
+
guidance=instruct.guidance,
|
124
|
+
context=instruct.context,
|
125
|
+
sender=sender,
|
126
|
+
recipient=recipient,
|
127
|
+
response_format=operative.request_type,
|
128
|
+
progression=progression,
|
129
|
+
imodel=chat_model, # or the override
|
130
|
+
images=images,
|
131
|
+
image_detail=image_detail,
|
132
|
+
tool_schemas=tool_schemas,
|
133
|
+
return_ins_res_message=True,
|
134
|
+
**kwargs,
|
135
|
+
)
|
136
|
+
branch.msgs.add_message(instruction=ins)
|
137
|
+
branch.msgs.add_message(assistant_response=res)
|
138
|
+
|
139
|
+
# 3) Populate the operative with the raw response
|
140
|
+
operative.response_str_dict = res.response
|
141
|
+
|
142
|
+
# 4) Possibly skip validation
|
143
|
+
if skip_validation:
|
144
|
+
return operative if return_operative else operative.response_str_dict
|
145
|
+
|
146
|
+
# 5) Parse or validate the response into the operative's model
|
147
|
+
response_model = operative.update_response_model(res.response)
|
148
|
+
if not isinstance(response_model, BaseModel):
|
149
|
+
# If the response isn't directly a model, attempt a parse
|
150
|
+
response_model = await branch.parse(
|
151
|
+
text=res.response,
|
152
|
+
request_type=operative.request_type,
|
153
|
+
max_retries=operative.max_retries,
|
154
|
+
handle_validation="return_value",
|
155
|
+
)
|
156
|
+
operative.response_model = operative.update_response_model(
|
157
|
+
text=response_model
|
158
|
+
)
|
159
|
+
|
160
|
+
# If we still fail to parse, handle according to user preference
|
161
|
+
if not isinstance(response_model, BaseModel):
|
162
|
+
match handle_validation:
|
163
|
+
case "return_value":
|
164
|
+
return response_model
|
165
|
+
case "return_none":
|
166
|
+
return None
|
167
|
+
case "raise":
|
168
|
+
raise ValueError(
|
169
|
+
"Failed to parse the LLM response into the requested format."
|
170
|
+
)
|
171
|
+
|
172
|
+
# 6) If no tool invocation is needed, return result or operative
|
173
|
+
if not invoke_actions:
|
174
|
+
return operative if return_operative else operative.response_model
|
175
|
+
|
176
|
+
# 7) If the model indicates an action is required, call the tools
|
177
|
+
if (
|
178
|
+
getattr(response_model, "action_required", None) is True
|
179
|
+
and getattr(response_model, "action_requests", None) is not None
|
180
|
+
):
|
181
|
+
action_response_models = await branch.act(
|
182
|
+
response_model.action_requests,
|
183
|
+
**(action_kwargs or {}),
|
184
|
+
)
|
185
|
+
# Possibly refine the operative with the tool outputs
|
186
|
+
operative = Step.respond_operative(
|
187
|
+
response_params=response_params,
|
188
|
+
operative=operative,
|
189
|
+
additional_data={"action_responses": action_response_models},
|
190
|
+
**(response_param_kwargs or {}),
|
191
|
+
)
|
192
|
+
|
193
|
+
# Return final result or the full operative
|
194
|
+
return operative if return_operative else operative.response_model
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING, Any, Literal
|
6
|
+
|
7
|
+
from pydantic import BaseModel
|
8
|
+
|
9
|
+
from lionagi.libs.validate.fuzzy_validate_mapping import fuzzy_validate_mapping
|
10
|
+
from lionagi.operatives.types import Operative
|
11
|
+
from lionagi.utils import breakdown_pydantic_annotation
|
12
|
+
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from lionagi.session.branch import Branch
|
15
|
+
|
16
|
+
|
17
|
+
async def parse(
|
18
|
+
branch: "Branch",
|
19
|
+
text: str,
|
20
|
+
handle_validation: Literal[
|
21
|
+
"raise", "return_value", "return_none"
|
22
|
+
] = "return_value",
|
23
|
+
max_retries: int = 3,
|
24
|
+
request_type: type[BaseModel] = None,
|
25
|
+
operative: Operative = None,
|
26
|
+
similarity_algo="jaro_winkler",
|
27
|
+
similarity_threshold: float = 0.85,
|
28
|
+
fuzzy_match: bool = True,
|
29
|
+
handle_unmatched: Literal[
|
30
|
+
"ignore", "raise", "remove", "fill", "force"
|
31
|
+
] = "force",
|
32
|
+
fill_value: Any = None,
|
33
|
+
fill_mapping: dict[str, Any] | None = None,
|
34
|
+
strict: bool = False,
|
35
|
+
suppress_conversion_errors: bool = False,
|
36
|
+
response_format=None,
|
37
|
+
):
|
38
|
+
_should_try = True
|
39
|
+
num_try = 0
|
40
|
+
response_model = text
|
41
|
+
if operative is not None:
|
42
|
+
max_retries = operative.max_retries
|
43
|
+
response_format = operative.request_type
|
44
|
+
|
45
|
+
while (
|
46
|
+
_should_try
|
47
|
+
and num_try < max_retries
|
48
|
+
and not isinstance(response_model, BaseModel)
|
49
|
+
):
|
50
|
+
num_try += 1
|
51
|
+
_, res = await branch.chat(
|
52
|
+
instruction="reformat text into specified model",
|
53
|
+
guidane="follow the required response format, using the model schema as a guide",
|
54
|
+
context=[{"text_to_format": text}],
|
55
|
+
response_format=response_format or request_type,
|
56
|
+
sender=branch.user,
|
57
|
+
recipient=branch.id,
|
58
|
+
imodel=branch.parse_model,
|
59
|
+
return_ins_res_message=True,
|
60
|
+
)
|
61
|
+
if operative is not None:
|
62
|
+
response_model = operative.update_response_model(res.response)
|
63
|
+
else:
|
64
|
+
response_model = fuzzy_validate_mapping(
|
65
|
+
res.response,
|
66
|
+
breakdown_pydantic_annotation(request_type),
|
67
|
+
similarity_algo=similarity_algo,
|
68
|
+
similarity_threshold=similarity_threshold,
|
69
|
+
fuzzy_match=fuzzy_match,
|
70
|
+
handle_unmatched=handle_unmatched,
|
71
|
+
fill_value=fill_value,
|
72
|
+
fill_mapping=fill_mapping,
|
73
|
+
strict=strict,
|
74
|
+
suppress_conversion_errors=suppress_conversion_errors,
|
75
|
+
)
|
76
|
+
response_model = request_type.model_validate(response_model)
|
77
|
+
|
78
|
+
if not isinstance(response_model, BaseModel):
|
79
|
+
match handle_validation:
|
80
|
+
case "return_value":
|
81
|
+
return response_model
|
82
|
+
case "return_none":
|
83
|
+
return None
|
84
|
+
case "raise":
|
85
|
+
raise ValueError(
|
86
|
+
"Failed to parse response into request format"
|
87
|
+
)
|
88
|
+
|
89
|
+
return response_model
|
lionagi/operations/plan/plan.py
CHANGED
@@ -6,7 +6,7 @@ from typing import Any, Literal
|
|
6
6
|
|
7
7
|
from pydantic import BaseModel
|
8
8
|
|
9
|
-
from lionagi.operatives.
|
9
|
+
from lionagi.operatives.types import (
|
10
10
|
LIST_INSTRUCT_FIELD_MODEL,
|
11
11
|
Instruct,
|
12
12
|
InstructResponse,
|
@@ -293,7 +293,7 @@ async def plan(
|
|
293
293
|
# ---------------------------------------------------------
|
294
294
|
# Strategy C: SEQUENTIAL_CONCURRENT_CHUNK
|
295
295
|
# - process plan steps in chunks (one chunk after another),
|
296
|
-
# - each chunk
|
296
|
+
# - each chunk's steps run in parallel.
|
297
297
|
# ---------------------------------------------------------
|
298
298
|
case "sequential_concurrent_chunk":
|
299
299
|
chunk_size = (execution_kwargs or {}).get("chunk_size", 5)
|
@@ -334,7 +334,7 @@ async def plan(
|
|
334
334
|
# Strategy D: CONCURRENT_SEQUENTIAL_CHUNK
|
335
335
|
# - split plan steps into chunks,
|
336
336
|
# - run all chunks in parallel,
|
337
|
-
# - but each chunk
|
337
|
+
# - but each chunk's steps run sequentially.
|
338
338
|
# ---------------------------------------------------------
|
339
339
|
case "concurrent_sequential_chunk":
|
340
340
|
chunk_size = (execution_kwargs or {}).get("chunk_size", 5)
|