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.

Files changed (147) hide show
  1. zrb/__init__.py +126 -113
  2. zrb/__main__.py +1 -1
  3. zrb/attr/type.py +10 -7
  4. zrb/builtin/__init__.py +2 -50
  5. zrb/builtin/git.py +12 -1
  6. zrb/builtin/group.py +31 -15
  7. zrb/builtin/http.py +7 -8
  8. zrb/builtin/llm/attachment.py +40 -0
  9. zrb/builtin/llm/chat_completion.py +274 -0
  10. zrb/builtin/llm/chat_session.py +152 -85
  11. zrb/builtin/llm/chat_session_cmd.py +288 -0
  12. zrb/builtin/llm/chat_trigger.py +79 -0
  13. zrb/builtin/llm/history.py +7 -9
  14. zrb/builtin/llm/llm_ask.py +221 -98
  15. zrb/builtin/llm/tool/api.py +74 -52
  16. zrb/builtin/llm/tool/cli.py +46 -17
  17. zrb/builtin/llm/tool/code.py +71 -90
  18. zrb/builtin/llm/tool/file.py +301 -241
  19. zrb/builtin/llm/tool/note.py +84 -0
  20. zrb/builtin/llm/tool/rag.py +38 -8
  21. zrb/builtin/llm/tool/sub_agent.py +67 -50
  22. zrb/builtin/llm/tool/web.py +146 -122
  23. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
  24. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
  25. zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
  26. zrb/builtin/searxng/config/settings.yml +5671 -0
  27. zrb/builtin/searxng/start.py +21 -0
  28. zrb/builtin/setup/latex/ubuntu.py +1 -0
  29. zrb/builtin/setup/ubuntu.py +1 -1
  30. zrb/builtin/shell/autocomplete/bash.py +4 -3
  31. zrb/builtin/shell/autocomplete/zsh.py +4 -3
  32. zrb/builtin/todo.py +13 -2
  33. zrb/config/config.py +614 -0
  34. zrb/config/default_prompt/file_extractor_system_prompt.md +112 -0
  35. zrb/config/default_prompt/interactive_system_prompt.md +29 -0
  36. zrb/config/default_prompt/persona.md +1 -0
  37. zrb/config/default_prompt/repo_extractor_system_prompt.md +112 -0
  38. zrb/config/default_prompt/repo_summarizer_system_prompt.md +29 -0
  39. zrb/config/default_prompt/summarization_prompt.md +57 -0
  40. zrb/config/default_prompt/system_prompt.md +38 -0
  41. zrb/config/llm_config.py +339 -0
  42. zrb/config/llm_context/config.py +166 -0
  43. zrb/config/llm_context/config_parser.py +40 -0
  44. zrb/config/llm_context/workflow.py +81 -0
  45. zrb/config/llm_rate_limitter.py +190 -0
  46. zrb/{runner → config}/web_auth_config.py +17 -22
  47. zrb/context/any_shared_context.py +17 -1
  48. zrb/context/context.py +16 -2
  49. zrb/context/shared_context.py +18 -8
  50. zrb/group/any_group.py +12 -5
  51. zrb/group/group.py +67 -3
  52. zrb/input/any_input.py +5 -1
  53. zrb/input/base_input.py +18 -6
  54. zrb/input/option_input.py +13 -1
  55. zrb/input/text_input.py +8 -25
  56. zrb/runner/cli.py +25 -23
  57. zrb/runner/common_util.py +24 -19
  58. zrb/runner/web_app.py +3 -3
  59. zrb/runner/web_route/docs_route.py +1 -1
  60. zrb/runner/web_route/error_page/serve_default_404.py +1 -1
  61. zrb/runner/web_route/error_page/show_error_page.py +1 -1
  62. zrb/runner/web_route/home_page/home_page_route.py +2 -2
  63. zrb/runner/web_route/login_api_route.py +1 -1
  64. zrb/runner/web_route/login_page/login_page_route.py +2 -2
  65. zrb/runner/web_route/logout_api_route.py +1 -1
  66. zrb/runner/web_route/logout_page/logout_page_route.py +2 -2
  67. zrb/runner/web_route/node_page/group/show_group_page.py +1 -1
  68. zrb/runner/web_route/node_page/node_page_route.py +1 -1
  69. zrb/runner/web_route/node_page/task/show_task_page.py +1 -1
  70. zrb/runner/web_route/refresh_token_api_route.py +1 -1
  71. zrb/runner/web_route/static/static_route.py +1 -1
  72. zrb/runner/web_route/task_input_api_route.py +6 -6
  73. zrb/runner/web_route/task_session_api_route.py +20 -12
  74. zrb/runner/web_util/cookie.py +1 -1
  75. zrb/runner/web_util/token.py +1 -1
  76. zrb/runner/web_util/user.py +8 -4
  77. zrb/session/any_session.py +24 -17
  78. zrb/session/session.py +50 -25
  79. zrb/session_state_logger/any_session_state_logger.py +9 -4
  80. zrb/session_state_logger/file_session_state_logger.py +16 -6
  81. zrb/session_state_logger/session_state_logger_factory.py +1 -1
  82. zrb/task/any_task.py +30 -9
  83. zrb/task/base/context.py +17 -9
  84. zrb/task/base/execution.py +15 -8
  85. zrb/task/base/lifecycle.py +8 -4
  86. zrb/task/base/monitoring.py +12 -7
  87. zrb/task/base_task.py +69 -5
  88. zrb/task/base_trigger.py +12 -5
  89. zrb/task/cmd_task.py +1 -1
  90. zrb/task/llm/agent.py +154 -161
  91. zrb/task/llm/agent_runner.py +152 -0
  92. zrb/task/llm/config.py +47 -18
  93. zrb/task/llm/conversation_history.py +209 -0
  94. zrb/task/llm/conversation_history_model.py +67 -0
  95. zrb/task/llm/default_workflow/coding/workflow.md +41 -0
  96. zrb/task/llm/default_workflow/copywriting/workflow.md +68 -0
  97. zrb/task/llm/default_workflow/git/workflow.md +118 -0
  98. zrb/task/llm/default_workflow/golang/workflow.md +128 -0
  99. zrb/task/llm/default_workflow/html-css/workflow.md +135 -0
  100. zrb/task/llm/default_workflow/java/workflow.md +146 -0
  101. zrb/task/llm/default_workflow/javascript/workflow.md +158 -0
  102. zrb/task/llm/default_workflow/python/workflow.md +160 -0
  103. zrb/task/llm/default_workflow/researching/workflow.md +153 -0
  104. zrb/task/llm/default_workflow/rust/workflow.md +162 -0
  105. zrb/task/llm/default_workflow/shell/workflow.md +299 -0
  106. zrb/task/llm/error.py +24 -10
  107. zrb/task/llm/file_replacement.py +206 -0
  108. zrb/task/llm/file_tool_model.py +57 -0
  109. zrb/task/llm/history_processor.py +206 -0
  110. zrb/task/llm/history_summarization.py +11 -166
  111. zrb/task/llm/print_node.py +193 -69
  112. zrb/task/llm/prompt.py +242 -45
  113. zrb/task/llm/subagent_conversation_history.py +41 -0
  114. zrb/task/llm/tool_wrapper.py +260 -57
  115. zrb/task/llm/workflow.py +76 -0
  116. zrb/task/llm_task.py +182 -171
  117. zrb/task/make_task.py +2 -3
  118. zrb/task/rsync_task.py +26 -11
  119. zrb/task/scheduler.py +4 -4
  120. zrb/util/attr.py +54 -39
  121. zrb/util/callable.py +23 -0
  122. zrb/util/cli/markdown.py +12 -0
  123. zrb/util/cli/text.py +30 -0
  124. zrb/util/file.py +29 -11
  125. zrb/util/git.py +8 -11
  126. zrb/util/git_diff_model.py +10 -0
  127. zrb/util/git_subtree.py +9 -14
  128. zrb/util/git_subtree_model.py +32 -0
  129. zrb/util/init_path.py +1 -1
  130. zrb/util/markdown.py +62 -0
  131. zrb/util/string/conversion.py +2 -2
  132. zrb/util/todo.py +17 -50
  133. zrb/util/todo_model.py +46 -0
  134. zrb/util/truncate.py +23 -0
  135. zrb/util/yaml.py +204 -0
  136. zrb/xcom/xcom.py +10 -0
  137. zrb-1.21.29.dist-info/METADATA +270 -0
  138. {zrb-1.8.10.dist-info → zrb-1.21.29.dist-info}/RECORD +140 -98
  139. {zrb-1.8.10.dist-info → zrb-1.21.29.dist-info}/WHEEL +1 -1
  140. zrb/config.py +0 -335
  141. zrb/llm_config.py +0 -411
  142. zrb/llm_rate_limitter.py +0 -125
  143. zrb/task/llm/context.py +0 -102
  144. zrb/task/llm/context_enrichment.py +0 -199
  145. zrb/task/llm/history.py +0 -211
  146. zrb-1.8.10.dist-info/METADATA +0 -264
  147. {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