zrb 1.13.1__py3-none-any.whl → 1.21.33__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.
Files changed (117) hide show
  1. zrb/__init__.py +2 -6
  2. zrb/attr/type.py +10 -7
  3. zrb/builtin/__init__.py +2 -0
  4. zrb/builtin/git.py +12 -1
  5. zrb/builtin/group.py +31 -15
  6. zrb/builtin/http.py +7 -8
  7. zrb/builtin/llm/attachment.py +40 -0
  8. zrb/builtin/llm/chat_completion.py +287 -0
  9. zrb/builtin/llm/chat_session.py +130 -144
  10. zrb/builtin/llm/chat_session_cmd.py +288 -0
  11. zrb/builtin/llm/chat_trigger.py +78 -0
  12. zrb/builtin/llm/history.py +4 -4
  13. zrb/builtin/llm/llm_ask.py +218 -110
  14. zrb/builtin/llm/tool/api.py +74 -62
  15. zrb/builtin/llm/tool/cli.py +56 -21
  16. zrb/builtin/llm/tool/code.py +57 -47
  17. zrb/builtin/llm/tool/file.py +292 -255
  18. zrb/builtin/llm/tool/note.py +84 -0
  19. zrb/builtin/llm/tool/rag.py +25 -18
  20. zrb/builtin/llm/tool/search/__init__.py +1 -0
  21. zrb/builtin/llm/tool/search/brave.py +66 -0
  22. zrb/builtin/llm/tool/search/searxng.py +61 -0
  23. zrb/builtin/llm/tool/search/serpapi.py +61 -0
  24. zrb/builtin/llm/tool/sub_agent.py +53 -26
  25. zrb/builtin/llm/tool/web.py +94 -157
  26. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
  27. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
  28. zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
  29. zrb/builtin/searxng/config/settings.yml +5671 -0
  30. zrb/builtin/searxng/start.py +21 -0
  31. zrb/builtin/setup/latex/ubuntu.py +1 -0
  32. zrb/builtin/setup/ubuntu.py +1 -1
  33. zrb/builtin/shell/autocomplete/bash.py +4 -3
  34. zrb/builtin/shell/autocomplete/zsh.py +4 -3
  35. zrb/config/config.py +297 -79
  36. zrb/config/default_prompt/file_extractor_system_prompt.md +109 -9
  37. zrb/config/default_prompt/interactive_system_prompt.md +25 -28
  38. zrb/config/default_prompt/persona.md +1 -1
  39. zrb/config/default_prompt/repo_extractor_system_prompt.md +31 -31
  40. zrb/config/default_prompt/repo_summarizer_system_prompt.md +27 -8
  41. zrb/config/default_prompt/summarization_prompt.md +57 -16
  42. zrb/config/default_prompt/system_prompt.md +29 -25
  43. zrb/config/llm_config.py +129 -24
  44. zrb/config/llm_context/config.py +127 -90
  45. zrb/config/llm_context/config_parser.py +1 -7
  46. zrb/config/llm_context/workflow.py +81 -0
  47. zrb/config/llm_rate_limitter.py +100 -47
  48. zrb/context/any_shared_context.py +7 -1
  49. zrb/context/context.py +8 -2
  50. zrb/context/shared_context.py +6 -8
  51. zrb/group/any_group.py +12 -5
  52. zrb/group/group.py +67 -3
  53. zrb/input/any_input.py +5 -1
  54. zrb/input/base_input.py +18 -6
  55. zrb/input/option_input.py +13 -1
  56. zrb/input/text_input.py +7 -24
  57. zrb/runner/cli.py +21 -20
  58. zrb/runner/common_util.py +24 -19
  59. zrb/runner/web_route/task_input_api_route.py +5 -5
  60. zrb/runner/web_route/task_session_api_route.py +1 -4
  61. zrb/runner/web_util/user.py +7 -3
  62. zrb/session/any_session.py +12 -6
  63. zrb/session/session.py +39 -18
  64. zrb/task/any_task.py +24 -3
  65. zrb/task/base/context.py +17 -9
  66. zrb/task/base/execution.py +15 -8
  67. zrb/task/base/lifecycle.py +8 -4
  68. zrb/task/base/monitoring.py +12 -7
  69. zrb/task/base_task.py +69 -5
  70. zrb/task/base_trigger.py +12 -5
  71. zrb/task/llm/agent.py +130 -145
  72. zrb/task/llm/agent_runner.py +152 -0
  73. zrb/task/llm/config.py +45 -13
  74. zrb/task/llm/conversation_history.py +110 -29
  75. zrb/task/llm/conversation_history_model.py +4 -179
  76. zrb/task/llm/default_workflow/coding/workflow.md +41 -0
  77. zrb/task/llm/default_workflow/copywriting/workflow.md +68 -0
  78. zrb/task/llm/default_workflow/git/workflow.md +118 -0
  79. zrb/task/llm/default_workflow/golang/workflow.md +128 -0
  80. zrb/task/llm/default_workflow/html-css/workflow.md +135 -0
  81. zrb/task/llm/default_workflow/java/workflow.md +146 -0
  82. zrb/task/llm/default_workflow/javascript/workflow.md +158 -0
  83. zrb/task/llm/default_workflow/python/workflow.md +160 -0
  84. zrb/task/llm/default_workflow/researching/workflow.md +153 -0
  85. zrb/task/llm/default_workflow/rust/workflow.md +162 -0
  86. zrb/task/llm/default_workflow/shell/workflow.md +299 -0
  87. zrb/task/llm/file_replacement.py +206 -0
  88. zrb/task/llm/file_tool_model.py +57 -0
  89. zrb/task/llm/history_processor.py +206 -0
  90. zrb/task/llm/history_summarization.py +2 -192
  91. zrb/task/llm/print_node.py +192 -64
  92. zrb/task/llm/prompt.py +198 -153
  93. zrb/task/llm/subagent_conversation_history.py +41 -0
  94. zrb/task/llm/tool_confirmation_completer.py +41 -0
  95. zrb/task/llm/tool_wrapper.py +216 -55
  96. zrb/task/llm/workflow.py +76 -0
  97. zrb/task/llm_task.py +122 -70
  98. zrb/task/make_task.py +2 -3
  99. zrb/task/rsync_task.py +25 -10
  100. zrb/task/scheduler.py +4 -4
  101. zrb/util/attr.py +54 -39
  102. zrb/util/cli/markdown.py +12 -0
  103. zrb/util/cli/text.py +30 -0
  104. zrb/util/file.py +27 -11
  105. zrb/util/git.py +2 -2
  106. zrb/util/{llm/prompt.py → markdown.py} +2 -3
  107. zrb/util/string/conversion.py +1 -1
  108. zrb/util/truncate.py +23 -0
  109. zrb/util/yaml.py +204 -0
  110. zrb/xcom/xcom.py +10 -0
  111. {zrb-1.13.1.dist-info → zrb-1.21.33.dist-info}/METADATA +40 -20
  112. {zrb-1.13.1.dist-info → zrb-1.21.33.dist-info}/RECORD +114 -83
  113. {zrb-1.13.1.dist-info → zrb-1.21.33.dist-info}/WHEEL +1 -1
  114. zrb/task/llm/default_workflow/coding.md +0 -24
  115. zrb/task/llm/default_workflow/copywriting.md +0 -17
  116. zrb/task/llm/default_workflow/researching.md +0 -18
  117. {zrb-1.13.1.dist-info → zrb-1.21.33.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,78 @@
1
+ import asyncio
2
+ import os
3
+ from asyncio import StreamReader
4
+ from typing import TYPE_CHECKING, Any, Callable, Coroutine
5
+
6
+ from zrb.builtin.llm.chat_completion import get_chat_completer
7
+ from zrb.context.any_context import AnyContext
8
+ from zrb.util.run import run_async
9
+
10
+ if TYPE_CHECKING:
11
+ from prompt_toolkit import PromptSession
12
+
13
+
14
+ ChatTrigger = Callable[[AnyContext], Coroutine[Any, Any, str] | str]
15
+
16
+
17
+ class LLMChatTrigger:
18
+
19
+ def __init__(self):
20
+ self._triggers: list[ChatTrigger] = []
21
+
22
+ def add_trigger(self, *trigger: ChatTrigger):
23
+ self.append_trigger(*trigger)
24
+
25
+ def append_trigger(self, *trigger: ChatTrigger):
26
+ for single_trigger in trigger:
27
+ self._triggers.append(single_trigger)
28
+
29
+ async def wait(
30
+ self, reader: "PromptSession[Any] | StreamReader", ctx: AnyContext
31
+ ) -> str:
32
+ trigger_tasks = [
33
+ asyncio.create_task(run_async(self._read_next_line(reader, ctx)))
34
+ ] + [asyncio.create_task(run_async(trigger(ctx))) for trigger in self._triggers]
35
+ final_result: str = ""
36
+ try:
37
+ done, pending = await asyncio.wait(
38
+ trigger_tasks, return_when=asyncio.FIRST_COMPLETED
39
+ )
40
+ for task in done:
41
+ final_result = await task
42
+ if pending:
43
+ for task in pending:
44
+ task.cancel()
45
+ for task in done:
46
+ break
47
+ except asyncio.CancelledError:
48
+ ctx.print("Task cancelled.", plain=True)
49
+ final_result = "/bye"
50
+ except KeyboardInterrupt:
51
+ ctx.print("KeyboardInterrupt detected. Exiting...", plain=True)
52
+ final_result = "/bye"
53
+ return final_result
54
+
55
+ async def _read_next_line(
56
+ self, reader: "PromptSession[Any] | StreamReader", ctx: AnyContext
57
+ ) -> str:
58
+ """Reads one line of input using the provided reader."""
59
+ from prompt_toolkit import PromptSession
60
+
61
+ try:
62
+ if isinstance(reader, PromptSession):
63
+ bottom_toolbar = f"📁 Current directory: {os.getcwd()}"
64
+ return await reader.prompt_async(
65
+ completer=get_chat_completer(), bottom_toolbar=bottom_toolbar
66
+ )
67
+ line_bytes = await reader.readline()
68
+ if not line_bytes:
69
+ return "/bye" # Signal to exit
70
+ user_input = line_bytes.decode().strip()
71
+ ctx.print(user_input, plain=True)
72
+ return user_input
73
+ except KeyboardInterrupt:
74
+ ctx.print("KeyboardInterrupt detected. Exiting...", plain=True)
75
+ return "/bye"
76
+
77
+
78
+ llm_chat_trigger = LLMChatTrigger()
@@ -3,12 +3,12 @@ import os
3
3
  from typing import Any
4
4
 
5
5
  from zrb.config.config import CFG
6
- from zrb.context.any_shared_context import AnySharedContext
6
+ from zrb.context.any_context import AnyContext
7
7
  from zrb.task.llm.conversation_history_model import ConversationHistory
8
8
  from zrb.util.file import read_file, write_file
9
9
 
10
10
 
11
- def read_chat_conversation(ctx: AnySharedContext) -> dict[str, Any] | list | None:
11
+ def read_chat_conversation(ctx: AnyContext) -> dict[str, Any] | list | None:
12
12
  """Reads conversation history from the session file.
13
13
  Returns the raw dictionary or list loaded from JSON, or None if not found/empty.
14
14
  The LLMTask will handle parsing this into ConversationHistory.
@@ -51,10 +51,10 @@ def read_chat_conversation(ctx: AnySharedContext) -> dict[str, Any] | list | Non
51
51
  return None
52
52
 
53
53
 
54
- def write_chat_conversation(ctx: AnySharedContext, history_data: ConversationHistory):
54
+ def write_chat_conversation(ctx: AnyContext, history_data: ConversationHistory):
55
55
  """Writes the conversation history data (including context) to a session file."""
56
56
  os.makedirs(CFG.LLM_HISTORY_DIR, exist_ok=True)
57
- current_session_name = ctx.session.name
57
+ current_session_name = ctx.session.name if ctx.session is not None else None
58
58
  if not current_session_name:
59
59
  ctx.log_warning("Cannot write history: Session name is empty.")
60
60
  return
@@ -1,127 +1,257 @@
1
+ import os
2
+ from collections.abc import Callable
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from zrb.builtin.group import llm_group
6
+ from zrb.builtin.llm.attachment import get_media_type
2
7
  from zrb.builtin.llm.chat_session import get_llm_ask_input_mapping, read_user_prompt
3
8
  from zrb.builtin.llm.history import read_chat_conversation, write_chat_conversation
4
9
  from zrb.builtin.llm.input import PreviousSessionInput
5
- from zrb.builtin.llm.tool.api import get_current_location, get_current_weather
10
+ from zrb.builtin.llm.tool.api import (
11
+ create_get_current_location,
12
+ create_get_current_weather,
13
+ )
6
14
  from zrb.builtin.llm.tool.cli import run_shell_command
7
15
  from zrb.builtin.llm.tool.code import analyze_repo
8
16
  from zrb.builtin.llm.tool.file import (
9
17
  analyze_file,
10
18
  list_files,
11
19
  read_from_file,
12
- read_many_files,
13
20
  replace_in_file,
14
21
  search_files,
15
- write_many_files,
16
22
  write_to_file,
17
23
  )
24
+ from zrb.builtin.llm.tool.note import (
25
+ read_contextual_note,
26
+ read_long_term_note,
27
+ write_contextual_note,
28
+ write_long_term_note,
29
+ )
18
30
  from zrb.builtin.llm.tool.web import (
19
31
  create_search_internet_tool,
20
32
  open_web_page,
21
- search_arxiv,
22
- search_wikipedia,
23
33
  )
24
34
  from zrb.callback.callback import Callback
25
35
  from zrb.config.config import CFG
36
+ from zrb.config.llm_config import llm_config
37
+ from zrb.context.any_context import AnyContext
38
+ from zrb.input.any_input import AnyInput
26
39
  from zrb.input.bool_input import BoolInput
27
40
  from zrb.input.str_input import StrInput
28
41
  from zrb.input.text_input import TextInput
29
42
  from zrb.task.base_trigger import BaseTrigger
30
43
  from zrb.task.llm_task import LLMTask
44
+ from zrb.util.string.conversion import to_boolean
31
45
 
32
- _llm_ask_inputs = [
33
- StrInput(
34
- "model",
35
- description="LLM Model",
36
- prompt="LLM Model",
37
- default="",
38
- allow_positional_parsing=False,
39
- always_prompt=False,
40
- allow_empty=True,
41
- ),
42
- StrInput(
43
- "base-url",
44
- description="LLM API Base URL",
45
- prompt="LLM API Base URL",
46
- default="",
47
- allow_positional_parsing=False,
48
- always_prompt=False,
49
- allow_empty=True,
50
- ),
51
- StrInput(
52
- "api-key",
53
- description="LLM API Key",
54
- prompt="LLM API Key",
55
- default="",
56
- allow_positional_parsing=False,
57
- always_prompt=False,
58
- allow_empty=True,
59
- ),
60
- TextInput(
61
- "system-prompt",
62
- description="System prompt",
63
- prompt="System prompt",
64
- default="",
65
- allow_positional_parsing=False,
66
- always_prompt=False,
67
- ),
68
- TextInput(
69
- "modes",
70
- description="Modes",
71
- prompt="Modes",
72
- default="coding",
73
- allow_positional_parsing=False,
74
- always_prompt=False,
75
- ),
76
- BoolInput(
77
- "start-new",
78
- description="Start new conversation (LLM will forget everything)",
79
- prompt="Start new conversation (LLM will forget everything)",
80
- default=False,
81
- allow_positional_parsing=False,
82
- always_prompt=False,
83
- ),
84
- TextInput("message", description="User message", prompt="Your message"),
85
- PreviousSessionInput(
86
- "previous-session",
87
- description="Previous conversation session",
88
- prompt="Previous conversation session (can be empty)",
89
- allow_positional_parsing=False,
90
- allow_empty=True,
91
- always_prompt=False,
92
- ),
93
- ]
94
-
95
- llm_ask: LLMTask = llm_group.add_task(
96
- LLMTask(
97
- name="llm-ask",
98
- input=_llm_ask_inputs,
99
- description="❓ Ask LLM",
100
- model=lambda ctx: None if ctx.input.model.strip() == "" else ctx.input.model,
101
- model_base_url=lambda ctx: (
102
- None if ctx.input.base_url.strip() == "" else ctx.input.base_url
46
+ if TYPE_CHECKING:
47
+ from pydantic_ai import AbstractToolset, Tool, UserContent
48
+
49
+ ToolOrCallable = Tool | Callable
50
+
51
+
52
+ def _get_toolset(ctx: AnyContext) -> list["AbstractToolset[None] | str"]:
53
+ cwd = os.getcwd()
54
+ toolsets = []
55
+ for config_path in [
56
+ os.path.join(cwd, "mcp_config.json"),
57
+ os.path.join(cwd, "mcp-config.json"),
58
+ ]:
59
+ if os.path.isfile(config_path):
60
+ toolsets.append(config_path)
61
+ return toolsets
62
+
63
+
64
+ def _get_tool(ctx: AnyContext) -> list["ToolOrCallable"]:
65
+ tools = [
66
+ read_long_term_note,
67
+ write_long_term_note,
68
+ read_contextual_note,
69
+ write_contextual_note,
70
+ ]
71
+ if CFG.LLM_ALLOW_ANALYZE_REPO:
72
+ tools.append(analyze_repo)
73
+ if CFG.LLM_ALLOW_ANALYZE_FILE:
74
+ tools.append(analyze_file)
75
+ if CFG.LLM_ALLOW_ACCESS_LOCAL_FILE:
76
+ tools.append(search_files)
77
+ tools.append(list_files)
78
+ tools.append(read_from_file)
79
+ tools.append(replace_in_file)
80
+ tools.append(write_to_file)
81
+ if CFG.LLM_ALLOW_ACCESS_SHELL:
82
+ tools.append(run_shell_command)
83
+ if CFG.LLM_ALLOW_OPEN_WEB_PAGE:
84
+ tools.append(open_web_page)
85
+ if CFG.LLM_ALLOW_GET_CURRENT_LOCATION:
86
+ tools.append(create_get_current_location())
87
+ if CFG.LLM_ALLOW_GET_CURRENT_WEATHER:
88
+ tools.append(create_get_current_weather())
89
+ if CFG.SERPAPI_KEY != "" and CFG.LLM_ALLOW_SEARCH_INTERNET:
90
+ tools.append(create_search_internet_tool())
91
+ return tools
92
+
93
+
94
+ def _get_default_yolo_mode(ctx: AnyContext) -> str:
95
+ default_value = llm_config.default_yolo_mode
96
+ if isinstance(default_value, list):
97
+ return ",".join(default_value)
98
+ return f"{default_value}"
99
+
100
+
101
+ def _render_yolo_mode_input(ctx: AnyContext) -> list[str] | bool:
102
+ if ctx.input.yolo.strip() == "":
103
+ return []
104
+ elements = [element.strip() for element in ctx.input.yolo.split(",")]
105
+ if len(elements) == 1:
106
+ try:
107
+ return to_boolean(elements[0])
108
+ except Exception:
109
+ pass
110
+ return elements
111
+
112
+
113
+ def _render_attach_input(ctx: AnyContext) -> "list[UserContent]":
114
+ from pathlib import Path
115
+
116
+ from pydantic_ai import BinaryContent
117
+
118
+ attachment_paths: list[str] = [
119
+ attachment_path.strip()
120
+ for attachment_path in ctx.input.attach.split(",")
121
+ if attachment_path.strip() != ""
122
+ ]
123
+ if len(attachment_paths) == 0:
124
+ return []
125
+ attachments = []
126
+ for attachment_path in attachment_paths:
127
+ attachment_path = os.path.abspath(os.path.expanduser(attachment_path))
128
+ media_type = get_media_type(attachment_path)
129
+ if media_type is None:
130
+ ctx.log_error(f"Cannot get media type of {attachment_path}")
131
+ continue
132
+ data = Path(attachment_path).read_bytes()
133
+ attachments.append(BinaryContent(data, media_type=media_type))
134
+ return attachments
135
+
136
+
137
+ def _get_inputs(require_message: bool = True) -> list[AnyInput | None]:
138
+ return [
139
+ StrInput(
140
+ "model",
141
+ description="LLM Model",
142
+ prompt="LLM Model",
143
+ default="",
144
+ allow_positional_parsing=False,
145
+ always_prompt=False,
146
+ allow_empty=True,
103
147
  ),
104
- model_api_key=lambda ctx: (
105
- None if ctx.input.api_key.strip() == "" else ctx.input.api_key
148
+ StrInput(
149
+ "base-url",
150
+ description="LLM API Base URL",
151
+ prompt="LLM API Base URL",
152
+ default="",
153
+ allow_positional_parsing=False,
154
+ always_prompt=False,
155
+ allow_empty=True,
106
156
  ),
107
- conversation_history_reader=read_chat_conversation,
108
- conversation_history_writer=write_chat_conversation,
109
- system_prompt=lambda ctx: (
110
- None if ctx.input.system_prompt.strip() == "" else ctx.input.system_prompt
157
+ StrInput(
158
+ "api-key",
159
+ description="LLM API Key",
160
+ prompt="LLM API Key",
161
+ default="",
162
+ allow_positional_parsing=False,
163
+ always_prompt=False,
164
+ allow_empty=True,
111
165
  ),
112
- modes=lambda ctx: (
113
- None if ctx.input.modes.strip() == "" else ctx.input.modes.split(",")
166
+ TextInput(
167
+ "system-prompt",
168
+ description="System prompt",
169
+ prompt="System prompt",
170
+ default="",
171
+ allow_positional_parsing=False,
172
+ always_prompt=False,
114
173
  ),
115
- message="{ctx.input.message}",
116
- retries=0,
174
+ TextInput(
175
+ "workflows",
176
+ description="Workflows",
177
+ prompt="Workflows",
178
+ default=lambda ctx: ",".join(llm_config.default_workflows),
179
+ allow_positional_parsing=False,
180
+ always_prompt=False,
181
+ ),
182
+ BoolInput(
183
+ "start-new",
184
+ description="Start new session (LLM Agent will forget past conversation)",
185
+ prompt="Start new session (LLM Agent will forget past conversation)",
186
+ default=False,
187
+ allow_positional_parsing=False,
188
+ always_prompt=False,
189
+ ),
190
+ StrInput(
191
+ "yolo",
192
+ description="YOLO mode (LLM Agent will start in YOLO Mode)",
193
+ prompt="YOLO mode (LLM Agent will start in YOLO Mode)",
194
+ default=_get_default_yolo_mode,
195
+ allow_positional_parsing=False,
196
+ always_prompt=False,
197
+ ),
198
+ TextInput(
199
+ "message",
200
+ description="User message",
201
+ prompt="Your message",
202
+ always_prompt=require_message,
203
+ allow_empty=not require_message,
204
+ ),
205
+ StrInput(
206
+ name="attach",
207
+ description="Comma separated attachments",
208
+ default="",
209
+ allow_positional_parsing=False,
210
+ always_prompt=False,
211
+ ),
212
+ PreviousSessionInput(
213
+ "previous-session",
214
+ description="Previous conversation session",
215
+ prompt="Previous conversation session (can be empty)",
216
+ allow_positional_parsing=False,
217
+ allow_empty=True,
218
+ always_prompt=False,
219
+ ),
220
+ ]
221
+
222
+
223
+ llm_ask = LLMTask(
224
+ name="llm-ask",
225
+ input=_get_inputs(True),
226
+ description="❓ Ask LLM",
227
+ model=lambda ctx: None if ctx.input.model.strip() == "" else ctx.input.model,
228
+ model_base_url=lambda ctx: (
229
+ None if ctx.input.base_url.strip() == "" else ctx.input.base_url
117
230
  ),
118
- alias="ask",
231
+ model_api_key=lambda ctx: (
232
+ None if ctx.input.api_key.strip() == "" else ctx.input.api_key
233
+ ),
234
+ conversation_history_reader=read_chat_conversation,
235
+ conversation_history_writer=write_chat_conversation,
236
+ system_prompt=lambda ctx: (
237
+ None if ctx.input.system_prompt.strip() == "" else ctx.input.system_prompt
238
+ ),
239
+ workflows=lambda ctx: (
240
+ None if ctx.input.workflows.strip() == "" else ctx.input.workflows.split(",")
241
+ ),
242
+ attachment=_render_attach_input,
243
+ message="{ctx.input.message}",
244
+ tools=_get_tool,
245
+ toolsets=_get_toolset,
246
+ yolo_mode=_render_yolo_mode_input,
247
+ retries=0,
119
248
  )
249
+ llm_group.add_task(llm_ask, alias="ask")
120
250
 
121
251
  llm_group.add_task(
122
252
  BaseTrigger(
123
253
  name="llm-chat",
124
- input=_llm_ask_inputs,
254
+ input=_get_inputs(False),
125
255
  description="💬 Chat with LLM",
126
256
  queue_name="ask_trigger",
127
257
  action=read_user_prompt,
@@ -137,25 +267,3 @@ llm_group.add_task(
137
267
  ),
138
268
  alias="chat",
139
269
  )
140
-
141
- if CFG.LLM_ALLOW_ACCESS_LOCAL_FILE:
142
- llm_ask.append_tool(
143
- analyze_repo,
144
- analyze_file,
145
- search_files,
146
- list_files,
147
- read_from_file,
148
- read_many_files,
149
- replace_in_file,
150
- write_to_file,
151
- write_many_files,
152
- )
153
-
154
- if CFG.LLM_ALLOW_ACCESS_SHELL:
155
- llm_ask.append_tool(run_shell_command)
156
-
157
- if CFG.LLM_ALLOW_ACCESS_INTERNET:
158
- llm_ask.append_tool(open_web_page, search_wikipedia, search_arxiv)
159
- if CFG.SERPAPI_KEY != "":
160
- llm_ask.append_tool(create_search_internet_tool(CFG.SERPAPI_KEY))
161
- llm_ask.append_tool(get_current_location, get_current_weather)
@@ -1,63 +1,75 @@
1
- import json
2
- from typing import Literal
3
-
4
-
5
- def get_current_location() -> str:
6
- """
7
- Fetches the user's current geographical location (latitude and longitude) based on their IP address.
8
-
9
- Use this tool when the user asks "Where am I?", "What is my current location?", or has a query that requires knowing their location to be answered.
10
-
11
- Returns:
12
- str: A JSON string containing the 'lat' and 'lon' of the current location.
13
- Example: '{"lat": 48.8584, "lon": 2.2945}'
14
- Raises:
15
- requests.RequestException: If the API request to the location service fails.
16
- """
17
- import requests
18
-
19
- try:
20
- response = requests.get("http://ip-api.com/json?fields=lat,lon", timeout=5)
21
- response.raise_for_status()
22
- return json.dumps(response.json())
23
- except requests.RequestException as e:
24
- raise requests.RequestException(f"Failed to get location: {e}") from None
25
-
26
-
27
- def get_current_weather(
28
- latitude: float,
29
- longitude: float,
30
- temperature_unit: Literal["celsius", "fahrenheit"],
31
- ) -> str:
32
- """
33
- Retrieves the current weather conditions for a given geographical location.
34
-
35
- Use this tool when the user asks about the weather. If the user does not provide a location, first use the `get_current_location` tool to determine their location.
36
-
37
- Args:
38
- latitude (float): The latitude of the location.
39
- longitude (float): The longitude of the location.
40
- temperature_unit (Literal["celsius", "fahrenheit"]): The desired unit for the temperature reading.
41
-
42
- Returns:
43
- str: A JSON string containing detailed weather data, including temperature, wind speed, and weather code.
44
- Raises:
45
- requests.RequestException: If the API request to the weather service fails.
46
- """
47
- import requests
48
-
49
- try:
50
- response = requests.get(
51
- "https://api.open-meteo.com/v1/forecast",
52
- params={
53
- "latitude": latitude,
54
- "longitude": longitude,
55
- "temperature_unit": temperature_unit,
56
- "current_weather": True,
57
- },
58
- timeout=5,
1
+ from typing import Any, Callable, Literal
2
+
3
+ from zrb.config.llm_config import llm_config
4
+
5
+
6
+ def create_get_current_location() -> Callable:
7
+ if llm_config.default_current_location_tool is not None:
8
+ return llm_config.default_current_location_tool
9
+
10
+ def get_current_location() -> dict[str, float]:
11
+ """
12
+ Gets the user's current geographical location (latitude and longitude).
13
+
14
+ Example:
15
+ get_current_location() # Returns {'lat': 48.8584, 'lon': 2.2945}
16
+
17
+ Returns:
18
+ dict: Dictionary with 'lat' and 'lon' keys.
19
+ """
20
+ import requests
21
+
22
+ try:
23
+ response = requests.get("http://ip-api.com/json?fields=lat,lon", timeout=5)
24
+ response.raise_for_status()
25
+ return dict(response.json())
26
+ except requests.RequestException as e:
27
+ raise requests.RequestException(f"Failed to get location: {e}")
28
+
29
+ return get_current_location
30
+
31
+
32
+ def create_get_current_weather() -> Callable:
33
+ if llm_config.default_current_weather_tool is not None:
34
+ return llm_config.default_current_weather_tool
35
+
36
+ def get_current_weather(
37
+ latitude: float,
38
+ longitude: float,
39
+ temperature_unit: Literal["celsius", "fahrenheit"],
40
+ ) -> dict[str, Any]:
41
+ """
42
+ Gets current weather conditions for a given location.
43
+
44
+ Example:
45
+ get_current_weather(
46
+ latitude=34.0522, longitude=-118.2437, temperature_unit='fahrenheit'
59
47
  )
60
- response.raise_for_status()
61
- return json.dumps(response.json())
62
- except requests.RequestException as e:
63
- raise requests.RequestException(f"Failed to get weather data: {e}") from None
48
+
49
+ Args:
50
+ latitude (float): Latitude of the location.
51
+ longitude (float): Longitude of the location.
52
+ temperature_unit (Literal): 'celsius' or 'fahrenheit'.
53
+
54
+ Returns:
55
+ dict: Detailed weather data.
56
+ """
57
+ import requests
58
+
59
+ try:
60
+ response = requests.get(
61
+ "https://api.open-meteo.com/v1/forecast",
62
+ params={
63
+ "latitude": latitude,
64
+ "longitude": longitude,
65
+ "temperature_unit": temperature_unit,
66
+ "current_weather": True,
67
+ },
68
+ timeout=5,
69
+ )
70
+ response.raise_for_status()
71
+ return dict(response.json())
72
+ except requests.RequestException as e:
73
+ raise requests.RequestException(f"Failed to get weather data: {e}")
74
+
75
+ return get_current_weather