zrb 1.21.29__py3-none-any.whl → 2.0.0a4__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/__init__.py +118 -129
- zrb/builtin/__init__.py +54 -2
- zrb/builtin/llm/chat.py +147 -0
- zrb/callback/callback.py +8 -1
- zrb/cmd/cmd_result.py +2 -1
- zrb/config/config.py +491 -280
- zrb/config/helper.py +84 -0
- zrb/config/web_auth_config.py +50 -35
- zrb/context/any_shared_context.py +13 -2
- zrb/context/context.py +31 -3
- zrb/context/print_fn.py +13 -0
- zrb/context/shared_context.py +14 -1
- zrb/input/option_input.py +30 -2
- zrb/llm/agent/__init__.py +9 -0
- zrb/llm/agent/agent.py +215 -0
- zrb/llm/agent/summarizer.py +20 -0
- zrb/llm/app/__init__.py +10 -0
- zrb/llm/app/completion.py +281 -0
- zrb/llm/app/confirmation/allow_tool.py +66 -0
- zrb/llm/app/confirmation/handler.py +178 -0
- zrb/llm/app/confirmation/replace_confirmation.py +77 -0
- zrb/llm/app/keybinding.py +34 -0
- zrb/llm/app/layout.py +117 -0
- zrb/llm/app/lexer.py +155 -0
- zrb/llm/app/redirection.py +28 -0
- zrb/llm/app/style.py +16 -0
- zrb/llm/app/ui.py +733 -0
- zrb/llm/config/__init__.py +4 -0
- zrb/llm/config/config.py +122 -0
- zrb/llm/config/limiter.py +247 -0
- zrb/llm/history_manager/__init__.py +4 -0
- zrb/llm/history_manager/any_history_manager.py +23 -0
- zrb/llm/history_manager/file_history_manager.py +91 -0
- zrb/llm/history_processor/summarizer.py +108 -0
- zrb/llm/note/__init__.py +3 -0
- zrb/llm/note/manager.py +122 -0
- zrb/llm/prompt/__init__.py +29 -0
- zrb/llm/prompt/claude_compatibility.py +92 -0
- zrb/llm/prompt/compose.py +55 -0
- zrb/llm/prompt/default.py +51 -0
- zrb/llm/prompt/markdown/mandate.md +23 -0
- zrb/llm/prompt/markdown/persona.md +3 -0
- zrb/llm/prompt/markdown/summarizer.md +21 -0
- zrb/llm/prompt/note.py +41 -0
- zrb/llm/prompt/system_context.py +46 -0
- zrb/llm/prompt/zrb.py +41 -0
- zrb/llm/skill/__init__.py +3 -0
- zrb/llm/skill/manager.py +86 -0
- zrb/llm/task/__init__.py +4 -0
- zrb/llm/task/llm_chat_task.py +316 -0
- zrb/llm/task/llm_task.py +245 -0
- zrb/llm/tool/__init__.py +39 -0
- zrb/llm/tool/bash.py +75 -0
- zrb/llm/tool/code.py +266 -0
- zrb/llm/tool/file.py +419 -0
- zrb/llm/tool/note.py +70 -0
- zrb/{builtin/llm → llm}/tool/rag.py +8 -5
- zrb/llm/tool/search/brave.py +53 -0
- zrb/llm/tool/search/searxng.py +47 -0
- zrb/llm/tool/search/serpapi.py +47 -0
- zrb/llm/tool/skill.py +19 -0
- zrb/llm/tool/sub_agent.py +70 -0
- zrb/llm/tool/web.py +97 -0
- zrb/llm/tool/zrb_task.py +66 -0
- zrb/llm/util/attachment.py +101 -0
- zrb/llm/util/prompt.py +104 -0
- zrb/llm/util/stream_response.py +178 -0
- zrb/session/any_session.py +0 -3
- zrb/session/session.py +1 -1
- zrb/task/base/context.py +25 -13
- zrb/task/base/execution.py +52 -47
- zrb/task/base/lifecycle.py +7 -4
- zrb/task/base_task.py +48 -49
- zrb/task/base_trigger.py +4 -1
- zrb/task/cmd_task.py +6 -0
- zrb/task/http_check.py +11 -5
- zrb/task/make_task.py +3 -0
- zrb/task/rsync_task.py +5 -0
- zrb/task/scaffolder.py +7 -4
- zrb/task/scheduler.py +3 -0
- zrb/task/tcp_check.py +6 -4
- zrb/util/ascii_art/art/bee.txt +17 -0
- zrb/util/ascii_art/art/cat.txt +9 -0
- zrb/util/ascii_art/art/ghost.txt +16 -0
- zrb/util/ascii_art/art/panda.txt +17 -0
- zrb/util/ascii_art/art/rose.txt +14 -0
- zrb/util/ascii_art/art/unicorn.txt +15 -0
- zrb/util/ascii_art/banner.py +92 -0
- zrb/util/cli/markdown.py +22 -2
- zrb/util/cmd/command.py +33 -10
- zrb/util/file.py +51 -32
- zrb/util/match.py +78 -0
- zrb/util/run.py +3 -3
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/METADATA +9 -15
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/RECORD +100 -128
- zrb/attr/__init__.py +0 -0
- zrb/builtin/llm/attachment.py +0 -40
- zrb/builtin/llm/chat_completion.py +0 -274
- zrb/builtin/llm/chat_session.py +0 -270
- zrb/builtin/llm/chat_session_cmd.py +0 -288
- zrb/builtin/llm/chat_trigger.py +0 -79
- zrb/builtin/llm/history.py +0 -71
- zrb/builtin/llm/input.py +0 -27
- zrb/builtin/llm/llm_ask.py +0 -269
- zrb/builtin/llm/previous-session.js +0 -21
- zrb/builtin/llm/tool/__init__.py +0 -0
- zrb/builtin/llm/tool/api.py +0 -75
- zrb/builtin/llm/tool/cli.py +0 -52
- zrb/builtin/llm/tool/code.py +0 -236
- zrb/builtin/llm/tool/file.py +0 -560
- zrb/builtin/llm/tool/note.py +0 -84
- zrb/builtin/llm/tool/sub_agent.py +0 -150
- zrb/builtin/llm/tool/web.py +0 -171
- zrb/builtin/project/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/service/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/__init__.py +0 -0
- zrb/builtin/project/create/__init__.py +0 -0
- zrb/builtin/shell/__init__.py +0 -0
- zrb/builtin/shell/autocomplete/__init__.py +0 -0
- zrb/callback/__init__.py +0 -0
- zrb/cmd/__init__.py +0 -0
- zrb/config/default_prompt/interactive_system_prompt.md +0 -29
- zrb/config/default_prompt/persona.md +0 -1
- zrb/config/default_prompt/summarization_prompt.md +0 -57
- zrb/config/default_prompt/system_prompt.md +0 -38
- zrb/config/llm_config.py +0 -339
- zrb/config/llm_context/config.py +0 -166
- zrb/config/llm_context/config_parser.py +0 -40
- zrb/config/llm_context/workflow.py +0 -81
- zrb/config/llm_rate_limitter.py +0 -190
- zrb/content_transformer/__init__.py +0 -0
- zrb/context/__init__.py +0 -0
- zrb/dot_dict/__init__.py +0 -0
- zrb/env/__init__.py +0 -0
- zrb/group/__init__.py +0 -0
- zrb/input/__init__.py +0 -0
- zrb/runner/__init__.py +0 -0
- zrb/runner/web_route/__init__.py +0 -0
- zrb/runner/web_route/home_page/__init__.py +0 -0
- zrb/session/__init__.py +0 -0
- zrb/session_state_log/__init__.py +0 -0
- zrb/session_state_logger/__init__.py +0 -0
- zrb/task/__init__.py +0 -0
- zrb/task/base/__init__.py +0 -0
- zrb/task/llm/__init__.py +0 -0
- zrb/task/llm/agent.py +0 -204
- zrb/task/llm/agent_runner.py +0 -152
- zrb/task/llm/config.py +0 -122
- zrb/task/llm/conversation_history.py +0 -209
- zrb/task/llm/conversation_history_model.py +0 -67
- zrb/task/llm/default_workflow/coding/workflow.md +0 -41
- zrb/task/llm/default_workflow/copywriting/workflow.md +0 -68
- zrb/task/llm/default_workflow/git/workflow.md +0 -118
- zrb/task/llm/default_workflow/golang/workflow.md +0 -128
- zrb/task/llm/default_workflow/html-css/workflow.md +0 -135
- zrb/task/llm/default_workflow/java/workflow.md +0 -146
- zrb/task/llm/default_workflow/javascript/workflow.md +0 -158
- zrb/task/llm/default_workflow/python/workflow.md +0 -160
- zrb/task/llm/default_workflow/researching/workflow.md +0 -153
- zrb/task/llm/default_workflow/rust/workflow.md +0 -162
- zrb/task/llm/default_workflow/shell/workflow.md +0 -299
- zrb/task/llm/error.py +0 -95
- zrb/task/llm/file_replacement.py +0 -206
- zrb/task/llm/file_tool_model.py +0 -57
- zrb/task/llm/history_processor.py +0 -206
- zrb/task/llm/history_summarization.py +0 -25
- zrb/task/llm/print_node.py +0 -221
- zrb/task/llm/prompt.py +0 -321
- zrb/task/llm/subagent_conversation_history.py +0 -41
- zrb/task/llm/tool_wrapper.py +0 -361
- zrb/task/llm/typing.py +0 -3
- zrb/task/llm/workflow.py +0 -76
- zrb/task/llm_task.py +0 -379
- zrb/task_status/__init__.py +0 -0
- zrb/util/__init__.py +0 -0
- zrb/util/cli/__init__.py +0 -0
- zrb/util/cmd/__init__.py +0 -0
- zrb/util/codemod/__init__.py +0 -0
- zrb/util/string/__init__.py +0 -0
- zrb/xcom/__init__.py +0 -0
- /zrb/{config/default_prompt/file_extractor_system_prompt.md → llm/prompt/markdown/file_extractor.md} +0 -0
- /zrb/{config/default_prompt/repo_extractor_system_prompt.md → llm/prompt/markdown/repo_extractor.md} +0 -0
- /zrb/{config/default_prompt/repo_summarizer_system_prompt.md → llm/prompt/markdown/repo_summarizer.md} +0 -0
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/WHEEL +0 -0
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/entry_points.txt +0 -0
zrb/llm/tool/note.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Callable, List
|
|
3
|
+
|
|
4
|
+
from zrb.llm.note.manager import NoteManager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_note_tools(note_manager: NoteManager) -> List[Callable]:
|
|
8
|
+
async def read_long_term_note() -> str:
|
|
9
|
+
"""
|
|
10
|
+
Retrieves your GLOBAL 🧠 Long-Term Memory.
|
|
11
|
+
This contains established preferences, personal facts, and context spanning multiple projects.
|
|
12
|
+
ALWAYS check this at the start of a session.
|
|
13
|
+
"""
|
|
14
|
+
return note_manager.read("~")
|
|
15
|
+
|
|
16
|
+
read_long_term_note.__name__ = "read_long_term_note"
|
|
17
|
+
|
|
18
|
+
async def write_long_term_note(content: str) -> str:
|
|
19
|
+
"""
|
|
20
|
+
Updates your GLOBAL 🧠 Long-Term Memory with CRITICAL information.
|
|
21
|
+
Use this to persist user preferences, personal facts, and cross-project rules.
|
|
22
|
+
|
|
23
|
+
**WARNING:** This COMPLETELY OVERWRITES the existing Long-Term Note.
|
|
24
|
+
|
|
25
|
+
**ARGS:**
|
|
26
|
+
- `content`: The full text to store in the global memory.
|
|
27
|
+
"""
|
|
28
|
+
note_manager.write("~", content)
|
|
29
|
+
return "Global long-term note saved."
|
|
30
|
+
|
|
31
|
+
write_long_term_note.__name__ = "write_long_term_note"
|
|
32
|
+
|
|
33
|
+
async def read_contextual_note(path: str | None = None) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Retrieves LOCAL 📝 Contextual Notes for a specific project or directory.
|
|
36
|
+
Use this to recall architectural decisions or project-specific guidelines.
|
|
37
|
+
|
|
38
|
+
**ARGS:**
|
|
39
|
+
- `path`: Target file/dir path. Defaults to current working directory.
|
|
40
|
+
"""
|
|
41
|
+
if path is None:
|
|
42
|
+
path = os.getcwd()
|
|
43
|
+
return note_manager.read(path)
|
|
44
|
+
|
|
45
|
+
read_contextual_note.__name__ = "read_contextual_note"
|
|
46
|
+
|
|
47
|
+
async def write_contextual_note(content: str, path: str | None = None) -> str:
|
|
48
|
+
"""
|
|
49
|
+
Persists LOCAL 📝 Contextual Notes for a specific project or directory.
|
|
50
|
+
Use this to save architectural patterns or progress markers for the current task.
|
|
51
|
+
|
|
52
|
+
**WARNING:** This COMPLETELY OVERWRITES the contextual note for the specified path.
|
|
53
|
+
|
|
54
|
+
**ARGS:**
|
|
55
|
+
- `content`: The full text to store in the local memory.
|
|
56
|
+
- `path`: Target file/dir path. Defaults to current working directory.
|
|
57
|
+
"""
|
|
58
|
+
if path is None:
|
|
59
|
+
path = os.getcwd()
|
|
60
|
+
note_manager.write(path, content)
|
|
61
|
+
return f"Contextual note saved for: {path}"
|
|
62
|
+
|
|
63
|
+
write_contextual_note.__name__ = "write_contextual_note"
|
|
64
|
+
|
|
65
|
+
return [
|
|
66
|
+
read_long_term_note,
|
|
67
|
+
write_long_term_note,
|
|
68
|
+
read_contextual_note,
|
|
69
|
+
write_contextual_note,
|
|
70
|
+
]
|
|
@@ -199,11 +199,14 @@ def create_rag_from_directory(
|
|
|
199
199
|
retrieve.__doc__ = dedent(
|
|
200
200
|
f"""
|
|
201
201
|
{tool_description}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
202
|
+
This tool performs a semantic search across a curated knowledge base of documents.
|
|
203
|
+
It is highly effective for answering questions that require specific project knowledge not found in general training data.
|
|
204
|
+
|
|
205
|
+
**ARGS:**
|
|
206
|
+
- `query` (str): The semantic search query or question.
|
|
207
|
+
|
|
208
|
+
**RETURNS:**
|
|
209
|
+
- A dictionary containing matching document chunks ("documents") and their metadata.
|
|
207
210
|
"""
|
|
208
211
|
).strip()
|
|
209
212
|
return retrieve
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from zrb.config.config import CFG
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def search_internet(
|
|
9
|
+
query: str,
|
|
10
|
+
page: int = 1,
|
|
11
|
+
safe_search: str | None = None,
|
|
12
|
+
language: str | None = None,
|
|
13
|
+
) -> dict[str, Any]:
|
|
14
|
+
"""
|
|
15
|
+
Performs a live internet search using Brave Search to retrieve up-to-date information, news, or documentation.
|
|
16
|
+
|
|
17
|
+
**WHEN TO USE:**
|
|
18
|
+
- To find the latest information on rapidly changing topics (e.g., library updates, current events).
|
|
19
|
+
- To search for documentation or examples not present in the local codebase.
|
|
20
|
+
- To verify facts or find external resources.
|
|
21
|
+
|
|
22
|
+
**ARGS:**
|
|
23
|
+
- `query`: The search string or question.
|
|
24
|
+
- `page`: Result page number (default 1).
|
|
25
|
+
"""
|
|
26
|
+
if safe_search is None:
|
|
27
|
+
safe_search = CFG.BRAVE_API_SAFE
|
|
28
|
+
if language is None:
|
|
29
|
+
language = CFG.BRAVE_API_LANG
|
|
30
|
+
|
|
31
|
+
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
|
32
|
+
|
|
33
|
+
response = requests.get(
|
|
34
|
+
"https://api.search.brave.com/res/v1/web/search",
|
|
35
|
+
headers={
|
|
36
|
+
"User-Agent": user_agent,
|
|
37
|
+
"Accept": "application/json",
|
|
38
|
+
"x-subscription-token": CFG.BRAVE_API_KEY,
|
|
39
|
+
},
|
|
40
|
+
params={
|
|
41
|
+
"q": query,
|
|
42
|
+
"count": "10",
|
|
43
|
+
"offset": (page - 1) * 10,
|
|
44
|
+
"safesearch": safe_search,
|
|
45
|
+
"search_lang": language,
|
|
46
|
+
"summary": "true",
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
if response.status_code != 200:
|
|
50
|
+
raise Exception(
|
|
51
|
+
f"Error: Unable to retrieve search results (status code: {response.status_code})"
|
|
52
|
+
)
|
|
53
|
+
return response.json()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from zrb.config.config import CFG
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def search_internet(
|
|
9
|
+
query: str,
|
|
10
|
+
page: int = 1,
|
|
11
|
+
safe_search: int | None = None,
|
|
12
|
+
language: str | None = None,
|
|
13
|
+
) -> dict[str, Any]:
|
|
14
|
+
"""
|
|
15
|
+
Performs a live internet search using SearXNG, an aggregator that combines results from multiple search engines.
|
|
16
|
+
|
|
17
|
+
**WHEN TO USE:**
|
|
18
|
+
- To gather diverse perspectives or information from across the web.
|
|
19
|
+
- To retrieve the latest data, documentation, or public resources.
|
|
20
|
+
|
|
21
|
+
**ARGS:**
|
|
22
|
+
- `query`: The search string or question.
|
|
23
|
+
- `page`: Result page number (default 1).
|
|
24
|
+
"""
|
|
25
|
+
if safe_search is None:
|
|
26
|
+
safe_search = CFG.SEARXNG_SAFE
|
|
27
|
+
if language is None:
|
|
28
|
+
language = CFG.SEARXNG_LANG
|
|
29
|
+
|
|
30
|
+
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
|
31
|
+
|
|
32
|
+
response = requests.get(
|
|
33
|
+
url=f"{CFG.SEARXNG_BASE_URL}/search",
|
|
34
|
+
headers={"User-Agent": user_agent},
|
|
35
|
+
params={
|
|
36
|
+
"q": query,
|
|
37
|
+
"format": "json",
|
|
38
|
+
"pageno": page,
|
|
39
|
+
"safesearch": safe_search,
|
|
40
|
+
"language": language,
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
if response.status_code != 200:
|
|
44
|
+
raise Exception(
|
|
45
|
+
f"Error: Unable to retrieve search results (status code: {response.status_code})"
|
|
46
|
+
)
|
|
47
|
+
return response.json()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from zrb.config.config import CFG
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def search_internet(
|
|
9
|
+
query: str,
|
|
10
|
+
page: int = 1,
|
|
11
|
+
safe_search: str | None = None,
|
|
12
|
+
language: str | None = None,
|
|
13
|
+
) -> dict[str, Any]:
|
|
14
|
+
"""
|
|
15
|
+
Performs a live internet search using SerpApi (Google Search) to retrieve the most relevant and current information from the web.
|
|
16
|
+
|
|
17
|
+
**WHEN TO USE:**
|
|
18
|
+
- When you need precise, high-quality search results from Google.
|
|
19
|
+
- To find the latest official documentation, technical articles, or community discussions.
|
|
20
|
+
|
|
21
|
+
**ARGS:**
|
|
22
|
+
- `query`: The search string or question.
|
|
23
|
+
- `page`: Result page number (default 1).
|
|
24
|
+
"""
|
|
25
|
+
if safe_search is None:
|
|
26
|
+
safe_search = CFG.SERPAPI_SAFE
|
|
27
|
+
if language is None:
|
|
28
|
+
language = CFG.SERPAPI_LANG
|
|
29
|
+
|
|
30
|
+
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
|
31
|
+
|
|
32
|
+
response = requests.get(
|
|
33
|
+
"https://serpapi.com/search",
|
|
34
|
+
headers={"User-Agent": user_agent},
|
|
35
|
+
params={
|
|
36
|
+
"q": query,
|
|
37
|
+
"start": (page - 1) * 10,
|
|
38
|
+
"hl": language,
|
|
39
|
+
"safe": safe_search,
|
|
40
|
+
"api_key": CFG.SERPAPI_KEY,
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
if response.status_code != 200:
|
|
44
|
+
raise Exception(
|
|
45
|
+
f"Error: Unable to retrieve search results (status code: {response.status_code})"
|
|
46
|
+
)
|
|
47
|
+
return response.json()
|
zrb/llm/tool/skill.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from zrb.llm.skill.manager import SkillManager
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def create_activate_skill_tool(skill_manager: SkillManager):
|
|
5
|
+
async def activate_skill_impl(name: str) -> str:
|
|
6
|
+
content = skill_manager.get_skill_content(name)
|
|
7
|
+
if content:
|
|
8
|
+
return f"<ACTIVATED_SKILL>\n{content}\n</ACTIVATED_SKILL>"
|
|
9
|
+
return f"Skill '{name}' not found."
|
|
10
|
+
|
|
11
|
+
activate_skill_impl.__name__ = "activate_skill"
|
|
12
|
+
activate_skill_impl.__doc__ = (
|
|
13
|
+
"Immediately activates a specialized expertise 'skill' to handle complex or domain-specific tasks. "
|
|
14
|
+
"Returns a set of authoritative instructions that YOU MUST follow to complete the task successfully. "
|
|
15
|
+
"Use this as soon as you identify a task that matches an available skill. "
|
|
16
|
+
"\n\n**ARGS:**"
|
|
17
|
+
"\n- `name`: The unique name of the skill to activate."
|
|
18
|
+
)
|
|
19
|
+
return activate_skill_impl
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from zrb.llm.agent.agent import create_agent, run_agent
|
|
4
|
+
from zrb.llm.config.config import LLMConfig
|
|
5
|
+
from zrb.llm.config.config import llm_config as default_config
|
|
6
|
+
from zrb.llm.config.limiter import LLMLimiter
|
|
7
|
+
from zrb.llm.config.limiter import llm_limiter as default_limiter
|
|
8
|
+
from zrb.llm.history_manager.any_history_manager import AnyHistoryManager
|
|
9
|
+
from zrb.llm.history_manager.file_history_manager import FileHistoryManager
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def create_sub_agent_tool(
|
|
13
|
+
name: str,
|
|
14
|
+
description: str,
|
|
15
|
+
model: str | None = None,
|
|
16
|
+
system_prompt: str = "",
|
|
17
|
+
tools: list = [],
|
|
18
|
+
llm_config: LLMConfig | None = None,
|
|
19
|
+
llm_limitter: LLMLimiter | None = None,
|
|
20
|
+
history_manager: AnyHistoryManager | None = None,
|
|
21
|
+
conversation_name: str = "sub_agent_default",
|
|
22
|
+
) -> Any:
|
|
23
|
+
"""
|
|
24
|
+
Creates a Tool that invokes a sub-agent.
|
|
25
|
+
The sub-agent manages its own persistent history via HistoryManager
|
|
26
|
+
and handles approval via CLI fallback if needed.
|
|
27
|
+
"""
|
|
28
|
+
config = llm_config or default_config
|
|
29
|
+
limiter = llm_limitter or default_limiter
|
|
30
|
+
manager = history_manager or FileHistoryManager(history_dir="~/.llm_chat/subagents")
|
|
31
|
+
final_model = model or config.model
|
|
32
|
+
|
|
33
|
+
agent_instance = None
|
|
34
|
+
|
|
35
|
+
async def run_sub_agent(prompt: str) -> str:
|
|
36
|
+
nonlocal agent_instance
|
|
37
|
+
if agent_instance is None:
|
|
38
|
+
agent_instance = create_agent(
|
|
39
|
+
model=final_model,
|
|
40
|
+
system_prompt=system_prompt,
|
|
41
|
+
tools=tools,
|
|
42
|
+
yolo=False,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Load persistent history
|
|
46
|
+
history = manager.load(conversation_name)
|
|
47
|
+
|
|
48
|
+
# Execute agent with blocking confirmation loop for approvals
|
|
49
|
+
result, new_history = await run_agent(
|
|
50
|
+
agent=agent_instance,
|
|
51
|
+
message=prompt,
|
|
52
|
+
message_history=history,
|
|
53
|
+
limiter=limiter,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Save updated history
|
|
57
|
+
manager.update(conversation_name, new_history)
|
|
58
|
+
manager.save(conversation_name)
|
|
59
|
+
|
|
60
|
+
return str(result)
|
|
61
|
+
|
|
62
|
+
run_sub_agent.__name__ = name
|
|
63
|
+
run_sub_agent.__doc__ = (
|
|
64
|
+
f"DELEGATION TOOL: {description}\n\n"
|
|
65
|
+
"Use this tool to delegate complex, multi-step sub-tasks to a specialized agent. "
|
|
66
|
+
"The sub-agent has its own memory and can perform its own tool calls."
|
|
67
|
+
"\n\n**ARGS:**"
|
|
68
|
+
"\n- `prompt`: The clear and detailed objective or instruction for the sub-agent."
|
|
69
|
+
)
|
|
70
|
+
return run_sub_agent
|
zrb/llm/tool/web.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from zrb.config.config import CFG
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
async def open_web_page(url: str) -> dict:
|
|
5
|
+
"""
|
|
6
|
+
Downloads and converts a web page into clean, readable Markdown.
|
|
7
|
+
|
|
8
|
+
**WHEN TO USE:**
|
|
9
|
+
- To read specific articles, documentation, or blog posts.
|
|
10
|
+
- To extract structured information from a known URL.
|
|
11
|
+
|
|
12
|
+
**ARGS:**
|
|
13
|
+
- `url`: The full web address to fetch.
|
|
14
|
+
"""
|
|
15
|
+
html_content, links = await _fetch_page_content(url)
|
|
16
|
+
markdown_content = _convert_html_to_markdown(html_content)
|
|
17
|
+
return {"content": markdown_content, "links_on_page": links}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def search_internet(
|
|
21
|
+
query: str,
|
|
22
|
+
page: int = 1,
|
|
23
|
+
) -> dict:
|
|
24
|
+
"""
|
|
25
|
+
Performs a broad internet search.
|
|
26
|
+
|
|
27
|
+
**WHEN TO USE:**
|
|
28
|
+
- When you need to find information but don't have a specific URL.
|
|
29
|
+
|
|
30
|
+
**ARGS:**
|
|
31
|
+
- `query`: The search string or question.
|
|
32
|
+
- `page`: Result page number (default 1).
|
|
33
|
+
"""
|
|
34
|
+
if (
|
|
35
|
+
CFG.SEARCH_INTERNET_METHOD.strip().lower() == "serpapi"
|
|
36
|
+
and CFG.SERPAPI_KEY != ""
|
|
37
|
+
):
|
|
38
|
+
from zrb.llm.tool.search.serpapi import search_internet as serpapi_search
|
|
39
|
+
|
|
40
|
+
return serpapi_search(query, page=page)
|
|
41
|
+
if (
|
|
42
|
+
CFG.SEARCH_INTERNET_METHOD.strip().lower() == "brave"
|
|
43
|
+
and CFG.BRAVE_API_KEY != ""
|
|
44
|
+
):
|
|
45
|
+
from zrb.llm.tool.search.brave import search_internet as brave_search
|
|
46
|
+
|
|
47
|
+
return brave_search(query, page=page)
|
|
48
|
+
from zrb.llm.tool.search.searxng import search_internet as searxng_search
|
|
49
|
+
|
|
50
|
+
return searxng_search(query, page=page)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _fetch_page_content(url: str) -> tuple:
|
|
54
|
+
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
|
55
|
+
try:
|
|
56
|
+
from playwright.async_api import async_playwright
|
|
57
|
+
|
|
58
|
+
async with async_playwright() as p:
|
|
59
|
+
browser = await p.chromium.launch(headless=True)
|
|
60
|
+
page = await browser.new_page()
|
|
61
|
+
await page.set_extra_http_headers({"User-Agent": user_agent})
|
|
62
|
+
await page.goto(url, wait_until="networkidle", timeout=30000)
|
|
63
|
+
content = await page.content()
|
|
64
|
+
links = await page.eval_on_selector_all(
|
|
65
|
+
"a[href]",
|
|
66
|
+
"(elements, baseUrl) => elements.map(el => { const href = el.getAttribute('href'); if (!href || href.startsWith('#')) return null; try { return new URL(href, baseUrl).href; } catch (e) { return null; } }).filter(href => href !== null)",
|
|
67
|
+
url,
|
|
68
|
+
)
|
|
69
|
+
await browser.close()
|
|
70
|
+
return content, links
|
|
71
|
+
except Exception:
|
|
72
|
+
from urllib.parse import urljoin
|
|
73
|
+
|
|
74
|
+
import requests
|
|
75
|
+
from bs4 import BeautifulSoup
|
|
76
|
+
|
|
77
|
+
response = requests.get(url, headers={"User-Agent": user_agent})
|
|
78
|
+
response.raise_for_status()
|
|
79
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
|
80
|
+
links = [
|
|
81
|
+
urljoin(url, a["href"])
|
|
82
|
+
for a in soup.find_all("a", href=True)
|
|
83
|
+
if not a["href"].startswith("#")
|
|
84
|
+
]
|
|
85
|
+
return response.text, links
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _convert_html_to_markdown(html_text: str) -> str:
|
|
89
|
+
from bs4 import BeautifulSoup
|
|
90
|
+
from markdownify import markdownify as md
|
|
91
|
+
|
|
92
|
+
soup = BeautifulSoup(html_text, "html.parser")
|
|
93
|
+
for tag in soup(
|
|
94
|
+
["script", "link", "meta", "style", "header", "footer", "nav", "aside"]
|
|
95
|
+
):
|
|
96
|
+
tag.decompose()
|
|
97
|
+
return md(str(soup))
|
zrb/llm/tool/zrb_task.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from zrb.config.config import CFG
|
|
2
|
+
from zrb.llm.tool.bash import run_shell_command
|
|
3
|
+
from zrb.runner.cli import cli
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_list_zrb_task_tool():
|
|
7
|
+
def list_zrb_tasks_impl(group_name: str | None = None) -> str:
|
|
8
|
+
target_group = cli
|
|
9
|
+
if group_name:
|
|
10
|
+
parts = group_name.split()
|
|
11
|
+
for part in parts:
|
|
12
|
+
next_group = target_group.get_group_by_alias(part)
|
|
13
|
+
if not next_group:
|
|
14
|
+
return f"Error: Group '{part}' not found in '{group_name}'."
|
|
15
|
+
target_group = next_group
|
|
16
|
+
output = [f"Tasks in '{target_group.name}':"]
|
|
17
|
+
# Subgroups
|
|
18
|
+
if target_group.subgroups:
|
|
19
|
+
output.append("\n Groups:")
|
|
20
|
+
for alias, grp in target_group.subgroups.items():
|
|
21
|
+
output.append(f" - {alias}: {grp.description}")
|
|
22
|
+
# Tasks
|
|
23
|
+
if target_group.subtasks:
|
|
24
|
+
output.append("\n Tasks:")
|
|
25
|
+
for alias, task in target_group.subtasks.items():
|
|
26
|
+
output.append(f" - {alias}: {task.description}")
|
|
27
|
+
return "\n".join(output)
|
|
28
|
+
|
|
29
|
+
zrb_cmd = CFG.ROOT_GROUP_NAME
|
|
30
|
+
list_zrb_tasks_impl.__name__ = f"list_{zrb_cmd}_tasks"
|
|
31
|
+
list_zrb_tasks_impl.__doc__ = (
|
|
32
|
+
f"Discovery tool to browse all available {zrb_cmd} tasks and automation groups. "
|
|
33
|
+
"Use this to understand what predefined workflows exist in the current project."
|
|
34
|
+
"\n\n**ARGS:**"
|
|
35
|
+
"\n- `group_name`: Optional name of the group to browse (e.g., 'server')."
|
|
36
|
+
)
|
|
37
|
+
return list_zrb_tasks_impl
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def create_run_zrb_task_tool():
|
|
41
|
+
async def run_zrb_task(
|
|
42
|
+
task_name: str, args: dict[str, str] = {}, timeout: int = 30
|
|
43
|
+
) -> str:
|
|
44
|
+
""" """
|
|
45
|
+
# Construct command
|
|
46
|
+
cmd_parts = ["zrb"] + task_name.split()
|
|
47
|
+
|
|
48
|
+
for key, val in args.items():
|
|
49
|
+
cmd_parts.append(f"--{key}")
|
|
50
|
+
cmd_parts.append(str(val))
|
|
51
|
+
|
|
52
|
+
command = " ".join(cmd_parts)
|
|
53
|
+
return await run_shell_command(command, timeout=timeout)
|
|
54
|
+
|
|
55
|
+
zrb_cmd = CFG.ROOT_GROUP_NAME
|
|
56
|
+
run_zrb_task.__name__ = f"run_{zrb_cmd}_task"
|
|
57
|
+
run_zrb_task.__doc__ = (
|
|
58
|
+
f"Executes a predefined {zrb_cmd} automation task with specified arguments. "
|
|
59
|
+
"This is the preferred way to run project-specific workflows (e.g., deployments, scaffolding, specialized builds)."
|
|
60
|
+
"\n\n**IMPORTANT:** You must provide all required arguments."
|
|
61
|
+
"\n\n**ARGS:**"
|
|
62
|
+
"\n- `task_name`: The full alias path of the task (e.g., 'server start')."
|
|
63
|
+
"\n- `args`: Dictionary of arguments (e.g., {'port': '8080'})."
|
|
64
|
+
"\n- `timeout`: Maximum wait time in seconds (default 30)."
|
|
65
|
+
)
|
|
66
|
+
return run_zrb_task
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
2
|
+
|
|
3
|
+
from zrb.context.any_context import AnyContext
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from pydantic_ai.messages import UserContent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def normalize_attachments(
|
|
10
|
+
attachments: "list[UserContent]", print_fn: Callable[[str], Any] = print
|
|
11
|
+
) -> "list[UserContent]":
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from pydantic_ai import BinaryContent
|
|
16
|
+
|
|
17
|
+
from zrb.llm.util.attachment import get_media_type
|
|
18
|
+
|
|
19
|
+
final_attachments = []
|
|
20
|
+
for item in attachments:
|
|
21
|
+
if isinstance(item, str):
|
|
22
|
+
# Treat as path
|
|
23
|
+
path = os.path.abspath(os.path.expanduser(item))
|
|
24
|
+
if os.path.exists(path):
|
|
25
|
+
media_type = get_media_type(path)
|
|
26
|
+
if media_type:
|
|
27
|
+
try:
|
|
28
|
+
data = Path(path).read_bytes()
|
|
29
|
+
final_attachments.append(
|
|
30
|
+
BinaryContent(data=data, media_type=media_type)
|
|
31
|
+
)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
print_fn(f"Failed to read attachment {path}: {e}")
|
|
34
|
+
else:
|
|
35
|
+
print_fn(f"Unknown media type for {path}")
|
|
36
|
+
else:
|
|
37
|
+
print_fn(f"Attachment file not found: {path}")
|
|
38
|
+
else:
|
|
39
|
+
# Assume it's already a suitable object (e.g. BinaryContent)
|
|
40
|
+
final_attachments.append(item)
|
|
41
|
+
return final_attachments
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_attachments(
|
|
45
|
+
ctx: AnyContext,
|
|
46
|
+
attachment: "UserContent | list[UserContent] | Callable[[AnyContext], UserContent | list[UserContent]] | None" = None, # noqa
|
|
47
|
+
) -> "list[UserContent]":
|
|
48
|
+
if attachment is None:
|
|
49
|
+
return []
|
|
50
|
+
if callable(attachment):
|
|
51
|
+
result = attachment(ctx)
|
|
52
|
+
if result is None:
|
|
53
|
+
return []
|
|
54
|
+
if isinstance(result, list):
|
|
55
|
+
return result
|
|
56
|
+
return [result]
|
|
57
|
+
if isinstance(attachment, list):
|
|
58
|
+
return attachment
|
|
59
|
+
return [attachment]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_media_type(filename: str) -> str | None:
|
|
63
|
+
"""Guess media type string based on file extension."""
|
|
64
|
+
ext = filename.lower().rsplit(".", 1)[-1] if "." in filename else ""
|
|
65
|
+
mapping: dict[str, str] = {
|
|
66
|
+
# Audio
|
|
67
|
+
"wav": "audio/wav",
|
|
68
|
+
"mp3": "audio/mpeg",
|
|
69
|
+
"ogg": "audio/ogg",
|
|
70
|
+
"flac": "audio/flac",
|
|
71
|
+
"aiff": "audio/aiff",
|
|
72
|
+
"aac": "audio/aac",
|
|
73
|
+
# Image
|
|
74
|
+
"jpg": "image/jpeg",
|
|
75
|
+
"jpeg": "image/jpeg",
|
|
76
|
+
"png": "image/png",
|
|
77
|
+
"gif": "image/gif",
|
|
78
|
+
"webp": "image/webp",
|
|
79
|
+
# Document
|
|
80
|
+
"pdf": "application/pdf",
|
|
81
|
+
"txt": "text/plain",
|
|
82
|
+
"csv": "text/csv",
|
|
83
|
+
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
84
|
+
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
85
|
+
"html": "text/html",
|
|
86
|
+
"htm": "text/html",
|
|
87
|
+
"md": "text/markdown",
|
|
88
|
+
"doc": "application/msword",
|
|
89
|
+
"xls": "application/vnd.ms-excel",
|
|
90
|
+
# Video
|
|
91
|
+
"mkv": "video/x-matroska",
|
|
92
|
+
"mov": "video/quicktime",
|
|
93
|
+
"mp4": "video/mp4",
|
|
94
|
+
"webm": "video/webm",
|
|
95
|
+
"flv": "video/x-flv",
|
|
96
|
+
"mpeg": "video/mpeg",
|
|
97
|
+
"mpg": "video/mpeg",
|
|
98
|
+
"wmv": "video/x-ms-wmv",
|
|
99
|
+
"3gp": "video/3gpp",
|
|
100
|
+
}
|
|
101
|
+
return mapping.get(ext)
|