Spaces:
Runtime error
Runtime error
Connor Adams
commited on
Commit
Β·
ef01a34
1
Parent(s):
b528a04
Add logfire and duckduckgo
Browse files- agent.py +15 -4
- requirements.lock +55 -2
- requirements.txt +2 -0
- tools/safe_duck.py +114 -0
agent.py
CHANGED
@@ -1,9 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
class BasicAgent:
|
2 |
def __init__(self):
|
3 |
-
|
|
|
|
|
|
|
|
|
4 |
|
5 |
def __call__(self, question: str) -> str:
|
6 |
print(f"Agent received question (first 50 chars): {question[:50]}...")
|
7 |
-
|
8 |
-
print(f"Agent returning fixed answer: {
|
9 |
-
return
|
|
|
1 |
+
import logfire
|
2 |
+
|
3 |
+
from pydantic_ai import Agent
|
4 |
+
from tools.safe_duck import safe_duckduckgo_search_tool
|
5 |
+
logfire.configure()
|
6 |
+
logfire.instrument_pydantic_ai()
|
7 |
+
|
8 |
class BasicAgent:
|
9 |
def __init__(self):
|
10 |
+
self.agent = Agent(
|
11 |
+
"openai:o3-mini",
|
12 |
+
tools=[safe_duckduckgo_search_tool()],
|
13 |
+
system_prompt="Search DuckDuckGo for the given query and return the results.",
|
14 |
+
)
|
15 |
|
16 |
def __call__(self, question: str) -> str:
|
17 |
print(f"Agent received question (first 50 chars): {question[:50]}...")
|
18 |
+
result = self.agent.run_sync(question).output
|
19 |
+
print(f"Agent returning fixed answer: {result}")
|
20 |
+
return result
|
requirements.lock
CHANGED
@@ -41,6 +41,7 @@ charset-normalizer==3.4.2
|
|
41 |
# via requests
|
42 |
click==8.1.8
|
43 |
# via
|
|
|
44 |
# typer
|
45 |
# uvicorn
|
46 |
cohere==5.15.0
|
@@ -50,12 +51,17 @@ colorama==0.4.6
|
|
50 |
cryptography==45.0.2
|
51 |
# via authlib
|
52 |
deprecated==1.2.18
|
53 |
-
# via
|
|
|
|
|
|
|
54 |
distro==1.9.0
|
55 |
# via
|
56 |
# anthropic
|
57 |
# groq
|
58 |
# openai
|
|
|
|
|
59 |
eval-type-backport==0.2.2
|
60 |
# via
|
61 |
# mistralai
|
@@ -65,6 +71,8 @@ exceptiongroup==1.3.0
|
|
65 |
# via
|
66 |
# anyio
|
67 |
# pydantic-ai-slim
|
|
|
|
|
68 |
fasta2a==0.2.7
|
69 |
# via pydantic-ai-slim
|
70 |
fastapi==0.115.12
|
@@ -85,6 +93,8 @@ google-auth==2.40.2
|
|
85 |
# pydantic-ai-slim
|
86 |
google-genai==1.16.1
|
87 |
# via pydantic-ai-slim
|
|
|
|
|
88 |
gradio==5.31.0
|
89 |
# via -r requirements.txt
|
90 |
gradio-client==1.10.1
|
@@ -145,10 +155,14 @@ jmespath==1.0.1
|
|
145 |
# via
|
146 |
# boto3
|
147 |
# botocore
|
|
|
|
|
148 |
logfire-api==3.16.0
|
149 |
# via
|
150 |
# pydantic-evals
|
151 |
# pydantic-graph
|
|
|
|
|
152 |
markdown-it-py==3.0.0
|
153 |
# via rich
|
154 |
markupsafe==3.0.2
|
@@ -170,7 +184,29 @@ openai==1.82.0
|
|
170 |
opentelemetry-api==1.33.1
|
171 |
# via
|
172 |
# fasta2a
|
|
|
|
|
|
|
|
|
173 |
# pydantic-ai-slim
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
orjson==3.10.18
|
175 |
# via gradio
|
176 |
packaging==25.0
|
@@ -178,12 +214,20 @@ packaging==25.0
|
|
178 |
# gradio
|
179 |
# gradio-client
|
180 |
# huggingface-hub
|
|
|
181 |
pandas==2.2.3
|
182 |
# via gradio
|
183 |
pillow==11.2.1
|
184 |
# via gradio
|
|
|
|
|
185 |
prompt-toolkit==3.0.51
|
186 |
# via pydantic-ai-slim
|
|
|
|
|
|
|
|
|
|
|
187 |
pyasn1==0.6.1
|
188 |
# via
|
189 |
# pyasn1-modules
|
@@ -212,6 +256,7 @@ pydantic-ai==0.2.7
|
|
212 |
# via -r requirements.txt
|
213 |
pydantic-ai-slim==0.2.7
|
214 |
# via
|
|
|
215 |
# pydantic-ai
|
216 |
# pydantic-evals
|
217 |
pydantic-core==2.33.2
|
@@ -252,9 +297,11 @@ requests==2.32.3
|
|
252 |
# cohere
|
253 |
# google-genai
|
254 |
# huggingface-hub
|
|
|
255 |
# pydantic-ai-slim
|
256 |
rich==14.0.0
|
257 |
# via
|
|
|
258 |
# pydantic-ai-slim
|
259 |
# pydantic-evals
|
260 |
# typer
|
@@ -289,6 +336,8 @@ starlette==0.46.2
|
|
289 |
# sse-starlette
|
290 |
tokenizers==0.21.1
|
291 |
# via cohere
|
|
|
|
|
292 |
tomlkit==0.13.2
|
293 |
# via gradio
|
294 |
tqdm==4.67.1
|
@@ -311,7 +360,9 @@ typing-extensions==4.13.2
|
|
311 |
# gradio-client
|
312 |
# groq
|
313 |
# huggingface-hub
|
|
|
314 |
# openai
|
|
|
315 |
# pydantic
|
316 |
# pydantic-core
|
317 |
# rich
|
@@ -343,6 +394,8 @@ websockets==15.0.1
|
|
343 |
# google-genai
|
344 |
# gradio-client
|
345 |
wrapt==1.17.2
|
346 |
-
# via
|
|
|
|
|
347 |
zipp==3.21.0
|
348 |
# via importlib-metadata
|
|
|
41 |
# via requests
|
42 |
click==8.1.8
|
43 |
# via
|
44 |
+
# duckduckgo-search
|
45 |
# typer
|
46 |
# uvicorn
|
47 |
cohere==5.15.0
|
|
|
51 |
cryptography==45.0.2
|
52 |
# via authlib
|
53 |
deprecated==1.2.18
|
54 |
+
# via
|
55 |
+
# opentelemetry-api
|
56 |
+
# opentelemetry-exporter-otlp-proto-http
|
57 |
+
# opentelemetry-semantic-conventions
|
58 |
distro==1.9.0
|
59 |
# via
|
60 |
# anthropic
|
61 |
# groq
|
62 |
# openai
|
63 |
+
duckduckgo-search==8.0.2
|
64 |
+
# via pydantic-ai-slim
|
65 |
eval-type-backport==0.2.2
|
66 |
# via
|
67 |
# mistralai
|
|
|
71 |
# via
|
72 |
# anyio
|
73 |
# pydantic-ai-slim
|
74 |
+
executing==2.2.0
|
75 |
+
# via logfire
|
76 |
fasta2a==0.2.7
|
77 |
# via pydantic-ai-slim
|
78 |
fastapi==0.115.12
|
|
|
93 |
# pydantic-ai-slim
|
94 |
google-genai==1.16.1
|
95 |
# via pydantic-ai-slim
|
96 |
+
googleapis-common-protos==1.70.0
|
97 |
+
# via opentelemetry-exporter-otlp-proto-http
|
98 |
gradio==5.31.0
|
99 |
# via -r requirements.txt
|
100 |
gradio-client==1.10.1
|
|
|
155 |
# via
|
156 |
# boto3
|
157 |
# botocore
|
158 |
+
logfire==3.16.0
|
159 |
+
# via pydantic-ai
|
160 |
logfire-api==3.16.0
|
161 |
# via
|
162 |
# pydantic-evals
|
163 |
# pydantic-graph
|
164 |
+
lxml==5.4.0
|
165 |
+
# via duckduckgo-search
|
166 |
markdown-it-py==3.0.0
|
167 |
# via rich
|
168 |
markupsafe==3.0.2
|
|
|
184 |
opentelemetry-api==1.33.1
|
185 |
# via
|
186 |
# fasta2a
|
187 |
+
# opentelemetry-exporter-otlp-proto-http
|
188 |
+
# opentelemetry-instrumentation
|
189 |
+
# opentelemetry-sdk
|
190 |
+
# opentelemetry-semantic-conventions
|
191 |
# pydantic-ai-slim
|
192 |
+
opentelemetry-exporter-otlp-proto-common==1.33.1
|
193 |
+
# via opentelemetry-exporter-otlp-proto-http
|
194 |
+
opentelemetry-exporter-otlp-proto-http==1.33.1
|
195 |
+
# via logfire
|
196 |
+
opentelemetry-instrumentation==0.54b1
|
197 |
+
# via logfire
|
198 |
+
opentelemetry-proto==1.33.1
|
199 |
+
# via
|
200 |
+
# opentelemetry-exporter-otlp-proto-common
|
201 |
+
# opentelemetry-exporter-otlp-proto-http
|
202 |
+
opentelemetry-sdk==1.33.1
|
203 |
+
# via
|
204 |
+
# logfire
|
205 |
+
# opentelemetry-exporter-otlp-proto-http
|
206 |
+
opentelemetry-semantic-conventions==0.54b1
|
207 |
+
# via
|
208 |
+
# opentelemetry-instrumentation
|
209 |
+
# opentelemetry-sdk
|
210 |
orjson==3.10.18
|
211 |
# via gradio
|
212 |
packaging==25.0
|
|
|
214 |
# gradio
|
215 |
# gradio-client
|
216 |
# huggingface-hub
|
217 |
+
# opentelemetry-instrumentation
|
218 |
pandas==2.2.3
|
219 |
# via gradio
|
220 |
pillow==11.2.1
|
221 |
# via gradio
|
222 |
+
primp==0.15.0
|
223 |
+
# via duckduckgo-search
|
224 |
prompt-toolkit==3.0.51
|
225 |
# via pydantic-ai-slim
|
226 |
+
protobuf==5.29.4
|
227 |
+
# via
|
228 |
+
# googleapis-common-protos
|
229 |
+
# logfire
|
230 |
+
# opentelemetry-proto
|
231 |
pyasn1==0.6.1
|
232 |
# via
|
233 |
# pyasn1-modules
|
|
|
256 |
# via -r requirements.txt
|
257 |
pydantic-ai-slim==0.2.7
|
258 |
# via
|
259 |
+
# -r requirements.txt
|
260 |
# pydantic-ai
|
261 |
# pydantic-evals
|
262 |
pydantic-core==2.33.2
|
|
|
297 |
# cohere
|
298 |
# google-genai
|
299 |
# huggingface-hub
|
300 |
+
# opentelemetry-exporter-otlp-proto-http
|
301 |
# pydantic-ai-slim
|
302 |
rich==14.0.0
|
303 |
# via
|
304 |
+
# logfire
|
305 |
# pydantic-ai-slim
|
306 |
# pydantic-evals
|
307 |
# typer
|
|
|
336 |
# sse-starlette
|
337 |
tokenizers==0.21.1
|
338 |
# via cohere
|
339 |
+
tomli==2.2.1
|
340 |
+
# via logfire
|
341 |
tomlkit==0.13.2
|
342 |
# via gradio
|
343 |
tqdm==4.67.1
|
|
|
360 |
# gradio-client
|
361 |
# groq
|
362 |
# huggingface-hub
|
363 |
+
# logfire
|
364 |
# openai
|
365 |
+
# opentelemetry-sdk
|
366 |
# pydantic
|
367 |
# pydantic-core
|
368 |
# rich
|
|
|
394 |
# google-genai
|
395 |
# gradio-client
|
396 |
wrapt==1.17.2
|
397 |
+
# via
|
398 |
+
# deprecated
|
399 |
+
# opentelemetry-instrumentation
|
400 |
zipp==3.21.0
|
401 |
# via importlib-metadata
|
requirements.txt
CHANGED
@@ -2,3 +2,5 @@ gradio
|
|
2 |
gradio[oauth]
|
3 |
requests
|
4 |
pydantic-ai
|
|
|
|
|
|
2 |
gradio[oauth]
|
3 |
requests
|
4 |
pydantic-ai
|
5 |
+
pydantic-ai-slim[duckduckgo]
|
6 |
+
pydantic-ai[logfire]
|
tools/safe_duck.py
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
safe_duck_tool.py
|
3 |
+
A resilient, family-friendly DuckDuckGo search Tool for Pydantic-AI.
|
4 |
+
"""
|
5 |
+
|
6 |
+
from __future__ import annotations
|
7 |
+
|
8 |
+
import functools
|
9 |
+
import time
|
10 |
+
from dataclasses import dataclass
|
11 |
+
from typing_extensions import TypedDict
|
12 |
+
|
13 |
+
import anyio
|
14 |
+
import anyio.to_thread
|
15 |
+
from duckduckgo_search import DDGS, exceptions
|
16 |
+
from pydantic import TypeAdapter
|
17 |
+
from pydantic_ai.tools import Tool
|
18 |
+
|
19 |
+
|
20 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
21 |
+
# 1. Types
|
22 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
23 |
+
class DuckDuckGoResult(TypedDict):
|
24 |
+
title: str
|
25 |
+
href: str
|
26 |
+
body: str
|
27 |
+
|
28 |
+
|
29 |
+
duckduckgo_ta = TypeAdapter(list[DuckDuckGoResult])
|
30 |
+
|
31 |
+
|
32 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
33 |
+
# 2. Search wrapper with cache + back-off
|
34 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
35 |
+
@functools.lru_cache(maxsize=512)
|
36 |
+
def _safe_search(
|
37 |
+
query: str,
|
38 |
+
*,
|
39 |
+
ddgs_constructor_kwargs_tuple: tuple,
|
40 |
+
safesearch: str,
|
41 |
+
max_results: int | None,
|
42 |
+
retries: int = 5,
|
43 |
+
) -> list[dict[str, str]]:
|
44 |
+
wait = 1
|
45 |
+
for _ in range(retries):
|
46 |
+
try:
|
47 |
+
|
48 |
+
ddgs = DDGS(**dict(ddgs_constructor_kwargs_tuple))
|
49 |
+
|
50 |
+
return list(
|
51 |
+
ddgs.text(query, safesearch=safesearch, max_results=max_results)
|
52 |
+
)
|
53 |
+
except exceptions.RatelimitException as e:
|
54 |
+
time.sleep(getattr(e, "retry_after", wait))
|
55 |
+
wait = min(wait * 2, 30)
|
56 |
+
raise RuntimeError("DuckDuckGo kept rate-limiting after multiple attempts")
|
57 |
+
|
58 |
+
|
59 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
60 |
+
# 3. Tool implementation
|
61 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
62 |
+
@dataclass
|
63 |
+
class _SafeDuckToolImpl:
|
64 |
+
ddgs_constructor_kwargs: dict # Renamed from client_kwargs
|
65 |
+
safesearch: str # Added to store safesearch setting
|
66 |
+
max_results: int | None
|
67 |
+
|
68 |
+
async def __call__(self, query: str) -> list[DuckDuckGoResult]:
|
69 |
+
search = functools.partial(
|
70 |
+
_safe_search,
|
71 |
+
# Convert dict to sorted tuple of items to make it hashable
|
72 |
+
ddgs_constructor_kwargs_tuple=tuple(
|
73 |
+
sorted(self.ddgs_constructor_kwargs.items())
|
74 |
+
),
|
75 |
+
safesearch=self.safesearch, # Pass stored safesearch
|
76 |
+
max_results=self.max_results,
|
77 |
+
)
|
78 |
+
results = await anyio.to_thread.run_sync(search, query)
|
79 |
+
# validate & coerce with Pydantic
|
80 |
+
return duckduckgo_ta.validate_python(results)
|
81 |
+
|
82 |
+
|
83 |
+
def safe_duckduckgo_search_tool(
|
84 |
+
*,
|
85 |
+
safesearch: str = "moderate", # "on" | "moderate" | "off"
|
86 |
+
timeout: int = 15,
|
87 |
+
max_results: int | None = None,
|
88 |
+
proxy: str | None = None, # e.g. "socks5h://user:pw@host:1080"
|
89 |
+
) -> Tool:
|
90 |
+
"""
|
91 |
+
Create a resilient, Safe-Search-enabled DuckDuckGo search Tool.
|
92 |
+
|
93 |
+
Drop-in replacement for `pydantic_ai.common_tools.duckduckgo.duckduckgo_search_tool`.
|
94 |
+
"""
|
95 |
+
# Arguments for DDGS constructor
|
96 |
+
ddgs_constructor_kwargs = dict(
|
97 |
+
timeout=timeout,
|
98 |
+
proxy=proxy,
|
99 |
+
)
|
100 |
+
# Arguments for ddgs.text() method are handled separately (safesearch, max_results)
|
101 |
+
|
102 |
+
impl = _SafeDuckToolImpl(
|
103 |
+
ddgs_constructor_kwargs=ddgs_constructor_kwargs,
|
104 |
+
safesearch=safesearch,
|
105 |
+
max_results=max_results,
|
106 |
+
)
|
107 |
+
return Tool(
|
108 |
+
impl.__call__,
|
109 |
+
name="safe_duckduckgo_search",
|
110 |
+
description=(
|
111 |
+
"DuckDuckGo web search with Safe Search, automatic back-off, and "
|
112 |
+
"LRU caching. Pass a plain-text query; returns a list of results."
|
113 |
+
),
|
114 |
+
)
|