lionagi 0.17.10__py3-none-any.whl → 0.18.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lionagi/__init__.py +1 -2
- lionagi/_class_registry.py +1 -2
- lionagi/_errors.py +1 -2
- lionagi/adapters/async_postgres_adapter.py +2 -10
- lionagi/config.py +1 -2
- lionagi/fields/action.py +1 -2
- lionagi/fields/base.py +3 -0
- lionagi/fields/code.py +3 -0
- lionagi/fields/file.py +3 -0
- lionagi/fields/instruct.py +1 -2
- lionagi/fields/reason.py +1 -2
- lionagi/fields/research.py +3 -0
- lionagi/libs/__init__.py +1 -2
- lionagi/libs/file/__init__.py +1 -2
- lionagi/libs/file/chunk.py +1 -2
- lionagi/libs/file/process.py +1 -2
- lionagi/libs/schema/__init__.py +1 -2
- lionagi/libs/schema/as_readable.py +1 -2
- lionagi/libs/schema/extract_code_block.py +1 -2
- lionagi/libs/schema/extract_docstring.py +1 -2
- lionagi/libs/schema/function_to_schema.py +1 -2
- lionagi/libs/schema/load_pydantic_model_from_schema.py +1 -2
- lionagi/libs/schema/minimal_yaml.py +98 -0
- lionagi/libs/validate/__init__.py +1 -2
- lionagi/libs/validate/common_field_validators.py +1 -2
- lionagi/libs/validate/validate_boolean.py +1 -2
- lionagi/ln/fuzzy/_string_similarity.py +1 -2
- lionagi/ln/types.py +32 -5
- lionagi/models/__init__.py +1 -2
- lionagi/models/field_model.py +9 -1
- lionagi/models/hashable_model.py +4 -2
- lionagi/models/model_params.py +1 -2
- lionagi/models/operable_model.py +1 -2
- lionagi/models/schema_model.py +1 -2
- lionagi/operations/ReAct/ReAct.py +475 -239
- lionagi/operations/ReAct/__init__.py +1 -2
- lionagi/operations/ReAct/utils.py +4 -2
- lionagi/operations/__init__.py +1 -2
- lionagi/operations/act/__init__.py +2 -0
- lionagi/operations/act/act.py +206 -0
- lionagi/operations/brainstorm/__init__.py +1 -2
- lionagi/operations/brainstorm/brainstorm.py +1 -2
- lionagi/operations/brainstorm/prompt.py +1 -2
- lionagi/operations/builder.py +1 -2
- lionagi/operations/chat/__init__.py +1 -2
- lionagi/operations/chat/chat.py +131 -116
- lionagi/operations/communicate/communicate.py +102 -44
- lionagi/operations/flow.py +5 -6
- lionagi/operations/instruct/__init__.py +1 -2
- lionagi/operations/instruct/instruct.py +1 -2
- lionagi/operations/interpret/__init__.py +1 -2
- lionagi/operations/interpret/interpret.py +66 -22
- lionagi/operations/operate/__init__.py +1 -2
- lionagi/operations/operate/operate.py +213 -108
- lionagi/operations/parse/__init__.py +1 -2
- lionagi/operations/parse/parse.py +171 -144
- lionagi/operations/plan/__init__.py +1 -2
- lionagi/operations/plan/plan.py +1 -2
- lionagi/operations/plan/prompt.py +1 -2
- lionagi/operations/select/__init__.py +1 -2
- lionagi/operations/select/select.py +79 -19
- lionagi/operations/select/utils.py +2 -3
- lionagi/operations/types.py +120 -25
- lionagi/operations/utils.py +1 -2
- lionagi/protocols/__init__.py +1 -2
- lionagi/protocols/_concepts.py +1 -2
- lionagi/protocols/action/__init__.py +1 -2
- lionagi/protocols/action/function_calling.py +3 -20
- lionagi/protocols/action/manager.py +34 -4
- lionagi/protocols/action/tool.py +1 -2
- lionagi/protocols/contracts.py +1 -2
- lionagi/protocols/forms/__init__.py +1 -2
- lionagi/protocols/forms/base.py +1 -2
- lionagi/protocols/forms/flow.py +1 -2
- lionagi/protocols/forms/form.py +1 -2
- lionagi/protocols/forms/report.py +1 -2
- lionagi/protocols/generic/__init__.py +1 -2
- lionagi/protocols/generic/element.py +17 -65
- lionagi/protocols/generic/event.py +1 -2
- lionagi/protocols/generic/log.py +17 -14
- lionagi/protocols/generic/pile.py +3 -4
- lionagi/protocols/generic/processor.py +1 -2
- lionagi/protocols/generic/progression.py +1 -2
- lionagi/protocols/graph/__init__.py +1 -2
- lionagi/protocols/graph/edge.py +1 -2
- lionagi/protocols/graph/graph.py +1 -2
- lionagi/protocols/graph/node.py +1 -2
- lionagi/protocols/ids.py +1 -2
- lionagi/protocols/mail/__init__.py +1 -2
- lionagi/protocols/mail/exchange.py +1 -2
- lionagi/protocols/mail/mail.py +1 -2
- lionagi/protocols/mail/mailbox.py +1 -2
- lionagi/protocols/mail/manager.py +1 -2
- lionagi/protocols/mail/package.py +1 -2
- lionagi/protocols/messages/__init__.py +28 -2
- lionagi/protocols/messages/action_request.py +87 -186
- lionagi/protocols/messages/action_response.py +74 -133
- lionagi/protocols/messages/assistant_response.py +131 -161
- lionagi/protocols/messages/base.py +27 -20
- lionagi/protocols/messages/instruction.py +281 -626
- lionagi/protocols/messages/manager.py +113 -64
- lionagi/protocols/messages/message.py +88 -199
- lionagi/protocols/messages/system.py +53 -125
- lionagi/protocols/operatives/__init__.py +1 -2
- lionagi/protocols/operatives/operative.py +1 -2
- lionagi/protocols/operatives/step.py +1 -2
- lionagi/protocols/types.py +1 -4
- lionagi/service/connections/__init__.py +1 -2
- lionagi/service/connections/api_calling.py +1 -2
- lionagi/service/connections/endpoint.py +1 -10
- lionagi/service/connections/endpoint_config.py +1 -2
- lionagi/service/connections/header_factory.py +1 -2
- lionagi/service/connections/match_endpoint.py +1 -2
- lionagi/service/connections/mcp/__init__.py +1 -2
- lionagi/service/connections/mcp/wrapper.py +1 -2
- lionagi/service/connections/providers/__init__.py +1 -2
- lionagi/service/connections/providers/anthropic_.py +1 -2
- lionagi/service/connections/providers/claude_code_cli.py +1 -2
- lionagi/service/connections/providers/exa_.py +1 -2
- lionagi/service/connections/providers/nvidia_nim_.py +2 -27
- lionagi/service/connections/providers/oai_.py +30 -96
- lionagi/service/connections/providers/ollama_.py +4 -4
- lionagi/service/connections/providers/perplexity_.py +1 -2
- lionagi/service/hooks/__init__.py +1 -1
- lionagi/service/hooks/_types.py +1 -1
- lionagi/service/hooks/_utils.py +1 -1
- lionagi/service/hooks/hook_event.py +1 -1
- lionagi/service/hooks/hook_registry.py +1 -1
- lionagi/service/hooks/hooked_event.py +3 -4
- lionagi/service/imodel.py +1 -2
- lionagi/service/manager.py +1 -2
- lionagi/service/rate_limited_processor.py +1 -2
- lionagi/service/resilience.py +1 -2
- lionagi/service/third_party/anthropic_models.py +1 -2
- lionagi/service/third_party/claude_code.py +4 -4
- lionagi/service/third_party/openai_models.py +433 -0
- lionagi/service/token_calculator.py +1 -2
- lionagi/session/__init__.py +1 -2
- lionagi/session/branch.py +171 -180
- lionagi/session/session.py +4 -11
- lionagi/tools/__init__.py +1 -2
- lionagi/tools/base.py +1 -2
- lionagi/tools/file/__init__.py +1 -2
- lionagi/tools/file/reader.py +3 -4
- lionagi/tools/types.py +1 -2
- lionagi/utils.py +1 -2
- lionagi/version.py +1 -1
- {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/METADATA +1 -2
- lionagi-0.18.0.dist-info/RECORD +191 -0
- lionagi/operations/_act/__init__.py +0 -3
- lionagi/operations/_act/act.py +0 -87
- lionagi/protocols/messages/templates/README.md +0 -28
- lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
- lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
- lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
- lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
- lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
- lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
- lionagi/service/connections/providers/types.py +0 -28
- lionagi/service/third_party/openai_model_names.py +0 -198
- lionagi/service/types.py +0 -59
- lionagi-0.17.10.dist-info/RECORD +0 -199
- {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/WHEEL +0 -0
- {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,21 +1,35 @@
|
|
1
|
-
# Copyright (c) 2023
|
2
|
-
#
|
1
|
+
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
3
2
|
# SPDX-License-Identifier: Apache-2.0
|
4
3
|
|
4
|
+
import contextlib
|
5
|
+
import warnings
|
5
6
|
from typing import TYPE_CHECKING, Any, Literal
|
6
7
|
|
7
8
|
from pydantic import BaseModel
|
8
9
|
|
10
|
+
from lionagi.ln import (
|
11
|
+
extract_json,
|
12
|
+
fuzzy_validate_mapping,
|
13
|
+
get_cancelled_exc_class,
|
14
|
+
to_list,
|
15
|
+
)
|
16
|
+
from lionagi.ln.fuzzy import FuzzyMatchKeysParams
|
17
|
+
from lionagi.protocols.types import AssistantResponse
|
18
|
+
from lionagi.session.branch import AlcallParams
|
19
|
+
|
20
|
+
from ..types import HandleValidation, ParseParam
|
21
|
+
|
9
22
|
if TYPE_CHECKING:
|
10
23
|
from lionagi.session.branch import Branch
|
11
24
|
|
12
25
|
|
13
|
-
|
26
|
+
_CALL = None # type: ignore
|
27
|
+
|
28
|
+
|
29
|
+
def prepare_parse_kws(
|
14
30
|
branch: "Branch",
|
15
31
|
text: str,
|
16
|
-
handle_validation:
|
17
|
-
"raise", "return_value", "return_none"
|
18
|
-
] = "return_value",
|
32
|
+
handle_validation: HandleValidation = "return_value",
|
19
33
|
max_retries: int = 3,
|
20
34
|
request_type: type[BaseModel] = None,
|
21
35
|
operative=None,
|
@@ -30,159 +44,172 @@ async def parse(
|
|
30
44
|
strict: bool = False,
|
31
45
|
suppress_conversion_errors: bool = False,
|
32
46
|
response_format=None,
|
47
|
+
request_fields=None,
|
48
|
+
return_res_message: bool = False,
|
49
|
+
**kw,
|
33
50
|
):
|
34
|
-
from lionagi.libs.schema.breakdown_pydantic_annotation import (
|
35
|
-
breakdown_pydantic_annotation,
|
36
|
-
)
|
37
|
-
from lionagi.ln.fuzzy._fuzzy_validate import fuzzy_validate_mapping
|
38
|
-
|
39
|
-
if operative is not None:
|
40
|
-
max_retries = operative.max_retries
|
41
|
-
response_format = operative.request_type or response_format
|
42
|
-
request_type = request_type or operative.request_type
|
43
51
|
|
44
|
-
if
|
45
|
-
|
46
|
-
"
|
52
|
+
if suppress_conversion_errors:
|
53
|
+
warnings.warn(
|
54
|
+
"Parameter 'suppress_conversion_errors' is deprecated and no longer used. "
|
55
|
+
"It will be removed in a future version.",
|
56
|
+
DeprecationWarning,
|
57
|
+
stacklevel=2,
|
47
58
|
)
|
48
59
|
|
49
|
-
|
60
|
+
response_format = (
|
61
|
+
operative.request_type
|
62
|
+
if operative
|
63
|
+
else response_format or request_type
|
64
|
+
)
|
65
|
+
_alcall_params = get_default_call()
|
66
|
+
max_retries = operative.max_retries if operative else max_retries or 3
|
67
|
+
|
68
|
+
fuzzy_params = FuzzyMatchKeysParams(
|
69
|
+
similarity_algo=similarity_algo,
|
70
|
+
similarity_threshold=similarity_threshold,
|
71
|
+
handle_unmatched=handle_unmatched,
|
72
|
+
fill_value=fill_value,
|
73
|
+
fill_mapping=fill_mapping,
|
74
|
+
strict=strict,
|
75
|
+
fuzzy_match=fuzzy_match,
|
76
|
+
)
|
50
77
|
|
51
|
-
|
52
|
-
|
78
|
+
return {
|
79
|
+
"text": text,
|
80
|
+
"parse_param": ParseParam(
|
81
|
+
response_format=response_format or request_fields,
|
82
|
+
fuzzy_match_params=fuzzy_params,
|
83
|
+
handle_validation=handle_validation,
|
84
|
+
alcall_params=_alcall_params.with_updates(
|
85
|
+
retry_attempts=max_retries
|
86
|
+
),
|
87
|
+
imodel=branch.parse_model,
|
88
|
+
imodel_kw=kw,
|
89
|
+
),
|
90
|
+
"return_res_message": return_res_message,
|
91
|
+
}
|
53
92
|
|
54
|
-
initial_error = None
|
55
|
-
parsed_data = None # Initialize to avoid scoping issues
|
56
93
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
94
|
+
async def parse(
|
95
|
+
branch: "Branch",
|
96
|
+
text: str,
|
97
|
+
parse_param: ParseParam,
|
98
|
+
return_res_message: bool = False,
|
99
|
+
) -> Any | tuple[Any, AssistantResponse | None]:
|
100
|
+
|
101
|
+
# Try direct validation first
|
102
|
+
with contextlib.suppress(Exception):
|
103
|
+
result = _validate_dict_or_model(
|
104
|
+
text, parse_param.response_format, parse_param.fuzzy_match_params
|
105
|
+
)
|
106
|
+
return result if not return_res_message else (result, None)
|
107
|
+
|
108
|
+
async def _inner_parse(i):
|
109
|
+
_, res = await branch.chat(
|
110
|
+
instruction="reformat text into specified model or structure",
|
111
|
+
guidance="follow the required response format, using the model schema as a guide",
|
112
|
+
context=[{"text_to_format": text}],
|
113
|
+
request_fields=(
|
114
|
+
parse_param.response_format
|
115
|
+
if isinstance(parse_param.response_format, dict)
|
116
|
+
else None
|
117
|
+
),
|
118
|
+
response_format=(
|
119
|
+
parse_param.response_format
|
120
|
+
if isinstance(parse_param.response_format, BaseModel)
|
121
|
+
else None
|
122
|
+
),
|
123
|
+
imodel=parse_param.imodel or branch.parse_model,
|
124
|
+
sender=branch.user,
|
125
|
+
recipient=branch.id,
|
126
|
+
return_ins_res_message=True,
|
70
127
|
)
|
71
128
|
|
72
|
-
|
129
|
+
res.metadata["is_parsed"] = True
|
130
|
+
res.metadata["original_text"] = text
|
73
131
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
132
|
+
return (
|
133
|
+
_validate_dict_or_model(
|
134
|
+
res.response,
|
135
|
+
parse_param.response_format,
|
136
|
+
parse_param.fuzzy_match_params,
|
137
|
+
),
|
138
|
+
res,
|
139
|
+
)
|
79
140
|
|
80
|
-
|
81
|
-
|
82
|
-
|
141
|
+
_call = parse_param.alcall_params or get_default_call()
|
142
|
+
if isinstance(parse_param.alcall_params, dict):
|
143
|
+
_call = AlcallParams(**parse_param.alcall_params)
|
83
144
|
|
145
|
+
try:
|
146
|
+
result = await _call([0], _inner_parse)
|
147
|
+
except get_cancelled_exc_class():
|
148
|
+
raise
|
84
149
|
except Exception as e:
|
85
|
-
|
86
|
-
|
87
|
-
logging.debug(
|
88
|
-
f"Initial parsing failed for text '{text[:100]}...': {e}"
|
89
|
-
)
|
90
|
-
logging.debug(
|
91
|
-
f"Parsed data was: {locals().get('parsed_data', 'not set')}"
|
92
|
-
)
|
93
|
-
|
94
|
-
# Only continue if we have retries left
|
95
|
-
if max_retries <= 0:
|
96
|
-
if handle_validation == "raise":
|
150
|
+
match parse_param.handle_validation:
|
151
|
+
case "raise":
|
97
152
|
raise ValueError(f"Failed to parse response: {e}") from e
|
98
|
-
|
99
|
-
return None
|
100
|
-
|
101
|
-
return text
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
if
|
117
|
-
|
118
|
-
):
|
119
|
-
|
120
|
-
|
121
|
-
|
153
|
+
case "return_none":
|
154
|
+
return (None, None) if return_res_message else None
|
155
|
+
case "return_value":
|
156
|
+
return (text, None) if return_res_message else text
|
157
|
+
return (*result[0],) if return_res_message else result[0][0]
|
158
|
+
|
159
|
+
|
160
|
+
def _validate_dict_or_model(
|
161
|
+
text: str,
|
162
|
+
response_format: type[BaseModel] | dict,
|
163
|
+
fuzzy_match_params: FuzzyMatchKeysParams | dict = None,
|
164
|
+
):
|
165
|
+
try:
|
166
|
+
if isinstance(fuzzy_match_params, dict):
|
167
|
+
fuzzy_match_params = FuzzyMatchKeysParams(**fuzzy_match_params)
|
168
|
+
|
169
|
+
d_ = extract_json(text, fuzzy_parse=True, return_one_if_single=False)
|
170
|
+
dict_, keys_ = None, None
|
171
|
+
if d_:
|
172
|
+
dict_ = to_list(d_, flatten=True)[0]
|
173
|
+
if isinstance(fuzzy_match_params, FuzzyMatchKeysParams):
|
174
|
+
keys_ = (
|
175
|
+
response_format.model_fields
|
176
|
+
if isinstance(response_format, type)
|
177
|
+
else response_format
|
122
178
|
)
|
123
|
-
|
124
|
-
|
125
|
-
f"Failed to parse response: {initial_error}"
|
126
|
-
) from initial_error
|
127
|
-
elif handle_validation == "return_none":
|
128
|
-
return None
|
129
|
-
else:
|
130
|
-
return text
|
131
|
-
|
132
|
-
while num_try < max_retries:
|
133
|
-
num_try += 1
|
134
|
-
|
135
|
-
try:
|
136
|
-
logging.debug(f"Retry {num_try}: Using parse model to reformat")
|
137
|
-
_, res = await branch.chat(
|
138
|
-
instruction="reformat text into specified model",
|
139
|
-
guidance="follow the required response format, using the model schema as a guide",
|
140
|
-
context=[{"text_to_format": text}],
|
141
|
-
response_format=request_type,
|
142
|
-
sender=branch.user,
|
143
|
-
recipient=branch.id,
|
144
|
-
imodel=branch.parse_model,
|
145
|
-
return_ins_res_message=True,
|
179
|
+
dict_ = fuzzy_validate_mapping(
|
180
|
+
dict_, keys_, **fuzzy_match_params.to_dict()
|
146
181
|
)
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
similarity_algo=similarity_algo,
|
153
|
-
similarity_threshold=similarity_threshold,
|
154
|
-
fuzzy_match=fuzzy_match,
|
155
|
-
handle_unmatched=handle_unmatched,
|
156
|
-
fill_value=fill_value,
|
157
|
-
fill_mapping=fill_mapping,
|
158
|
-
strict=strict,
|
159
|
-
suppress_conversion_errors=suppress_conversion_errors,
|
182
|
+
elif fuzzy_match_params:
|
183
|
+
keys_ = (
|
184
|
+
response_format.model_fields
|
185
|
+
if isinstance(response_format, type)
|
186
|
+
else response_format
|
160
187
|
)
|
188
|
+
dict_ = fuzzy_validate_mapping(
|
189
|
+
dict_,
|
190
|
+
keys_,
|
191
|
+
handle_unmatched="force",
|
192
|
+
fill_value=None,
|
193
|
+
strict=False,
|
194
|
+
)
|
195
|
+
if isinstance(response_format, type) and issubclass(
|
196
|
+
response_format, BaseModel
|
197
|
+
):
|
198
|
+
return response_format.model_validate(dict_)
|
199
|
+
return dict_
|
161
200
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
# All retries exhausted
|
179
|
-
match handle_validation:
|
180
|
-
case "return_value":
|
181
|
-
return text
|
182
|
-
case "return_none":
|
183
|
-
return None
|
184
|
-
case "raise":
|
185
|
-
error_msg = "Failed to parse response into request format"
|
186
|
-
if last_error:
|
187
|
-
error_msg += f": {last_error}"
|
188
|
-
raise ValueError(error_msg) from last_error
|
201
|
+
except Exception as e:
|
202
|
+
raise ValueError(f"Failed to parse text: {e}") from e
|
203
|
+
|
204
|
+
|
205
|
+
def get_default_call() -> AlcallParams:
|
206
|
+
global _CALL
|
207
|
+
if _CALL is None:
|
208
|
+
_CALL = AlcallParams(
|
209
|
+
retry_initial_delay=1,
|
210
|
+
retry_backoff=1.85,
|
211
|
+
retry_attempts=3,
|
212
|
+
max_concurrent=1,
|
213
|
+
throttle_period=1,
|
214
|
+
)
|
215
|
+
return _CALL
|
lionagi/operations/plan/plan.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
# Copyright (c) 2023
|
2
|
-
#
|
1
|
+
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
3
2
|
# SPDX-License-Identifier: Apache-2.0
|
4
3
|
|
5
4
|
from enum import Enum
|
@@ -9,7 +8,7 @@ from pydantic import BaseModel
|
|
9
8
|
|
10
9
|
from lionagi.fields.instruct import Instruct
|
11
10
|
|
12
|
-
from .utils import SelectionModel
|
11
|
+
from .utils import SelectionModel, parse_selection, parse_to_representation
|
13
12
|
|
14
13
|
if TYPE_CHECKING:
|
15
14
|
from lionagi.session.branch import Branch
|
@@ -25,42 +24,104 @@ async def select(
|
|
25
24
|
verbose: bool = False,
|
26
25
|
**kwargs: Any,
|
27
26
|
) -> SelectionModel | tuple[SelectionModel, "Branch"]:
|
27
|
+
"""
|
28
|
+
Select from choices using LLM - legacy wrapper with backwards compatibility.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
branch: Branch instance to use
|
32
|
+
instruct: Instruction for selection
|
33
|
+
choices: Available choices (list, dict, or Enum)
|
34
|
+
max_num_selections: Max number of selections
|
35
|
+
branch_kwargs: Kwargs for branch creation (deprecated)
|
36
|
+
return_branch: Return (result, branch) tuple
|
37
|
+
verbose: Print progress
|
38
|
+
**kwargs: Additional operate kwargs
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
SelectionModel or (SelectionModel, Branch) tuple
|
42
|
+
"""
|
28
43
|
if verbose:
|
29
44
|
print(f"Starting selection with up to {max_num_selections} choices.")
|
30
45
|
|
31
|
-
|
46
|
+
# Handle branch creation for backwards compatibility
|
47
|
+
if branch is None and branch_kwargs:
|
48
|
+
from lionagi.session.branch import Branch
|
32
49
|
|
33
|
-
|
50
|
+
branch = Branch(**branch_kwargs)
|
51
|
+
|
52
|
+
result = await select_v1(
|
53
|
+
branch=branch,
|
54
|
+
instruct=instruct,
|
55
|
+
choices=choices,
|
56
|
+
max_num_selections=max_num_selections,
|
57
|
+
verbose=verbose,
|
58
|
+
**kwargs,
|
59
|
+
)
|
60
|
+
|
61
|
+
if return_branch:
|
62
|
+
return result, branch
|
63
|
+
return result
|
64
|
+
|
65
|
+
|
66
|
+
async def select_v1(
|
67
|
+
branch: "Branch",
|
68
|
+
instruct: Instruct | dict[str, Any],
|
69
|
+
choices: list[str] | type[Enum] | dict[str, Any],
|
70
|
+
max_num_selections: int = 1,
|
71
|
+
verbose: bool = False,
|
72
|
+
**operate_kwargs: Any,
|
73
|
+
) -> SelectionModel:
|
74
|
+
"""
|
75
|
+
Context-based selection implementation.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
branch: Branch instance
|
79
|
+
instruct: Selection instruction
|
80
|
+
choices: Available choices
|
81
|
+
max_num_selections: Maximum selections allowed
|
82
|
+
verbose: Print progress
|
83
|
+
**operate_kwargs: Additional operate parameters
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
SelectionModel with corrected selections
|
87
|
+
"""
|
88
|
+
# Parse choices into keys and representations
|
34
89
|
selections, contents = parse_to_representation(choices)
|
35
90
|
prompt = SelectionModel.PROMPT.format(
|
36
91
|
max_num_selections=max_num_selections, choices=selections
|
37
92
|
)
|
38
93
|
|
94
|
+
# Build instruction dictionary
|
39
95
|
if isinstance(instruct, Instruct):
|
40
|
-
|
41
|
-
|
42
|
-
|
96
|
+
instruct_dict = instruct.to_dict()
|
97
|
+
else:
|
98
|
+
instruct_dict = instruct or {}
|
43
99
|
|
44
|
-
|
45
|
-
|
46
|
-
|
100
|
+
# Append selection prompt to instruction
|
101
|
+
if instruct_dict.get("instruction", None) is not None:
|
102
|
+
instruct_dict["instruction"] = (
|
103
|
+
f"{instruct_dict['instruction']}\n\n{prompt} \n\n "
|
47
104
|
)
|
48
105
|
else:
|
49
|
-
|
106
|
+
instruct_dict["instruction"] = prompt
|
50
107
|
|
51
|
-
|
108
|
+
# Add choice representations to context
|
109
|
+
context = instruct_dict.get("context", None) or []
|
52
110
|
context = [context] if not isinstance(context, list) else context
|
53
111
|
context.extend([{k: v} for k, v in zip(selections, contents)])
|
54
|
-
|
112
|
+
instruct_dict["context"] = context
|
55
113
|
|
114
|
+
# Call branch.operate with SelectionModel as response format
|
56
115
|
response_model: SelectionModel = await branch.operate(
|
57
116
|
response_format=SelectionModel,
|
58
|
-
**
|
59
|
-
**
|
117
|
+
**operate_kwargs,
|
118
|
+
**instruct_dict,
|
60
119
|
)
|
120
|
+
|
61
121
|
if verbose:
|
62
122
|
print(f"Received selection: {response_model.selected}")
|
63
123
|
|
124
|
+
# Extract and normalize selected values
|
64
125
|
selected = response_model
|
65
126
|
if isinstance(response_model, BaseModel) and hasattr(
|
66
127
|
response_model, "selected"
|
@@ -68,14 +129,13 @@ async def select(
|
|
68
129
|
selected = response_model.selected
|
69
130
|
selected = [selected] if not isinstance(selected, list) else selected
|
70
131
|
|
132
|
+
# Parse selections back to original choice values
|
71
133
|
corrected_selections = [parse_selection(i, choices) for i in selected]
|
72
134
|
|
135
|
+
# Update response model with corrected selections
|
73
136
|
if isinstance(response_model, BaseModel):
|
74
137
|
response_model.selected = corrected_selections
|
75
|
-
|
76
138
|
elif isinstance(response_model, dict):
|
77
139
|
response_model["selected"] = corrected_selections
|
78
140
|
|
79
|
-
if return_branch:
|
80
|
-
return response_model, branch
|
81
141
|
return response_model
|
@@ -1,5 +1,4 @@
|
|
1
|
-
# Copyright (c) 2023
|
2
|
-
#
|
1
|
+
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
3
2
|
# SPDX-License-Identifier: Apache-2.0
|
4
3
|
|
5
4
|
import inspect
|
@@ -8,7 +7,7 @@ from typing import Any, ClassVar
|
|
8
7
|
|
9
8
|
from pydantic import BaseModel, Field, JsonValue
|
10
9
|
|
11
|
-
from lionagi.
|
10
|
+
from lionagi.ln.fuzzy import string_similarity
|
12
11
|
from lionagi.utils import is_same_dtype
|
13
12
|
|
14
13
|
|