zrb 1.21.29__py3-none-any.whl → 2.0.0a4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of zrb might be problematic. Click here for more details.

Files changed (192) hide show
  1. zrb/__init__.py +118 -129
  2. zrb/builtin/__init__.py +54 -2
  3. zrb/builtin/llm/chat.py +147 -0
  4. zrb/callback/callback.py +8 -1
  5. zrb/cmd/cmd_result.py +2 -1
  6. zrb/config/config.py +491 -280
  7. zrb/config/helper.py +84 -0
  8. zrb/config/web_auth_config.py +50 -35
  9. zrb/context/any_shared_context.py +13 -2
  10. zrb/context/context.py +31 -3
  11. zrb/context/print_fn.py +13 -0
  12. zrb/context/shared_context.py +14 -1
  13. zrb/input/option_input.py +30 -2
  14. zrb/llm/agent/__init__.py +9 -0
  15. zrb/llm/agent/agent.py +215 -0
  16. zrb/llm/agent/summarizer.py +20 -0
  17. zrb/llm/app/__init__.py +10 -0
  18. zrb/llm/app/completion.py +281 -0
  19. zrb/llm/app/confirmation/allow_tool.py +66 -0
  20. zrb/llm/app/confirmation/handler.py +178 -0
  21. zrb/llm/app/confirmation/replace_confirmation.py +77 -0
  22. zrb/llm/app/keybinding.py +34 -0
  23. zrb/llm/app/layout.py +117 -0
  24. zrb/llm/app/lexer.py +155 -0
  25. zrb/llm/app/redirection.py +28 -0
  26. zrb/llm/app/style.py +16 -0
  27. zrb/llm/app/ui.py +733 -0
  28. zrb/llm/config/__init__.py +4 -0
  29. zrb/llm/config/config.py +122 -0
  30. zrb/llm/config/limiter.py +247 -0
  31. zrb/llm/history_manager/__init__.py +4 -0
  32. zrb/llm/history_manager/any_history_manager.py +23 -0
  33. zrb/llm/history_manager/file_history_manager.py +91 -0
  34. zrb/llm/history_processor/summarizer.py +108 -0
  35. zrb/llm/note/__init__.py +3 -0
  36. zrb/llm/note/manager.py +122 -0
  37. zrb/llm/prompt/__init__.py +29 -0
  38. zrb/llm/prompt/claude_compatibility.py +92 -0
  39. zrb/llm/prompt/compose.py +55 -0
  40. zrb/llm/prompt/default.py +51 -0
  41. zrb/llm/prompt/markdown/mandate.md +23 -0
  42. zrb/llm/prompt/markdown/persona.md +3 -0
  43. zrb/llm/prompt/markdown/summarizer.md +21 -0
  44. zrb/llm/prompt/note.py +41 -0
  45. zrb/llm/prompt/system_context.py +46 -0
  46. zrb/llm/prompt/zrb.py +41 -0
  47. zrb/llm/skill/__init__.py +3 -0
  48. zrb/llm/skill/manager.py +86 -0
  49. zrb/llm/task/__init__.py +4 -0
  50. zrb/llm/task/llm_chat_task.py +316 -0
  51. zrb/llm/task/llm_task.py +245 -0
  52. zrb/llm/tool/__init__.py +39 -0
  53. zrb/llm/tool/bash.py +75 -0
  54. zrb/llm/tool/code.py +266 -0
  55. zrb/llm/tool/file.py +419 -0
  56. zrb/llm/tool/note.py +70 -0
  57. zrb/{builtin/llm → llm}/tool/rag.py +8 -5
  58. zrb/llm/tool/search/brave.py +53 -0
  59. zrb/llm/tool/search/searxng.py +47 -0
  60. zrb/llm/tool/search/serpapi.py +47 -0
  61. zrb/llm/tool/skill.py +19 -0
  62. zrb/llm/tool/sub_agent.py +70 -0
  63. zrb/llm/tool/web.py +97 -0
  64. zrb/llm/tool/zrb_task.py +66 -0
  65. zrb/llm/util/attachment.py +101 -0
  66. zrb/llm/util/prompt.py +104 -0
  67. zrb/llm/util/stream_response.py +178 -0
  68. zrb/session/any_session.py +0 -3
  69. zrb/session/session.py +1 -1
  70. zrb/task/base/context.py +25 -13
  71. zrb/task/base/execution.py +52 -47
  72. zrb/task/base/lifecycle.py +7 -4
  73. zrb/task/base_task.py +48 -49
  74. zrb/task/base_trigger.py +4 -1
  75. zrb/task/cmd_task.py +6 -0
  76. zrb/task/http_check.py +11 -5
  77. zrb/task/make_task.py +3 -0
  78. zrb/task/rsync_task.py +5 -0
  79. zrb/task/scaffolder.py +7 -4
  80. zrb/task/scheduler.py +3 -0
  81. zrb/task/tcp_check.py +6 -4
  82. zrb/util/ascii_art/art/bee.txt +17 -0
  83. zrb/util/ascii_art/art/cat.txt +9 -0
  84. zrb/util/ascii_art/art/ghost.txt +16 -0
  85. zrb/util/ascii_art/art/panda.txt +17 -0
  86. zrb/util/ascii_art/art/rose.txt +14 -0
  87. zrb/util/ascii_art/art/unicorn.txt +15 -0
  88. zrb/util/ascii_art/banner.py +92 -0
  89. zrb/util/cli/markdown.py +22 -2
  90. zrb/util/cmd/command.py +33 -10
  91. zrb/util/file.py +51 -32
  92. zrb/util/match.py +78 -0
  93. zrb/util/run.py +3 -3
  94. {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/METADATA +9 -15
  95. {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/RECORD +100 -128
  96. zrb/attr/__init__.py +0 -0
  97. zrb/builtin/llm/attachment.py +0 -40
  98. zrb/builtin/llm/chat_completion.py +0 -274
  99. zrb/builtin/llm/chat_session.py +0 -270
  100. zrb/builtin/llm/chat_session_cmd.py +0 -288
  101. zrb/builtin/llm/chat_trigger.py +0 -79
  102. zrb/builtin/llm/history.py +0 -71
  103. zrb/builtin/llm/input.py +0 -27
  104. zrb/builtin/llm/llm_ask.py +0 -269
  105. zrb/builtin/llm/previous-session.js +0 -21
  106. zrb/builtin/llm/tool/__init__.py +0 -0
  107. zrb/builtin/llm/tool/api.py +0 -75
  108. zrb/builtin/llm/tool/cli.py +0 -52
  109. zrb/builtin/llm/tool/code.py +0 -236
  110. zrb/builtin/llm/tool/file.py +0 -560
  111. zrb/builtin/llm/tool/note.py +0 -84
  112. zrb/builtin/llm/tool/sub_agent.py +0 -150
  113. zrb/builtin/llm/tool/web.py +0 -171
  114. zrb/builtin/project/__init__.py +0 -0
  115. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/__init__.py +0 -0
  116. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/service/__init__.py +0 -0
  117. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/__init__.py +0 -0
  118. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/__init__.py +0 -0
  119. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/__init__.py +0 -0
  120. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/__init__.py +0 -0
  121. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/__init__.py +0 -0
  122. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/__init__.py +0 -0
  123. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/__init__.py +0 -0
  124. zrb/builtin/project/create/__init__.py +0 -0
  125. zrb/builtin/shell/__init__.py +0 -0
  126. zrb/builtin/shell/autocomplete/__init__.py +0 -0
  127. zrb/callback/__init__.py +0 -0
  128. zrb/cmd/__init__.py +0 -0
  129. zrb/config/default_prompt/interactive_system_prompt.md +0 -29
  130. zrb/config/default_prompt/persona.md +0 -1
  131. zrb/config/default_prompt/summarization_prompt.md +0 -57
  132. zrb/config/default_prompt/system_prompt.md +0 -38
  133. zrb/config/llm_config.py +0 -339
  134. zrb/config/llm_context/config.py +0 -166
  135. zrb/config/llm_context/config_parser.py +0 -40
  136. zrb/config/llm_context/workflow.py +0 -81
  137. zrb/config/llm_rate_limitter.py +0 -190
  138. zrb/content_transformer/__init__.py +0 -0
  139. zrb/context/__init__.py +0 -0
  140. zrb/dot_dict/__init__.py +0 -0
  141. zrb/env/__init__.py +0 -0
  142. zrb/group/__init__.py +0 -0
  143. zrb/input/__init__.py +0 -0
  144. zrb/runner/__init__.py +0 -0
  145. zrb/runner/web_route/__init__.py +0 -0
  146. zrb/runner/web_route/home_page/__init__.py +0 -0
  147. zrb/session/__init__.py +0 -0
  148. zrb/session_state_log/__init__.py +0 -0
  149. zrb/session_state_logger/__init__.py +0 -0
  150. zrb/task/__init__.py +0 -0
  151. zrb/task/base/__init__.py +0 -0
  152. zrb/task/llm/__init__.py +0 -0
  153. zrb/task/llm/agent.py +0 -204
  154. zrb/task/llm/agent_runner.py +0 -152
  155. zrb/task/llm/config.py +0 -122
  156. zrb/task/llm/conversation_history.py +0 -209
  157. zrb/task/llm/conversation_history_model.py +0 -67
  158. zrb/task/llm/default_workflow/coding/workflow.md +0 -41
  159. zrb/task/llm/default_workflow/copywriting/workflow.md +0 -68
  160. zrb/task/llm/default_workflow/git/workflow.md +0 -118
  161. zrb/task/llm/default_workflow/golang/workflow.md +0 -128
  162. zrb/task/llm/default_workflow/html-css/workflow.md +0 -135
  163. zrb/task/llm/default_workflow/java/workflow.md +0 -146
  164. zrb/task/llm/default_workflow/javascript/workflow.md +0 -158
  165. zrb/task/llm/default_workflow/python/workflow.md +0 -160
  166. zrb/task/llm/default_workflow/researching/workflow.md +0 -153
  167. zrb/task/llm/default_workflow/rust/workflow.md +0 -162
  168. zrb/task/llm/default_workflow/shell/workflow.md +0 -299
  169. zrb/task/llm/error.py +0 -95
  170. zrb/task/llm/file_replacement.py +0 -206
  171. zrb/task/llm/file_tool_model.py +0 -57
  172. zrb/task/llm/history_processor.py +0 -206
  173. zrb/task/llm/history_summarization.py +0 -25
  174. zrb/task/llm/print_node.py +0 -221
  175. zrb/task/llm/prompt.py +0 -321
  176. zrb/task/llm/subagent_conversation_history.py +0 -41
  177. zrb/task/llm/tool_wrapper.py +0 -361
  178. zrb/task/llm/typing.py +0 -3
  179. zrb/task/llm/workflow.py +0 -76
  180. zrb/task/llm_task.py +0 -379
  181. zrb/task_status/__init__.py +0 -0
  182. zrb/util/__init__.py +0 -0
  183. zrb/util/cli/__init__.py +0 -0
  184. zrb/util/cmd/__init__.py +0 -0
  185. zrb/util/codemod/__init__.py +0 -0
  186. zrb/util/string/__init__.py +0 -0
  187. zrb/xcom/__init__.py +0 -0
  188. /zrb/{config/default_prompt/file_extractor_system_prompt.md → llm/prompt/markdown/file_extractor.md} +0 -0
  189. /zrb/{config/default_prompt/repo_extractor_system_prompt.md → llm/prompt/markdown/repo_extractor.md} +0 -0
  190. /zrb/{config/default_prompt/repo_summarizer_system_prompt.md → llm/prompt/markdown/repo_summarizer.md} +0 -0
  191. {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/WHEEL +0 -0
  192. {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/entry_points.txt +0 -0
zrb/config/llm_config.py DELETED
@@ -1,339 +0,0 @@
1
- import os
2
- from typing import TYPE_CHECKING, Any, Callable
3
-
4
- from zrb.config.config import CFG
5
-
6
- if TYPE_CHECKING:
7
- from pydantic_ai.models import Model
8
- from pydantic_ai.providers import Provider
9
- from pydantic_ai.settings import ModelSettings
10
-
11
-
12
- class LLMConfig:
13
- def __init__(
14
- self,
15
- default_model_name: str | None = None,
16
- default_model_base_url: str | None = None,
17
- default_model_api_key: str | None = None,
18
- default_small_model_name: str | None = None,
19
- default_small_model_base_url: str | None = None,
20
- default_small_model_api_key: str | None = None,
21
- default_persona: str | None = None,
22
- default_system_prompt: str | None = None,
23
- default_interactive_system_prompt: str | None = None,
24
- default_special_instruction_prompt: str | None = None,
25
- default_summarization_prompt: str | None = None,
26
- default_summarize_history: bool | None = None,
27
- default_history_summarization_token_threshold: int | None = None,
28
- default_workflows: list[str] | None = None,
29
- default_model: "Model | None" = None,
30
- default_model_settings: "ModelSettings | None" = None,
31
- default_model_provider: "Provider | None" = None,
32
- default_small_model: "Model | None" = None,
33
- default_small_model_settings: "ModelSettings | None" = None,
34
- default_small_model_provider: "Provider | None" = None,
35
- default_yolo_mode: bool | list[str] | None = None,
36
- default_current_weather_tool: Callable | None = None,
37
- default_current_location_tool: Callable | None = None,
38
- default_search_internet_tool: Callable | None = None,
39
- ):
40
- self.__internal_default_prompt: dict[str, str] = {}
41
- self._default_model_name = default_model_name
42
- self._default_model_base_url = default_model_base_url
43
- self._default_model_api_key = default_model_api_key
44
- self._default_small_model_name = default_small_model_name
45
- self._default_small_model_base_url = default_small_model_base_url
46
- self._default_small_model_api_key = default_small_model_api_key
47
- self._default_persona = default_persona
48
- self._default_system_prompt = default_system_prompt
49
- self._default_interactive_system_prompt = default_interactive_system_prompt
50
- self._default_special_instruction_prompt = default_special_instruction_prompt
51
- self._default_summarization_prompt = default_summarization_prompt
52
- self._default_summarize_history = default_summarize_history
53
- self._default_history_summarization_token_threshold = (
54
- default_history_summarization_token_threshold
55
- )
56
- self._default_workflows = default_workflows
57
- self._default_model = default_model
58
- self._default_model_settings = default_model_settings
59
- self._default_model_provider = default_model_provider
60
- self._default_small_model = default_small_model
61
- self._default_small_model_settings = default_small_model_settings
62
- self._default_small_model_provider = default_small_model_provider
63
- self._default_yolo_mode = default_yolo_mode
64
- self._default_current_weather_tool = default_current_weather_tool
65
- self._default_current_location_tool = default_current_location_tool
66
- self._default_search_internet_tool = default_search_internet_tool
67
-
68
- def _get_internal_default_prompt(self, name: str) -> str:
69
- if name not in self.__internal_default_prompt:
70
- file_path = os.path.join(
71
- os.path.dirname(__file__), "default_prompt", f"{name}.md"
72
- )
73
- with open(file_path, "r") as f:
74
- self.__internal_default_prompt[name] = f.read().strip()
75
- return self.__internal_default_prompt[name]
76
-
77
- def _get_property(
78
- self,
79
- instance_var: Any,
80
- config_var: Any,
81
- default_func: Callable[[], Any],
82
- ) -> Any:
83
- if instance_var is not None:
84
- return instance_var
85
- if config_var is not None:
86
- return config_var
87
- return default_func()
88
-
89
- @property
90
- def default_model_name(self) -> str | None:
91
- return self._get_property(self._default_model_name, CFG.LLM_MODEL, lambda: None)
92
-
93
- @property
94
- def default_model_base_url(self) -> str | None:
95
- return self._get_property(
96
- self._default_model_base_url, CFG.LLM_BASE_URL, lambda: None
97
- )
98
-
99
- @property
100
- def default_model_api_key(self) -> str | None:
101
- return self._get_property(
102
- self._default_model_api_key, CFG.LLM_API_KEY, lambda: None
103
- )
104
-
105
- @property
106
- def default_model_settings(self) -> "ModelSettings | None":
107
- return self._get_property(self._default_model_settings, None, lambda: None)
108
-
109
- @property
110
- def default_model_provider(self) -> "Provider | str":
111
- if self._default_model_provider is not None:
112
- return self._default_model_provider
113
- if self.default_model_base_url is None and self.default_model_api_key is None:
114
- return "openai"
115
- from pydantic_ai.providers.openai import OpenAIProvider
116
-
117
- return OpenAIProvider(
118
- base_url=self.default_model_base_url, api_key=self.default_model_api_key
119
- )
120
-
121
- @property
122
- def default_small_model_name(self) -> str | None:
123
- return self._get_property(
124
- self._default_small_model_name,
125
- CFG.LLM_MODEL_SMALL,
126
- lambda: self.default_model_name,
127
- )
128
-
129
- @property
130
- def default_small_model_base_url(self) -> str | None:
131
- return self._get_property(
132
- self._default_small_model_base_url,
133
- CFG.LLM_BASE_URL_SMALL,
134
- lambda: self.default_model_base_url,
135
- )
136
-
137
- @property
138
- def default_small_model_api_key(self) -> str | None:
139
- return self._get_property(
140
- self._default_small_model_api_key,
141
- CFG.LLM_API_KEY_SMALL,
142
- lambda: self.default_model_api_key,
143
- )
144
-
145
- @property
146
- def default_small_model_settings(self) -> "ModelSettings | None":
147
- return self._get_property(
148
- self._default_small_model_settings,
149
- None,
150
- lambda: self.default_model_settings,
151
- )
152
-
153
- @property
154
- def default_small_model_provider(self) -> "Provider | str":
155
- if self._default_small_model_provider is not None:
156
- return self._default_small_model_provider
157
- if (
158
- self.default_small_model_base_url is None
159
- and self.default_small_model_api_key is None
160
- ):
161
- return self.default_model_provider
162
- from pydantic_ai.providers.openai import OpenAIProvider
163
-
164
- return OpenAIProvider(
165
- base_url=self.default_small_model_base_url,
166
- api_key=self.default_small_model_api_key,
167
- )
168
-
169
- @property
170
- def default_system_prompt(self) -> str:
171
- return self._get_property(
172
- self._default_system_prompt,
173
- CFG.LLM_SYSTEM_PROMPT,
174
- lambda: self._get_internal_default_prompt("system_prompt"),
175
- )
176
-
177
- @property
178
- def default_interactive_system_prompt(self) -> str:
179
- return self._get_property(
180
- self._default_interactive_system_prompt,
181
- CFG.LLM_INTERACTIVE_SYSTEM_PROMPT,
182
- lambda: self._get_internal_default_prompt("interactive_system_prompt"),
183
- )
184
-
185
- @property
186
- def default_persona(self) -> str:
187
- return self._get_property(
188
- self._default_persona,
189
- CFG.LLM_PERSONA,
190
- lambda: self._get_internal_default_prompt("persona"),
191
- )
192
-
193
- @property
194
- def default_workflows(self) -> list[str]:
195
- return self._get_property(
196
- self._default_workflows, CFG.LLM_WORKFLOWS, lambda: []
197
- )
198
-
199
- @property
200
- def default_special_instruction_prompt(self) -> str:
201
- return self._get_property(
202
- self._default_special_instruction_prompt,
203
- CFG.LLM_SPECIAL_INSTRUCTION_PROMPT,
204
- lambda: "",
205
- )
206
-
207
- @property
208
- def default_summarization_prompt(self) -> str:
209
- return self._get_property(
210
- self._default_summarization_prompt,
211
- CFG.LLM_SUMMARIZATION_PROMPT,
212
- lambda: self._get_internal_default_prompt("summarization_prompt"),
213
- )
214
-
215
- @property
216
- def default_model(self) -> "Model | str":
217
- if self._default_model is not None:
218
- return self._default_model
219
- model_name = self.default_model_name
220
- if model_name is None:
221
- return "openai:gpt-4o"
222
- from pydantic_ai.models.openai import OpenAIChatModel
223
-
224
- return OpenAIChatModel(
225
- model_name=model_name,
226
- provider=self.default_model_provider,
227
- )
228
-
229
- @property
230
- def default_small_model(self) -> "Model | str":
231
- if self._default_small_model is not None:
232
- return self._default_small_model
233
- model_name = self.default_small_model_name
234
- if model_name is None:
235
- return "openai:gpt-4o"
236
- return self.default_model
237
-
238
- @property
239
- def default_summarize_history(self) -> bool:
240
- return self._get_property(
241
- self._default_summarize_history, CFG.LLM_SUMMARIZE_HISTORY, lambda: False
242
- )
243
-
244
- @property
245
- def default_history_summarization_token_threshold(self) -> int:
246
- return self._get_property(
247
- self._default_history_summarization_token_threshold,
248
- CFG.LLM_HISTORY_SUMMARIZATION_TOKEN_THRESHOLD,
249
- lambda: 1000,
250
- )
251
-
252
- @property
253
- def default_yolo_mode(self) -> bool | list[str]:
254
- return self._get_property(
255
- self._default_yolo_mode, CFG.LLM_YOLO_MODE, lambda: False
256
- )
257
-
258
- @property
259
- def default_current_weather_tool(self) -> Callable | None:
260
- return self._default_current_weather_tool
261
-
262
- @property
263
- def default_current_location_tool(self) -> Callable | None:
264
- return self._default_current_location_tool
265
-
266
- @property
267
- def default_search_internet_tool(self) -> Callable | None:
268
- return self._default_search_internet_tool
269
-
270
- def set_default_persona(self, persona: str):
271
- self._default_persona = persona
272
-
273
- def set_default_system_prompt(self, system_prompt: str):
274
- self._default_system_prompt = system_prompt
275
-
276
- def set_default_interactive_system_prompt(self, interactive_system_prompt: str):
277
- self._default_interactive_system_prompt = interactive_system_prompt
278
-
279
- def set_default_special_instruction_prompt(self, special_instruction_prompt: str):
280
- self._default_special_instruction_prompt = special_instruction_prompt
281
-
282
- def set_default_workflows(self, workflows: list[str]):
283
- self._default_workflows = workflows
284
-
285
- def add_default_workflow(self, workflow: str):
286
- if self._default_workflows is None:
287
- self._default_workflows = []
288
- self._default_workflows.append(workflow)
289
-
290
- def remove_default_workflow(self, workflow: str):
291
- if self._default_workflows is None:
292
- self._default_workflows = []
293
- self._default_workflows.remove(workflow)
294
-
295
- def set_default_summarization_prompt(self, summarization_prompt: str):
296
- self._default_summarization_prompt = summarization_prompt
297
-
298
- def set_default_model_name(self, model_name: str):
299
- self._default_model_name = model_name
300
-
301
- def set_default_model_api_key(self, model_api_key: str):
302
- self._default_model_api_key = model_api_key
303
-
304
- def set_default_model_base_url(self, model_base_url: str):
305
- self._default_model_base_url = model_base_url
306
-
307
- def set_default_model_provider(self, provider: "Provider | str"):
308
- self._default_model_provider = provider
309
-
310
- def set_default_model(self, model: "Model | str"):
311
- self._default_model = model
312
-
313
- def set_default_summarize_history(self, summarize_history: bool):
314
- self._default_summarize_history = summarize_history
315
-
316
- def set_default_history_summarization_token_threshold(
317
- self, history_summarization_token_threshold: int
318
- ):
319
- self._default_history_summarization_token_threshold = (
320
- history_summarization_token_threshold
321
- )
322
-
323
- def set_default_model_settings(self, model_settings: "ModelSettings"):
324
- self._default_model_settings = model_settings
325
-
326
- def set_default_yolo_mode(self, yolo_mode: bool | list[str]):
327
- self._default_yolo_mode = yolo_mode
328
-
329
- def set_default_current_weather_tool(self, tool: Callable):
330
- self._default_current_weather_tool = tool
331
-
332
- def set_default_current_location_tool(self, tool: Callable):
333
- self._default_current_location_tool = tool
334
-
335
- def set_default_search_internet_tool(self, tool: Callable):
336
- self._default_search_internet_tool = tool
337
-
338
-
339
- llm_config = LLMConfig()
@@ -1,166 +0,0 @@
1
- import os
2
-
3
- from zrb.config.config import CFG
4
- from zrb.config.llm_context.config_parser import markdown_to_dict
5
- from zrb.config.llm_context.workflow import LLMWorkflow
6
- from zrb.util.markdown import demote_markdown_headers
7
-
8
-
9
- class LLMContextConfig:
10
- """High-level API for interacting with cascaded configurations."""
11
-
12
- def write_note(
13
- self,
14
- content: str,
15
- context_path: str | None = None,
16
- cwd: str | None = None,
17
- ):
18
- """Writes content to a note block in the user's home configuration file."""
19
- if cwd is None:
20
- cwd = os.getcwd()
21
- if context_path is None:
22
- context_path = cwd
23
- config_file = self._get_home_config_file()
24
- sections = {}
25
- if os.path.exists(config_file):
26
- sections = self._parse_config(config_file)
27
- abs_context_path = os.path.abspath(os.path.join(cwd, context_path))
28
- found_key = None
29
- for key in sections.keys():
30
- if not key.startswith("Note:"):
31
- continue
32
- context_path_str = key[len("Note:") :].strip()
33
- abs_key_path = self._normalize_context_path(
34
- context_path_str,
35
- os.path.dirname(config_file),
36
- )
37
- if abs_key_path == abs_context_path:
38
- found_key = key
39
- break
40
- if found_key:
41
- sections[found_key] = content
42
- else:
43
- config_dir = os.path.dirname(config_file)
44
- formatted_path = self._format_context_path_for_writing(
45
- abs_context_path,
46
- config_dir,
47
- )
48
- new_key = f"Note: {formatted_path}"
49
- sections[new_key] = content
50
- # Serialize back to markdown
51
- new_file_content = ""
52
- for key, value in sections.items():
53
- new_file_content += f"# {key}\n{demote_markdown_headers(value)}\n\n"
54
- with open(config_file, "w") as f:
55
- f.write(new_file_content)
56
-
57
- def get_notes(self, cwd: str | None = None) -> dict[str, str]:
58
- """Gathers all notes for a given path."""
59
- if cwd is None:
60
- cwd = os.getcwd()
61
- config_file = self._get_home_config_file()
62
- if not os.path.exists(config_file):
63
- return {}
64
- config_dir = os.path.dirname(config_file)
65
- sections = self._parse_config(config_file)
66
- notes: dict[str, str] = {}
67
- for key, value in sections.items():
68
- if key.lower().startswith("note:"):
69
- context_path_str = key[len("note:") :].strip()
70
- abs_context_path = self._normalize_context_path(
71
- context_path_str,
72
- config_dir,
73
- )
74
- # A context is relevant if its path is an ancestor of cwd
75
- if os.path.commonpath([cwd, abs_context_path]) == abs_context_path:
76
- notes[abs_context_path] = value
77
- return notes
78
-
79
- def get_workflows(self, cwd: str | None = None) -> dict[str, LLMWorkflow]:
80
- """Gathers all relevant workflows for a given path."""
81
- if cwd is None:
82
- cwd = os.getcwd()
83
- all_sections = self._get_all_sections(cwd)
84
- workflows: dict[str, LLMWorkflow] = {}
85
- # Iterate from closest to farthest
86
- for config_dir, sections in all_sections:
87
- for key, value in sections.items():
88
- if key.lower().startswith("workflow:"):
89
- workflow_name = key[len("workflow:") :].strip().lower()
90
- # First one found wins
91
- if workflow_name not in workflows:
92
- workflows[workflow_name] = LLMWorkflow(
93
- name=workflow_name,
94
- content=value,
95
- path=config_dir,
96
- )
97
- return workflows
98
-
99
- def _format_context_path_for_writing(
100
- self,
101
- path_to_write: str,
102
- relative_to_dir: str,
103
- ) -> str:
104
- """Formats a path for writing into a context file key."""
105
- home_dir = os.path.expanduser("~")
106
- abs_path_to_write = os.path.abspath(
107
- os.path.join(relative_to_dir, path_to_write)
108
- )
109
- abs_relative_to_dir = os.path.abspath(relative_to_dir)
110
- # Rule 1: Inside relative_to_dir
111
- if abs_path_to_write.startswith(abs_relative_to_dir):
112
- if abs_path_to_write == abs_relative_to_dir:
113
- return "."
114
- return os.path.relpath(abs_path_to_write, abs_relative_to_dir)
115
- # Rule 2: Inside Home
116
- if abs_path_to_write.startswith(home_dir):
117
- if abs_path_to_write == home_dir:
118
- return "~"
119
- return os.path.join("~", os.path.relpath(abs_path_to_write, home_dir))
120
- # Rule 3: Absolute
121
- return abs_path_to_write
122
-
123
- def _find_config_files(self, cwd: str) -> list[str]:
124
- configs = []
125
- current_dir = cwd
126
- home_dir = os.path.expanduser("~")
127
- while True:
128
- config_path = os.path.join(current_dir, CFG.LLM_CONTEXT_FILE)
129
- if os.path.exists(config_path):
130
- configs.append(config_path)
131
- if current_dir == home_dir or current_dir == "/":
132
- break
133
- current_dir = os.path.dirname(current_dir)
134
- return configs
135
-
136
- def _get_home_config_file(self) -> str:
137
- home_dir = os.path.expanduser("~")
138
- return os.path.join(home_dir, CFG.LLM_CONTEXT_FILE)
139
-
140
- def _parse_config(self, file_path: str) -> dict[str, str]:
141
- with open(file_path, "r") as f:
142
- content = f.read()
143
- return markdown_to_dict(content)
144
-
145
- def _get_all_sections(self, cwd: str) -> list[tuple[str, dict[str, str]]]:
146
- config_files = self._find_config_files(cwd)
147
- all_sections = []
148
- for config_file in config_files:
149
- config_dir = os.path.dirname(config_file)
150
- sections = self._parse_config(config_file)
151
- all_sections.append((config_dir, sections))
152
- return all_sections
153
-
154
- def _normalize_context_path(
155
- self,
156
- path_str: str,
157
- relative_to_dir: str,
158
- ) -> str:
159
- """Normalizes a context path string to an absolute path."""
160
- expanded_path = os.path.expanduser(path_str)
161
- if os.path.isabs(expanded_path):
162
- return os.path.abspath(expanded_path)
163
- return os.path.abspath(os.path.join(relative_to_dir, expanded_path))
164
-
165
-
166
- llm_context_config = LLMContextConfig()
@@ -1,40 +0,0 @@
1
- import re
2
-
3
- from zrb.util.markdown import promote_markdown_headers
4
-
5
-
6
- def markdown_to_dict(markdown: str) -> dict[str, str]:
7
- sections: dict[str, str] = {}
8
- current_title = ""
9
- current_content: list[str] = []
10
- fence_stack: list[str] = []
11
- fence_pattern = re.compile(r"^([`~]{3,})(.*)$")
12
- h1_pattern = re.compile(r"^# (.+)$")
13
- for line in markdown.splitlines():
14
- # Detect code fence open/close
15
- fence_match = fence_pattern.match(line.strip())
16
- if fence_match:
17
- fence = fence_match.group(1)
18
- if fence_stack and fence_stack[-1] == fence:
19
- fence_stack.pop() # close current fence
20
- else:
21
- fence_stack.append(fence) # open new fence
22
- # Only parse H1 when not inside a code fence
23
- if not fence_stack:
24
- h1_match = h1_pattern.match(line)
25
- if h1_match:
26
- # Save previous section
27
- if current_title:
28
- sections[current_title] = "\n".join(current_content).strip()
29
- # Start new section
30
- current_title = h1_match.group(1).strip()
31
- current_content = []
32
- continue
33
- current_content.append(line)
34
- # Save final section
35
- if current_title:
36
- sections[current_title] = "\n".join(current_content).strip()
37
- return {
38
- header: promote_markdown_headers(content)
39
- for header, content in sections.items()
40
- }
@@ -1,81 +0,0 @@
1
- class LLMWorkflow:
2
- def __init__(
3
- self, name: str, path: str, content: str, description: str | None = None
4
- ):
5
- self._name = name
6
- self._path = path
7
-
8
- # Extract YAML metadata and clean content
9
- (
10
- extracted_description,
11
- cleaned_content,
12
- ) = self._extract_yaml_metadata_and_clean_content(content)
13
- self._content = cleaned_content
14
-
15
- # Use provided description or extracted one
16
- self._description = (
17
- description if description is not None else extracted_description
18
- )
19
-
20
- def _extract_yaml_metadata_and_clean_content(
21
- self, content: str
22
- ) -> tuple[str | None, str]:
23
- """Extract YAML metadata and clean content.
24
-
25
- Looks for YAML metadata between --- lines, extracts the 'description' field,
26
- and returns the content without the YAML metadata.
27
- """
28
- import re
29
-
30
- import yaml
31
-
32
- # Pattern to match YAML metadata between --- delimiters
33
- yaml_pattern = r"^---\s*\n(.*?)\n---\s*\n"
34
- match = re.search(yaml_pattern, content, re.DOTALL | re.MULTILINE)
35
-
36
- if match:
37
- yaml_content = match.group(1)
38
- try:
39
- metadata = yaml.safe_load(yaml_content)
40
- description = (
41
- metadata.get("description") if isinstance(metadata, dict) else None
42
- )
43
- # Remove the YAML metadata from content
44
- cleaned_content = re.sub(
45
- yaml_pattern, "", content, count=1, flags=re.DOTALL | re.MULTILINE
46
- )
47
- return description, cleaned_content.strip()
48
- except yaml.YAMLError:
49
- # If YAML parsing fails, return original content
50
- pass
51
-
52
- # No YAML metadata found, return original content
53
- return None, content
54
-
55
- @property
56
- def name(self) -> str:
57
- return self._name
58
-
59
- @property
60
- def path(self) -> str:
61
- return self._path
62
-
63
- @property
64
- def content(self) -> str:
65
- return self._content
66
-
67
- @property
68
- def description(self) -> str:
69
- if self._description is not None:
70
- return self._description
71
- if len(self._content) > 1000:
72
- non_empty_lines = [
73
- line for line in self._content.split("\n") if line.strip() != ""
74
- ]
75
- first_non_empty_line = (
76
- non_empty_lines[0] if len(non_empty_lines) > 0 else ""
77
- )
78
- if len(first_non_empty_line) > 200:
79
- return first_non_empty_line[:200] + "... (more)"
80
- return first_non_empty_line
81
- return self._content