zrb 1.8.10__py3-none-any.whl → 1.21.29__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 +126 -113
- zrb/__main__.py +1 -1
- zrb/attr/type.py +10 -7
- zrb/builtin/__init__.py +2 -50
- zrb/builtin/git.py +12 -1
- zrb/builtin/group.py +31 -15
- zrb/builtin/http.py +7 -8
- zrb/builtin/llm/attachment.py +40 -0
- zrb/builtin/llm/chat_completion.py +274 -0
- zrb/builtin/llm/chat_session.py +152 -85
- zrb/builtin/llm/chat_session_cmd.py +288 -0
- zrb/builtin/llm/chat_trigger.py +79 -0
- zrb/builtin/llm/history.py +7 -9
- zrb/builtin/llm/llm_ask.py +221 -98
- zrb/builtin/llm/tool/api.py +74 -52
- zrb/builtin/llm/tool/cli.py +46 -17
- zrb/builtin/llm/tool/code.py +71 -90
- zrb/builtin/llm/tool/file.py +301 -241
- zrb/builtin/llm/tool/note.py +84 -0
- zrb/builtin/llm/tool/rag.py +38 -8
- zrb/builtin/llm/tool/sub_agent.py +67 -50
- zrb/builtin/llm/tool/web.py +146 -122
- 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/setup/latex/ubuntu.py +1 -0
- zrb/builtin/setup/ubuntu.py +1 -1
- zrb/builtin/shell/autocomplete/bash.py +4 -3
- zrb/builtin/shell/autocomplete/zsh.py +4 -3
- zrb/builtin/todo.py +13 -2
- zrb/config/config.py +614 -0
- zrb/config/default_prompt/file_extractor_system_prompt.md +112 -0
- zrb/config/default_prompt/interactive_system_prompt.md +29 -0
- zrb/config/default_prompt/persona.md +1 -0
- zrb/config/default_prompt/repo_extractor_system_prompt.md +112 -0
- zrb/config/default_prompt/repo_summarizer_system_prompt.md +29 -0
- zrb/config/default_prompt/summarization_prompt.md +57 -0
- zrb/config/default_prompt/system_prompt.md +38 -0
- zrb/config/llm_config.py +339 -0
- zrb/config/llm_context/config.py +166 -0
- zrb/config/llm_context/config_parser.py +40 -0
- zrb/config/llm_context/workflow.py +81 -0
- zrb/config/llm_rate_limitter.py +190 -0
- zrb/{runner → config}/web_auth_config.py +17 -22
- zrb/context/any_shared_context.py +17 -1
- zrb/context/context.py +16 -2
- zrb/context/shared_context.py +18 -8
- zrb/group/any_group.py +12 -5
- zrb/group/group.py +67 -3
- zrb/input/any_input.py +5 -1
- zrb/input/base_input.py +18 -6
- zrb/input/option_input.py +13 -1
- zrb/input/text_input.py +8 -25
- zrb/runner/cli.py +25 -23
- zrb/runner/common_util.py +24 -19
- zrb/runner/web_app.py +3 -3
- zrb/runner/web_route/docs_route.py +1 -1
- zrb/runner/web_route/error_page/serve_default_404.py +1 -1
- zrb/runner/web_route/error_page/show_error_page.py +1 -1
- zrb/runner/web_route/home_page/home_page_route.py +2 -2
- zrb/runner/web_route/login_api_route.py +1 -1
- zrb/runner/web_route/login_page/login_page_route.py +2 -2
- zrb/runner/web_route/logout_api_route.py +1 -1
- zrb/runner/web_route/logout_page/logout_page_route.py +2 -2
- zrb/runner/web_route/node_page/group/show_group_page.py +1 -1
- zrb/runner/web_route/node_page/node_page_route.py +1 -1
- zrb/runner/web_route/node_page/task/show_task_page.py +1 -1
- zrb/runner/web_route/refresh_token_api_route.py +1 -1
- zrb/runner/web_route/static/static_route.py +1 -1
- zrb/runner/web_route/task_input_api_route.py +6 -6
- zrb/runner/web_route/task_session_api_route.py +20 -12
- zrb/runner/web_util/cookie.py +1 -1
- zrb/runner/web_util/token.py +1 -1
- zrb/runner/web_util/user.py +8 -4
- zrb/session/any_session.py +24 -17
- zrb/session/session.py +50 -25
- zrb/session_state_logger/any_session_state_logger.py +9 -4
- zrb/session_state_logger/file_session_state_logger.py +16 -6
- zrb/session_state_logger/session_state_logger_factory.py +1 -1
- zrb/task/any_task.py +30 -9
- zrb/task/base/context.py +17 -9
- zrb/task/base/execution.py +15 -8
- zrb/task/base/lifecycle.py +8 -4
- zrb/task/base/monitoring.py +12 -7
- zrb/task/base_task.py +69 -5
- zrb/task/base_trigger.py +12 -5
- zrb/task/cmd_task.py +1 -1
- zrb/task/llm/agent.py +154 -161
- zrb/task/llm/agent_runner.py +152 -0
- zrb/task/llm/config.py +47 -18
- zrb/task/llm/conversation_history.py +209 -0
- zrb/task/llm/conversation_history_model.py +67 -0
- zrb/task/llm/default_workflow/coding/workflow.md +41 -0
- zrb/task/llm/default_workflow/copywriting/workflow.md +68 -0
- zrb/task/llm/default_workflow/git/workflow.md +118 -0
- zrb/task/llm/default_workflow/golang/workflow.md +128 -0
- zrb/task/llm/default_workflow/html-css/workflow.md +135 -0
- zrb/task/llm/default_workflow/java/workflow.md +146 -0
- zrb/task/llm/default_workflow/javascript/workflow.md +158 -0
- zrb/task/llm/default_workflow/python/workflow.md +160 -0
- zrb/task/llm/default_workflow/researching/workflow.md +153 -0
- zrb/task/llm/default_workflow/rust/workflow.md +162 -0
- zrb/task/llm/default_workflow/shell/workflow.md +299 -0
- zrb/task/llm/error.py +24 -10
- zrb/task/llm/file_replacement.py +206 -0
- zrb/task/llm/file_tool_model.py +57 -0
- zrb/task/llm/history_processor.py +206 -0
- zrb/task/llm/history_summarization.py +11 -166
- zrb/task/llm/print_node.py +193 -69
- zrb/task/llm/prompt.py +242 -45
- zrb/task/llm/subagent_conversation_history.py +41 -0
- zrb/task/llm/tool_wrapper.py +260 -57
- zrb/task/llm/workflow.py +76 -0
- zrb/task/llm_task.py +182 -171
- zrb/task/make_task.py +2 -3
- zrb/task/rsync_task.py +26 -11
- zrb/task/scheduler.py +4 -4
- zrb/util/attr.py +54 -39
- zrb/util/callable.py +23 -0
- zrb/util/cli/markdown.py +12 -0
- zrb/util/cli/text.py +30 -0
- zrb/util/file.py +29 -11
- zrb/util/git.py +8 -11
- zrb/util/git_diff_model.py +10 -0
- zrb/util/git_subtree.py +9 -14
- zrb/util/git_subtree_model.py +32 -0
- zrb/util/init_path.py +1 -1
- zrb/util/markdown.py +62 -0
- zrb/util/string/conversion.py +2 -2
- zrb/util/todo.py +17 -50
- zrb/util/todo_model.py +46 -0
- zrb/util/truncate.py +23 -0
- zrb/util/yaml.py +204 -0
- zrb/xcom/xcom.py +10 -0
- zrb-1.21.29.dist-info/METADATA +270 -0
- {zrb-1.8.10.dist-info → zrb-1.21.29.dist-info}/RECORD +140 -98
- {zrb-1.8.10.dist-info → zrb-1.21.29.dist-info}/WHEEL +1 -1
- zrb/config.py +0 -335
- zrb/llm_config.py +0 -411
- zrb/llm_rate_limitter.py +0 -125
- zrb/task/llm/context.py +0 -102
- zrb/task/llm/context_enrichment.py +0 -199
- zrb/task/llm/history.py +0 -211
- zrb-1.8.10.dist-info/METADATA +0 -264
- {zrb-1.8.10.dist-info → zrb-1.21.29.dist-info}/entry_points.txt +0 -0
zrb/llm_config.py
DELETED
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING, Any
|
|
2
|
-
|
|
3
|
-
if TYPE_CHECKING:
|
|
4
|
-
from pydantic_ai.models import Model
|
|
5
|
-
from pydantic_ai.providers import Provider
|
|
6
|
-
from pydantic_ai.settings import ModelSettings
|
|
7
|
-
else:
|
|
8
|
-
Model = Any
|
|
9
|
-
ModelSettings = Any
|
|
10
|
-
Provider = Any
|
|
11
|
-
|
|
12
|
-
from zrb.config import CFG
|
|
13
|
-
|
|
14
|
-
DEFAULT_PERSONA = (
|
|
15
|
-
"You are a helpful and precise expert assistant. Your goal is to follow "
|
|
16
|
-
"instructions carefully to provide accurate and efficient help. Get "
|
|
17
|
-
"straight to the point."
|
|
18
|
-
).strip()
|
|
19
|
-
|
|
20
|
-
DEFAULT_SYSTEM_PROMPT = (
|
|
21
|
-
"You have access to tools and two forms of memory:\n"
|
|
22
|
-
"1. A structured summary of the immediate task (including a payload) AND "
|
|
23
|
-
"the raw text of the last few turns.\n"
|
|
24
|
-
"2. A structured JSON object of long-term facts (user profile, project "
|
|
25
|
-
"details).\n\n"
|
|
26
|
-
"Your goal is to complete the user's task by following a strict workflow."
|
|
27
|
-
"\n\n"
|
|
28
|
-
"**YOUR CORE WORKFLOW**\n"
|
|
29
|
-
"You MUST follow these steps in order for every task:\n\n"
|
|
30
|
-
"1. **Synthesize and Verify:**\n"
|
|
31
|
-
" - Review all parts of your memory: the long-term facts, the recent "
|
|
32
|
-
"conversation history, and the summary of the next action.\n"
|
|
33
|
-
" - Compare this with the user's absolute LATEST message.\n"
|
|
34
|
-
" - **If your memory seems out of date or contradicts the user's new "
|
|
35
|
-
"request, you MUST ask for clarification before doing anything else.**\n"
|
|
36
|
-
" - Example: If memory says 'ready to build the app' but the user "
|
|
37
|
-
"asks to 'add a new file', ask: 'My notes say we were about to build. "
|
|
38
|
-
"Are you sure you want to add a new file first? Please confirm.'\n\n"
|
|
39
|
-
"2. **Plan:**\n"
|
|
40
|
-
" - Use the `Action Payload` from your memory if it exists.\n"
|
|
41
|
-
" - State your plan in simple, numbered steps.\n\n"
|
|
42
|
-
"3. **Execute:**\n"
|
|
43
|
-
" - Follow your plan and use your tools to complete the task.\n\n"
|
|
44
|
-
"**CRITICAL RULES**\n"
|
|
45
|
-
"- **TRUST YOUR MEMORY (AFTER VERIFICATION):** Once you confirm your "
|
|
46
|
-
"memory is correct, do NOT re-gather information. Use the `Action "
|
|
47
|
-
"Payload` directly.\n"
|
|
48
|
-
"- **ASK IF UNSURE:** If a required parameter (like a filename) is not in "
|
|
49
|
-
"your memory or the user's last message, you MUST ask for it. Do not "
|
|
50
|
-
"guess."
|
|
51
|
-
).strip()
|
|
52
|
-
|
|
53
|
-
DEFAULT_SPECIAL_INSTRUCTION_PROMPT = (
|
|
54
|
-
"## Technical Task Protocol\n"
|
|
55
|
-
"When performing technical tasks, strictly follow this protocol.\n\n"
|
|
56
|
-
"**1. Guiding Principles**\n"
|
|
57
|
-
"Your work must be **Correct, Secure, and Readable**.\n\n"
|
|
58
|
-
"**2. Code Modification: Surgical Precision**\n"
|
|
59
|
-
"- Your primary goal is to preserve the user's work.\n"
|
|
60
|
-
"- Find the **exact start and end lines** you need to change.\n"
|
|
61
|
-
"- **ADD or MODIFY only those specific lines.** Do not touch any other "
|
|
62
|
-
"part of the file.\n"
|
|
63
|
-
"- Do not REPLACE a whole file unless the user explicitly tells you "
|
|
64
|
-
"to.\n\n"
|
|
65
|
-
"**3. Git Workflow: A Safe, Step-by-Step Process**\n"
|
|
66
|
-
"Whenever you work in a git repository, you MUST follow these steps "
|
|
67
|
-
"exactly:\n"
|
|
68
|
-
"1. **Check Status:** Run `git status` to ensure the working directory "
|
|
69
|
-
"is clean.\n"
|
|
70
|
-
"2. **Halt if Dirty:** If the directory is not clean, STOP. Inform the "
|
|
71
|
-
"user and wait for their instructions.\n"
|
|
72
|
-
"3. **Propose and Confirm Branch:**\n"
|
|
73
|
-
" - Tell the user you need to create a new branch and propose a "
|
|
74
|
-
"name.\n"
|
|
75
|
-
" - Example: 'I will create a branch named `feature/add-user-login`. "
|
|
76
|
-
"Is this okay?'\n"
|
|
77
|
-
" - **Wait for the user to say 'yes' or approve.**\n"
|
|
78
|
-
"4. **Execute on Branch:** Once the user confirms, create the branch and "
|
|
79
|
-
"perform all your work and commits there.\n\n"
|
|
80
|
-
"**4. Debugging Protocol**\n"
|
|
81
|
-
"1. **Hypothesize:** State the most likely cause of the bug in one "
|
|
82
|
-
"sentence.\n"
|
|
83
|
-
"2. **Solve:** Provide the targeted code fix and explain simply *why* "
|
|
84
|
-
"it works.\n"
|
|
85
|
-
"3. **Verify:** Tell the user what command to run or what to check to "
|
|
86
|
-
"confirm the fix works."
|
|
87
|
-
).strip()
|
|
88
|
-
|
|
89
|
-
DEFAULT_SUMMARIZATION_PROMPT = (
|
|
90
|
-
"You are a summarization assistant. Your job is to create a two-part "
|
|
91
|
-
"summary to give the main assistant perfect context for its next "
|
|
92
|
-
"action.\n\n"
|
|
93
|
-
"**PART 1: RECENT CONVERSATION HISTORY**\n"
|
|
94
|
-
"- Copy the last 3-4 turns of the conversation verbatim.\n"
|
|
95
|
-
"- Use the format `user:` and `assistant:`.\n\n"
|
|
96
|
-
"**PART 2: ANALYSIS OF CURRENT STATE**\n"
|
|
97
|
-
"- Fill in the template to analyze the immediate task.\n"
|
|
98
|
-
"- **CRITICAL RULE:** If the next action requires specific data (like "
|
|
99
|
-
"text for a file or a command), you MUST include that exact data in the "
|
|
100
|
-
"`Action Payload` field.\n\n"
|
|
101
|
-
"---\n"
|
|
102
|
-
"**TEMPLATE FOR YOUR ENTIRE OUTPUT**\n\n"
|
|
103
|
-
"## Part 1: Recent Conversation History\n"
|
|
104
|
-
"user: [The user's second-to-last message]\n"
|
|
105
|
-
"assistant: [The assistant's last message]\n"
|
|
106
|
-
"user: [The user's most recent message]\n\n"
|
|
107
|
-
"---\n"
|
|
108
|
-
"## Part 2: Analysis of Current State\n\n"
|
|
109
|
-
"**User's Main Goal:**\n"
|
|
110
|
-
"[Describe the user's overall objective in one simple sentence.]\n\n"
|
|
111
|
-
"**Next Action for Assistant:**\n"
|
|
112
|
-
"[Describe the immediate next step. Example: 'Write new content to the "
|
|
113
|
-
"README.md file.']\n\n"
|
|
114
|
-
"**Action Payload:**\n"
|
|
115
|
-
"[IMPORTANT: Provide the exact content, code, or command for the "
|
|
116
|
-
"action. If no data is needed, write 'None'.]\n\n"
|
|
117
|
-
"---\n"
|
|
118
|
-
"**EXAMPLE SCENARIO & CORRECT OUTPUT**\n\n"
|
|
119
|
-
"*PREVIOUS CONVERSATION:*\n"
|
|
120
|
-
"user: Can you help me update my project's documentation?\n"
|
|
121
|
-
"assistant: Of course. I have drafted the new content: '# Project "
|
|
122
|
-
"Apollo\\nThis is the new documentation for the project.' Do you "
|
|
123
|
-
"approve?\n"
|
|
124
|
-
"user: Yes, that looks great. Please proceed.\n\n"
|
|
125
|
-
"*YOUR CORRECT OUTPUT:*\n\n"
|
|
126
|
-
"## Part 1: Recent Conversation History\n"
|
|
127
|
-
"user: Can you help me update my project's documentation?\n"
|
|
128
|
-
"assistant: Of course. I have drafted the new content: '# Project "
|
|
129
|
-
"Apollo\\nThis is the new documentation for the project.' Do you "
|
|
130
|
-
"approve?\n"
|
|
131
|
-
"user: Yes, that looks great. Please proceed.\n\n"
|
|
132
|
-
"---\n"
|
|
133
|
-
"## Part 2: Analysis of Current State\n\n"
|
|
134
|
-
"**User's Main Goal:**\n"
|
|
135
|
-
"Update the project documentation.\n\n"
|
|
136
|
-
"**Next Action for Assistant:**\n"
|
|
137
|
-
"Write new content to the README.md file.\n\n"
|
|
138
|
-
"**Action Payload:**\n"
|
|
139
|
-
"# Project Apollo\n"
|
|
140
|
-
"This is the new documentation for the project.\n"
|
|
141
|
-
"---"
|
|
142
|
-
).strip()
|
|
143
|
-
|
|
144
|
-
DEFAULT_CONTEXT_ENRICHMENT_PROMPT = (
|
|
145
|
-
"You are an information extraction robot. Your sole purpose is to "
|
|
146
|
-
"extract long-term, stable facts from the conversation and update a "
|
|
147
|
-
"JSON object.\n\n"
|
|
148
|
-
"**DEFINITIONS:**\n"
|
|
149
|
-
"- **Stable Facts:** Information that does not change often. Examples: "
|
|
150
|
-
"user's name, project name, preferred programming language.\n"
|
|
151
|
-
"- **Volatile Facts (IGNORE THESE):** Information about the current, "
|
|
152
|
-
"immediate task. Examples: the user's last request, the next action to "
|
|
153
|
-
"take.\n\n"
|
|
154
|
-
"**CRITICAL RULES:**\n"
|
|
155
|
-
"1. Your ENTIRE response MUST be a single, valid JSON object. The root "
|
|
156
|
-
"object must contain a single key named 'response'.\n"
|
|
157
|
-
"2. DO NOT add any text, explanations, or markdown formatting before or "
|
|
158
|
-
"after the JSON object.\n"
|
|
159
|
-
"3. Your job is to update the JSON. If a value already exists, only "
|
|
160
|
-
"change it if the user provides new information.\n"
|
|
161
|
-
'4. If you cannot find a value for a key, use an empty string `""`. DO '
|
|
162
|
-
"NOT GUESS.\n\n"
|
|
163
|
-
"---\n"
|
|
164
|
-
"**JSON TEMPLATE TO FILL:**\n\n"
|
|
165
|
-
"Copy this exact structure. Only fill in values for stable facts you "
|
|
166
|
-
"find.\n\n"
|
|
167
|
-
"{\n"
|
|
168
|
-
' "response": {\n'
|
|
169
|
-
' "user_profile": {\n'
|
|
170
|
-
' "user_name": "",\n'
|
|
171
|
-
' "language_preference": ""\n'
|
|
172
|
-
" },\n"
|
|
173
|
-
' "project_details": {\n'
|
|
174
|
-
' "project_name": "",\n'
|
|
175
|
-
' "primary_file_path": ""\n'
|
|
176
|
-
" }\n"
|
|
177
|
-
" }\n"
|
|
178
|
-
"}\n\n"
|
|
179
|
-
"---\n"
|
|
180
|
-
"**EXAMPLE SCENARIO**\n\n"
|
|
181
|
-
"*CONVERSATION CONTEXT:*\n"
|
|
182
|
-
"User: Hi, I'm Sarah, and I'm working on the 'Apollo' project. Let's fix "
|
|
183
|
-
"a bug in `src/auth.js`.\n"
|
|
184
|
-
"Assistant: Okay Sarah, let's look at `src/auth.js` from the 'Apollo' "
|
|
185
|
-
"project.\n\n"
|
|
186
|
-
"*CORRECT JSON OUTPUT:*\n\n"
|
|
187
|
-
"{\n"
|
|
188
|
-
' "response": {\n'
|
|
189
|
-
' "user_profile": {\n'
|
|
190
|
-
' "user_name": "Sarah",\n'
|
|
191
|
-
' "language_preference": "javascript"\n'
|
|
192
|
-
" },\n"
|
|
193
|
-
' "project_details": {\n'
|
|
194
|
-
' "project_name": "Apollo",\n'
|
|
195
|
-
' "primary_file_path": "src/auth.js"\n'
|
|
196
|
-
" }\n"
|
|
197
|
-
" }\n"
|
|
198
|
-
"}"
|
|
199
|
-
).strip()
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
class LLMConfig:
|
|
203
|
-
|
|
204
|
-
def __init__(
|
|
205
|
-
self,
|
|
206
|
-
default_model_name: str | None = None,
|
|
207
|
-
default_base_url: str | None = None,
|
|
208
|
-
default_api_key: str | None = None,
|
|
209
|
-
default_persona: str | None = None,
|
|
210
|
-
default_system_prompt: str | None = None,
|
|
211
|
-
default_special_instruction_prompt: str | None = None,
|
|
212
|
-
default_summarization_prompt: str | None = None,
|
|
213
|
-
default_context_enrichment_prompt: str | None = None,
|
|
214
|
-
default_summarize_history: bool | None = None,
|
|
215
|
-
default_history_summarization_threshold: int | None = None,
|
|
216
|
-
default_enrich_context: bool | None = None,
|
|
217
|
-
default_context_enrichment_threshold: int | None = None,
|
|
218
|
-
default_model: Model | None = None,
|
|
219
|
-
default_model_settings: ModelSettings | None = None,
|
|
220
|
-
default_model_provider: Provider | None = None,
|
|
221
|
-
):
|
|
222
|
-
self._default_model_name = default_model_name
|
|
223
|
-
self._default_model_base_url = default_base_url
|
|
224
|
-
self._default_model_api_key = default_api_key
|
|
225
|
-
self._default_persona = default_persona
|
|
226
|
-
self._default_system_prompt = default_system_prompt
|
|
227
|
-
self._default_special_instruction_prompt = default_special_instruction_prompt
|
|
228
|
-
self._default_summarization_prompt = default_summarization_prompt
|
|
229
|
-
self._default_context_enrichment_prompt = default_context_enrichment_prompt
|
|
230
|
-
self._default_summarize_history = default_summarize_history
|
|
231
|
-
self._default_history_summarization_threshold = (
|
|
232
|
-
default_history_summarization_threshold
|
|
233
|
-
)
|
|
234
|
-
self._default_enrich_context = default_enrich_context
|
|
235
|
-
self._default_context_enrichment_threshold = (
|
|
236
|
-
default_context_enrichment_threshold
|
|
237
|
-
)
|
|
238
|
-
self._default_model_settings = default_model_settings
|
|
239
|
-
self._default_model_provider = default_model_provider
|
|
240
|
-
self._default_model = default_model
|
|
241
|
-
|
|
242
|
-
@property
|
|
243
|
-
def default_model_name(self) -> str | None:
|
|
244
|
-
if self._default_model_name is not None:
|
|
245
|
-
return self._default_model_name
|
|
246
|
-
if CFG.LLM_MODEL is not None:
|
|
247
|
-
return CFG.LLM_MODEL
|
|
248
|
-
return None
|
|
249
|
-
|
|
250
|
-
@property
|
|
251
|
-
def default_model_base_url(self) -> str | None:
|
|
252
|
-
if self._default_model_base_url is not None:
|
|
253
|
-
return self._default_model_base_url
|
|
254
|
-
if CFG.LLM_BASE_URL is not None:
|
|
255
|
-
return CFG.LLM_BASE_URL
|
|
256
|
-
return None
|
|
257
|
-
|
|
258
|
-
@property
|
|
259
|
-
def default_model_api_key(self) -> str | None:
|
|
260
|
-
if self._default_model_api_key is not None:
|
|
261
|
-
return self._default_model_api_key
|
|
262
|
-
if CFG.LLM_API_KEY is not None:
|
|
263
|
-
return CFG.LLM_API_KEY
|
|
264
|
-
|
|
265
|
-
@property
|
|
266
|
-
def default_model_settings(self) -> ModelSettings | None:
|
|
267
|
-
if self._default_model_settings is not None:
|
|
268
|
-
return self._default_model_settings
|
|
269
|
-
return None
|
|
270
|
-
|
|
271
|
-
@property
|
|
272
|
-
def default_model_provider(self) -> Provider | str:
|
|
273
|
-
if self._default_model_provider is not None:
|
|
274
|
-
return self._default_model_provider
|
|
275
|
-
if self.default_model_base_url is None and self.default_model_api_key is None:
|
|
276
|
-
return "openai"
|
|
277
|
-
from pydantic_ai.providers.openai import OpenAIProvider
|
|
278
|
-
|
|
279
|
-
return OpenAIProvider(
|
|
280
|
-
base_url=self.default_model_base_url, api_key=self.default_model_api_key
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
@property
|
|
284
|
-
def default_system_prompt(self) -> str:
|
|
285
|
-
if self._default_system_prompt is not None:
|
|
286
|
-
return self._default_system_prompt
|
|
287
|
-
if CFG.LLM_SYSTEM_PROMPT is not None:
|
|
288
|
-
return CFG.LLM_SYSTEM_PROMPT
|
|
289
|
-
return DEFAULT_SYSTEM_PROMPT
|
|
290
|
-
|
|
291
|
-
@property
|
|
292
|
-
def default_persona(self) -> str:
|
|
293
|
-
if self._default_persona is not None:
|
|
294
|
-
return self._default_persona
|
|
295
|
-
if CFG.LLM_PERSONA is not None:
|
|
296
|
-
return CFG.LLM_PERSONA
|
|
297
|
-
return DEFAULT_PERSONA
|
|
298
|
-
|
|
299
|
-
@property
|
|
300
|
-
def default_special_instruction_prompt(self) -> str:
|
|
301
|
-
if self._default_special_instruction_prompt is not None:
|
|
302
|
-
return self._default_special_instruction_prompt
|
|
303
|
-
if CFG.LLM_SPECIAL_INSTRUCTION_PROMPT is not None:
|
|
304
|
-
return CFG.LLM_SPECIAL_INSTRUCTION_PROMPT
|
|
305
|
-
return DEFAULT_SPECIAL_INSTRUCTION_PROMPT
|
|
306
|
-
|
|
307
|
-
@property
|
|
308
|
-
def default_summarization_prompt(self) -> str:
|
|
309
|
-
if self._default_summarization_prompt is not None:
|
|
310
|
-
return self._default_summarization_prompt
|
|
311
|
-
if CFG.LLM_SUMMARIZATION_PROMPT is not None:
|
|
312
|
-
return CFG.LLM_SUMMARIZATION_PROMPT
|
|
313
|
-
return DEFAULT_SUMMARIZATION_PROMPT
|
|
314
|
-
|
|
315
|
-
@property
|
|
316
|
-
def default_context_enrichment_prompt(self) -> str:
|
|
317
|
-
if self._default_context_enrichment_prompt is not None:
|
|
318
|
-
return self._default_context_enrichment_prompt
|
|
319
|
-
if CFG.LLM_CONTEXT_ENRICHMENT_PROMPT is not None:
|
|
320
|
-
return CFG.LLM_CONTEXT_ENRICHMENT_PROMPT
|
|
321
|
-
return DEFAULT_CONTEXT_ENRICHMENT_PROMPT
|
|
322
|
-
|
|
323
|
-
@property
|
|
324
|
-
def default_model(self) -> Model | str | None:
|
|
325
|
-
if self._default_model is not None:
|
|
326
|
-
return self._default_model
|
|
327
|
-
model_name = self.default_model_name
|
|
328
|
-
if model_name is None:
|
|
329
|
-
return None
|
|
330
|
-
from pydantic_ai.models.openai import OpenAIModel
|
|
331
|
-
|
|
332
|
-
return OpenAIModel(
|
|
333
|
-
model_name=model_name,
|
|
334
|
-
provider=self.default_model_provider,
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
@property
|
|
338
|
-
def default_summarize_history(self) -> bool:
|
|
339
|
-
if self._default_summarize_history is not None:
|
|
340
|
-
return self._default_summarize_history
|
|
341
|
-
return CFG.LLM_SUMMARIZE_HISTORY
|
|
342
|
-
|
|
343
|
-
@property
|
|
344
|
-
def default_history_summarization_threshold(self) -> int:
|
|
345
|
-
if self._default_history_summarization_threshold is not None:
|
|
346
|
-
return self._default_history_summarization_threshold
|
|
347
|
-
return CFG.LLM_HISTORY_SUMMARIZATION_THRESHOLD
|
|
348
|
-
|
|
349
|
-
@property
|
|
350
|
-
def default_enrich_context(self) -> bool:
|
|
351
|
-
if self._default_enrich_context is not None:
|
|
352
|
-
return self._default_enrich_context
|
|
353
|
-
return CFG.LLM_ENRICH_CONTEXT
|
|
354
|
-
|
|
355
|
-
@property
|
|
356
|
-
def default_context_enrichment_threshold(self) -> int:
|
|
357
|
-
if self._default_context_enrichment_threshold is not None:
|
|
358
|
-
return self._default_context_enrichment_threshold
|
|
359
|
-
return CFG.LLM_CONTEXT_ENRICHMENT_THRESHOLD
|
|
360
|
-
|
|
361
|
-
def set_default_persona(self, persona: str):
|
|
362
|
-
self._default_persona = persona
|
|
363
|
-
|
|
364
|
-
def set_default_system_prompt(self, system_prompt: str):
|
|
365
|
-
self._default_system_prompt = system_prompt
|
|
366
|
-
|
|
367
|
-
def set_default_special_instruction_prompt(self, special_instruction_prompt: str):
|
|
368
|
-
self._default_special_instruction_prompt = special_instruction_prompt
|
|
369
|
-
|
|
370
|
-
def set_default_summarization_prompt(self, summarization_prompt: str):
|
|
371
|
-
self._default_summarization_prompt = summarization_prompt
|
|
372
|
-
|
|
373
|
-
def set_default_context_enrichment_prompt(self, context_enrichment_prompt: str):
|
|
374
|
-
self._default_context_enrichment_prompt = context_enrichment_prompt
|
|
375
|
-
|
|
376
|
-
def set_default_model_name(self, model_name: str):
|
|
377
|
-
self._default_model_name = model_name
|
|
378
|
-
|
|
379
|
-
def set_default_model_api_key(self, model_api_key: str):
|
|
380
|
-
self._default_model_api_key = model_api_key
|
|
381
|
-
|
|
382
|
-
def set_default_model_base_url(self, model_base_url: str):
|
|
383
|
-
self._default_model_base_url = model_base_url
|
|
384
|
-
|
|
385
|
-
def set_default_model_provider(self, provider: Provider | str):
|
|
386
|
-
self._default_model_provider = provider
|
|
387
|
-
|
|
388
|
-
def set_default_model(self, model: Model | str):
|
|
389
|
-
self._default_model = model
|
|
390
|
-
|
|
391
|
-
def set_default_summarize_history(self, summarize_history: bool):
|
|
392
|
-
self._default_summarize_history = summarize_history
|
|
393
|
-
|
|
394
|
-
def set_default_history_summarization_threshold(
|
|
395
|
-
self, history_summarization_threshold: int
|
|
396
|
-
):
|
|
397
|
-
self._default_history_summarization_threshold = history_summarization_threshold
|
|
398
|
-
|
|
399
|
-
def set_default_enrich_context(self, enrich_context: bool):
|
|
400
|
-
self._default_enrich_context = enrich_context
|
|
401
|
-
|
|
402
|
-
def set_default_context_enrichment_threshold(
|
|
403
|
-
self, context_enrichment_threshold: int
|
|
404
|
-
):
|
|
405
|
-
self._default_context_enrichment_threshold = context_enrichment_threshold
|
|
406
|
-
|
|
407
|
-
def set_default_model_settings(self, model_settings: ModelSettings):
|
|
408
|
-
self._default_model_settings = model_settings
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
llm_config = LLMConfig()
|
zrb/llm_rate_limitter.py
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import time
|
|
3
|
-
from collections import deque
|
|
4
|
-
from typing import Callable
|
|
5
|
-
|
|
6
|
-
import tiktoken
|
|
7
|
-
|
|
8
|
-
from zrb.config import CFG
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _estimate_token(text: str) -> int:
|
|
12
|
-
enc = tiktoken.encoding_for_model("gpt-4o")
|
|
13
|
-
return len(enc.encode(text))
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class LLMRateLimiter:
|
|
17
|
-
"""
|
|
18
|
-
Helper class to enforce LLM API rate limits and throttling.
|
|
19
|
-
Tracks requests and tokens in a rolling 60-second window.
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
def __init__(
|
|
23
|
-
self,
|
|
24
|
-
max_requests_per_minute: int | None = None,
|
|
25
|
-
max_tokens_per_minute: int | None = None,
|
|
26
|
-
max_tokens_per_request: int | None = None,
|
|
27
|
-
throttle_sleep: float | None = None,
|
|
28
|
-
token_counter_fn: Callable[[str], int] | None = None,
|
|
29
|
-
):
|
|
30
|
-
self._max_requests_per_minute = max_requests_per_minute
|
|
31
|
-
self._max_tokens_per_minute = max_tokens_per_minute
|
|
32
|
-
self._max_tokens_per_request = max_tokens_per_request
|
|
33
|
-
self._throttle_sleep = throttle_sleep
|
|
34
|
-
self._token_counter_fn = token_counter_fn
|
|
35
|
-
self.request_times = deque()
|
|
36
|
-
self.token_times = deque()
|
|
37
|
-
|
|
38
|
-
@property
|
|
39
|
-
def max_requests_per_minute(self) -> int:
|
|
40
|
-
if self._max_requests_per_minute is not None:
|
|
41
|
-
return self._max_requests_per_minute
|
|
42
|
-
return CFG.LLM_MAX_REQUESTS_PER_MINUTE
|
|
43
|
-
|
|
44
|
-
@property
|
|
45
|
-
def max_tokens_per_minute(self) -> int:
|
|
46
|
-
if self._max_tokens_per_minute is not None:
|
|
47
|
-
return self._max_tokens_per_minute
|
|
48
|
-
return CFG.LLM_MAX_TOKENS_PER_MINUTE
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def max_tokens_per_request(self) -> int:
|
|
52
|
-
if self._max_tokens_per_request is not None:
|
|
53
|
-
return self._max_tokens_per_request
|
|
54
|
-
return CFG.LLM_MAX_TOKENS_PER_REQUEST
|
|
55
|
-
|
|
56
|
-
@property
|
|
57
|
-
def throttle_sleep(self) -> float:
|
|
58
|
-
if self._throttle_sleep is not None:
|
|
59
|
-
return self._throttle_sleep
|
|
60
|
-
return CFG.LLM_THROTTLE_SLEEP
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def count_token(self) -> Callable[[str], int]:
|
|
64
|
-
if self._token_counter_fn is not None:
|
|
65
|
-
return self._token_counter_fn
|
|
66
|
-
return _estimate_token
|
|
67
|
-
|
|
68
|
-
def set_max_requests_per_minute(self, value: int):
|
|
69
|
-
self._max_requests_per_minute = value
|
|
70
|
-
|
|
71
|
-
def set_max_tokens_per_minute(self, value: int):
|
|
72
|
-
self._max_tokens_per_minute = value
|
|
73
|
-
|
|
74
|
-
def set_max_tokens_per_request(self, value: int):
|
|
75
|
-
self._max_tokens_per_request = value
|
|
76
|
-
|
|
77
|
-
def set_throttle_sleep(self, value: float):
|
|
78
|
-
self._throttle_sleep = value
|
|
79
|
-
|
|
80
|
-
def set_token_counter_fn(self, fn: Callable[[str], int]):
|
|
81
|
-
self._token_counter_fn = fn
|
|
82
|
-
|
|
83
|
-
def clip_prompt(self, prompt: str, limit: int) -> str:
|
|
84
|
-
token_count = self.count_token(prompt)
|
|
85
|
-
if token_count <= limit:
|
|
86
|
-
return prompt
|
|
87
|
-
while token_count > limit:
|
|
88
|
-
prompt_parts = prompt.split(" ")
|
|
89
|
-
last_part_index = len(prompt_parts) - 2
|
|
90
|
-
clipped_prompt = " ".join(prompt_parts[:last_part_index])
|
|
91
|
-
token_count = self.count_token(clipped_prompt)
|
|
92
|
-
if token_count < limit:
|
|
93
|
-
return clipped_prompt
|
|
94
|
-
return prompt[:limit]
|
|
95
|
-
|
|
96
|
-
async def throttle(self, prompt: str):
|
|
97
|
-
now = time.time()
|
|
98
|
-
tokens = self.count_token(prompt)
|
|
99
|
-
# Clean up old entries
|
|
100
|
-
while self.request_times and now - self.request_times[0] > 60:
|
|
101
|
-
self.request_times.popleft()
|
|
102
|
-
while self.token_times and now - self.token_times[0][0] > 60:
|
|
103
|
-
self.token_times.popleft()
|
|
104
|
-
# Check per-request token limit
|
|
105
|
-
if tokens > self.max_tokens_per_request:
|
|
106
|
-
raise ValueError(
|
|
107
|
-
f"Request exceeds max_tokens_per_request ({self.max_tokens_per_request})."
|
|
108
|
-
)
|
|
109
|
-
# Wait if over per-minute request or token limit
|
|
110
|
-
while (
|
|
111
|
-
len(self.request_times) >= self.max_requests_per_minute
|
|
112
|
-
or sum(t for _, t in self.token_times) + tokens > self.max_tokens_per_minute
|
|
113
|
-
):
|
|
114
|
-
await asyncio.sleep(self.throttle_sleep)
|
|
115
|
-
now = time.time()
|
|
116
|
-
while self.request_times and now - self.request_times[0] > 60:
|
|
117
|
-
self.request_times.popleft()
|
|
118
|
-
while self.token_times and now - self.token_times[0][0] > 60:
|
|
119
|
-
self.token_times.popleft()
|
|
120
|
-
# Record this request
|
|
121
|
-
self.request_times.append(now)
|
|
122
|
-
self.token_times.append((now, tokens))
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
llm_rate_limitter = LLMRateLimiter()
|
zrb/task/llm/context.py
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
import inspect
|
|
3
|
-
import os
|
|
4
|
-
import platform
|
|
5
|
-
import re
|
|
6
|
-
from collections.abc import Callable
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
from zrb.context.any_context import AnyContext
|
|
10
|
-
from zrb.context.any_shared_context import AnySharedContext
|
|
11
|
-
from zrb.util.attr import get_attr
|
|
12
|
-
from zrb.util.file import read_dir, read_file_with_line_numbers
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def extract_default_context(user_message: str) -> tuple[str, dict[str, Any]]:
|
|
16
|
-
"""
|
|
17
|
-
Return modified user message and default context including time, OS, and file references.
|
|
18
|
-
"""
|
|
19
|
-
modified_user_message = user_message
|
|
20
|
-
# Match “@” + any non-space/comma sequence that contains at least one “/”
|
|
21
|
-
pattern = r"(?<!\w)@(?=[^,\s]*/)([^,\s]+)"
|
|
22
|
-
potential_resource_path = re.findall(pattern, user_message)
|
|
23
|
-
current_references = []
|
|
24
|
-
|
|
25
|
-
for ref in potential_resource_path:
|
|
26
|
-
resource_path = os.path.abspath(os.path.expanduser(ref))
|
|
27
|
-
print("RESOURCE PATH", resource_path)
|
|
28
|
-
if os.path.isfile(resource_path):
|
|
29
|
-
content = read_file_with_line_numbers(resource_path)
|
|
30
|
-
current_references.append(
|
|
31
|
-
{
|
|
32
|
-
"reference": ref,
|
|
33
|
-
"name": resource_path,
|
|
34
|
-
"type": "file",
|
|
35
|
-
"note": "line numbers are included in the content",
|
|
36
|
-
"content": content,
|
|
37
|
-
}
|
|
38
|
-
)
|
|
39
|
-
# Remove the '@' from the modified user message for valid file paths
|
|
40
|
-
modified_user_message = modified_user_message.replace(f"@{ref}", ref, 1)
|
|
41
|
-
elif os.path.isdir(resource_path):
|
|
42
|
-
content = read_dir(resource_path)
|
|
43
|
-
current_references.append(
|
|
44
|
-
{
|
|
45
|
-
"reference": ref,
|
|
46
|
-
"name": resource_path,
|
|
47
|
-
"type": "directory",
|
|
48
|
-
"content": content,
|
|
49
|
-
}
|
|
50
|
-
)
|
|
51
|
-
# Remove the '@' from the modified user message for valid directory paths
|
|
52
|
-
modified_user_message = modified_user_message.replace(f"@{ref}", ref, 1)
|
|
53
|
-
|
|
54
|
-
context = {
|
|
55
|
-
"current_time": datetime.datetime.now().isoformat(),
|
|
56
|
-
"current_working_directory": os.getcwd(),
|
|
57
|
-
"current_os": platform.system(),
|
|
58
|
-
"os_version": platform.version(),
|
|
59
|
-
"python_version": platform.python_version(),
|
|
60
|
-
"current_references": current_references,
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return modified_user_message, context
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def get_conversation_context(
|
|
67
|
-
ctx: AnyContext,
|
|
68
|
-
conversation_context_attr: (
|
|
69
|
-
dict[str, Any] | Callable[[AnySharedContext], dict[str, Any]] | None
|
|
70
|
-
),
|
|
71
|
-
) -> dict[str, Any]:
|
|
72
|
-
"""
|
|
73
|
-
Retrieves the conversation context.
|
|
74
|
-
If a value in the context dict is callable, it executes it with ctx.
|
|
75
|
-
"""
|
|
76
|
-
raw_context = get_attr(ctx, conversation_context_attr, {}, auto_render=False)
|
|
77
|
-
if not isinstance(raw_context, dict):
|
|
78
|
-
ctx.log_warning(
|
|
79
|
-
f"Conversation context resolved to type {type(raw_context)}, "
|
|
80
|
-
"expected dict. Returning empty context."
|
|
81
|
-
)
|
|
82
|
-
return {}
|
|
83
|
-
# If conversation_context contains callable value, execute them.
|
|
84
|
-
processed_context: dict[str, Any] = {}
|
|
85
|
-
for key, value in raw_context.items():
|
|
86
|
-
if callable(value):
|
|
87
|
-
try:
|
|
88
|
-
# Check if the callable expects 'ctx'
|
|
89
|
-
sig = inspect.signature(value)
|
|
90
|
-
if "ctx" in sig.parameters:
|
|
91
|
-
processed_context[key] = value(ctx)
|
|
92
|
-
else:
|
|
93
|
-
processed_context[key] = value()
|
|
94
|
-
except Exception as e:
|
|
95
|
-
ctx.log_warning(
|
|
96
|
-
f"Error executing callable for context key '{key}': {e}. "
|
|
97
|
-
"Skipping."
|
|
98
|
-
)
|
|
99
|
-
processed_context[key] = None
|
|
100
|
-
else:
|
|
101
|
-
processed_context[key] = value
|
|
102
|
-
return processed_context
|