zrb 1.21.31__py3-none-any.whl → 1.21.43__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.
Potentially problematic release.
This version of zrb might be problematic. Click here for more details.
- zrb/builtin/llm/chat_completion.py +94 -84
- zrb/builtin/llm/chat_session.py +90 -30
- zrb/builtin/llm/chat_session_cmd.py +115 -22
- zrb/builtin/llm/chat_trigger.py +92 -5
- zrb/builtin/llm/history.py +14 -7
- zrb/builtin/llm/llm_ask.py +16 -7
- zrb/builtin/llm/tool/cli.py +34 -15
- zrb/builtin/llm/tool/file.py +14 -2
- zrb/builtin/llm/tool/search/brave.py +8 -2
- zrb/builtin/llm/tool/search/searxng.py +8 -2
- zrb/builtin/llm/tool/search/serpapi.py +8 -2
- zrb/builtin/llm/tool/sub_agent.py +4 -1
- zrb/builtin/llm/tool/web.py +5 -0
- zrb/builtin/llm/xcom_names.py +3 -0
- zrb/callback/callback.py +8 -1
- zrb/cmd/cmd_result.py +2 -1
- zrb/config/config.py +6 -2
- zrb/config/default_prompt/interactive_system_prompt.md +15 -12
- zrb/config/default_prompt/system_prompt.md +16 -18
- zrb/config/llm_rate_limitter.py +36 -13
- zrb/context/context.py +11 -0
- zrb/input/option_input.py +30 -2
- zrb/task/base/context.py +25 -13
- zrb/task/base/execution.py +52 -47
- zrb/task/base/lifecycle.py +1 -1
- zrb/task/base_task.py +31 -45
- zrb/task/base_trigger.py +0 -1
- zrb/task/cmd_task.py +3 -0
- zrb/task/llm/agent.py +39 -31
- zrb/task/llm/agent_runner.py +65 -3
- zrb/task/llm/default_workflow/researching/workflow.md +2 -0
- zrb/task/llm/history_list.py +13 -0
- zrb/task/llm/history_processor.py +4 -13
- zrb/task/llm/print_node.py +79 -25
- zrb/task/llm/prompt.py +70 -40
- zrb/task/llm/tool_wrapper.py +4 -1
- zrb/task/llm/workflow.py +54 -15
- zrb/task/llm_task.py +87 -33
- zrb/task/rsync_task.py +2 -0
- zrb/util/cmd/command.py +33 -10
- zrb/util/match.py +71 -0
- zrb/util/run.py +3 -3
- {zrb-1.21.31.dist-info → zrb-1.21.43.dist-info}/METADATA +1 -1
- {zrb-1.21.31.dist-info → zrb-1.21.43.dist-info}/RECORD +46 -43
- {zrb-1.21.31.dist-info → zrb-1.21.43.dist-info}/WHEEL +0 -0
- {zrb-1.21.31.dist-info → zrb-1.21.43.dist-info}/entry_points.txt +0 -0
|
@@ -11,16 +11,24 @@ from zrb.builtin.llm.chat_session_cmd import (
|
|
|
11
11
|
CLEAR_SUB_CMD,
|
|
12
12
|
HELP_CMD,
|
|
13
13
|
HELP_CMD_DESC,
|
|
14
|
+
LOAD_CMD,
|
|
15
|
+
LOAD_CMD_DESC,
|
|
14
16
|
MULTILINE_END_CMD,
|
|
15
17
|
MULTILINE_END_CMD_DESC,
|
|
16
18
|
MULTILINE_START_CMD,
|
|
17
19
|
MULTILINE_START_CMD_DESC,
|
|
18
20
|
QUIT_CMD,
|
|
19
21
|
QUIT_CMD_DESC,
|
|
22
|
+
RESPONSE_CMD,
|
|
23
|
+
RESPONSE_CMD_DESC,
|
|
24
|
+
RESPONSE_SAVE_SUB_CMD_DESC,
|
|
20
25
|
RUN_CLI_CMD,
|
|
21
26
|
RUN_CLI_CMD_DESC,
|
|
22
27
|
SAVE_CMD,
|
|
23
28
|
SAVE_CMD_DESC,
|
|
29
|
+
SAVE_SUB_CMD,
|
|
30
|
+
SESSION_CMD,
|
|
31
|
+
SESSION_CMD_DESC,
|
|
24
32
|
SET_SUB_CMD,
|
|
25
33
|
WORKFLOW_ADD_SUB_CMD_DESC,
|
|
26
34
|
WORKFLOW_CLEAR_SUB_CMD_DESC,
|
|
@@ -33,6 +41,8 @@ from zrb.builtin.llm.chat_session_cmd import (
|
|
|
33
41
|
YOLO_SET_FALSE_CMD_DESC,
|
|
34
42
|
YOLO_SET_TRUE_CMD_DESC,
|
|
35
43
|
)
|
|
44
|
+
from zrb.config.config import CFG
|
|
45
|
+
from zrb.util.match import fuzzy_match
|
|
36
46
|
|
|
37
47
|
if TYPE_CHECKING:
|
|
38
48
|
from prompt_toolkit.completion import Completer
|
|
@@ -51,10 +61,39 @@ def get_chat_completer() -> "Completer":
|
|
|
51
61
|
yield completion
|
|
52
62
|
for completion in self._complete_slash_file_command(document):
|
|
53
63
|
yield completion
|
|
64
|
+
for completion in self._complete_slash_session_load_command(document):
|
|
65
|
+
yield completion
|
|
54
66
|
# Appendix
|
|
55
67
|
for completion in self._complete_appendix(document):
|
|
56
68
|
yield completion
|
|
57
69
|
|
|
70
|
+
def _complete_slash_session_load_command(self, document: Document):
|
|
71
|
+
text = document.text_before_cursor
|
|
72
|
+
prefixes = []
|
|
73
|
+
for cmd in LOAD_CMD:
|
|
74
|
+
prefixes.append(f"{cmd} ")
|
|
75
|
+
|
|
76
|
+
for prefix in prefixes:
|
|
77
|
+
if text.startswith(prefix):
|
|
78
|
+
pattern = text[len(prefix) :]
|
|
79
|
+
# Use fuzzy_path_search but in LLM_HISTORY_DIR/save-point
|
|
80
|
+
save_point_dir = os.path.join(CFG.LLM_HISTORY_DIR, "save-point")
|
|
81
|
+
if not os.path.isdir(save_point_dir):
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
potential_options = self._fuzzy_path_search(
|
|
85
|
+
pattern, root=save_point_dir, dirs=False, files=True
|
|
86
|
+
)
|
|
87
|
+
for option in potential_options:
|
|
88
|
+
if option.startswith(save_point_dir):
|
|
89
|
+
rel_option = os.path.relpath(option, save_point_dir)
|
|
90
|
+
else:
|
|
91
|
+
rel_option = option
|
|
92
|
+
yield Completion(
|
|
93
|
+
f"{prefix}{rel_option}",
|
|
94
|
+
start_position=-len(text),
|
|
95
|
+
)
|
|
96
|
+
|
|
58
97
|
def _complete_slash_file_command(self, document: Document):
|
|
59
98
|
text = document.text_before_cursor
|
|
60
99
|
prefixes = []
|
|
@@ -64,7 +103,7 @@ def get_chat_completer() -> "Completer":
|
|
|
64
103
|
for prefix in prefixes:
|
|
65
104
|
if text.startswith(prefix):
|
|
66
105
|
pattern = text[len(prefix) :]
|
|
67
|
-
potential_options = self._fuzzy_path_search(pattern, dirs=
|
|
106
|
+
potential_options = self._fuzzy_path_search(pattern, dirs=True)
|
|
68
107
|
for prefixed_option in [
|
|
69
108
|
f"{prefix}{option}" for option in potential_options
|
|
70
109
|
]:
|
|
@@ -91,7 +130,7 @@ def get_chat_completer() -> "Completer":
|
|
|
91
130
|
if not token.startswith(prefix):
|
|
92
131
|
return
|
|
93
132
|
pattern = token[len(prefix) :]
|
|
94
|
-
potential_options = self._fuzzy_path_search(pattern, dirs=
|
|
133
|
+
potential_options = self._fuzzy_path_search(pattern, dirs=True)
|
|
95
134
|
for prefixed_option in [
|
|
96
135
|
f"{prefix}{option}" for option in potential_options
|
|
97
136
|
]:
|
|
@@ -117,8 +156,16 @@ def get_chat_completer() -> "Completer":
|
|
|
117
156
|
cmd_options[f"{cmd} {subcmd}"] = WORKFLOW_CLEAR_SUB_CMD_DESC
|
|
118
157
|
for subcmd in SET_SUB_CMD:
|
|
119
158
|
cmd_options[f"{cmd} {subcmd}"] = WORKFLOW_SET_SUB_CMD_DESC
|
|
159
|
+
for cmd in SESSION_CMD:
|
|
160
|
+
cmd_options[cmd] = SESSION_CMD_DESC
|
|
120
161
|
for cmd in SAVE_CMD:
|
|
121
162
|
cmd_options[cmd] = SAVE_CMD_DESC
|
|
163
|
+
for cmd in LOAD_CMD:
|
|
164
|
+
cmd_options[cmd] = LOAD_CMD_DESC
|
|
165
|
+
for cmd in RESPONSE_CMD:
|
|
166
|
+
cmd_options[cmd] = RESPONSE_CMD_DESC
|
|
167
|
+
for subcmd in SAVE_SUB_CMD:
|
|
168
|
+
cmd_options[f"{cmd} {subcmd}"] = RESPONSE_SAVE_SUB_CMD_DESC
|
|
122
169
|
for cmd in ATTACHMENT_CMD:
|
|
123
170
|
cmd_options[cmd] = ATTACHMENT_CMD_DESC
|
|
124
171
|
for subcmd in ADD_SUB_CMD:
|
|
@@ -156,47 +203,14 @@ def get_chat_completer() -> "Completer":
|
|
|
156
203
|
- dirs/files booleans let you restrict results
|
|
157
204
|
- returns list of relative paths (from root), sorted best-first
|
|
158
205
|
"""
|
|
159
|
-
search_pattern = pattern
|
|
160
|
-
if root is None:
|
|
161
|
-
# Determine root and adjust pattern if necessary
|
|
162
|
-
expanded_pattern = os.path.expanduser(pattern)
|
|
163
|
-
if os.path.isabs(expanded_pattern) or pattern.startswith("~"):
|
|
164
|
-
# For absolute paths, find the deepest existing directory
|
|
165
|
-
if os.path.isdir(expanded_pattern):
|
|
166
|
-
root = expanded_pattern
|
|
167
|
-
search_pattern = ""
|
|
168
|
-
else:
|
|
169
|
-
root = os.path.dirname(expanded_pattern)
|
|
170
|
-
while root and not os.path.isdir(root) and len(root) > 1:
|
|
171
|
-
root = os.path.dirname(root)
|
|
172
|
-
if not os.path.isdir(root):
|
|
173
|
-
root = "." # Fallback
|
|
174
|
-
search_pattern = pattern
|
|
175
|
-
else:
|
|
176
|
-
try:
|
|
177
|
-
search_pattern = os.path.relpath(expanded_pattern, root)
|
|
178
|
-
if search_pattern == ".":
|
|
179
|
-
search_pattern = ""
|
|
180
|
-
except ValueError:
|
|
181
|
-
search_pattern = os.path.basename(pattern)
|
|
182
|
-
else:
|
|
183
|
-
root = "."
|
|
184
|
-
search_pattern = pattern
|
|
185
|
-
# Normalize pattern -> tokens split on path separators or whitespace
|
|
186
|
-
search_pattern = search_pattern.strip()
|
|
187
|
-
if search_pattern:
|
|
188
|
-
raw_tokens = [t for t in search_pattern.split(os.path.sep) if t]
|
|
189
|
-
else:
|
|
190
|
-
raw_tokens = []
|
|
191
|
-
# prepare tokens (case)
|
|
192
|
-
if not case_sensitive:
|
|
193
|
-
tokens = [t.lower() for t in raw_tokens]
|
|
194
|
-
else:
|
|
195
|
-
tokens = raw_tokens
|
|
206
|
+
root, search_pattern = self._get_root_and_search_pattern(pattern, root)
|
|
196
207
|
# specific ignore list
|
|
197
208
|
try:
|
|
198
|
-
|
|
199
|
-
|
|
209
|
+
abs_root = os.path.abspath(os.path.expanduser(root))
|
|
210
|
+
abs_cwd = os.path.abspath(os.getcwd())
|
|
211
|
+
# Check if root is a subdirectory of cwd or is cwd itself
|
|
212
|
+
is_recursive = (
|
|
213
|
+
abs_root.startswith(abs_cwd + os.sep) or abs_root == abs_cwd
|
|
200
214
|
)
|
|
201
215
|
except Exception:
|
|
202
216
|
is_recursive = False
|
|
@@ -230,29 +244,8 @@ def get_chat_completer() -> "Completer":
|
|
|
230
244
|
):
|
|
231
245
|
continue
|
|
232
246
|
cand = display_path.replace(os.sep, "/") # unify separator
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
score = 0.0
|
|
236
|
-
matched_all = True
|
|
237
|
-
for token in tokens:
|
|
238
|
-
# try contiguous substring search first
|
|
239
|
-
idx = cand_cmp.find(token, last_pos)
|
|
240
|
-
if idx != -1:
|
|
241
|
-
# good match: reward contiguous early matches
|
|
242
|
-
score += idx # smaller idx preferred
|
|
243
|
-
last_pos = idx + len(token)
|
|
244
|
-
else:
|
|
245
|
-
# fallback to subsequence matching
|
|
246
|
-
pos = self._find_subsequence_pos(cand_cmp, token, last_pos)
|
|
247
|
-
if pos is None:
|
|
248
|
-
matched_all = False
|
|
249
|
-
break
|
|
250
|
-
# subsequence match is less preferred than contiguous substring
|
|
251
|
-
score += pos + 0.5 * len(token)
|
|
252
|
-
last_pos = pos + len(token)
|
|
253
|
-
if matched_all:
|
|
254
|
-
# prefer shorter paths when score ties, so include length as tiebreaker
|
|
255
|
-
score += 0.01 * len(cand)
|
|
247
|
+
matched, score = fuzzy_match(cand, search_pattern)
|
|
248
|
+
if matched:
|
|
256
249
|
out = (
|
|
257
250
|
cand
|
|
258
251
|
if os.path.abspath(cand) == cand
|
|
@@ -263,25 +256,42 @@ def get_chat_completer() -> "Completer":
|
|
|
263
256
|
candidates.sort(key=lambda x: (x[0], x[1]))
|
|
264
257
|
return [p for _, p in candidates[:max_results]]
|
|
265
258
|
|
|
266
|
-
def
|
|
267
|
-
self,
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
259
|
+
def _get_root_and_search_pattern(
|
|
260
|
+
self,
|
|
261
|
+
pattern: str,
|
|
262
|
+
root: str | None = None,
|
|
263
|
+
) -> tuple[str, str]:
|
|
264
|
+
search_pattern = pattern
|
|
265
|
+
if root is None:
|
|
266
|
+
# Determine root and adjust pattern if necessary
|
|
267
|
+
expanded_pattern = os.path.expanduser(pattern)
|
|
268
|
+
if os.path.isabs(expanded_pattern) or pattern.startswith("~"):
|
|
269
|
+
# For absolute paths, find the deepest existing directory
|
|
270
|
+
if os.path.isdir(expanded_pattern):
|
|
271
|
+
root = expanded_pattern
|
|
272
|
+
return (root, "")
|
|
273
|
+
root = os.path.dirname(expanded_pattern)
|
|
274
|
+
while root and not os.path.isdir(root) and len(root) > 1:
|
|
275
|
+
root = os.path.dirname(root)
|
|
276
|
+
if not os.path.isdir(root):
|
|
277
|
+
return (".", pattern) # Fallback
|
|
278
|
+
try:
|
|
279
|
+
search_pattern = os.path.relpath(expanded_pattern, root)
|
|
280
|
+
if search_pattern == ".":
|
|
281
|
+
return (root, "")
|
|
282
|
+
except ValueError:
|
|
283
|
+
return (root, os.path.basename(pattern))
|
|
284
|
+
# Handle redundant current directory prefixes (e.g., ./ or .\)
|
|
285
|
+
if pattern.startswith(f".{os.sep}"):
|
|
286
|
+
return (".", pattern[len(f".{os.sep}") :])
|
|
287
|
+
if os.sep != "/" and pattern.startswith("./"):
|
|
288
|
+
return (".", pattern[2:])
|
|
289
|
+
return (".", pattern)
|
|
290
|
+
|
|
291
|
+
if pattern.startswith(f".{os.sep}"):
|
|
292
|
+
pattern = pattern[len(f".{os.sep}") :]
|
|
293
|
+
elif os.sep != "/" and pattern.startswith("./"):
|
|
294
|
+
pattern = pattern[2:]
|
|
295
|
+
return (root, pattern)
|
|
286
296
|
|
|
287
297
|
return ChatCompleter()
|
zrb/builtin/llm/chat_session.py
CHANGED
|
@@ -5,27 +5,39 @@ from typing import TYPE_CHECKING, Any
|
|
|
5
5
|
from zrb.builtin.llm.chat_session_cmd import (
|
|
6
6
|
ATTACHMENT_CMD,
|
|
7
7
|
HELP_CMD,
|
|
8
|
+
LOAD_CMD,
|
|
8
9
|
MULTILINE_END_CMD,
|
|
9
10
|
MULTILINE_START_CMD,
|
|
10
11
|
QUIT_CMD,
|
|
12
|
+
RESPONSE_CMD,
|
|
11
13
|
RUN_CLI_CMD,
|
|
12
14
|
SAVE_CMD,
|
|
15
|
+
SESSION_CMD,
|
|
13
16
|
WORKFLOW_CMD,
|
|
14
17
|
YOLO_CMD,
|
|
15
18
|
get_new_attachments,
|
|
16
19
|
get_new_workflows,
|
|
17
20
|
get_new_yolo_mode,
|
|
21
|
+
handle_response_cmd,
|
|
22
|
+
handle_session,
|
|
18
23
|
is_command_match,
|
|
19
24
|
print_commands,
|
|
20
25
|
print_current_attachments,
|
|
21
26
|
print_current_workflows,
|
|
22
27
|
print_current_yolo_mode,
|
|
23
28
|
run_cli_command,
|
|
24
|
-
save_final_result,
|
|
25
29
|
)
|
|
26
30
|
from zrb.builtin.llm.chat_trigger import llm_chat_trigger
|
|
31
|
+
from zrb.builtin.llm.history import get_last_session_name
|
|
32
|
+
from zrb.builtin.llm.xcom_names import (
|
|
33
|
+
LLM_ASK_ERROR_XCOM_NAME,
|
|
34
|
+
LLM_ASK_RESULT_XCOM_NAME,
|
|
35
|
+
LLM_ASK_SESSION_XCOM_NAME,
|
|
36
|
+
)
|
|
27
37
|
from zrb.config.llm_config import llm_config
|
|
28
38
|
from zrb.context.any_context import AnyContext
|
|
39
|
+
from zrb.context.any_shared_context import AnySharedContext
|
|
40
|
+
from zrb.task.llm.workflow import get_llm_loaded_workflow_xcom
|
|
29
41
|
from zrb.util.cli.markdown import render_markdown
|
|
30
42
|
|
|
31
43
|
if TYPE_CHECKING:
|
|
@@ -44,30 +56,39 @@ async def read_user_prompt(ctx: AnyContext) -> str:
|
|
|
44
56
|
reader: PromptSession[Any] | StreamReader = await _setup_input_reader(is_tty)
|
|
45
57
|
multiline_mode = False
|
|
46
58
|
is_first_time = True
|
|
47
|
-
current_workflows: str = ctx.input.
|
|
59
|
+
current_workflows: str = ctx.input.workflow
|
|
48
60
|
current_yolo_mode: bool | str = ctx.input.yolo
|
|
49
61
|
current_attachments: str = ctx.input.attach
|
|
50
62
|
user_inputs: list[str] = []
|
|
51
63
|
final_result: str = ""
|
|
52
64
|
should_end = False
|
|
65
|
+
start_new: bool = ctx.input.start_new
|
|
66
|
+
if not start_new and ctx.input.previous_session == "":
|
|
67
|
+
session = ctx.session
|
|
68
|
+
if session is not None:
|
|
69
|
+
# Automatically inject last session name as previous session
|
|
70
|
+
last_session_name = get_last_session_name()
|
|
71
|
+
session.shared_ctx.input["previous_session"] = last_session_name
|
|
72
|
+
session.shared_ctx.input["previous-session"] = last_session_name
|
|
73
|
+
current_session_name: str | None = ctx.input.previous_session
|
|
53
74
|
while not should_end:
|
|
54
|
-
await asyncio.sleep(0
|
|
55
|
-
previous_session_name: str | None = (
|
|
56
|
-
ctx.input.previous_session if is_first_time else ""
|
|
57
|
-
)
|
|
58
|
-
start_new: bool = ctx.input.start_new if is_first_time else False
|
|
75
|
+
await asyncio.sleep(0)
|
|
59
76
|
if is_first_time and ctx.input.message.strip() != "":
|
|
60
77
|
user_input = ctx.input.message
|
|
61
78
|
else:
|
|
62
79
|
# Get user input based on mode
|
|
63
80
|
if not multiline_mode:
|
|
64
81
|
ctx.print("💬 >>", plain=True)
|
|
65
|
-
user_input = await llm_chat_trigger.wait(
|
|
82
|
+
user_input = await llm_chat_trigger.wait(
|
|
83
|
+
ctx, reader, current_session_name, is_first_time
|
|
84
|
+
)
|
|
66
85
|
if not multiline_mode:
|
|
67
86
|
ctx.print("", plain=True)
|
|
68
87
|
# At this point, is_first_time has to be False
|
|
69
88
|
if is_first_time:
|
|
70
89
|
is_first_time = False
|
|
90
|
+
# Add additional workflows activated by LLM in the previous session
|
|
91
|
+
current_workflows = _get_new_workflows_from_xcom(ctx, current_workflows)
|
|
71
92
|
# Handle user input (including slash commands)
|
|
72
93
|
if multiline_mode:
|
|
73
94
|
if is_command_match(user_input, MULTILINE_END_CMD):
|
|
@@ -87,8 +108,8 @@ async def read_user_prompt(ctx: AnyContext) -> str:
|
|
|
87
108
|
current_workflows = get_new_workflows(current_workflows, user_input)
|
|
88
109
|
print_current_workflows(ctx, current_workflows)
|
|
89
110
|
continue
|
|
90
|
-
elif is_command_match(user_input,
|
|
91
|
-
|
|
111
|
+
elif is_command_match(user_input, RESPONSE_CMD):
|
|
112
|
+
handle_response_cmd(ctx, user_input, final_result)
|
|
92
113
|
continue
|
|
93
114
|
elif is_command_match(user_input, ATTACHMENT_CMD):
|
|
94
115
|
current_attachments = get_new_attachments(
|
|
@@ -101,25 +122,35 @@ async def read_user_prompt(ctx: AnyContext) -> str:
|
|
|
101
122
|
print_current_yolo_mode(ctx, current_yolo_mode)
|
|
102
123
|
continue
|
|
103
124
|
elif is_command_match(user_input, RUN_CLI_CMD):
|
|
104
|
-
run_cli_command(ctx, user_input)
|
|
125
|
+
await run_cli_command(ctx, user_input)
|
|
105
126
|
continue
|
|
106
127
|
elif is_command_match(user_input, HELP_CMD):
|
|
107
128
|
print_commands(ctx)
|
|
108
129
|
continue
|
|
130
|
+
elif (
|
|
131
|
+
is_command_match(user_input, SESSION_CMD)
|
|
132
|
+
or is_command_match(user_input, SAVE_CMD)
|
|
133
|
+
or is_command_match(user_input, LOAD_CMD)
|
|
134
|
+
):
|
|
135
|
+
current_session_name, start_new = handle_session(
|
|
136
|
+
ctx, current_session_name, start_new, user_input
|
|
137
|
+
)
|
|
109
138
|
else:
|
|
110
139
|
user_inputs.append(user_input)
|
|
111
140
|
# Trigger LLM
|
|
112
141
|
user_prompt = "\n".join(user_inputs)
|
|
113
142
|
user_inputs = []
|
|
114
|
-
result = await _trigger_ask_and_wait_for_result(
|
|
143
|
+
result, current_session_name = await _trigger_ask_and_wait_for_result(
|
|
115
144
|
ctx=ctx,
|
|
116
145
|
user_prompt=user_prompt,
|
|
117
146
|
attach=current_attachments,
|
|
118
147
|
workflows=current_workflows,
|
|
119
148
|
yolo_mode=current_yolo_mode,
|
|
120
|
-
previous_session_name=
|
|
149
|
+
previous_session_name=current_session_name,
|
|
121
150
|
start_new=start_new,
|
|
122
151
|
)
|
|
152
|
+
# After the first trigger, we no longer need to force start_new
|
|
153
|
+
start_new = False
|
|
123
154
|
current_attachments = ""
|
|
124
155
|
final_result = final_result if result is None else result
|
|
125
156
|
if ctx.is_web_mode or not is_tty:
|
|
@@ -127,6 +158,23 @@ async def read_user_prompt(ctx: AnyContext) -> str:
|
|
|
127
158
|
return final_result
|
|
128
159
|
|
|
129
160
|
|
|
161
|
+
def _get_new_workflows_from_xcom(ctx: AnyContext, current_workflows: str):
|
|
162
|
+
llm_loaded_workflow_xcom = get_llm_loaded_workflow_xcom(ctx)
|
|
163
|
+
new_workflow_names = [
|
|
164
|
+
workflow_name.strip()
|
|
165
|
+
for workflow_name in current_workflows.split(",")
|
|
166
|
+
if workflow_name.strip() != ""
|
|
167
|
+
]
|
|
168
|
+
while len(llm_loaded_workflow_xcom) > 0:
|
|
169
|
+
additional_workflow_names = [
|
|
170
|
+
workflow_name
|
|
171
|
+
for workflow_name in llm_loaded_workflow_xcom.pop()
|
|
172
|
+
if workflow_name not in new_workflow_names
|
|
173
|
+
]
|
|
174
|
+
new_workflow_names += additional_workflow_names
|
|
175
|
+
return ",".join(new_workflow_names)
|
|
176
|
+
|
|
177
|
+
|
|
130
178
|
async def _setup_input_reader(
|
|
131
179
|
is_interactive: bool,
|
|
132
180
|
) -> "PromptSession[Any] | StreamReader":
|
|
@@ -151,7 +199,7 @@ async def _trigger_ask_and_wait_for_result(
|
|
|
151
199
|
yolo_mode: bool | str,
|
|
152
200
|
previous_session_name: str | None = None,
|
|
153
201
|
start_new: bool = False,
|
|
154
|
-
) -> str | None:
|
|
202
|
+
) -> tuple[str | None, str | None]:
|
|
155
203
|
"""
|
|
156
204
|
Triggers the LLM ask task and waits for the result via XCom.
|
|
157
205
|
|
|
@@ -162,22 +210,27 @@ async def _trigger_ask_and_wait_for_result(
|
|
|
162
210
|
start_new: Whether to start a new conversation (optional).
|
|
163
211
|
|
|
164
212
|
Returns:
|
|
165
|
-
The result from the LLM task
|
|
213
|
+
The result from the LLM task and the session name.
|
|
166
214
|
"""
|
|
167
215
|
if user_prompt.strip() == "":
|
|
168
|
-
return None
|
|
216
|
+
return None, previous_session_name
|
|
169
217
|
await _trigger_ask(
|
|
170
218
|
ctx, user_prompt, attach, workflows, yolo_mode, previous_session_name, start_new
|
|
171
219
|
)
|
|
172
220
|
result = await _wait_ask_result(ctx)
|
|
221
|
+
|
|
222
|
+
resolved_session_name = previous_session_name
|
|
223
|
+
if result is not None:
|
|
224
|
+
resolved_session_name = await _wait_ask_session_name(ctx)
|
|
225
|
+
|
|
173
226
|
md_result = render_markdown(result) if result is not None else ""
|
|
174
227
|
ctx.print("\n🤖 >>", plain=True)
|
|
175
228
|
ctx.print(md_result, plain=True)
|
|
176
229
|
ctx.print("", plain=True)
|
|
177
|
-
return result
|
|
230
|
+
return result, resolved_session_name
|
|
178
231
|
|
|
179
232
|
|
|
180
|
-
def get_llm_ask_input_mapping(callback_ctx: AnyContext):
|
|
233
|
+
def get_llm_ask_input_mapping(callback_ctx: AnyContext | AnySharedContext):
|
|
181
234
|
"""
|
|
182
235
|
Generates the input mapping for the LLM ask task from the callback context.
|
|
183
236
|
|
|
@@ -200,7 +253,7 @@ def get_llm_ask_input_mapping(callback_ctx: AnyContext):
|
|
|
200
253
|
"previous-session": data.get("previous_session_name"),
|
|
201
254
|
"message": data.get("message"),
|
|
202
255
|
"attach": data.get("attach"),
|
|
203
|
-
"
|
|
256
|
+
"workflow": data.get("workflow"),
|
|
204
257
|
"yolo": data.get("yolo"),
|
|
205
258
|
}
|
|
206
259
|
|
|
@@ -223,15 +276,13 @@ async def _trigger_ask(
|
|
|
223
276
|
previous_session_name: The name of the previous chat session (optional).
|
|
224
277
|
start_new: Whether to start a new conversation (optional).
|
|
225
278
|
"""
|
|
226
|
-
if previous_session_name is None:
|
|
227
|
-
previous_session_name = await _wait_ask_session_name(ctx)
|
|
228
279
|
ctx.xcom["ask_trigger"].push(
|
|
229
280
|
{
|
|
230
281
|
"previous_session_name": previous_session_name,
|
|
231
282
|
"start_new": start_new,
|
|
232
283
|
"message": user_prompt,
|
|
233
284
|
"attach": attach,
|
|
234
|
-
"
|
|
285
|
+
"workflow": workflows,
|
|
235
286
|
"yolo": yolo_mode,
|
|
236
287
|
}
|
|
237
288
|
)
|
|
@@ -247,12 +298,18 @@ async def _wait_ask_result(ctx: AnyContext) -> str | None:
|
|
|
247
298
|
Returns:
|
|
248
299
|
The result string from the LLM task.
|
|
249
300
|
"""
|
|
250
|
-
while
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
301
|
+
while (
|
|
302
|
+
LLM_ASK_RESULT_XCOM_NAME not in ctx.xcom
|
|
303
|
+
or len(ctx.xcom[LLM_ASK_RESULT_XCOM_NAME]) == 0
|
|
304
|
+
):
|
|
305
|
+
await asyncio.sleep(0)
|
|
306
|
+
if (
|
|
307
|
+
LLM_ASK_ERROR_XCOM_NAME in ctx.xcom
|
|
308
|
+
and len(ctx.xcom[LLM_ASK_ERROR_XCOM_NAME]) > 0
|
|
309
|
+
):
|
|
310
|
+
ctx.xcom[LLM_ASK_ERROR_XCOM_NAME].pop()
|
|
254
311
|
return None
|
|
255
|
-
return ctx.xcom.
|
|
312
|
+
return ctx.xcom[LLM_ASK_RESULT_XCOM_NAME].pop()
|
|
256
313
|
|
|
257
314
|
|
|
258
315
|
async def _wait_ask_session_name(ctx: AnyContext) -> str:
|
|
@@ -265,6 +322,9 @@ async def _wait_ask_session_name(ctx: AnyContext) -> str:
|
|
|
265
322
|
Returns:
|
|
266
323
|
The session name string.
|
|
267
324
|
"""
|
|
268
|
-
while
|
|
269
|
-
|
|
270
|
-
|
|
325
|
+
while (
|
|
326
|
+
LLM_ASK_SESSION_XCOM_NAME not in ctx.xcom
|
|
327
|
+
or len(ctx.xcom[LLM_ASK_SESSION_XCOM_NAME]) == 0
|
|
328
|
+
):
|
|
329
|
+
await asyncio.sleep(0)
|
|
330
|
+
return ctx.xcom[LLM_ASK_SESSION_XCOM_NAME].pop()
|