zrb 1.14.5__py3-none-any.whl → 1.14.6__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.
- zrb/builtin/llm/tool/sub_agent.py +3 -4
- zrb/builtin/llm/tool/web.py +75 -77
- zrb/builtin/setup/latex/ubuntu.py +1 -0
- zrb/builtin/setup/ubuntu.py +1 -1
- zrb/config/config.py +92 -77
- zrb/task/llm/agent.py +32 -13
- zrb/task/llm/history_summarization.py +1 -0
- zrb/task/llm/print_node.py +1 -0
- zrb/task/llm/prompt.py +24 -1
- zrb/task/llm_task.py +14 -8
- zrb/util/file.py +15 -8
- {zrb-1.14.5.dist-info → zrb-1.14.6.dist-info}/METADATA +4 -6
- {zrb-1.14.5.dist-info → zrb-1.14.6.dist-info}/RECORD +15 -15
- {zrb-1.14.5.dist-info → zrb-1.14.6.dist-info}/WHEEL +0 -0
- {zrb-1.14.5.dist-info → zrb-1.14.6.dist-info}/entry_points.txt +0 -0
@@ -15,8 +15,6 @@ if TYPE_CHECKING:
|
|
15
15
|
from pydantic_ai.toolsets import AbstractToolset
|
16
16
|
|
17
17
|
ToolOrCallable = Tool | Callable
|
18
|
-
else:
|
19
|
-
ToolOrCallable = Any
|
20
18
|
|
21
19
|
|
22
20
|
def create_sub_agent_tool(
|
@@ -25,7 +23,7 @@ def create_sub_agent_tool(
|
|
25
23
|
system_prompt: str | None = None,
|
26
24
|
model: "str | Model | None" = None,
|
27
25
|
model_settings: "ModelSettings | None" = None,
|
28
|
-
tools: list[ToolOrCallable] = [],
|
26
|
+
tools: "list[ToolOrCallable]" = [],
|
29
27
|
toolsets: list["AbstractToolset[Agent]"] = [],
|
30
28
|
is_yolo_mode: bool | None = None,
|
31
29
|
) -> Callable[[AnyContext, str], Coroutine[Any, Any, str]]:
|
@@ -97,7 +95,8 @@ def create_sub_agent_tool(
|
|
97
95
|
ctx=ctx,
|
98
96
|
agent=sub_agent_agent,
|
99
97
|
user_prompt=query,
|
100
|
-
|
98
|
+
attachments=[],
|
99
|
+
history_list=[],
|
101
100
|
)
|
102
101
|
|
103
102
|
# Return the sub-agent's final message content
|
zrb/builtin/llm/tool/web.py
CHANGED
@@ -1,64 +1,25 @@
|
|
1
1
|
import json
|
2
2
|
from collections.abc import Callable
|
3
|
+
from urllib.parse import urljoin
|
4
|
+
|
5
|
+
from bs4 import BeautifulSoup
|
3
6
|
|
4
7
|
|
5
8
|
async def open_web_page(url: str) -> str:
|
6
9
|
"""
|
7
|
-
Fetches and
|
10
|
+
Fetches, parses, and converts the content of a web page to Markdown.
|
8
11
|
|
9
|
-
|
12
|
+
This tool "reads" a web page by fetching its content, stripping away non-essential elements like scripts and styles, and then converting the cleaned HTML into Markdown format. This preserves the semantic structure of the content (headings, lists, etc.) while removing clutter. It also extracts all hyperlinks and resolves them to absolute URLs.
|
10
13
|
|
11
14
|
Args:
|
12
15
|
url (str): The full URL of the web page to open (e.g., "https://example.com/article").
|
13
16
|
|
14
17
|
Returns:
|
15
|
-
str: A JSON object containing the
|
18
|
+
str: A JSON object containing the page's content in Markdown format and a list of all absolute links found on the page.
|
16
19
|
"""
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
try:
|
21
|
-
from playwright.async_api import async_playwright
|
22
|
-
|
23
|
-
async with async_playwright() as p:
|
24
|
-
browser = await p.chromium.launch(headless=True)
|
25
|
-
page = await browser.new_page()
|
26
|
-
await page.set_extra_http_headers({"User-Agent": user_agent})
|
27
|
-
try:
|
28
|
-
# Navigate to the URL with a timeout of 30 seconds
|
29
|
-
await page.goto(page_url, wait_until="networkidle", timeout=30000)
|
30
|
-
# Wait for the content to load
|
31
|
-
await page.wait_for_load_state("domcontentloaded")
|
32
|
-
# Get the page content
|
33
|
-
content = await page.content()
|
34
|
-
# Extract all links from the page
|
35
|
-
links = await page.eval_on_selector_all(
|
36
|
-
"a[href]",
|
37
|
-
"""
|
38
|
-
(elements) => elements.map(el => {
|
39
|
-
const href = el.getAttribute('href');
|
40
|
-
if (href && !href.startsWith('#') && !href.startsWith('/')) {
|
41
|
-
return href;
|
42
|
-
}
|
43
|
-
return null;
|
44
|
-
}).filter(href => href !== null)
|
45
|
-
""",
|
46
|
-
)
|
47
|
-
return {"content": content, "links_on_page": links}
|
48
|
-
finally:
|
49
|
-
await browser.close()
|
50
|
-
except BaseException:
|
51
|
-
import requests
|
52
|
-
|
53
|
-
response = requests.get(url, headers={"User-Agent": user_agent})
|
54
|
-
if response.status_code != 200:
|
55
|
-
msg = f"Unable to retrieve search results. Status code: {response.status_code}"
|
56
|
-
raise Exception(msg)
|
57
|
-
return {"content": response.text, "links_on_page": []}
|
58
|
-
|
59
|
-
result = await get_page_content(url)
|
60
|
-
# Parse the HTML content
|
61
|
-
return json.dumps(parse_html_text(result["content"]))
|
20
|
+
html_content, links = await _fetch_page_content(url)
|
21
|
+
markdown_content = _convert_html_to_markdown(html_content)
|
22
|
+
return json.dumps({"content": markdown_content, "links_on_page": links})
|
62
23
|
|
63
24
|
|
64
25
|
def create_search_internet_tool(serp_api_key: str) -> Callable[[str, int], str]:
|
@@ -85,14 +46,14 @@ def create_search_internet_tool(serp_api_key: str) -> Callable[[str, int], str]:
|
|
85
46
|
num_results (int, optional): The desired number of search results. Defaults to 10.
|
86
47
|
|
87
48
|
Returns:
|
88
|
-
str: A
|
49
|
+
str: A formatted string summarizing the search results, including titles, links, and snippets.
|
89
50
|
"""
|
90
51
|
import requests
|
91
52
|
|
92
53
|
response = requests.get(
|
93
54
|
"https://serpapi.com/search",
|
94
55
|
headers={
|
95
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
56
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
96
57
|
},
|
97
58
|
params={
|
98
59
|
"q": query,
|
@@ -104,9 +65,9 @@ def create_search_internet_tool(serp_api_key: str) -> Callable[[str, int], str]:
|
|
104
65
|
)
|
105
66
|
if response.status_code != 200:
|
106
67
|
raise Exception(
|
107
|
-
f"Error: Unable to retrieve search results (status code: {response.status_code})"
|
68
|
+
f"Error: Unable to retrieve search results (status code: {response.status_code})"
|
108
69
|
)
|
109
|
-
return json
|
70
|
+
return response.json()
|
110
71
|
|
111
72
|
return search_internet
|
112
73
|
|
@@ -150,30 +111,67 @@ def search_arxiv(query: str, num_results: int = 10) -> str:
|
|
150
111
|
return response.content
|
151
112
|
|
152
113
|
|
153
|
-
def
|
154
|
-
from
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
114
|
+
async def _fetch_page_content(url: str) -> tuple[str, list[str]]:
|
115
|
+
"""Fetches the HTML content and all absolute links from a URL."""
|
116
|
+
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
117
|
+
try:
|
118
|
+
from playwright.async_api import async_playwright
|
119
|
+
|
120
|
+
async with async_playwright() as p:
|
121
|
+
browser = await p.chromium.launch(headless=True)
|
122
|
+
page = await browser.new_page()
|
123
|
+
await page.set_extra_http_headers({"User-Agent": user_agent})
|
124
|
+
try:
|
125
|
+
await page.goto(url, wait_until="networkidle", timeout=30000)
|
126
|
+
await page.wait_for_load_state("domcontentloaded")
|
127
|
+
content = await page.content()
|
128
|
+
links = await page.eval_on_selector_all(
|
129
|
+
"a[href]",
|
130
|
+
"""
|
131
|
+
(elements, baseUrl) => elements.map(el => {
|
132
|
+
const href = el.getAttribute('href');
|
133
|
+
if (!href || href.startsWith('#')) return null;
|
134
|
+
try {
|
135
|
+
return new URL(href, baseUrl).href;
|
136
|
+
} catch (e) {
|
137
|
+
return null;
|
138
|
+
}
|
139
|
+
}).filter(href => href !== null)
|
140
|
+
""",
|
141
|
+
url,
|
142
|
+
)
|
143
|
+
return content, links
|
144
|
+
# return json.dumps({"content": content, "links": links})
|
145
|
+
finally:
|
146
|
+
await browser.close()
|
147
|
+
except Exception:
|
148
|
+
import requests
|
149
|
+
|
150
|
+
response = requests.get(url, headers={"User-Agent": user_agent})
|
151
|
+
if response.status_code != 200:
|
152
|
+
raise Exception(
|
153
|
+
f"Unable to retrieve page content. Status code: {response.status_code}"
|
154
|
+
)
|
155
|
+
content = response.text
|
156
|
+
soup = BeautifulSoup(content, "html.parser")
|
157
|
+
links = [
|
158
|
+
urljoin(url, a["href"])
|
159
|
+
for a in soup.find_all("a", href=True)
|
160
|
+
if not a["href"].startswith("#")
|
161
|
+
]
|
162
|
+
return content, links
|
163
|
+
# return json.dumps({"content": content, "links": links})
|
164
|
+
|
165
|
+
|
166
|
+
def _convert_html_to_markdown(html_text: str) -> str:
|
167
|
+
"""Converts HTML content to a clean Markdown representation."""
|
168
|
+
from markdownify import markdownify as md
|
169
|
+
|
167
170
|
soup = BeautifulSoup(html_text, "html.parser")
|
168
|
-
|
169
|
-
for
|
170
|
-
|
171
|
-
|
172
|
-
link: str = anchor["href"]
|
173
|
-
if link.startswith("#") or link.startswith("/"):
|
174
|
-
continue
|
175
|
-
links.append(link)
|
176
|
-
for tag in soup(ignored_tags):
|
171
|
+
# Remove non-content tags
|
172
|
+
for tag in soup(
|
173
|
+
["script", "link", "meta", "style", "header", "footer", "nav", "aside"]
|
174
|
+
):
|
177
175
|
tag.decompose()
|
178
|
-
|
179
|
-
return
|
176
|
+
# Convert the cleaned HTML to Markdown
|
177
|
+
return md(str(soup))
|
zrb/builtin/setup/ubuntu.py
CHANGED
@@ -22,7 +22,7 @@ setup_ubuntu = setup_group.add_task(
|
|
22
22
|
description="🐧 Setup ubuntu",
|
23
23
|
cmd=[
|
24
24
|
"sudo apt install -y \\",
|
25
|
-
"build-essential
|
25
|
+
"build-essential libssl-dev zlib1g-dev \\"
|
26
26
|
"libbz2-dev libreadline-dev libsqlite3-dev libpq-dev python3-dev \\",
|
27
27
|
"llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev \\",
|
28
28
|
"liblzma-dev python3-openssl libblas-dev liblapack-dev rustc \\",
|
zrb/config/config.py
CHANGED
@@ -24,6 +24,13 @@ class Config:
|
|
24
24
|
def __init__(self):
|
25
25
|
self.__internal_default_prompt: dict[str, str] = {}
|
26
26
|
|
27
|
+
@property
|
28
|
+
def ENV_PREFIX(self) -> str:
|
29
|
+
return os.getenv("_ZRB_ENV_PREFIX", "ZRB")
|
30
|
+
|
31
|
+
def _getenv(self, env_name: str, default: str = "") -> str:
|
32
|
+
return os.getenv(f"{self.ENV_PREFIX}_{env_name}", default)
|
33
|
+
|
27
34
|
def _get_internal_default_prompt(self, name: str) -> str:
|
28
35
|
if name not in self.__internal_default_prompt:
|
29
36
|
file_path = os.path.join(
|
@@ -39,7 +46,7 @@ class Config:
|
|
39
46
|
|
40
47
|
@property
|
41
48
|
def DEFAULT_SHELL(self) -> str:
|
42
|
-
return
|
49
|
+
return self._getenv("SHELL", self._get_current_shell())
|
43
50
|
|
44
51
|
def _get_current_shell(self) -> str:
|
45
52
|
if platform.system() == "Windows":
|
@@ -51,11 +58,11 @@ class Config:
|
|
51
58
|
|
52
59
|
@property
|
53
60
|
def DEFAULT_EDITOR(self) -> str:
|
54
|
-
return
|
61
|
+
return self._getenv("EDITOR", "nano")
|
55
62
|
|
56
63
|
@property
|
57
64
|
def INIT_MODULES(self) -> list[str]:
|
58
|
-
init_modules_str =
|
65
|
+
init_modules_str = self._getenv("INIT_MODULES", "")
|
59
66
|
if init_modules_str != "":
|
60
67
|
return [
|
61
68
|
module.strip()
|
@@ -66,15 +73,15 @@ class Config:
|
|
66
73
|
|
67
74
|
@property
|
68
75
|
def ROOT_GROUP_NAME(self) -> str:
|
69
|
-
return
|
76
|
+
return self._getenv("ROOT_GROUP_NAME", "zrb")
|
70
77
|
|
71
78
|
@property
|
72
79
|
def ROOT_GROUP_DESCRIPTION(self) -> str:
|
73
|
-
return
|
80
|
+
return self._getenv("ROOT_GROUP_DESCRIPTION", "Your Automation Powerhouse")
|
74
81
|
|
75
82
|
@property
|
76
83
|
def INIT_SCRIPTS(self) -> list[str]:
|
77
|
-
init_scripts_str =
|
84
|
+
init_scripts_str = self._getenv("INIT_SCRIPTS", "")
|
78
85
|
if init_scripts_str != "":
|
79
86
|
return [
|
80
87
|
script.strip()
|
@@ -85,11 +92,11 @@ class Config:
|
|
85
92
|
|
86
93
|
@property
|
87
94
|
def INIT_FILE_NAME(self) -> str:
|
88
|
-
return
|
95
|
+
return self._getenv("INIT_FILE_NAME", "zrb_init.py")
|
89
96
|
|
90
97
|
@property
|
91
98
|
def LOGGING_LEVEL(self) -> int:
|
92
|
-
return self._get_log_level(
|
99
|
+
return self._get_log_level(self._getenv("LOGGING_LEVEL", "WARNING"))
|
93
100
|
|
94
101
|
def _get_log_level(self, level: str) -> int:
|
95
102
|
level = level.upper()
|
@@ -108,11 +115,11 @@ class Config:
|
|
108
115
|
|
109
116
|
@property
|
110
117
|
def LOAD_BUILTIN(self) -> bool:
|
111
|
-
return to_boolean(
|
118
|
+
return to_boolean(self._getenv("LOAD_BUILTIN", "1"))
|
112
119
|
|
113
120
|
@property
|
114
121
|
def WARN_UNRECOMMENDED_COMMAND(self) -> bool:
|
115
|
-
return to_boolean(
|
122
|
+
return to_boolean(self._getenv("WARN_UNRECOMMENDED_COMMAND", "1"))
|
116
123
|
|
117
124
|
@property
|
118
125
|
def SESSION_LOG_DIR(self) -> str:
|
@@ -122,15 +129,15 @@ class Config:
|
|
122
129
|
|
123
130
|
@property
|
124
131
|
def TODO_DIR(self) -> str:
|
125
|
-
return
|
132
|
+
return self._getenv("TODO_DIR", os.path.expanduser(os.path.join("~", "todo")))
|
126
133
|
|
127
134
|
@property
|
128
135
|
def TODO_VISUAL_FILTER(self) -> str:
|
129
|
-
return
|
136
|
+
return self._getenv("TODO_FILTER", "")
|
130
137
|
|
131
138
|
@property
|
132
139
|
def TODO_RETENTION(self) -> str:
|
133
|
-
return
|
140
|
+
return self._getenv("TODO_RETENTION", "2w")
|
134
141
|
|
135
142
|
@property
|
136
143
|
def VERSION(self) -> str:
|
@@ -141,7 +148,7 @@ class Config:
|
|
141
148
|
|
142
149
|
@property
|
143
150
|
def WEB_CSS_PATH(self) -> list[str]:
|
144
|
-
web_css_path_str =
|
151
|
+
web_css_path_str = self._getenv("WEB_CSS_PATH", "")
|
145
152
|
if web_css_path_str != "":
|
146
153
|
return [
|
147
154
|
path.strip()
|
@@ -152,7 +159,7 @@ class Config:
|
|
152
159
|
|
153
160
|
@property
|
154
161
|
def WEB_JS_PATH(self) -> list[str]:
|
155
|
-
web_js_path_str =
|
162
|
+
web_js_path_str = self._getenv("WEB_JS_PATH", "")
|
156
163
|
if web_js_path_str != "":
|
157
164
|
return [
|
158
165
|
path.strip()
|
@@ -163,103 +170,111 @@ class Config:
|
|
163
170
|
|
164
171
|
@property
|
165
172
|
def WEB_FAVICON_PATH(self) -> str:
|
166
|
-
return
|
173
|
+
return self._getenv("WEB_FAVICON_PATH", "/static/favicon-32x32.png")
|
167
174
|
|
168
175
|
@property
|
169
176
|
def WEB_COLOR(self) -> str:
|
170
|
-
return
|
177
|
+
return self._getenv("WEB_COLOR", "")
|
171
178
|
|
172
179
|
@property
|
173
180
|
def WEB_HTTP_PORT(self) -> int:
|
174
|
-
return int(
|
181
|
+
return int(self._getenv("WEB_HTTP_PORT", "21213"))
|
175
182
|
|
176
183
|
@property
|
177
184
|
def WEB_GUEST_USERNAME(self) -> str:
|
178
|
-
return
|
185
|
+
return self._getenv("WEB_GUEST_USERNAME", "user")
|
179
186
|
|
180
187
|
@property
|
181
188
|
def WEB_SUPER_ADMIN_USERNAME(self) -> str:
|
182
|
-
return
|
189
|
+
return self._getenv("WEB_SUPER_ADMIN_USERNAME", "admin")
|
183
190
|
|
184
191
|
@property
|
185
192
|
def WEB_SUPER_ADMIN_PASSWORD(self) -> str:
|
186
|
-
return
|
193
|
+
return self._getenv("WEB_SUPER_ADMIN_PASSWORD", "admin")
|
187
194
|
|
188
195
|
@property
|
189
196
|
def WEB_ACCESS_TOKEN_COOKIE_NAME(self) -> str:
|
190
|
-
return
|
197
|
+
return self._getenv("WEB_ACCESS_TOKEN_COOKIE_NAME", "access_token")
|
191
198
|
|
192
199
|
@property
|
193
200
|
def WEB_REFRESH_TOKEN_COOKIE_NAME(self) -> str:
|
194
|
-
return
|
201
|
+
return self._getenv("WEB_REFRESH_TOKEN_COOKIE_NAME", "refresh_token")
|
195
202
|
|
196
203
|
@property
|
197
204
|
def WEB_SECRET_KEY(self) -> str:
|
198
|
-
return
|
205
|
+
return self._getenv("WEB_SECRET", "zrb")
|
199
206
|
|
200
207
|
@property
|
201
208
|
def WEB_ENABLE_AUTH(self) -> bool:
|
202
|
-
return to_boolean(
|
209
|
+
return to_boolean(self._getenv("WEB_ENABLE_AUTH", "0"))
|
203
210
|
|
204
211
|
@property
|
205
212
|
def WEB_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES(self) -> int:
|
206
|
-
return int(
|
213
|
+
return int(self._getenv("WEB_ACCESS_TOKEN_EXPIRE_MINUTES", "30"))
|
207
214
|
|
208
215
|
@property
|
209
216
|
def WEB_AUTH_REFRESH_TOKEN_EXPIRE_MINUTES(self) -> int:
|
210
|
-
return int(
|
217
|
+
return int(self._getenv("WEB_REFRESH_TOKEN_EXPIRE_MINUTES", "60"))
|
211
218
|
|
212
219
|
@property
|
213
220
|
def WEB_TITLE(self) -> str:
|
214
|
-
return
|
221
|
+
return self._getenv("WEB_TITLE", "Zrb")
|
215
222
|
|
216
223
|
@property
|
217
224
|
def WEB_JARGON(self) -> str:
|
218
|
-
return
|
225
|
+
return self._getenv("WEB_JARGON", "Your Automation PowerHouse")
|
219
226
|
|
220
227
|
@property
|
221
228
|
def WEB_HOMEPAGE_INTRO(self) -> str:
|
222
|
-
return
|
229
|
+
return self._getenv("WEB_HOMEPAGE_INTRO", "Welcome to Zrb Web Interface")
|
223
230
|
|
224
231
|
@property
|
225
232
|
def LLM_MODEL(self) -> str | None:
|
226
|
-
|
233
|
+
value = self._getenv("LLM_MODEL")
|
234
|
+
return None if value == "" else value
|
227
235
|
|
228
236
|
@property
|
229
237
|
def LLM_BASE_URL(self) -> str | None:
|
230
|
-
|
238
|
+
value = self._getenv("LLM_BASE_URL")
|
239
|
+
return None if value == "" else value
|
231
240
|
|
232
241
|
@property
|
233
242
|
def LLM_API_KEY(self) -> str | None:
|
234
|
-
|
243
|
+
value = self._getenv("LLM_API_KEY")
|
244
|
+
return None if value == "" else value
|
235
245
|
|
236
246
|
@property
|
237
247
|
def LLM_SYSTEM_PROMPT(self) -> str | None:
|
238
|
-
|
248
|
+
value = self._getenv("LLM_SYSTEM_PROMPT")
|
249
|
+
return None if value == "" else value
|
239
250
|
|
240
251
|
@property
|
241
252
|
def LLM_INTERACTIVE_SYSTEM_PROMPT(self) -> str | None:
|
242
|
-
|
253
|
+
value = self._getenv("LLM_INTERACTIVE_SYSTEM_PROMPT")
|
254
|
+
return None if value == "" else value
|
243
255
|
|
244
256
|
@property
|
245
257
|
def LLM_PERSONA(self) -> str | None:
|
246
|
-
|
258
|
+
value = self._getenv("LLM_PERSONA")
|
259
|
+
return None if value == "" else value
|
247
260
|
|
248
261
|
@property
|
249
262
|
def LLM_MODES(self) -> list[str]:
|
250
263
|
return [
|
251
264
|
mode.strip()
|
252
|
-
for mode in
|
265
|
+
for mode in self._getenv("LLM_MODES", "coding").split(",")
|
253
266
|
if mode.strip() != ""
|
254
267
|
]
|
255
268
|
|
256
269
|
@property
|
257
270
|
def LLM_SPECIAL_INSTRUCTION_PROMPT(self) -> str | None:
|
258
|
-
|
271
|
+
value = self._getenv("LLM_SPECIAL_INSTRUCTION_PROMPT")
|
272
|
+
return None if value == "" else value
|
259
273
|
|
260
274
|
@property
|
261
275
|
def LLM_SUMMARIZATION_PROMPT(self) -> str | None:
|
262
|
-
|
276
|
+
value = self._getenv("LLM_SUMMARIZATION_PROMPT")
|
277
|
+
return None if value == "" else value
|
263
278
|
|
264
279
|
@property
|
265
280
|
def LLM_MAX_REQUESTS_PER_MINUTE(self) -> int:
|
@@ -267,7 +282,7 @@ class Config:
|
|
267
282
|
Maximum number of LLM requests allowed per minute.
|
268
283
|
Default is conservative to accommodate free-tier LLM providers.
|
269
284
|
"""
|
270
|
-
return int(
|
285
|
+
return int(self._getenv("LLM_MAX_REQUESTS_PER_MINUTE", "15"))
|
271
286
|
|
272
287
|
@property
|
273
288
|
def LLM_MAX_TOKENS_PER_MINUTE(self) -> int:
|
@@ -275,127 +290,127 @@ class Config:
|
|
275
290
|
Maximum number of LLM tokens allowed per minute.
|
276
291
|
Default is conservative to accommodate free-tier LLM providers.
|
277
292
|
"""
|
278
|
-
return int(
|
293
|
+
return int(self._getenv("LLM_MAX_TOKENS_PER_MINUTE", "100000"))
|
279
294
|
|
280
295
|
@property
|
281
296
|
def LLM_MAX_TOKENS_PER_REQUEST(self) -> int:
|
282
297
|
"""Maximum number of tokens allowed per individual LLM request."""
|
283
|
-
return int(
|
298
|
+
return int(self._getenv("LLM_MAX_TOKENS_PER_REQUEST", "50000"))
|
284
299
|
|
285
300
|
@property
|
286
301
|
def LLM_THROTTLE_SLEEP(self) -> float:
|
287
302
|
"""Number of seconds to sleep when throttling is required."""
|
288
|
-
return float(
|
303
|
+
return float(self._getenv("LLM_THROTTLE_SLEEP", "1.0"))
|
289
304
|
|
290
305
|
@property
|
291
306
|
def LLM_YOLO_MODE(self) -> bool:
|
292
|
-
return to_boolean(
|
307
|
+
return to_boolean(self._getenv("LLM_YOLO_MODE", "false"))
|
293
308
|
|
294
309
|
@property
|
295
310
|
def LLM_SUMMARIZE_HISTORY(self) -> bool:
|
296
|
-
return to_boolean(
|
311
|
+
return to_boolean(self._getenv("LLM_SUMMARIZE_HISTORY", "true"))
|
297
312
|
|
298
313
|
@property
|
299
314
|
def LLM_HISTORY_SUMMARIZATION_TOKEN_THRESHOLD(self) -> int:
|
300
|
-
return int(
|
315
|
+
return int(self._getenv("LLM_HISTORY_SUMMARIZATION_TOKEN_THRESHOLD", "20000"))
|
301
316
|
|
302
317
|
@property
|
303
318
|
def LLM_REPO_ANALYSIS_EXTRACTION_TOKEN_THRESHOLD(self) -> int:
|
304
|
-
return int(
|
319
|
+
return int(self._getenv("LLM_REPO_ANALYSIS_EXTRACTION_TOKEN_LIMIT", "35000"))
|
305
320
|
|
306
321
|
@property
|
307
322
|
def LLM_REPO_ANALYSIS_SUMMARIZATION_TOKEN_THRESHOLD(self) -> int:
|
308
|
-
return int(
|
309
|
-
os.getenv("ZRB_LLM_REPO_ANALYSIS_SUMMARIZATION_TOKEN_LIMIT", "35000")
|
310
|
-
)
|
323
|
+
return int(self._getenv("LLM_REPO_ANALYSIS_SUMMARIZATION_TOKEN_LIMIT", "35000"))
|
311
324
|
|
312
325
|
@property
|
313
326
|
def LLM_FILE_ANALYSIS_TOKEN_LIMIT(self) -> int:
|
314
|
-
return int(
|
327
|
+
return int(self._getenv("LLM_FILE_ANALYSIS_TOKEN_LIMIT", "35000"))
|
315
328
|
|
316
329
|
@property
|
317
330
|
def LLM_FILE_EXTRACTOR_SYSTEM_PROMPT(self) -> str:
|
318
|
-
return
|
319
|
-
"
|
331
|
+
return self._getenv(
|
332
|
+
"LLM_FILE_EXTRACTOR_SYSTEM_PROMPT",
|
320
333
|
self._get_internal_default_prompt("file_extractor_system_prompt"),
|
321
334
|
)
|
322
335
|
|
323
336
|
@property
|
324
337
|
def LLM_REPO_EXTRACTOR_SYSTEM_PROMPT(self) -> str:
|
325
|
-
return
|
326
|
-
"
|
338
|
+
return self._getenv(
|
339
|
+
"LLM_REPO_EXTRACTOR_SYSTEM_PROMPT",
|
327
340
|
self._get_internal_default_prompt("repo_extractor_system_prompt"),
|
328
341
|
)
|
329
342
|
|
330
343
|
@property
|
331
344
|
def LLM_REPO_SUMMARIZER_SYSTEM_PROMPT(self) -> str:
|
332
|
-
return
|
333
|
-
"
|
345
|
+
return self._getenv(
|
346
|
+
"LLM_REPO_SUMMARIZER_SYSTEM_PROMPT",
|
334
347
|
self._get_internal_default_prompt("repo_summarizer_system_prompt"),
|
335
348
|
)
|
336
349
|
|
337
350
|
@property
|
338
351
|
def LLM_HISTORY_DIR(self) -> str:
|
339
|
-
return
|
340
|
-
"
|
352
|
+
return self._getenv(
|
353
|
+
"LLM_HISTORY_DIR",
|
341
354
|
os.path.expanduser(os.path.join("~", ".zrb-llm-history")),
|
342
355
|
)
|
343
356
|
|
344
357
|
@property
|
345
358
|
def LLM_ALLOW_ACCESS_LOCAL_FILE(self) -> bool:
|
346
|
-
return to_boolean(
|
359
|
+
return to_boolean(self._getenv("LLM_ACCESS_LOCAL_FILE", "1"))
|
347
360
|
|
348
361
|
@property
|
349
362
|
def LLM_ALLOW_ACCESS_SHELL(self) -> bool:
|
350
|
-
return to_boolean(
|
363
|
+
return to_boolean(self._getenv("LLM_ACCESS_SHELL", "1"))
|
351
364
|
|
352
365
|
@property
|
353
366
|
def LLM_ALLOW_OPEN_WEB_PAGE(self) -> bool:
|
354
|
-
return to_boolean(
|
367
|
+
return to_boolean(self._getenv("LLM_ALLOW_OPEN_WEB_PAGE", "1"))
|
355
368
|
|
356
369
|
@property
|
357
370
|
def LLM_ALLOW_SEARCH_INTERNET(self) -> bool:
|
358
|
-
return to_boolean(
|
371
|
+
return to_boolean(self._getenv("LLM_ALLOW_SEARCH_INTERNET", "1"))
|
359
372
|
|
360
373
|
@property
|
361
374
|
def LLM_ALLOW_SEARCH_ARXIV(self) -> bool:
|
362
|
-
return to_boolean(
|
375
|
+
return to_boolean(self._getenv("LLM_ALLOW_SEARCH_ARXIV", "1"))
|
363
376
|
|
364
377
|
@property
|
365
378
|
def LLM_ALLOW_SEARCH_WIKIPEDIA(self) -> bool:
|
366
|
-
return to_boolean(
|
379
|
+
return to_boolean(self._getenv("LLM_ALLOW_SEARCH_WIKIPEDIA", "1"))
|
367
380
|
|
368
381
|
@property
|
369
382
|
def LLM_ALLOW_GET_CURRENT_LOCATION(self) -> bool:
|
370
|
-
return to_boolean(
|
383
|
+
return to_boolean(self._getenv("LLM_ALLOW_GET_CURRENT_LOCATION", "1"))
|
371
384
|
|
372
385
|
@property
|
373
386
|
def LLM_ALLOW_GET_CURRENT_WEATHER(self) -> bool:
|
374
|
-
return to_boolean(
|
387
|
+
return to_boolean(self._getenv("LLM_ALLOW_GET_CURRENT_WEATHER", "1"))
|
375
388
|
|
376
389
|
@property
|
377
|
-
def RAG_EMBEDDING_API_KEY(self) -> str:
|
378
|
-
|
390
|
+
def RAG_EMBEDDING_API_KEY(self) -> str | None:
|
391
|
+
value = self._getenv("RAG_EMBEDDING_API_KEY")
|
392
|
+
return None if value == "" else value
|
379
393
|
|
380
394
|
@property
|
381
|
-
def RAG_EMBEDDING_BASE_URL(self) -> str:
|
382
|
-
|
395
|
+
def RAG_EMBEDDING_BASE_URL(self) -> str | None:
|
396
|
+
value = self._getenv("RAG_EMBEDDING_BASE_URL")
|
397
|
+
return None if value == "" else value
|
383
398
|
|
384
399
|
@property
|
385
400
|
def RAG_EMBEDDING_MODEL(self) -> str:
|
386
|
-
return
|
401
|
+
return self._getenv("RAG_EMBEDDING_MODEL", "text-embedding-ada-002")
|
387
402
|
|
388
403
|
@property
|
389
404
|
def RAG_CHUNK_SIZE(self) -> int:
|
390
|
-
return int(
|
405
|
+
return int(self._getenv("RAG_CHUNK_SIZE", "1024"))
|
391
406
|
|
392
407
|
@property
|
393
408
|
def RAG_OVERLAP(self) -> int:
|
394
|
-
return int(
|
409
|
+
return int(self._getenv("RAG_OVERLAP", "128"))
|
395
410
|
|
396
411
|
@property
|
397
412
|
def RAG_MAX_RESULT_COUNT(self) -> int:
|
398
|
-
return int(
|
413
|
+
return int(self._getenv("RAG_MAX_RESULT_COUNT", "5"))
|
399
414
|
|
400
415
|
@property
|
401
416
|
def SERPAPI_KEY(self) -> str:
|
@@ -404,13 +419,13 @@ class Config:
|
|
404
419
|
@property
|
405
420
|
def BANNER(self) -> str:
|
406
421
|
return fstring_format(
|
407
|
-
|
422
|
+
self._getenv("BANNER", _DEFAULT_BANNER),
|
408
423
|
{"VERSION": self.VERSION},
|
409
424
|
)
|
410
425
|
|
411
426
|
@property
|
412
427
|
def LLM_CONTEXT_FILE(self) -> str:
|
413
|
-
return
|
428
|
+
return self._getenv("LLM_CONTEXT_FILE", "ZRB.md")
|
414
429
|
|
415
430
|
|
416
431
|
CFG = Config()
|
zrb/task/llm/agent.py
CHANGED
@@ -13,13 +13,12 @@ from zrb.task.llm.typing import ListOfDict
|
|
13
13
|
if TYPE_CHECKING:
|
14
14
|
from pydantic_ai import Agent, Tool
|
15
15
|
from pydantic_ai.agent import AgentRun
|
16
|
+
from pydantic_ai.messages import UserContent
|
16
17
|
from pydantic_ai.models import Model
|
17
18
|
from pydantic_ai.settings import ModelSettings
|
18
19
|
from pydantic_ai.toolsets import AbstractToolset
|
19
20
|
|
20
21
|
ToolOrCallable = Tool | Callable
|
21
|
-
else:
|
22
|
-
ToolOrCallable = Any
|
23
22
|
|
24
23
|
|
25
24
|
def create_agent_instance(
|
@@ -27,7 +26,7 @@ def create_agent_instance(
|
|
27
26
|
model: "str | Model | None" = None,
|
28
27
|
system_prompt: str = "",
|
29
28
|
model_settings: "ModelSettings | None" = None,
|
30
|
-
tools: list[ToolOrCallable] = [],
|
29
|
+
tools: "list[ToolOrCallable]" = [],
|
31
30
|
toolsets: list["AbstractToolset[Agent]"] = [],
|
32
31
|
retries: int = 3,
|
33
32
|
is_yolo_mode: bool | None = None,
|
@@ -80,9 +79,9 @@ def get_agent(
|
|
80
79
|
system_prompt: str,
|
81
80
|
model_settings: "ModelSettings | None",
|
82
81
|
tools_attr: (
|
83
|
-
list[ToolOrCallable] | Callable[[AnySharedContext], list[ToolOrCallable]]
|
82
|
+
"list[ToolOrCallable] | Callable[[AnySharedContext], list[ToolOrCallable]]"
|
84
83
|
),
|
85
|
-
additional_tools: list[ToolOrCallable],
|
84
|
+
additional_tools: "list[ToolOrCallable]",
|
86
85
|
toolsets_attr: "list[AbstractToolset[Agent]] | Callable[[AnySharedContext], list[AbstractToolset[Agent]]]", # noqa
|
87
86
|
additional_toolsets: "list[AbstractToolset[Agent]]",
|
88
87
|
retries: int = 3,
|
@@ -126,7 +125,8 @@ async def run_agent_iteration(
|
|
126
125
|
ctx: AnyContext,
|
127
126
|
agent: "Agent",
|
128
127
|
user_prompt: str,
|
129
|
-
|
128
|
+
attachments: "list[UserContent] | None" = None,
|
129
|
+
history_list: ListOfDict | None = None,
|
130
130
|
rate_limitter: LLMRateLimiter | None = None,
|
131
131
|
max_retry: int = 2,
|
132
132
|
) -> "AgentRun":
|
@@ -154,8 +154,11 @@ async def run_agent_iteration(
|
|
154
154
|
ctx=ctx,
|
155
155
|
agent=agent,
|
156
156
|
user_prompt=user_prompt,
|
157
|
-
|
158
|
-
|
157
|
+
attachments=[] if attachments is None else attachments,
|
158
|
+
history_list=[] if history_list is None else history_list,
|
159
|
+
rate_limitter=(
|
160
|
+
llm_rate_limitter if rate_limitter is None else rate_limitter
|
161
|
+
),
|
159
162
|
)
|
160
163
|
except BaseException:
|
161
164
|
attempt += 1
|
@@ -168,21 +171,25 @@ async def _run_single_agent_iteration(
|
|
168
171
|
ctx: AnyContext,
|
169
172
|
agent: "Agent",
|
170
173
|
user_prompt: str,
|
174
|
+
attachments: "list[UserContent]",
|
171
175
|
history_list: ListOfDict,
|
172
|
-
rate_limitter: LLMRateLimiter
|
176
|
+
rate_limitter: LLMRateLimiter,
|
173
177
|
) -> "AgentRun":
|
174
178
|
from openai import APIError
|
175
179
|
from pydantic_ai.messages import ModelMessagesTypeAdapter
|
176
180
|
|
177
|
-
agent_payload =
|
181
|
+
agent_payload = _estimate_request_payload(
|
182
|
+
agent, user_prompt, attachments, history_list
|
183
|
+
)
|
178
184
|
if rate_limitter:
|
179
185
|
await rate_limitter.throttle(agent_payload)
|
180
186
|
else:
|
181
187
|
await llm_rate_limitter.throttle(agent_payload)
|
182
188
|
|
189
|
+
user_prompt_with_attachments = [user_prompt] + attachments
|
183
190
|
async with agent:
|
184
191
|
async with agent.iter(
|
185
|
-
user_prompt=
|
192
|
+
user_prompt=user_prompt_with_attachments,
|
186
193
|
message_history=ModelMessagesTypeAdapter.validate_python(history_list),
|
187
194
|
) as agent_run:
|
188
195
|
async for node in agent_run:
|
@@ -202,8 +209,11 @@ async def _run_single_agent_iteration(
|
|
202
209
|
return agent_run
|
203
210
|
|
204
211
|
|
205
|
-
def
|
206
|
-
agent: "Agent",
|
212
|
+
def _estimate_request_payload(
|
213
|
+
agent: "Agent",
|
214
|
+
user_prompt: str,
|
215
|
+
attachments: "list[UserContent]",
|
216
|
+
history_list: ListOfDict,
|
207
217
|
) -> str:
|
208
218
|
system_prompts = agent._system_prompts if hasattr(agent, "_system_prompts") else ()
|
209
219
|
return json.dumps(
|
@@ -211,10 +221,19 @@ def estimate_request_payload(
|
|
211
221
|
{"role": "system", "content": "\n".join(system_prompts)},
|
212
222
|
*history_list,
|
213
223
|
{"role": "user", "content": user_prompt},
|
224
|
+
*[_estimate_attachment_payload(attachment) for attachment in attachments],
|
214
225
|
]
|
215
226
|
)
|
216
227
|
|
217
228
|
|
229
|
+
def _estimate_attachment_payload(attachment: "UserContent") -> Any:
|
230
|
+
if hasattr(attachment, "url"):
|
231
|
+
return {"role": "user", "content": attachment.url}
|
232
|
+
if hasattr(attachment, "data"):
|
233
|
+
return {"role": "user", "content": "x" * len(attachment.data)}
|
234
|
+
return ""
|
235
|
+
|
236
|
+
|
218
237
|
def _get_plain_printer(ctx: AnyContext):
|
219
238
|
def printer(*args, **kwargs):
|
220
239
|
if "plain" not in kwargs:
|
zrb/task/llm/print_node.py
CHANGED
@@ -23,6 +23,7 @@ async def print_node(print_func: Callable, agent_run: Any, node: Any):
|
|
23
23
|
elif Agent.is_model_request_node(node):
|
24
24
|
# A model request node => We can stream tokens from the model's request
|
25
25
|
print_func(stylize_faint("🧠 Processing..."))
|
26
|
+
# Reference: https://ai.pydantic.dev/agents/#streaming
|
26
27
|
async with node.stream(agent_run.ctx) as request_stream:
|
27
28
|
is_streaming = False
|
28
29
|
async for event in request_stream:
|
zrb/task/llm/prompt.py
CHANGED
@@ -2,16 +2,21 @@ import os
|
|
2
2
|
import platform
|
3
3
|
import re
|
4
4
|
from datetime import datetime, timezone
|
5
|
+
from typing import TYPE_CHECKING, Callable
|
5
6
|
|
6
7
|
from zrb.attr.type import StrAttr, StrListAttr
|
7
8
|
from zrb.config.llm_config import llm_config as llm_config
|
8
9
|
from zrb.config.llm_context.config import llm_context_config
|
9
10
|
from zrb.context.any_context import AnyContext
|
11
|
+
from zrb.context.any_shared_context import AnySharedContext
|
10
12
|
from zrb.task.llm.conversation_history_model import ConversationHistory
|
11
13
|
from zrb.util.attr import get_attr, get_str_attr, get_str_list_attr
|
12
14
|
from zrb.util.file import read_dir, read_file_with_line_numbers
|
13
15
|
from zrb.util.llm.prompt import make_prompt_section
|
14
16
|
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from pydantic_ai.messages import UserContent
|
19
|
+
|
15
20
|
|
16
21
|
def get_persona(
|
17
22
|
ctx: AnyContext,
|
@@ -193,7 +198,7 @@ def get_system_and_user_prompt(
|
|
193
198
|
def extract_conversation_context(user_message: str) -> tuple[str, str]:
|
194
199
|
modified_user_message = user_message
|
195
200
|
# Match “@” + any non-space/comma sequence that contains at least one “/”
|
196
|
-
pattern = r"(?<!\w)@(?=[^,\s]*\/)([
|
201
|
+
pattern = r"(?<!\w)@(?=[^,\s]*\/)([^,\?\!\s]+)"
|
197
202
|
potential_resource_path = re.findall(pattern, user_message)
|
198
203
|
apendixes = []
|
199
204
|
for i, ref in enumerate(potential_resource_path):
|
@@ -274,3 +279,21 @@ def get_summarization_system_prompt(
|
|
274
279
|
if summarization_prompt is not None:
|
275
280
|
return summarization_prompt
|
276
281
|
return llm_config.default_summarization_prompt
|
282
|
+
|
283
|
+
|
284
|
+
def get_attachments(
|
285
|
+
ctx: AnyContext,
|
286
|
+
attachment: "UserContent | list[UserContent] | Callable[[AnySharedContext], UserContent | list[UserContent]] | None" = None, # noqa
|
287
|
+
) -> "list[UserContent]":
|
288
|
+
if attachment is None:
|
289
|
+
return []
|
290
|
+
if callable(attachment):
|
291
|
+
result = attachment(ctx)
|
292
|
+
if result is None:
|
293
|
+
return []
|
294
|
+
if isinstance(result, list):
|
295
|
+
return result
|
296
|
+
return [result]
|
297
|
+
if isinstance(attachment, list):
|
298
|
+
return attachment
|
299
|
+
return [attachment]
|
zrb/task/llm_task.py
CHANGED
@@ -23,6 +23,7 @@ from zrb.task.llm.conversation_history import (
|
|
23
23
|
from zrb.task.llm.conversation_history_model import ConversationHistory
|
24
24
|
from zrb.task.llm.history_summarization import maybe_summarize_history
|
25
25
|
from zrb.task.llm.prompt import (
|
26
|
+
get_attachments,
|
26
27
|
get_summarization_system_prompt,
|
27
28
|
get_system_and_user_prompt,
|
28
29
|
get_user_message,
|
@@ -32,13 +33,12 @@ from zrb.xcom.xcom import Xcom
|
|
32
33
|
|
33
34
|
if TYPE_CHECKING:
|
34
35
|
from pydantic_ai import Agent, Tool
|
36
|
+
from pydantic_ai.messages import UserContent
|
35
37
|
from pydantic_ai.models import Model
|
36
38
|
from pydantic_ai.settings import ModelSettings
|
37
39
|
from pydantic_ai.toolsets import AbstractToolset
|
38
40
|
|
39
41
|
ToolOrCallable = Tool | Callable
|
40
|
-
else:
|
41
|
-
ToolOrCallable = Any
|
42
42
|
|
43
43
|
|
44
44
|
class LLMTask(BaseTask):
|
@@ -72,6 +72,7 @@ class LLMTask(BaseTask):
|
|
72
72
|
modes: StrListAttr | None = None,
|
73
73
|
render_modes: bool = True,
|
74
74
|
message: StrAttr | None = None,
|
75
|
+
attachment: "UserContent | list[UserContent] | Callable[[AnySharedContext], UserContent | list[UserContent]] | None" = None, # noqa
|
75
76
|
render_message: bool = True,
|
76
77
|
tools: (
|
77
78
|
list["ToolOrCallable"]
|
@@ -184,11 +185,12 @@ class LLMTask(BaseTask):
|
|
184
185
|
self._conversation_context = conversation_context
|
185
186
|
self._is_yolo_mode = is_yolo_mode
|
186
187
|
self._render_yolo_mode = render_yolo_mode
|
188
|
+
self._attachment = attachment
|
187
189
|
|
188
|
-
def add_tool(self, *tool: ToolOrCallable):
|
190
|
+
def add_tool(self, *tool: "ToolOrCallable"):
|
189
191
|
self.append_tool(*tool)
|
190
192
|
|
191
|
-
def append_tool(self, *tool: ToolOrCallable):
|
193
|
+
def append_tool(self, *tool: "ToolOrCallable"):
|
192
194
|
for single_tool in tool:
|
193
195
|
self._additional_tools.append(single_tool)
|
194
196
|
|
@@ -230,6 +232,7 @@ class LLMTask(BaseTask):
|
|
230
232
|
render_summarization_prompt=self._render_summarization_prompt,
|
231
233
|
)
|
232
234
|
user_message = get_user_message(ctx, self._message, self._render_message)
|
235
|
+
attachments = get_attachments(ctx, self._attachment)
|
233
236
|
# 1. Prepare initial state (read history from previous session)
|
234
237
|
conversation_history = await read_conversation_history(
|
235
238
|
ctx=ctx,
|
@@ -268,10 +271,11 @@ class LLMTask(BaseTask):
|
|
268
271
|
)
|
269
272
|
# 4. Run the agent iteration and save the results/history
|
270
273
|
result = await self._execute_agent(
|
271
|
-
ctx,
|
272
|
-
agent,
|
273
|
-
user_message,
|
274
|
-
|
274
|
+
ctx=ctx,
|
275
|
+
agent=agent,
|
276
|
+
user_prompt=user_message,
|
277
|
+
attachments=attachments,
|
278
|
+
conversation_history=conversation_history,
|
275
279
|
)
|
276
280
|
# 5. Summarize
|
277
281
|
conversation_history = await maybe_summarize_history(
|
@@ -305,6 +309,7 @@ class LLMTask(BaseTask):
|
|
305
309
|
ctx: AnyContext,
|
306
310
|
agent: "Agent",
|
307
311
|
user_prompt: str,
|
312
|
+
attachments: "list[UserContent]",
|
308
313
|
conversation_history: ConversationHistory,
|
309
314
|
) -> Any:
|
310
315
|
"""Executes the agent, processes results, and saves history."""
|
@@ -313,6 +318,7 @@ class LLMTask(BaseTask):
|
|
313
318
|
ctx=ctx,
|
314
319
|
agent=agent,
|
315
320
|
user_prompt=user_prompt,
|
321
|
+
attachments=attachments,
|
316
322
|
history_list=conversation_history.history,
|
317
323
|
rate_limitter=self._rate_limitter,
|
318
324
|
)
|
zrb/util/file.py
CHANGED
@@ -14,14 +14,21 @@ def read_file(file_path: str, replace_map: dict[str, str] = {}) -> str:
|
|
14
14
|
"""
|
15
15
|
abs_file_path = os.path.abspath(os.path.expanduser(file_path))
|
16
16
|
is_pdf = abs_file_path.lower().endswith(".pdf")
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
try:
|
18
|
+
content = (
|
19
|
+
_read_pdf_file_content(abs_file_path)
|
20
|
+
if is_pdf
|
21
|
+
else _read_text_file_content(abs_file_path)
|
22
|
+
)
|
23
|
+
for key, val in replace_map.items():
|
24
|
+
content = content.replace(key, val)
|
25
|
+
return content
|
26
|
+
except Exception:
|
27
|
+
import base64
|
28
|
+
from pathlib import Path
|
29
|
+
|
30
|
+
data = Path(abs_file_path).read_bytes()
|
31
|
+
return base64.b64encode(data).decode("ascii")
|
25
32
|
|
26
33
|
|
27
34
|
def _read_text_file_content(file_path: str) -> str:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: zrb
|
3
|
-
Version: 1.14.
|
3
|
+
Version: 1.14.6
|
4
4
|
Summary: Your Automation Powerhouse
|
5
5
|
Home-page: https://github.com/state-alchemists/zrb
|
6
6
|
License: AGPL-3.0-or-later
|
@@ -22,17 +22,15 @@ Requires-Dist: chromadb (>=0.6.3,<0.7.0) ; extra == "rag" or extra == "all"
|
|
22
22
|
Requires-Dist: fastapi[standard] (>=0.116.1,<0.117.0)
|
23
23
|
Requires-Dist: isort (>=6.0.1,<6.1.0)
|
24
24
|
Requires-Dist: libcst (>=1.7.0,<2.0.0)
|
25
|
-
Requires-Dist: openai (>=1.
|
26
|
-
Requires-Dist: pdfplumber (>=0.11.6,<0.12.0)
|
25
|
+
Requires-Dist: openai (>=1.92.0,<=1.99.1)
|
26
|
+
Requires-Dist: pdfplumber (>=0.11.6,<0.12.0)
|
27
27
|
Requires-Dist: playwright (>=1.53.0,<2.0.0) ; extra == "playwright" or extra == "all"
|
28
|
-
Requires-Dist: prompt-toolkit (>=3.0.51,<4.0.0)
|
29
28
|
Requires-Dist: psutil (>=7.0.0,<8.0.0)
|
30
|
-
Requires-Dist: pydantic-ai (>=0.
|
29
|
+
Requires-Dist: pydantic-ai-slim[anthropic,bedrock,cli,cohere,google,groq,huggingface,mcp,mistral,openai,vertexai] (>=0.6.2,<0.7.0)
|
31
30
|
Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
|
32
31
|
Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
|
33
32
|
Requires-Dist: python-jose[cryptography] (>=3.4.0,<4.0.0)
|
34
33
|
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
35
|
-
Requires-Dist: rich (>=14.0.0,<15.0.0)
|
36
34
|
Requires-Dist: tiktoken (>=0.8.0,<0.9.0)
|
37
35
|
Requires-Dist: ulid-py (>=1.1.0,<2.0.0)
|
38
36
|
Project-URL: Documentation, https://github.com/state-alchemists/zrb
|
@@ -20,8 +20,8 @@ zrb/builtin/llm/tool/cli.py,sha256=dUWZrW2X5J_lONuzR__6-SbewSdi28E3RRuksjd4mWo,1
|
|
20
20
|
zrb/builtin/llm/tool/code.py,sha256=GRP_IZAkeL6RIlUm407BQRF992ES57pdzPaQdC5UsJU,8218
|
21
21
|
zrb/builtin/llm/tool/file.py,sha256=hKqT-r4S9FTMv3CmNWuIL5y6q13hp9Ggo0vwUGg3HxI,22585
|
22
22
|
zrb/builtin/llm/tool/rag.py,sha256=wB74JV7bxs0ec77b_09Z2lPjoR1WzPUvZbuXOdb9Q9g,9675
|
23
|
-
zrb/builtin/llm/tool/sub_agent.py,sha256=
|
24
|
-
zrb/builtin/llm/tool/web.py,sha256=
|
23
|
+
zrb/builtin/llm/tool/sub_agent.py,sha256=0YA6OTLHa-w4VKsxboGDLCrZV2k3XnLQDW0HZev_W2M,4900
|
24
|
+
zrb/builtin/llm/tool/web.py,sha256=csSX7dHVIszGTcBWFykpNKXwXD-ayqkRew-kHoV8PGA,7092
|
25
25
|
zrb/builtin/md5.py,sha256=690RV2LbW7wQeTFxY-lmmqTSVEEZv3XZbjEUW1Q3XpE,1480
|
26
26
|
zrb/builtin/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
27
|
zrb/builtin/project/add/fastapp/fastapp_input.py,sha256=MKlWR_LxWhM_DcULCtLfL_IjTxpDnDBkn9KIqNmajFs,310
|
@@ -196,11 +196,11 @@ zrb/builtin/random.py,sha256=p9kCvosSiSJyuGQrlrXuIQT8TRDGxXhfiUbsm3GFPd0,1793
|
|
196
196
|
zrb/builtin/setup/asdf/asdf.py,sha256=_k60yiRiKbRPh_eJVI4Nx_ZmmClOlOb9G0b0KhSGo1M,2444
|
197
197
|
zrb/builtin/setup/asdf/asdf_helper.py,sha256=6jARtyIAE1H82HKVQ84D25RrMAsAip_gD28X9ZlaTCk,1205
|
198
198
|
zrb/builtin/setup/common_input.py,sha256=zIVVsZsNlSoV2Fw8kyt7g5B8XAU8cWBsT865NPoddwo,848
|
199
|
-
zrb/builtin/setup/latex/ubuntu.py,sha256=
|
199
|
+
zrb/builtin/setup/latex/ubuntu.py,sha256=Wc6k2OJckXH11hqD9idJf6jWiJ_VoBEMPwDb_k5hssU,606
|
200
200
|
zrb/builtin/setup/tmux/tmux.py,sha256=cV-azDzLtNYtwxucmYqpPqoeSVz3lxP2GOc1-I1yhKs,1428
|
201
201
|
zrb/builtin/setup/tmux/tmux_config.sh,sha256=wQCb4Q-mNkxIPOcvpN84X9RUWkGY16u3Vd-pOhVidgg,416
|
202
202
|
zrb/builtin/setup/tmux/tmux_helper.py,sha256=M03l0wfL25TzGGp6lnVfX40ayT_x7N2lz-nz2chO7PU,396
|
203
|
-
zrb/builtin/setup/ubuntu.py,sha256=
|
203
|
+
zrb/builtin/setup/ubuntu.py,sha256=hNvkwYzUrjKcYUzjPhR1mncGUx57XU5hR7BGjVH2X1Q,1104
|
204
204
|
zrb/builtin/setup/zsh/zsh.py,sha256=wy7WSYIie6GprCqH4J2ygcTjWdOeRnxmOZcz8fRykcg,1845
|
205
205
|
zrb/builtin/setup/zsh/zsh_config.sh,sha256=SRkcXvVT3tdfS1UDT0-dSj2PKXPLohhyakY6tUEQPjc,4764
|
206
206
|
zrb/builtin/setup/zsh/zsh_helper.py,sha256=1zF1FH0oEPVAVhMA20tsdk1H0RPMCkLusYX8twsTbGI,393
|
@@ -217,7 +217,7 @@ zrb/callback/callback.py,sha256=PFhCqzfxdk6IAthmXcZ13DokT62xtBzJr_ciLw6I8Zg,4030
|
|
217
217
|
zrb/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
218
218
|
zrb/cmd/cmd_result.py,sha256=L8bQJzWCpcYexIxHBNsXj2pT3BtLmWex0iJSMkvimOA,597
|
219
219
|
zrb/cmd/cmd_val.py,sha256=7Doowyg6BK3ISSGBLt-PmlhzaEkBjWWm51cED6fAUOQ,1014
|
220
|
-
zrb/config/config.py,sha256=
|
220
|
+
zrb/config/config.py,sha256=YKw6DGD9oJDhKFjEdxuJlOXovtqD1dGw99At3z-mOW8,13589
|
221
221
|
zrb/config/default_prompt/file_extractor_system_prompt.md,sha256=tmeZMPzF9MGExsZZw7M2PZN6V0oFVRp1nIjiqUPvQ9M,1013
|
222
222
|
zrb/config/default_prompt/interactive_system_prompt.md,sha256=ZFPeDEV2vlcksHiVG2o-TCehmqkFolDjtH0_Fzo1gGI,3566
|
223
223
|
zrb/config/default_prompt/persona.md,sha256=WU4JKp-p7qJePDA6NZ_CYdBggo2B3PEq8IEnNVblIHU,41
|
@@ -346,7 +346,7 @@ zrb/task/base_trigger.py,sha256=WSGcmBcGAZw8EzUXfmCjqJQkz8GEmi1RzogpF6A1V4s,6902
|
|
346
346
|
zrb/task/cmd_task.py,sha256=myM8WZm6NrUD-Wv0Vb5sTOrutrAVZLt5LVsSBKwX6SM,10860
|
347
347
|
zrb/task/http_check.py,sha256=Gf5rOB2Se2EdizuN9rp65HpGmfZkGc-clIAlHmPVehs,2565
|
348
348
|
zrb/task/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
349
|
-
zrb/task/llm/agent.py,sha256=
|
349
|
+
zrb/task/llm/agent.py,sha256=KJKmlLqXsDlIxauQYbTpH_qmgI0KUCkrJwEnRN2YZ3w,8493
|
350
350
|
zrb/task/llm/config.py,sha256=UwTsC3YqQPaNfl75T8wMRZWhBjGQce-xaselRqs37Lc,3788
|
351
351
|
zrb/task/llm/conversation_history.py,sha256=B_PDWYL_q66s0xwWBzMSomqPN6u3gkXlIeXBD5A0Apg,4416
|
352
352
|
zrb/task/llm/conversation_history_model.py,sha256=DJ0KDBB0BriQuE5ugC_q0aSHhjNIBcfjUk1f0S_3I9U,9245
|
@@ -354,12 +354,12 @@ zrb/task/llm/default_workflow/coding.md,sha256=2uythvPsnBpYfIhiIH1cCinQXX0i0yUqs
|
|
354
354
|
zrb/task/llm/default_workflow/copywriting.md,sha256=xSO7GeDolwGxiuz6kXsK2GKGpwp8UgtG0yRqTmill_s,1999
|
355
355
|
zrb/task/llm/default_workflow/researching.md,sha256=KD-aYHFHir6Ti-4FsBBtGwiI0seSVgleYbKJZi_POXA,2139
|
356
356
|
zrb/task/llm/error.py,sha256=QR-nIohS6pBpC_16cWR-fw7Mevo1sNYAiXMBsh_CJDE,4157
|
357
|
-
zrb/task/llm/history_summarization.py,sha256=
|
358
|
-
zrb/task/llm/print_node.py,sha256=
|
359
|
-
zrb/task/llm/prompt.py,sha256=
|
357
|
+
zrb/task/llm/history_summarization.py,sha256=YaK3BR7gJVi9f8G5loosNAaO4y7K3ck_4bJ9GuOChXk,8028
|
358
|
+
zrb/task/llm/print_node.py,sha256=Z6J1n024MkYIeO4xGnDHmUHk9yTjJRVZRxAC5schE7w,4242
|
359
|
+
zrb/task/llm/prompt.py,sha256=p_SWitTj6twftae0c_GM7mBdlF9kO9TpmmdKaXGJPAY,10460
|
360
360
|
zrb/task/llm/tool_wrapper.py,sha256=6PSxX466u4TraJgd9PdZBSOpo2Xjn_FPw-E-LT6V4DE,8257
|
361
361
|
zrb/task/llm/typing.py,sha256=c8VAuPBw_4A3DxfYdydkgedaP-LU61W9_wj3m3CAX1E,58
|
362
|
-
zrb/task/llm_task.py,sha256=
|
362
|
+
zrb/task/llm_task.py,sha256=O1WJigcpvkZM3-Cu-ru-i9wgiJ4oCqc8V0IPFctH_fQ,14391
|
363
363
|
zrb/task/make_task.py,sha256=PD3b_aYazthS8LHeJsLAhwKDEgdurQZpymJDKeN60u0,2265
|
364
364
|
zrb/task/rsync_task.py,sha256=WfqNSaicJgYWpunNU34eYxXDqHDHOftuDHyWJKjqwg0,6365
|
365
365
|
zrb/task/scaffolder.py,sha256=rME18w1HJUHXgi9eTYXx_T2G4JdqDYzBoNOkdOOo5-o,6806
|
@@ -388,7 +388,7 @@ zrb/util/codemod/modify_function_call.py,sha256=wbyoRRsM4V9fPYkT5kHN0zpBetpRDq2S
|
|
388
388
|
zrb/util/codemod/modify_method.py,sha256=5fioXjqNQmrf4CV2qlTZHpViF9PMnNer4FvuKam7byo,6344
|
389
389
|
zrb/util/codemod/modify_module.py,sha256=2mzi_NxJ-kNFo5G0U_Rqb3JoYQl1s6Izt7b_wAl10F0,715
|
390
390
|
zrb/util/cron.py,sha256=UWqqhhM7DDTPx6wjCIdBndnVh3NRu-sdKazp8aZkXh8,3833
|
391
|
-
zrb/util/file.py,sha256=
|
391
|
+
zrb/util/file.py,sha256=FBuLpyMLvJMflK2shssct2TA5lBGaIIX2SJKHSZ7DXQ,3027
|
392
392
|
zrb/util/git.py,sha256=GS08OqE-gs6mN-_VqACN82DbI81nzG3FiXeim_-jVAU,8193
|
393
393
|
zrb/util/git_diff_model.py,sha256=Tg2q6oxQ5chryThzjs0cLcKFGM03CnqvusTo_Ug_oX4,369
|
394
394
|
zrb/util/git_subtree.py,sha256=AyQWCWEi2EIzEpYXRnYN55157KMUql0WHj70QNw5PHU,4612
|
@@ -406,7 +406,7 @@ zrb/util/todo.py,sha256=r9_KYF2-hLKMNjsp6AFK9zivykMrywd-kJ4bCwfdafI,19323
|
|
406
406
|
zrb/util/todo_model.py,sha256=hhzAX-uFl5rsg7iVX1ULlJOfBtblwQ_ieNUxBWfc-Os,1670
|
407
407
|
zrb/xcom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
408
408
|
zrb/xcom/xcom.py,sha256=o79rxR9wphnShrcIushA0Qt71d_p3ZTxjNf7x9hJB78,1571
|
409
|
-
zrb-1.14.
|
410
|
-
zrb-1.14.
|
411
|
-
zrb-1.14.
|
412
|
-
zrb-1.14.
|
409
|
+
zrb-1.14.6.dist-info/METADATA,sha256=kQWZFnqtvILW2mqlUbWfaI0_77DcQkYleTMlqPaqm4g,9709
|
410
|
+
zrb-1.14.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
411
|
+
zrb-1.14.6.dist-info/entry_points.txt,sha256=-Pg3ElWPfnaSM-XvXqCxEAa-wfVI6BEgcs386s8C8v8,46
|
412
|
+
zrb-1.14.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|