zrb 1.15.3__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 -133
- zrb/attr/type.py +10 -7
- zrb/builtin/__init__.py +55 -1
- zrb/builtin/git.py +12 -1
- zrb/builtin/group.py +31 -15
- zrb/builtin/llm/chat.py +147 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
- zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
- zrb/builtin/searxng/config/settings.yml +5671 -0
- zrb/builtin/searxng/start.py +21 -0
- zrb/builtin/shell/autocomplete/bash.py +4 -3
- zrb/builtin/shell/autocomplete/zsh.py +4 -3
- zrb/callback/callback.py +8 -1
- zrb/cmd/cmd_result.py +2 -1
- zrb/config/config.py +555 -169
- zrb/config/helper.py +84 -0
- zrb/config/web_auth_config.py +50 -35
- zrb/context/any_shared_context.py +20 -3
- zrb/context/context.py +39 -5
- zrb/context/print_fn.py +13 -0
- zrb/context/shared_context.py +17 -8
- zrb/group/any_group.py +3 -3
- zrb/group/group.py +3 -3
- zrb/input/any_input.py +5 -1
- zrb/input/base_input.py +18 -6
- zrb/input/option_input.py +41 -1
- zrb/input/text_input.py +7 -24
- 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/file_extractor.md +112 -0
- zrb/llm/prompt/markdown/mandate.md +23 -0
- zrb/llm/prompt/markdown/persona.md +3 -0
- zrb/llm/prompt/markdown/repo_extractor.md +112 -0
- zrb/llm/prompt/markdown/repo_summarizer.md +29 -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 +33 -37
- 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/runner/cli.py +21 -20
- zrb/runner/common_util.py +24 -19
- zrb/runner/web_route/task_input_api_route.py +5 -5
- zrb/runner/web_util/user.py +7 -3
- zrb/session/any_session.py +12 -9
- zrb/session/session.py +38 -17
- zrb/task/any_task.py +24 -3
- zrb/task/base/context.py +42 -22
- zrb/task/base/execution.py +67 -55
- zrb/task/base/lifecycle.py +14 -7
- zrb/task/base/monitoring.py +12 -7
- zrb/task/base_task.py +113 -50
- zrb/task/base_trigger.py +16 -6
- zrb/task/cmd_task.py +6 -0
- zrb/task/http_check.py +11 -5
- zrb/task/make_task.py +5 -3
- zrb/task/rsync_task.py +30 -10
- zrb/task/scaffolder.py +7 -4
- zrb/task/scheduler.py +7 -4
- 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/attr.py +54 -39
- zrb/util/cli/markdown.py +32 -0
- zrb/util/cli/text.py +30 -0
- zrb/util/cmd/command.py +33 -10
- zrb/util/file.py +61 -33
- zrb/util/git.py +2 -2
- zrb/util/{llm/prompt.py → markdown.py} +2 -3
- zrb/util/match.py +78 -0
- zrb/util/run.py +3 -3
- zrb/util/string/conversion.py +1 -1
- zrb/util/truncate.py +23 -0
- zrb/util/yaml.py +204 -0
- zrb/xcom/xcom.py +10 -0
- {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/METADATA +41 -27
- {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/RECORD +129 -131
- {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/WHEEL +1 -1
- zrb/attr/__init__.py +0 -0
- zrb/builtin/llm/chat_session.py +0 -311
- zrb/builtin/llm/history.py +0 -71
- zrb/builtin/llm/input.py +0 -27
- zrb/builtin/llm/llm_ask.py +0 -187
- zrb/builtin/llm/previous-session.js +0 -21
- zrb/builtin/llm/tool/__init__.py +0 -0
- zrb/builtin/llm/tool/api.py +0 -71
- zrb/builtin/llm/tool/cli.py +0 -38
- zrb/builtin/llm/tool/code.py +0 -254
- zrb/builtin/llm/tool/file.py +0 -626
- zrb/builtin/llm/tool/sub_agent.py +0 -137
- zrb/builtin/llm/tool/web.py +0 -195
- 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/file_extractor_system_prompt.md +0 -12
- zrb/config/default_prompt/interactive_system_prompt.md +0 -35
- zrb/config/default_prompt/persona.md +0 -1
- zrb/config/default_prompt/repo_extractor_system_prompt.md +0 -112
- zrb/config/default_prompt/repo_summarizer_system_prompt.md +0 -10
- zrb/config/default_prompt/summarization_prompt.md +0 -16
- zrb/config/default_prompt/system_prompt.md +0 -32
- zrb/config/llm_config.py +0 -243
- zrb/config/llm_context/config.py +0 -129
- zrb/config/llm_context/config_parser.py +0 -46
- zrb/config/llm_rate_limitter.py +0 -137
- 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 -243
- zrb/task/llm/config.py +0 -103
- zrb/task/llm/conversation_history.py +0 -128
- zrb/task/llm/conversation_history_model.py +0 -242
- zrb/task/llm/default_workflow/coding.md +0 -24
- zrb/task/llm/default_workflow/copywriting.md +0 -17
- zrb/task/llm/default_workflow/researching.md +0 -18
- zrb/task/llm/error.py +0 -95
- zrb/task/llm/history_summarization.py +0 -216
- zrb/task/llm/print_node.py +0 -101
- zrb/task/llm/prompt.py +0 -325
- zrb/task/llm/tool_wrapper.py +0 -220
- zrb/task/llm/typing.py +0 -3
- zrb/task/llm_task.py +0 -341
- 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-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/entry_points.txt +0 -0
zrb/llm/note/manager.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
from zrb.config.config import CFG
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NoteManager:
|
|
9
|
+
def __init__(self, context_file: str = CFG.LLM_NOTE_FILE):
|
|
10
|
+
self.context_file = os.path.abspath(os.path.expanduser(context_file))
|
|
11
|
+
|
|
12
|
+
def _get_normalized_path(self, path: str) -> str:
|
|
13
|
+
"""
|
|
14
|
+
Convert path to absolute path, then try to make it relative to home.
|
|
15
|
+
If it's inside home, return '~/rel/path'.
|
|
16
|
+
Otherwise return absolute path.
|
|
17
|
+
"""
|
|
18
|
+
abs_path = os.path.abspath(os.path.expanduser(path))
|
|
19
|
+
home = os.path.expanduser("~")
|
|
20
|
+
|
|
21
|
+
if abs_path.startswith(home):
|
|
22
|
+
rel_path = os.path.relpath(abs_path, home)
|
|
23
|
+
if rel_path == ".":
|
|
24
|
+
return "~"
|
|
25
|
+
return os.path.join("~", rel_path)
|
|
26
|
+
|
|
27
|
+
return abs_path
|
|
28
|
+
|
|
29
|
+
def _load_data(self) -> Dict[str, str]:
|
|
30
|
+
if not os.path.exists(self.context_file):
|
|
31
|
+
return {}
|
|
32
|
+
try:
|
|
33
|
+
with open(self.context_file, "r", encoding="utf-8") as f:
|
|
34
|
+
return json.load(f)
|
|
35
|
+
except (json.JSONDecodeError, OSError):
|
|
36
|
+
return {}
|
|
37
|
+
|
|
38
|
+
def _save_data(self, data: Dict[str, str]):
|
|
39
|
+
os.makedirs(os.path.dirname(self.context_file), exist_ok=True)
|
|
40
|
+
with open(self.context_file, "w", encoding="utf-8") as f:
|
|
41
|
+
json.dump(data, f, indent=2, sort_keys=True)
|
|
42
|
+
|
|
43
|
+
def write(self, context_path: str, content: str):
|
|
44
|
+
"""
|
|
45
|
+
Write content to context file for a specific path.
|
|
46
|
+
Turn context path to be relative to home directory (unless it is outside home directory).
|
|
47
|
+
"""
|
|
48
|
+
key = self._get_normalized_path(context_path)
|
|
49
|
+
data = self._load_data()
|
|
50
|
+
data[key] = content
|
|
51
|
+
self._save_data(data)
|
|
52
|
+
|
|
53
|
+
def read(self, context_path: str) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Seek for any key in context file that match context path.
|
|
56
|
+
"""
|
|
57
|
+
key = self._get_normalized_path(context_path)
|
|
58
|
+
data = self._load_data()
|
|
59
|
+
return data.get(key, "")
|
|
60
|
+
|
|
61
|
+
def read_all(self, context_path: str) -> Dict[str, str]:
|
|
62
|
+
"""
|
|
63
|
+
Seek for all key in context file that match context path or any of its parent, return as key-value.
|
|
64
|
+
"""
|
|
65
|
+
target_path = self._get_normalized_path(context_path)
|
|
66
|
+
data = self._load_data()
|
|
67
|
+
|
|
68
|
+
result = {}
|
|
69
|
+
|
|
70
|
+
# Check for root/home notes (~)
|
|
71
|
+
if "~" in data:
|
|
72
|
+
result["~"] = data["~"]
|
|
73
|
+
|
|
74
|
+
# If target_path is just "~", we are done
|
|
75
|
+
if target_path == "~":
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
# We need to find all parents of target_path that are in data
|
|
79
|
+
# Example target: ~/zrb/src
|
|
80
|
+
# Parents: ~, ~/zrb
|
|
81
|
+
|
|
82
|
+
# Split the path parts
|
|
83
|
+
# If path starts with "~", we treat it specially
|
|
84
|
+
|
|
85
|
+
parts = []
|
|
86
|
+
if target_path.startswith("~" + os.sep) or target_path == "~":
|
|
87
|
+
# Remove ~
|
|
88
|
+
rel_part = target_path[2:] # "zrb/src"
|
|
89
|
+
parts = rel_part.split(os.sep)
|
|
90
|
+
current = "~"
|
|
91
|
+
else:
|
|
92
|
+
# Absolute path like /opt/project
|
|
93
|
+
parts = target_path.split(os.sep)
|
|
94
|
+
# parts for /opt/project -> ['', 'opt', 'project']
|
|
95
|
+
current = parts[0] # '' (root)
|
|
96
|
+
if current == "":
|
|
97
|
+
current = "/"
|
|
98
|
+
parts = parts[1:]
|
|
99
|
+
|
|
100
|
+
# Iterate and build up path
|
|
101
|
+
for part in parts:
|
|
102
|
+
if current == "~":
|
|
103
|
+
current = os.path.join("~", part)
|
|
104
|
+
elif current == "/":
|
|
105
|
+
current = os.path.join("/", part)
|
|
106
|
+
else:
|
|
107
|
+
current = os.path.join(current, part)
|
|
108
|
+
|
|
109
|
+
# Normalize just in case os.path.join changes things
|
|
110
|
+
# But wait, our keys in JSON are stored with separators.
|
|
111
|
+
# We should probably reconstruct the key carefully.
|
|
112
|
+
|
|
113
|
+
# Let's simplify:
|
|
114
|
+
# Check if 'current' exists in data
|
|
115
|
+
if current in data:
|
|
116
|
+
result[current] = data[current]
|
|
117
|
+
|
|
118
|
+
# Finally check the target_path itself if not covered
|
|
119
|
+
if target_path in data and target_path not in result:
|
|
120
|
+
result[target_path] = data[target_path]
|
|
121
|
+
|
|
122
|
+
return result
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from zrb.llm.prompt.claude_compatibility import create_claude_compatibility_prompt
|
|
2
|
+
from zrb.llm.prompt.compose import PromptManager, PromptMiddleware, new_prompt
|
|
3
|
+
from zrb.llm.prompt.default import (
|
|
4
|
+
get_file_extractor_system_prompt,
|
|
5
|
+
get_mandate_prompt,
|
|
6
|
+
get_persona_prompt,
|
|
7
|
+
get_repo_extractor_system_prompt,
|
|
8
|
+
get_repo_summarizer_system_prompt,
|
|
9
|
+
get_summarizer_system_prompt,
|
|
10
|
+
)
|
|
11
|
+
from zrb.llm.prompt.note import create_note_prompt
|
|
12
|
+
from zrb.llm.prompt.system_context import system_context
|
|
13
|
+
from zrb.llm.prompt.zrb import create_zrb_prompt
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"create_claude_compatibility_prompt",
|
|
17
|
+
"PromptManager",
|
|
18
|
+
"PromptMiddleware",
|
|
19
|
+
"new_prompt",
|
|
20
|
+
"get_file_extractor_system_prompt",
|
|
21
|
+
"get_mandate_prompt",
|
|
22
|
+
"get_persona_prompt",
|
|
23
|
+
"get_repo_extractor_system_prompt",
|
|
24
|
+
"get_repo_summarizer_system_prompt",
|
|
25
|
+
"get_summarizer_system_prompt",
|
|
26
|
+
"create_note_prompt",
|
|
27
|
+
"system_context",
|
|
28
|
+
"create_zrb_prompt",
|
|
29
|
+
]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Callable, List, Optional
|
|
3
|
+
|
|
4
|
+
from zrb.context.any_context import AnyContext
|
|
5
|
+
from zrb.llm.skill.manager import SkillManager
|
|
6
|
+
from zrb.util.markdown import make_markdown_section
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_claude_compatibility_prompt(skill_manager: SkillManager):
|
|
10
|
+
def claude_compatibility(
|
|
11
|
+
ctx: AnyContext,
|
|
12
|
+
current_prompt: str,
|
|
13
|
+
next_handler: Callable[[AnyContext, str], str],
|
|
14
|
+
) -> str:
|
|
15
|
+
search_dirs = _get_search_directories()
|
|
16
|
+
additional_context = []
|
|
17
|
+
|
|
18
|
+
# 1. CLAUDE.md
|
|
19
|
+
claude_content = _get_combined_content("CLAUDE.md", search_dirs)
|
|
20
|
+
if claude_content:
|
|
21
|
+
additional_context.append(
|
|
22
|
+
make_markdown_section(
|
|
23
|
+
"Project Instructions (CLAUDE.md)", claude_content
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# 2. AGENTS.md
|
|
28
|
+
agents_content = _get_combined_content("AGENTS.md", search_dirs)
|
|
29
|
+
if agents_content:
|
|
30
|
+
additional_context.append(
|
|
31
|
+
make_markdown_section("Agent Definitions (AGENTS.md)", agents_content)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# 3. Available Claude Skills
|
|
35
|
+
skills_section = _get_skills_section(skill_manager)
|
|
36
|
+
if skills_section:
|
|
37
|
+
additional_context.append(skills_section)
|
|
38
|
+
|
|
39
|
+
new_section = "\n\n".join(additional_context)
|
|
40
|
+
return next_handler(ctx, f"{current_prompt}\n\n{new_section}")
|
|
41
|
+
|
|
42
|
+
return claude_compatibility
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _get_search_directories() -> List[Path]:
|
|
46
|
+
search_dirs: List[Path] = []
|
|
47
|
+
# 1. User global config (~/.claude)
|
|
48
|
+
try:
|
|
49
|
+
home = Path.home()
|
|
50
|
+
search_dirs.append(home / ".claude")
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
# 2. Project directories (Root -> ... -> CWD)
|
|
55
|
+
try:
|
|
56
|
+
cwd = Path.cwd()
|
|
57
|
+
# Parents returns [parent, grandparent...]. We want reversed (Root first)
|
|
58
|
+
# This allows specific configs (closer to CWD) to override general ones
|
|
59
|
+
project_dirs = list(cwd.parents)[::-1] + [cwd]
|
|
60
|
+
search_dirs.extend(project_dirs)
|
|
61
|
+
except Exception:
|
|
62
|
+
pass
|
|
63
|
+
return search_dirs
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _get_combined_content(filename: str, search_dirs: List[Path]) -> str:
|
|
67
|
+
contents = []
|
|
68
|
+
for directory in search_dirs:
|
|
69
|
+
file_path = directory / filename
|
|
70
|
+
if file_path.exists() and file_path.is_file():
|
|
71
|
+
try:
|
|
72
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
73
|
+
content = f.read().strip()
|
|
74
|
+
if content:
|
|
75
|
+
contents.append(content)
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
return "\n\n".join(contents)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _get_skills_section(skill_manager: SkillManager) -> Optional[str]:
|
|
82
|
+
skills = skill_manager.scan()
|
|
83
|
+
if not skills:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
skills_context = ["Use 'activate_skill' to load instructions for a skill."]
|
|
87
|
+
for skill in skills:
|
|
88
|
+
skills_context.append(f"- {skill.name}")
|
|
89
|
+
|
|
90
|
+
return make_markdown_section(
|
|
91
|
+
"Available Skills (Claude Skills)", "\n".join(skills_context)
|
|
92
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
from zrb.context.any_context import AnyContext
|
|
4
|
+
from zrb.util.attr import get_str_attr
|
|
5
|
+
|
|
6
|
+
PromptMiddleware = Callable[[AnyContext, str, Callable[[AnyContext, str], str]], str]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PromptManager:
|
|
10
|
+
def __init__(self, middlewares: list[PromptMiddleware] = []):
|
|
11
|
+
self._middlewares = middlewares
|
|
12
|
+
|
|
13
|
+
def add_middleware(self, *middleware: PromptMiddleware):
|
|
14
|
+
self.append_middlewear(*middleware)
|
|
15
|
+
|
|
16
|
+
def append_middlewear(self, *middleware: PromptMiddleware):
|
|
17
|
+
self._middlewares += middleware
|
|
18
|
+
|
|
19
|
+
def compose_prompt(self) -> Callable[[AnyContext], str]:
|
|
20
|
+
"""
|
|
21
|
+
Composes a list of prompt middlewares into a single prompt factory function.
|
|
22
|
+
|
|
23
|
+
Each middleware should have the signature:
|
|
24
|
+
(ctx: AnyContext, prompt: str, next: Callable[[AnyContext, str], str]) -> str
|
|
25
|
+
|
|
26
|
+
The resulting function takes an AnyContext and returns the final prompt string.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def composed_prompt_factory(ctx: AnyContext) -> str:
|
|
30
|
+
def dispatch(index: int, current_prompt: str) -> str:
|
|
31
|
+
if index >= len(self._middlewares):
|
|
32
|
+
return current_prompt
|
|
33
|
+
|
|
34
|
+
middleware = self._middlewares[index]
|
|
35
|
+
|
|
36
|
+
def next_handler(c: AnyContext, p: str) -> str:
|
|
37
|
+
return dispatch(index + 1, p)
|
|
38
|
+
|
|
39
|
+
return middleware(ctx, current_prompt, next_handler)
|
|
40
|
+
|
|
41
|
+
return dispatch(0, "")
|
|
42
|
+
|
|
43
|
+
return composed_prompt_factory
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def new_prompt(new_prompt: str, render: bool = False):
|
|
47
|
+
def new_prompt_middleware(
|
|
48
|
+
ctx: AnyContext, current_prompt: str, next: Callable[[AnyContext, str], str]
|
|
49
|
+
):
|
|
50
|
+
effective_new_prompt = new_prompt
|
|
51
|
+
if render:
|
|
52
|
+
effective_new_prompt = get_str_attr(ctx, new_prompt, auto_render=True)
|
|
53
|
+
return next(ctx, f"{current_prompt}\n{effective_new_prompt}")
|
|
54
|
+
|
|
55
|
+
return new_prompt_middleware
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from zrb.config.config import CFG
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_default_prompt(name: str) -> str:
|
|
7
|
+
# 1. Check for local project override (configured via LLM_PROMPT_DIR)
|
|
8
|
+
prompt_dir = getattr(CFG, "LLM_PROMPT_DIR", ".zrb/llm/prompt")
|
|
9
|
+
local_prompt_path = os.path.abspath(
|
|
10
|
+
os.path.join(os.getcwd(), prompt_dir, f"{name}.md")
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
if os.path.exists(local_prompt_path):
|
|
14
|
+
try:
|
|
15
|
+
with open(local_prompt_path, "r", encoding="utf-8") as f:
|
|
16
|
+
return f.read()
|
|
17
|
+
except Exception:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
# 2. Fallback to package default
|
|
21
|
+
cwd = os.path.dirname(__file__)
|
|
22
|
+
with open(os.path.join(cwd, "markdown", f"{name}.md"), "r", encoding="utf-8") as f:
|
|
23
|
+
return f.read()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_persona_prompt(assistant_name: str | None = None) -> str:
|
|
27
|
+
effective_assistant_name = (
|
|
28
|
+
assistant_name if assistant_name else CFG.LLM_ASSISTANT_NAME
|
|
29
|
+
)
|
|
30
|
+
prompt = get_default_prompt("persona")
|
|
31
|
+
return prompt.replace("{ASSISTANT_NAME}", effective_assistant_name)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_mandate_prompt() -> str:
|
|
35
|
+
return get_default_prompt("mandate")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_summarizer_system_prompt() -> str:
|
|
39
|
+
return get_default_prompt("summarizer")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_file_extractor_system_prompt() -> str:
|
|
43
|
+
return get_default_prompt("file_extractor")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_repo_extractor_system_prompt() -> str:
|
|
47
|
+
return get_default_prompt("repo_extractor")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_repo_summarizer_system_prompt() -> str:
|
|
51
|
+
return get_default_prompt("repo_summarizer")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
You are an expert code and configuration analysis agent. Your purpose is to analyze a single file and create a concise, structured markdown summary of its most important components.
|
|
2
|
+
|
|
3
|
+
### Instructions
|
|
4
|
+
|
|
5
|
+
1. **Analyze File Content**: Determine the file's type (e.g., Python, Dockerfile, YAML, Markdown).
|
|
6
|
+
2. **Extract Key Information**: Based on the file type, extract only the most relevant information.
|
|
7
|
+
* **Source Code** (`.py`, `.js`, `.go`): Extract classes, functions, key variables, and their purpose.
|
|
8
|
+
* **Configuration** (`.yaml`, `.toml`, `.json`): Extract main sections, keys, and values.
|
|
9
|
+
* **Infrastructure** (`Dockerfile`, `.tf`): Extract resources, settings, and commands.
|
|
10
|
+
* **Documentation** (`.md`): Extract headings, summaries, and code blocks.
|
|
11
|
+
3. **Format Output**: Present the summary in structured markdown.
|
|
12
|
+
|
|
13
|
+
### Guiding Principles
|
|
14
|
+
|
|
15
|
+
* **Clarity over Completeness**: Do not reproduce the entire file. Capture its essence.
|
|
16
|
+
* **Relevance is Key**: The summary must help an AI assistant quickly understand the file's role and function.
|
|
17
|
+
* **Use Markdown**: Structure the output logically with headings, lists, and code blocks.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
### Examples
|
|
22
|
+
|
|
23
|
+
Here are examples of the expected output.
|
|
24
|
+
|
|
25
|
+
#### Example 1: Python Source File (`database.py`)
|
|
26
|
+
|
|
27
|
+
**Input File:**
|
|
28
|
+
```python
|
|
29
|
+
# src/database.py
|
|
30
|
+
import os
|
|
31
|
+
from sqlalchemy import create_engine, Column, Integer, String
|
|
32
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
33
|
+
from sqlalchemy.orm import sessionmaker
|
|
34
|
+
|
|
35
|
+
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./test.db")
|
|
36
|
+
|
|
37
|
+
engine = create_engine(DATABASE_URL)
|
|
38
|
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
39
|
+
Base = declarative_base()
|
|
40
|
+
|
|
41
|
+
class User(Base):
|
|
42
|
+
__tablename__ = "users"
|
|
43
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
44
|
+
username = Column(String, unique=True, index=True)
|
|
45
|
+
email = Column(String, unique=True, index=True)
|
|
46
|
+
|
|
47
|
+
def get_db():
|
|
48
|
+
db = SessionLocal()
|
|
49
|
+
try:
|
|
50
|
+
yield db
|
|
51
|
+
finally:
|
|
52
|
+
db.close()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Expected Markdown Output:**
|
|
56
|
+
```markdown
|
|
57
|
+
### File Summary: `src/database.py`
|
|
58
|
+
|
|
59
|
+
This file sets up the database connection and defines the `User` model using SQLAlchemy.
|
|
60
|
+
|
|
61
|
+
**Key Components:**
|
|
62
|
+
|
|
63
|
+
* **Configuration:**
|
|
64
|
+
* `DATABASE_URL`: Determined by the `DATABASE_URL` environment variable, defaulting to a local SQLite database.
|
|
65
|
+
* **SQLAlchemy Objects:**
|
|
66
|
+
* `engine`: The core SQLAlchemy engine connected to the `DATABASE_URL`.
|
|
67
|
+
* `SessionLocal`: A factory for creating new database sessions.
|
|
68
|
+
* `Base`: The declarative base for ORM models.
|
|
69
|
+
* **ORM Models:**
|
|
70
|
+
* **`User` class:**
|
|
71
|
+
* Table: `users`
|
|
72
|
+
* Columns: `id` (Integer, Primary Key), `username` (String), `email` (String).
|
|
73
|
+
* **Functions:**
|
|
74
|
+
* `get_db()`: A generator function to provide a database session for dependency injection, ensuring the session is closed after use.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Example 2: Infrastructure File (`Dockerfile`)
|
|
78
|
+
|
|
79
|
+
**Input File:**
|
|
80
|
+
```dockerfile
|
|
81
|
+
FROM python:3.9-slim
|
|
82
|
+
|
|
83
|
+
WORKDIR /app
|
|
84
|
+
|
|
85
|
+
COPY requirements.txt .
|
|
86
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
87
|
+
|
|
88
|
+
COPY . .
|
|
89
|
+
|
|
90
|
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Expected Markdown Output:**
|
|
94
|
+
```markdown
|
|
95
|
+
### File Summary: `Dockerfile`
|
|
96
|
+
|
|
97
|
+
This Dockerfile defines a container for a Python 3.9 application.
|
|
98
|
+
|
|
99
|
+
**Resources and Commands:**
|
|
100
|
+
|
|
101
|
+
* **Base Image:** `python:3.9-slim`
|
|
102
|
+
* **Working Directory:** `/app`
|
|
103
|
+
* **Dependency Installation:**
|
|
104
|
+
* Copies `requirements.txt` into the container.
|
|
105
|
+
* Installs the dependencies using `pip`.
|
|
106
|
+
* **Application Code:**
|
|
107
|
+
* Copies the rest of the application code into the `/app` directory.
|
|
108
|
+
* **Execution Command:**
|
|
109
|
+
* Starts the application using `uvicorn`, making it accessible on port 80.
|
|
110
|
+
```
|
|
111
|
+
---
|
|
112
|
+
Produce only the markdown summary for the files provided. Do not add any conversational text or introductory phrases.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Mandate
|
|
2
|
+
|
|
3
|
+
This mandate represents your core operating principles. You must follow these instructions by default unless explicitly overridden by the user, `CLAUDE.md`, or `AGENTS.md`.
|
|
4
|
+
|
|
5
|
+
## Core Philosophy
|
|
6
|
+
|
|
7
|
+
* **Safety & Precision**: You are an expert engineer. Do not guess. Verify assumptions. Read code before editing.
|
|
8
|
+
* **Conventions**: Respect the existing coding style, naming conventions, and architectural patterns of the project.
|
|
9
|
+
* **Minimalism**: Do not introduce unnecessary dependencies or complexities. Use existing tools and libraries whenever possible.
|
|
10
|
+
|
|
11
|
+
## Operational Rules
|
|
12
|
+
|
|
13
|
+
1. **Investigation First**: Before making changes, you must understand the context. Use `list_files`, `read_file`, or `search_files` to gather information.
|
|
14
|
+
2. **Atomic Changes**: Break down complex tasks into smaller, verifiable steps.
|
|
15
|
+
3. **No Hallucinations**: Do not reference files, functions, or libraries that do not exist or that you haven't verified.
|
|
16
|
+
4. **Test-Driven**: When feasible, create or update tests to verify your changes.
|
|
17
|
+
5. **Clean Code**: Write code that is readable, maintainable, and documented (where necessary).
|
|
18
|
+
6. **Self-Correction**: If a tool fails or produces unexpected results, analyze the error, adjust your approach, and try again.
|
|
19
|
+
|
|
20
|
+
## Communication
|
|
21
|
+
|
|
22
|
+
* **Direct & Professional**: Be concise. Avoid fluff.
|
|
23
|
+
* **Transparent**: Briefly explain *why* you are doing something if it involves significant changes or risks.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
You are an expert code and configuration analysis agent. Your purpose is to analyze a single file and create a concise, structured markdown summary of its most important components.
|
|
2
|
+
|
|
3
|
+
### Instructions
|
|
4
|
+
|
|
5
|
+
1. **Analyze File Content**: Determine the file's type (e.g., Python, Dockerfile, YAML, Markdown).
|
|
6
|
+
2. **Extract Key Information**: Based on the file type, extract only the most relevant information.
|
|
7
|
+
* **Source Code** (`.py`, `.js`, `.go`): Extract classes, functions, key variables, and their purpose.
|
|
8
|
+
* **Configuration** (`.yaml`, `.toml`, `.json`): Extract main sections, keys, and values.
|
|
9
|
+
* **Infrastructure** (`Dockerfile`, `.tf`): Extract resources, settings, and commands.
|
|
10
|
+
* **Documentation** (`.md`): Extract headings, summaries, and code blocks.
|
|
11
|
+
3. **Format Output**: Present the summary in structured markdown.
|
|
12
|
+
|
|
13
|
+
### Guiding Principles
|
|
14
|
+
|
|
15
|
+
* **Clarity over Completeness**: Do not reproduce the entire file. Capture its essence.
|
|
16
|
+
* **Relevance is Key**: The summary must help an AI assistant quickly understand the file's role and function.
|
|
17
|
+
* **Use Markdown**: Structure the output logically with headings, lists, and code blocks.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
### Examples
|
|
22
|
+
|
|
23
|
+
Here are examples of the expected output.
|
|
24
|
+
|
|
25
|
+
#### Example 1: Python Source File (`database.py`)
|
|
26
|
+
|
|
27
|
+
**Input File:**
|
|
28
|
+
```python
|
|
29
|
+
# src/database.py
|
|
30
|
+
import os
|
|
31
|
+
from sqlalchemy import create_engine, Column, Integer, String
|
|
32
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
33
|
+
from sqlalchemy.orm import sessionmaker
|
|
34
|
+
|
|
35
|
+
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./test.db")
|
|
36
|
+
|
|
37
|
+
engine = create_engine(DATABASE_URL)
|
|
38
|
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
39
|
+
Base = declarative_base()
|
|
40
|
+
|
|
41
|
+
class User(Base):
|
|
42
|
+
__tablename__ = "users"
|
|
43
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
44
|
+
username = Column(String, unique=True, index=True)
|
|
45
|
+
email = Column(String, unique=True, index=True)
|
|
46
|
+
|
|
47
|
+
def get_db():
|
|
48
|
+
db = SessionLocal()
|
|
49
|
+
try:
|
|
50
|
+
yield db
|
|
51
|
+
finally:
|
|
52
|
+
db.close()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Expected Markdown Output:**
|
|
56
|
+
```markdown
|
|
57
|
+
### File Summary: `src/database.py`
|
|
58
|
+
|
|
59
|
+
This file sets up the database connection and defines the `User` model using SQLAlchemy.
|
|
60
|
+
|
|
61
|
+
**Key Components:**
|
|
62
|
+
|
|
63
|
+
* **Configuration:**
|
|
64
|
+
* `DATABASE_URL`: Determined by the `DATABASE_URL` environment variable, defaulting to a local SQLite database.
|
|
65
|
+
* **SQLAlchemy Objects:**
|
|
66
|
+
* `engine`: The core SQLAlchemy engine connected to the `DATABASE_URL`.
|
|
67
|
+
* `SessionLocal`: A factory for creating new database sessions.
|
|
68
|
+
* `Base`: The declarative base for ORM models.
|
|
69
|
+
* **ORM Models:**
|
|
70
|
+
* **`User` class:**
|
|
71
|
+
* Table: `users`
|
|
72
|
+
* Columns: `id` (Integer, Primary Key), `username` (String), `email` (String).
|
|
73
|
+
* **Functions:**
|
|
74
|
+
* `get_db()`: A generator function to provide a database session for dependency injection, ensuring the session is closed after use.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Example 2: Infrastructure File (`Dockerfile`)
|
|
78
|
+
|
|
79
|
+
**Input File:**
|
|
80
|
+
```dockerfile
|
|
81
|
+
FROM python:3.9-slim
|
|
82
|
+
|
|
83
|
+
WORKDIR /app
|
|
84
|
+
|
|
85
|
+
COPY requirements.txt .
|
|
86
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
87
|
+
|
|
88
|
+
COPY . .
|
|
89
|
+
|
|
90
|
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Expected Markdown Output:**
|
|
94
|
+
```markdown
|
|
95
|
+
### File Summary: `Dockerfile`
|
|
96
|
+
|
|
97
|
+
This Dockerfile defines a container for a Python 3.9 application.
|
|
98
|
+
|
|
99
|
+
**Resources and Commands:**
|
|
100
|
+
|
|
101
|
+
* **Base Image:** `python:3.9-slim`
|
|
102
|
+
* **Working Directory:** `/app`
|
|
103
|
+
* **Dependency Installation:**
|
|
104
|
+
* Copies `requirements.txt` into the container.
|
|
105
|
+
* Installs the dependencies using `pip`.
|
|
106
|
+
* **Application Code:**
|
|
107
|
+
* Copies the rest of the application code into the `/app` directory.
|
|
108
|
+
* **Execution Command:**
|
|
109
|
+
* Starts the application using `uvicorn`, making it accessible on port 80.
|
|
110
|
+
```
|
|
111
|
+
---
|
|
112
|
+
Produce only the markdown summary for the files provided. Do not add any conversational text or introductory phrases.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
You are an expert synthesis agent. Your goal is to consolidate multiple file summaries into a single, coherent repository overview that directly addresses the user's objective.
|
|
2
|
+
|
|
3
|
+
### Instructions
|
|
4
|
+
|
|
5
|
+
1. **Synthesize, Don't List**: Do not simply concatenate the summaries. Weave the information together into a unified narrative.
|
|
6
|
+
2. **Identify Core Purpose**: Start by identifying the repository's primary purpose (e.g., "This is a Python web service using FastAPI and SQLAlchemy").
|
|
7
|
+
3. **Structure the Output**: Organize the summary logically:
|
|
8
|
+
* **High-Level Architecture**: Describe the main components and how they interact (e.g., "It uses a Dockerfile for containerization, `main.py` as the entrypoint, and connects to a PostgreSQL database defined in `database.py`.").
|
|
9
|
+
* **Key Files**: Briefly explain the role of the most important files.
|
|
10
|
+
* **Configuration**: Summarize the key configuration points (e.g., "Configuration is handled in `config.py` and sourced from environment variables.").
|
|
11
|
+
4. **Focus on Relevance**: The final summary must be tailored to help the main assistant achieve its goal. Omit trivial details.
|
|
12
|
+
|
|
13
|
+
### Example
|
|
14
|
+
|
|
15
|
+
**User Goal:** "Understand how to run this project."
|
|
16
|
+
|
|
17
|
+
**Input Summaries:**
|
|
18
|
+
* `Dockerfile`: "Defines a Python 3.9 container, installs dependencies from `requirements.txt`, and runs the app with `uvicorn`."
|
|
19
|
+
* `main.py`: "A FastAPI application with a single endpoint `/` that returns 'Hello, World!'."
|
|
20
|
+
* `requirements.txt`: "Lists `fastapi` and `uvicorn` as dependencies."
|
|
21
|
+
|
|
22
|
+
**Expected Output:**
|
|
23
|
+
```markdown
|
|
24
|
+
This repository contains a simple Python web service built with FastAPI.
|
|
25
|
+
|
|
26
|
+
It is designed to be run as a container. The `Dockerfile` sets up a Python 3.9 environment, installs dependencies from `requirements.txt` (which includes `fastapi` and `uvicorn`), and starts the server. The main application logic is in `main.py`, which defines a single API endpoint.
|
|
27
|
+
|
|
28
|
+
To run this project, you would build the Docker image and then run the container.
|
|
29
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
You are an expert conversation summarizer. Your goal is to condense conversation history into a concise summary that preserves critical context for an LLM.
|
|
2
|
+
|
|
3
|
+
# Guidelines:
|
|
4
|
+
1. **Preserve User Intent:** Clearly state what the user wanted to achieve.
|
|
5
|
+
2. **Capture Key Information:** Retain specific values, file paths, code snippets, or error messages that are relevant to ongoing tasks.
|
|
6
|
+
3. **Summarize Tool Usage:** Briefly mention what tools were used and their significant outcomes (e.g., "User listed files and found 'main.py'").
|
|
7
|
+
4. **Maintain Flow:** The summary should read as a coherent narrative of the session so far.
|
|
8
|
+
5. **Conciseness:** Remove filler words, pleasantries, and redundant repetitions.
|
|
9
|
+
|
|
10
|
+
# Example:
|
|
11
|
+
|
|
12
|
+
*Original:*
|
|
13
|
+
User: "Hi, can you help me find the bug in my code?"
|
|
14
|
+
AI: "Sure, please share the code."
|
|
15
|
+
User: "Here it is: `def add(a, b): return a - b`"
|
|
16
|
+
AI: "I see, it subtracts instead of adds."
|
|
17
|
+
User: "Oh, right. Fix it please."
|
|
18
|
+
AI: "I'll fix it." (Tool Call: replace_in_file) (Tool Result: Success)
|
|
19
|
+
|
|
20
|
+
*Summary:*
|
|
21
|
+
The user asked for help finding a bug in a provided `add` function. The AI identified that it performed subtraction instead of addition. The user requested a fix, and the AI successfully applied a patch using `replace_in_file`.
|