lionagi 0.17.11__py3-none-any.whl → 0.18.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/_errors.py +0 -5
- lionagi/fields.py +83 -0
- lionagi/libs/schema/minimal_yaml.py +98 -0
- lionagi/ln/__init__.py +3 -1
- lionagi/ln/concurrency/primitives.py +4 -4
- lionagi/ln/concurrency/task.py +1 -0
- lionagi/ln/types.py +32 -5
- lionagi/models/field_model.py +21 -4
- lionagi/models/hashable_model.py +2 -3
- lionagi/operations/ReAct/ReAct.py +475 -238
- lionagi/operations/ReAct/utils.py +3 -0
- lionagi/operations/act/act.py +206 -0
- lionagi/operations/builder.py +5 -7
- lionagi/operations/chat/chat.py +130 -114
- lionagi/operations/communicate/communicate.py +101 -42
- lionagi/operations/fields.py +380 -0
- lionagi/operations/flow.py +8 -10
- lionagi/operations/interpret/interpret.py +65 -20
- lionagi/operations/node.py +4 -4
- lionagi/operations/operate/operate.py +216 -108
- lionagi/{protocols/operatives → operations/operate}/operative.py +4 -5
- lionagi/{protocols/operatives → operations/operate}/step.py +34 -39
- lionagi/operations/parse/parse.py +170 -142
- lionagi/operations/select/select.py +79 -18
- lionagi/operations/select/utils.py +8 -2
- lionagi/operations/types.py +119 -23
- lionagi/protocols/action/manager.py +5 -6
- lionagi/protocols/contracts.py +2 -2
- lionagi/protocols/generic/__init__.py +22 -0
- lionagi/protocols/generic/element.py +36 -127
- lionagi/protocols/generic/log.py +3 -2
- lionagi/protocols/generic/pile.py +9 -10
- lionagi/protocols/generic/progression.py +23 -22
- lionagi/protocols/graph/edge.py +6 -5
- lionagi/protocols/ids.py +6 -49
- lionagi/protocols/messages/__init__.py +29 -0
- lionagi/protocols/messages/action_request.py +86 -184
- lionagi/protocols/messages/action_response.py +73 -131
- lionagi/protocols/messages/assistant_response.py +130 -159
- lionagi/protocols/messages/base.py +31 -22
- lionagi/protocols/messages/instruction.py +280 -625
- lionagi/protocols/messages/manager.py +112 -62
- lionagi/protocols/messages/message.py +87 -197
- lionagi/protocols/messages/system.py +52 -123
- lionagi/protocols/types.py +1 -13
- lionagi/service/connections/__init__.py +3 -0
- lionagi/service/connections/endpoint.py +0 -8
- lionagi/service/connections/providers/claude_code_cli.py +3 -2
- lionagi/service/connections/providers/oai_.py +29 -94
- lionagi/service/connections/providers/ollama_.py +3 -2
- lionagi/service/hooks/_types.py +1 -1
- lionagi/service/hooks/_utils.py +1 -1
- lionagi/service/hooks/hook_event.py +3 -8
- lionagi/service/hooks/hook_registry.py +5 -5
- lionagi/service/hooks/hooked_event.py +63 -3
- lionagi/service/imodel.py +24 -20
- lionagi/service/third_party/claude_code.py +3 -3
- lionagi/service/third_party/openai_models.py +435 -0
- lionagi/service/token_calculator.py +1 -94
- lionagi/session/branch.py +190 -400
- lionagi/session/session.py +8 -99
- lionagi/tools/file/reader.py +2 -2
- lionagi/version.py +1 -1
- {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/METADATA +6 -6
- lionagi-0.18.1.dist-info/RECORD +164 -0
- lionagi/fields/__init__.py +0 -47
- lionagi/fields/action.py +0 -188
- lionagi/fields/base.py +0 -153
- lionagi/fields/code.py +0 -239
- lionagi/fields/file.py +0 -234
- lionagi/fields/instruct.py +0 -135
- lionagi/fields/reason.py +0 -55
- lionagi/fields/research.py +0 -52
- lionagi/operations/_act/act.py +0 -86
- lionagi/operations/brainstorm/__init__.py +0 -2
- lionagi/operations/brainstorm/brainstorm.py +0 -498
- lionagi/operations/brainstorm/prompt.py +0 -11
- lionagi/operations/instruct/__init__.py +0 -2
- lionagi/operations/instruct/instruct.py +0 -28
- lionagi/operations/plan/__init__.py +0 -6
- lionagi/operations/plan/plan.py +0 -386
- lionagi/operations/plan/prompt.py +0 -25
- lionagi/operations/utils.py +0 -45
- lionagi/protocols/forms/__init__.py +0 -2
- lionagi/protocols/forms/base.py +0 -85
- lionagi/protocols/forms/flow.py +0 -79
- lionagi/protocols/forms/form.py +0 -86
- lionagi/protocols/forms/report.py +0 -48
- lionagi/protocols/mail/__init__.py +0 -2
- lionagi/protocols/mail/exchange.py +0 -220
- lionagi/protocols/mail/mail.py +0 -51
- lionagi/protocols/mail/mailbox.py +0 -103
- lionagi/protocols/mail/manager.py +0 -218
- lionagi/protocols/mail/package.py +0 -101
- 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/protocols/operatives/__init__.py +0 -2
- lionagi/service/connections/providers/types.py +0 -28
- lionagi/service/third_party/openai_model_names.py +0 -198
- lionagi/service/types.py +0 -58
- lionagi-0.17.11.dist-info/RECORD +0 -199
- /lionagi/operations/{_act → act}/__init__.py +0 -0
- {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/WHEEL +0 -0
- {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,40 +3,85 @@
|
|
|
3
3
|
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
+
from ..types import ChatParam, InterpretParam
|
|
7
|
+
|
|
6
8
|
if TYPE_CHECKING:
|
|
9
|
+
from lionagi.service.imodel import iModel
|
|
7
10
|
from lionagi.session.branch import Branch
|
|
8
11
|
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
def prepare_interpret_kw(
|
|
11
14
|
branch: "Branch",
|
|
12
15
|
text: str,
|
|
13
16
|
domain: str | None = None,
|
|
14
17
|
style: str | None = None,
|
|
15
18
|
sample_writing: str | None = None,
|
|
16
|
-
interpret_model:
|
|
19
|
+
interpret_model: "iModel | None" = None,
|
|
17
20
|
**kwargs,
|
|
18
21
|
) -> str:
|
|
22
|
+
"""Interpret and refine user input into clearer prompts."""
|
|
23
|
+
|
|
24
|
+
# Build InterpretParam
|
|
25
|
+
intp_param = InterpretParam(
|
|
26
|
+
domain=domain or "general",
|
|
27
|
+
style=style or "concise",
|
|
28
|
+
sample_writing=sample_writing or "",
|
|
29
|
+
imodel=interpret_model or branch.chat_model,
|
|
30
|
+
imodel_kw=kwargs,
|
|
31
|
+
)
|
|
32
|
+
return {
|
|
33
|
+
"text": text,
|
|
34
|
+
"intp_param": intp_param,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def interpret(
|
|
39
|
+
branch: "Branch",
|
|
40
|
+
text: str,
|
|
41
|
+
intp_param: InterpretParam,
|
|
42
|
+
) -> str:
|
|
43
|
+
"""Execute interpretation with context - clean implementation."""
|
|
44
|
+
|
|
45
|
+
from ..chat.chat import chat
|
|
46
|
+
|
|
19
47
|
instruction = (
|
|
20
|
-
"You are given a user's raw instruction or question. Your task is to rewrite it into a clearer,"
|
|
48
|
+
"You are given a user's raw instruction or question. Your task is to rewrite it into a clearer, "
|
|
21
49
|
"more structured prompt for an LLM or system, making any implicit or missing details explicit. "
|
|
22
50
|
"Return only the re-written prompt. Do not assume any details not mentioned in the input, nor "
|
|
23
51
|
"give additional instruction than what is explicitly stated."
|
|
24
52
|
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
context = [f"User input: {text}"]
|
|
30
|
-
|
|
31
|
-
# Default temperature if none provided
|
|
32
|
-
kwargs["guidance"] = guidance + "\n" + kwargs.get("guidance", "")
|
|
33
|
-
kwargs["instruction"] = instruction + "\n" + kwargs.get("instruction", "")
|
|
34
|
-
kwargs["temperature"] = kwargs.get("temperature", 0.1)
|
|
35
|
-
if interpret_model:
|
|
36
|
-
kwargs["chat_model"] = interpret_model
|
|
37
|
-
|
|
38
|
-
refined_prompt = await branch.chat(
|
|
39
|
-
context=context,
|
|
40
|
-
**kwargs,
|
|
53
|
+
|
|
54
|
+
guidance = (
|
|
55
|
+
f"Domain hint: {intp_param.domain}. Desired style: {intp_param.style}."
|
|
41
56
|
)
|
|
42
|
-
|
|
57
|
+
if intp_param.sample_writing:
|
|
58
|
+
guidance += f" Sample writing: {intp_param.sample_writing}"
|
|
59
|
+
|
|
60
|
+
# Build ChatParam
|
|
61
|
+
chat_param = ChatParam(
|
|
62
|
+
guidance=guidance,
|
|
63
|
+
context=[f"User input: {text}"],
|
|
64
|
+
sender=branch.user or "user",
|
|
65
|
+
recipient=branch.id,
|
|
66
|
+
response_format=None,
|
|
67
|
+
progression=None,
|
|
68
|
+
tool_schemas=[],
|
|
69
|
+
images=[],
|
|
70
|
+
image_detail="auto",
|
|
71
|
+
plain_content="",
|
|
72
|
+
include_token_usage_to_model=False,
|
|
73
|
+
imodel=intp_param.imodel,
|
|
74
|
+
imodel_kw={
|
|
75
|
+
**intp_param.imodel_kw,
|
|
76
|
+
"temperature": intp_param.imodel_kw.get("temperature", 0.1),
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
result = await chat(
|
|
81
|
+
branch,
|
|
82
|
+
instruction=instruction,
|
|
83
|
+
chat_param=chat_param,
|
|
84
|
+
return_ins_res_message=False,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return str(result)
|
lionagi/operations/node.py
CHANGED
|
@@ -6,7 +6,7 @@ from uuid import UUID
|
|
|
6
6
|
from anyio import get_cancelled_exc_class
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
9
|
-
from lionagi.protocols.types import ID, Event, EventStatus,
|
|
9
|
+
from lionagi.protocols.types import ID, Event, EventStatus, Node
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from lionagi.session.branch import Branch
|
|
@@ -37,12 +37,12 @@ class Operation(Node, Event):
|
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
@property
|
|
40
|
-
def branch_id(self) ->
|
|
40
|
+
def branch_id(self) -> UUID | None:
|
|
41
41
|
if a := self.metadata.get("branch_id"):
|
|
42
42
|
return ID.get_id(a)
|
|
43
43
|
|
|
44
44
|
@branch_id.setter
|
|
45
|
-
def branch_id(self, value: str | UUID |
|
|
45
|
+
def branch_id(self, value: str | UUID | None):
|
|
46
46
|
if value is None:
|
|
47
47
|
self.metadata.pop("branch_id", None)
|
|
48
48
|
else:
|
|
@@ -54,7 +54,7 @@ class Operation(Node, Event):
|
|
|
54
54
|
return ID.get_id(a)
|
|
55
55
|
|
|
56
56
|
@graph_id.setter
|
|
57
|
-
def graph_id(self, value: str | UUID |
|
|
57
|
+
def graph_id(self, value: str | UUID | None):
|
|
58
58
|
if value is None:
|
|
59
59
|
self.metadata.pop("graph_id", None)
|
|
60
60
|
else:
|
|
@@ -1,46 +1,28 @@
|
|
|
1
1
|
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import warnings
|
|
5
5
|
from typing import TYPE_CHECKING, Literal
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel, JsonValue
|
|
8
8
|
|
|
9
|
-
from lionagi.
|
|
9
|
+
from lionagi.ln import AlcallParams
|
|
10
|
+
from lionagi.ln.fuzzy import FuzzyMatchKeysParams
|
|
10
11
|
from lionagi.models import FieldModel, ModelParams
|
|
11
|
-
from lionagi.protocols.
|
|
12
|
-
from lionagi.protocols.
|
|
13
|
-
|
|
14
|
-
from
|
|
12
|
+
from lionagi.protocols.generic import Progression
|
|
13
|
+
from lionagi.protocols.messages import Instruction, SenderRecipient
|
|
14
|
+
|
|
15
|
+
from ..fields import Instruct
|
|
16
|
+
from ..types import ActionParam, ChatParam, HandleValidation, ParseParam
|
|
15
17
|
|
|
16
18
|
if TYPE_CHECKING:
|
|
19
|
+
from lionagi.service.imodel import iModel
|
|
17
20
|
from lionagi.session.branch import Branch, ToolRef
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
def _handle_response_format_kwargs(
|
|
21
|
-
operative_model: type[BaseModel] = None,
|
|
22
|
-
request_model: type[BaseModel] = None,
|
|
23
|
-
response_format: type[BaseModel] = None,
|
|
24
|
-
):
|
|
25
|
-
if operative_model:
|
|
26
|
-
logging.warning(
|
|
27
|
-
"`operative_model` is deprecated. Use `response_format` instead."
|
|
28
|
-
)
|
|
29
|
-
if (
|
|
30
|
-
(operative_model and response_format)
|
|
31
|
-
or (operative_model and request_model)
|
|
32
|
-
or (response_format and request_model)
|
|
33
|
-
):
|
|
34
|
-
raise ValueError(
|
|
35
|
-
"Cannot specify both `operative_model` and `response_format` (or `request_model`) "
|
|
36
|
-
"as they are aliases of each other."
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
# Use the final chosen format
|
|
40
|
-
return response_format or operative_model or request_model
|
|
22
|
+
from .operative import Operative
|
|
41
23
|
|
|
42
24
|
|
|
43
|
-
|
|
25
|
+
def prepare_operate_kw(
|
|
44
26
|
branch: "Branch",
|
|
45
27
|
*,
|
|
46
28
|
instruct: Instruct = None,
|
|
@@ -50,18 +32,17 @@ async def operate(
|
|
|
50
32
|
sender: SenderRecipient = None,
|
|
51
33
|
recipient: SenderRecipient = None,
|
|
52
34
|
progression: Progression = None,
|
|
53
|
-
imodel: iModel = None, # deprecated, alias of chat_model
|
|
54
|
-
chat_model: iModel = None,
|
|
35
|
+
imodel: "iModel" = None, # deprecated, alias of chat_model
|
|
36
|
+
chat_model: "iModel" = None,
|
|
55
37
|
invoke_actions: bool = True,
|
|
56
38
|
tool_schemas: list[dict] = None,
|
|
57
39
|
images: list = None,
|
|
58
40
|
image_detail: Literal["low", "high", "auto"] = None,
|
|
59
|
-
parse_model: iModel = None,
|
|
41
|
+
parse_model: "iModel" = None,
|
|
60
42
|
skip_validation: bool = False,
|
|
61
43
|
tools: "ToolRef" = None,
|
|
62
44
|
operative: "Operative" = None,
|
|
63
45
|
response_format: type[BaseModel] = None, # alias of operative.request_type
|
|
64
|
-
return_operative: bool = False,
|
|
65
46
|
actions: bool = False,
|
|
66
47
|
reason: bool = False,
|
|
67
48
|
call_params: AlcallParams = None,
|
|
@@ -71,21 +52,38 @@ async def operate(
|
|
|
71
52
|
exclude_fields: list | dict | None = None,
|
|
72
53
|
request_params: ModelParams = None,
|
|
73
54
|
request_param_kwargs: dict = None,
|
|
74
|
-
|
|
75
|
-
response_param_kwargs: dict = None,
|
|
76
|
-
handle_validation: Literal[
|
|
77
|
-
"raise", "return_value", "return_none"
|
|
78
|
-
] = "return_value",
|
|
55
|
+
handle_validation: HandleValidation = "return_value",
|
|
79
56
|
operative_model: type[BaseModel] = None,
|
|
80
57
|
request_model: type[BaseModel] = None,
|
|
81
58
|
include_token_usage_to_model: bool = False,
|
|
59
|
+
clear_messages: bool = False,
|
|
82
60
|
**kwargs,
|
|
83
61
|
) -> list | BaseModel | None | dict | str:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
62
|
+
# Handle deprecated parameters
|
|
63
|
+
if operative_model:
|
|
64
|
+
warnings.warn(
|
|
65
|
+
"Parameter 'operative_model' is deprecated. Use 'response_format' instead.",
|
|
66
|
+
DeprecationWarning,
|
|
67
|
+
stacklevel=2,
|
|
68
|
+
)
|
|
69
|
+
if imodel:
|
|
70
|
+
warnings.warn(
|
|
71
|
+
"Parameter 'imodel' is deprecated. Use 'chat_model' instead.",
|
|
72
|
+
DeprecationWarning,
|
|
73
|
+
stacklevel=2,
|
|
74
|
+
)
|
|
87
75
|
|
|
88
|
-
|
|
76
|
+
if (
|
|
77
|
+
(operative_model and response_format)
|
|
78
|
+
or (operative_model and request_model)
|
|
79
|
+
or (response_format and request_model)
|
|
80
|
+
):
|
|
81
|
+
raise ValueError(
|
|
82
|
+
"Cannot specify both `operative_model` and `response_format` (or `request_model`) "
|
|
83
|
+
"as they are aliases of each other."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
response_format = response_format or operative_model or request_model
|
|
89
87
|
chat_model = chat_model or imodel or branch.chat_model
|
|
90
88
|
parse_model = parse_model or chat_model
|
|
91
89
|
|
|
@@ -108,103 +106,213 @@ async def operate(
|
|
|
108
106
|
if action_strategy:
|
|
109
107
|
instruct.action_strategy = action_strategy
|
|
110
108
|
|
|
111
|
-
#
|
|
109
|
+
# Build the Operative - always create it for backwards compatibility
|
|
110
|
+
from .step import Step
|
|
111
|
+
|
|
112
112
|
operative = Step.request_operative(
|
|
113
113
|
request_params=request_params,
|
|
114
114
|
reason=instruct.reason,
|
|
115
|
-
actions=instruct.actions,
|
|
115
|
+
actions=instruct.actions or actions,
|
|
116
116
|
exclude_fields=exclude_fields,
|
|
117
117
|
base_type=response_format,
|
|
118
118
|
field_models=field_models,
|
|
119
119
|
**(request_param_kwargs or {}),
|
|
120
120
|
)
|
|
121
|
+
# Use the operative's request_type which is a proper Pydantic model
|
|
122
|
+
# created from field_models if provided
|
|
123
|
+
final_response_format = operative.request_type
|
|
121
124
|
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
tools = tools or True
|
|
125
|
-
|
|
126
|
-
# If we want to auto-invoke tools, fetch or generate the schemas
|
|
127
|
-
if invoke_actions and tools:
|
|
128
|
-
tool_schemas = tool_schemas or branch.acts.get_tool_schema(tools=tools)
|
|
129
|
-
|
|
130
|
-
# 2) Send the instruction to the chat model
|
|
131
|
-
ins, res = await branch.chat(
|
|
132
|
-
instruction=instruct.instruction,
|
|
125
|
+
# Build contexts
|
|
126
|
+
chat_param = ChatParam(
|
|
133
127
|
guidance=instruct.guidance,
|
|
134
128
|
context=instruct.context,
|
|
135
|
-
sender=sender,
|
|
136
|
-
recipient=recipient,
|
|
137
|
-
response_format=
|
|
129
|
+
sender=sender or branch.user or "user",
|
|
130
|
+
recipient=recipient or branch.id,
|
|
131
|
+
response_format=final_response_format,
|
|
138
132
|
progression=progression,
|
|
139
|
-
|
|
133
|
+
tool_schemas=tool_schemas,
|
|
140
134
|
images=images,
|
|
141
135
|
image_detail=image_detail,
|
|
142
|
-
|
|
143
|
-
return_ins_res_message=True,
|
|
136
|
+
plain_content=None,
|
|
144
137
|
include_token_usage_to_model=include_token_usage_to_model,
|
|
145
|
-
|
|
138
|
+
imodel=chat_model,
|
|
139
|
+
imodel_kw=kwargs,
|
|
146
140
|
)
|
|
147
|
-
branch.msgs.add_message(instruction=ins)
|
|
148
|
-
branch.msgs.add_message(assistant_response=res)
|
|
149
141
|
|
|
150
|
-
|
|
151
|
-
|
|
142
|
+
parse_param = None
|
|
143
|
+
if final_response_format and not skip_validation:
|
|
144
|
+
from ..parse.parse import get_default_call
|
|
152
145
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
# 5) Parse or validate the response into the operative's model
|
|
158
|
-
response_model = operative.update_response_model(res.response)
|
|
159
|
-
if not isinstance(response_model, BaseModel):
|
|
160
|
-
# If the response isn't directly a model, attempt a parse
|
|
161
|
-
response_model = await branch.parse(
|
|
162
|
-
text=res.response,
|
|
163
|
-
request_type=operative.request_type,
|
|
164
|
-
max_retries=operative.max_retries,
|
|
146
|
+
parse_param = ParseParam(
|
|
147
|
+
response_format=final_response_format,
|
|
148
|
+
fuzzy_match_params=FuzzyMatchKeysParams(),
|
|
165
149
|
handle_validation="return_value",
|
|
150
|
+
alcall_params=get_default_call(),
|
|
151
|
+
imodel=parse_model,
|
|
152
|
+
imodel_kw={},
|
|
166
153
|
)
|
|
167
|
-
|
|
168
|
-
|
|
154
|
+
|
|
155
|
+
action_param = None
|
|
156
|
+
if invoke_actions and (instruct.actions or actions):
|
|
157
|
+
from ..act.act import _get_default_call_params
|
|
158
|
+
|
|
159
|
+
action_param = ActionParam(
|
|
160
|
+
action_call_params=call_params or _get_default_call_params(),
|
|
161
|
+
tools=tools,
|
|
162
|
+
strategy=action_strategy
|
|
163
|
+
or instruct.action_strategy
|
|
164
|
+
or "concurrent",
|
|
165
|
+
suppress_errors=True,
|
|
166
|
+
verbose_action=verbose_action,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
"instruction": instruct.instruction,
|
|
171
|
+
"chat_param": chat_param,
|
|
172
|
+
"parse_param": parse_param,
|
|
173
|
+
"action_param": action_param,
|
|
174
|
+
"handle_validation": handle_validation,
|
|
175
|
+
"invoke_actions": invoke_actions,
|
|
176
|
+
"skip_validation": skip_validation,
|
|
177
|
+
"clear_messages": clear_messages,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
async def operate(
|
|
182
|
+
branch: "Branch",
|
|
183
|
+
instruction: JsonValue | Instruction,
|
|
184
|
+
chat_param: ChatParam,
|
|
185
|
+
action_param: ActionParam | None = None,
|
|
186
|
+
parse_param: ParseParam | None = None,
|
|
187
|
+
handle_validation: HandleValidation = "return_value",
|
|
188
|
+
invoke_actions: bool = True,
|
|
189
|
+
skip_validation: bool = False,
|
|
190
|
+
clear_messages: bool = False,
|
|
191
|
+
reason: bool = False,
|
|
192
|
+
field_models: list[FieldModel] | None = None,
|
|
193
|
+
) -> BaseModel | dict | str | None:
|
|
194
|
+
|
|
195
|
+
# 1. communicate chat context building to avoid changing parameters
|
|
196
|
+
# Start with base chat param
|
|
197
|
+
_cctx = chat_param
|
|
198
|
+
_pctx = (
|
|
199
|
+
parse_param.with_updates(handle_validation="return_value")
|
|
200
|
+
if parse_param
|
|
201
|
+
else ParseParam(
|
|
202
|
+
response_format=chat_param.response_format,
|
|
203
|
+
imodel=branch.parse_model,
|
|
204
|
+
handle_validation="return_value",
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Update tool schemas if needed
|
|
209
|
+
if tools := (action_param.tools or True) if action_param else None:
|
|
210
|
+
tool_schemas = branch.acts.get_tool_schema(tools=tools)
|
|
211
|
+
_cctx = _cctx.with_updates(tool_schemas=tool_schemas)
|
|
212
|
+
|
|
213
|
+
# Extract model class from response_format (can be class, instance, or dict)
|
|
214
|
+
model_class = None
|
|
215
|
+
if chat_param.response_format is not None:
|
|
216
|
+
if isinstance(chat_param.response_format, type) and issubclass(
|
|
217
|
+
chat_param.response_format, BaseModel
|
|
218
|
+
):
|
|
219
|
+
model_class = chat_param.response_format
|
|
220
|
+
elif isinstance(chat_param.response_format, BaseModel):
|
|
221
|
+
model_class = type(chat_param.response_format)
|
|
222
|
+
|
|
223
|
+
def normalize_field_model(fms):
|
|
224
|
+
if not fms:
|
|
225
|
+
return []
|
|
226
|
+
if not isinstance(fms, list):
|
|
227
|
+
return [fms]
|
|
228
|
+
return fms
|
|
229
|
+
|
|
230
|
+
fms = normalize_field_model(field_models)
|
|
231
|
+
operative = None
|
|
232
|
+
|
|
233
|
+
if model_class:
|
|
234
|
+
from .step import Step
|
|
235
|
+
|
|
236
|
+
operative = Step.request_operative(
|
|
237
|
+
reason=reason,
|
|
238
|
+
actions=bool(action_param is not None),
|
|
239
|
+
base_type=model_class,
|
|
240
|
+
field_models=fms,
|
|
169
241
|
)
|
|
242
|
+
# Update contexts with new response format
|
|
243
|
+
_cctx = _cctx.with_updates(response_format=operative.request_type)
|
|
244
|
+
_pctx = _pctx.with_updates(response_format=operative.request_type)
|
|
245
|
+
elif field_models:
|
|
246
|
+
dict_ = {}
|
|
247
|
+
for fm in fms:
|
|
248
|
+
if fm.name:
|
|
249
|
+
dict_[fm.name] = str(fm.annotated())
|
|
250
|
+
# Update contexts with dict format
|
|
251
|
+
_cctx = _cctx.with_updates(response_format=dict_)
|
|
252
|
+
_pctx = _pctx.with_updates(response_format=dict_)
|
|
253
|
+
|
|
254
|
+
from ..communicate.communicate import communicate
|
|
170
255
|
|
|
171
|
-
|
|
172
|
-
|
|
256
|
+
result = await communicate(
|
|
257
|
+
branch,
|
|
258
|
+
instruction,
|
|
259
|
+
_cctx,
|
|
260
|
+
_pctx,
|
|
261
|
+
clear_messages,
|
|
262
|
+
skip_validation=skip_validation,
|
|
263
|
+
request_fields=None,
|
|
264
|
+
)
|
|
265
|
+
if skip_validation:
|
|
266
|
+
return result
|
|
267
|
+
if model_class and not isinstance(result, model_class):
|
|
173
268
|
match handle_validation:
|
|
174
269
|
case "return_value":
|
|
175
|
-
return
|
|
270
|
+
return result
|
|
176
271
|
case "return_none":
|
|
177
272
|
return None
|
|
178
273
|
case "raise":
|
|
179
274
|
raise ValueError(
|
|
180
275
|
"Failed to parse the LLM response into the requested format."
|
|
181
276
|
)
|
|
182
|
-
|
|
183
|
-
# 6) If no tool invocation is needed, return result or operative
|
|
184
277
|
if not invoke_actions:
|
|
185
|
-
return
|
|
278
|
+
return result
|
|
186
279
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
# Possibly refine the operative with the tool outputs
|
|
202
|
-
operative = Step.respond_operative(
|
|
203
|
-
response_params=response_params,
|
|
204
|
-
operative=operative,
|
|
205
|
-
additional_data={"action_responses": action_response_models},
|
|
206
|
-
**(response_param_kwargs or {}),
|
|
280
|
+
requests = (
|
|
281
|
+
getattr(result, "action_requests", None)
|
|
282
|
+
if model_class
|
|
283
|
+
else result.get("action_requests", None)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
action_response_models = None
|
|
287
|
+
if action_param and requests is not None:
|
|
288
|
+
from ..act.act import act
|
|
289
|
+
|
|
290
|
+
action_response_models = await act(
|
|
291
|
+
branch,
|
|
292
|
+
requests,
|
|
293
|
+
action_param,
|
|
207
294
|
)
|
|
208
295
|
|
|
209
|
-
|
|
210
|
-
|
|
296
|
+
if not action_response_models:
|
|
297
|
+
return result
|
|
298
|
+
|
|
299
|
+
# Filter out None values from action responses
|
|
300
|
+
action_response_models = [
|
|
301
|
+
r for r in action_response_models if r is not None
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
if not action_response_models: # All were None
|
|
305
|
+
return result
|
|
306
|
+
|
|
307
|
+
if not model_class: # Dict response
|
|
308
|
+
result.update({"action_responses": action_response_models})
|
|
309
|
+
return result
|
|
310
|
+
|
|
311
|
+
from .step import Step
|
|
312
|
+
|
|
313
|
+
operative.response_model = result
|
|
314
|
+
operative = Step.respond_operative(
|
|
315
|
+
operative=operative,
|
|
316
|
+
additional_data={"action_responses": action_response_models},
|
|
317
|
+
)
|
|
318
|
+
return operative.response_model
|
|
@@ -6,10 +6,9 @@ from typing import Any
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
from pydantic.fields import FieldInfo
|
|
8
8
|
|
|
9
|
-
from lionagi.ln import extract_json
|
|
10
|
-
from lionagi.ln.
|
|
9
|
+
from lionagi.ln import extract_json, fuzzy_match_keys
|
|
10
|
+
from lionagi.ln.types import Undefined
|
|
11
11
|
from lionagi.models import FieldModel, ModelParams, OperableModel
|
|
12
|
-
from lionagi.utils import UNDEFINED
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class Operative:
|
|
@@ -169,7 +168,7 @@ class Operative:
|
|
|
169
168
|
d_ = fuzzy_match_keys(
|
|
170
169
|
d_, self.request_type.model_fields, handle_unmatched="raise"
|
|
171
170
|
)
|
|
172
|
-
d_ = {k: v for k, v in d_.items() if v !=
|
|
171
|
+
d_ = {k: v for k, v in d_.items() if v != Undefined}
|
|
173
172
|
self.response_model = self.request_type.model_validate(d_)
|
|
174
173
|
self._should_retry = False
|
|
175
174
|
except Exception:
|
|
@@ -190,7 +189,7 @@ class Operative:
|
|
|
190
189
|
d_ = fuzzy_match_keys(
|
|
191
190
|
d_, self.request_type.model_fields, handle_unmatched="force"
|
|
192
191
|
)
|
|
193
|
-
d_ = {k: v for k, v in d_.items() if v !=
|
|
192
|
+
d_ = {k: v for k, v in d_.items() if v != Undefined}
|
|
194
193
|
self.response_model = self.request_type.model_validate(d_)
|
|
195
194
|
self._should_retry = False
|
|
196
195
|
except Exception:
|
|
@@ -4,14 +4,10 @@
|
|
|
4
4
|
from pydantic import BaseModel
|
|
5
5
|
from pydantic.fields import FieldInfo
|
|
6
6
|
|
|
7
|
-
from lionagi.fields.action import (
|
|
8
|
-
ACTION_REQUESTS_FIELD,
|
|
9
|
-
ACTION_REQUIRED_FIELD,
|
|
10
|
-
ACTION_RESPONSES_FIELD,
|
|
11
|
-
)
|
|
12
|
-
from lionagi.fields.reason import REASON_FIELD
|
|
13
7
|
from lionagi.models import FieldModel, ModelParams
|
|
14
|
-
|
|
8
|
+
|
|
9
|
+
from ..fields import get_default_field
|
|
10
|
+
from .operative import Operative
|
|
15
11
|
|
|
16
12
|
|
|
17
13
|
class Step:
|
|
@@ -71,18 +67,17 @@ class Step:
|
|
|
71
67
|
field_models = field_models or []
|
|
72
68
|
exclude_fields = exclude_fields or []
|
|
73
69
|
field_descriptions = field_descriptions or {}
|
|
74
|
-
if reason and
|
|
75
|
-
field_models.append(
|
|
76
|
-
if
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
83
|
-
|
|
70
|
+
if reason and (fm := get_default_field("reason")) not in field_models:
|
|
71
|
+
field_models.append(fm)
|
|
72
|
+
if (
|
|
73
|
+
actions
|
|
74
|
+
and (fm := get_default_field("action_requests"))
|
|
75
|
+
not in field_models
|
|
76
|
+
):
|
|
77
|
+
fm2 = get_default_field("action_required")
|
|
78
|
+
field_models.extend([fm, fm2])
|
|
84
79
|
if isinstance(request_params, ModelParams):
|
|
85
|
-
request_params = request_params.
|
|
80
|
+
request_params = request_params.to_dict()
|
|
86
81
|
|
|
87
82
|
request_params = request_params or {}
|
|
88
83
|
request_params_fields = {
|
|
@@ -143,15 +138,17 @@ class Step:
|
|
|
143
138
|
additional_data = additional_data or {}
|
|
144
139
|
field_models = field_models or []
|
|
145
140
|
if hasattr(operative.response_model, "action_required"):
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
141
|
+
for i in {
|
|
142
|
+
"action_requests",
|
|
143
|
+
"action_required",
|
|
144
|
+
"action_responses",
|
|
145
|
+
}:
|
|
146
|
+
fm = get_default_field(i)
|
|
147
|
+
if fm not in field_models:
|
|
148
|
+
field_models.append(fm)
|
|
149
|
+
|
|
153
150
|
if "reason" in type(operative.response_model).model_fields:
|
|
154
|
-
field_models.
|
|
151
|
+
field_models.append(get_default_field("reason"))
|
|
155
152
|
|
|
156
153
|
operative = Step._create_response_type(
|
|
157
154
|
operative=operative,
|
|
@@ -201,24 +198,22 @@ class Step:
|
|
|
201
198
|
hasattr(operative.request_type, "action_required")
|
|
202
199
|
and operative.response_model.action_required
|
|
203
200
|
):
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
field_models.extend([REASON_FIELD])
|
|
201
|
+
for i in {
|
|
202
|
+
"action_requests",
|
|
203
|
+
"action_required",
|
|
204
|
+
"action_responses",
|
|
205
|
+
}:
|
|
206
|
+
fm = get_default_field(i)
|
|
207
|
+
if fm not in field_models:
|
|
208
|
+
field_models.append(fm)
|
|
213
209
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
# since Operative doesn't store ModelParams anymore
|
|
210
|
+
if hasattr(operative.request_type, "reason"):
|
|
211
|
+
field_models.append(get_default_field("reason"))
|
|
217
212
|
|
|
218
213
|
operative.create_response_type(
|
|
219
214
|
response_params=response_params,
|
|
220
215
|
field_models=field_models,
|
|
221
|
-
exclude_fields=exclude_fields,
|
|
216
|
+
exclude_fields=exclude_fields or [],
|
|
222
217
|
doc=response_doc,
|
|
223
218
|
config_dict=response_config_dict,
|
|
224
219
|
frozen=frozen_response,
|