Skip to content

Commit ccaee0a

Browse files
Rook1exjasinluo
andauthored
feature: 新增 web_search 工具,支持 duckduckgo 与 google (#46)
新增 web_fetch 工具 新增 相关示例和使用文档 Co-authored-by: jasinluo <jasinluo@tencent.com>
1 parent 851ec62 commit ccaee0a

22 files changed

Lines changed: 5347 additions & 1 deletion

File tree

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[flake8]
22
max-line-length = 120
3-
ignore = E402
3+
ignore = E402, W503

docs/mkdocs/en/tool.md

Lines changed: 581 additions & 0 deletions
Large diffs are not rendered by default.

docs/mkdocs/zh/tool.md

Lines changed: 585 additions & 0 deletions
Large diffs are not rendered by default.

examples/webfetch_tool/.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Set TRPC_AGENT_API_KEY、TRPC_AGENT_BASE_URL、TRPC_AGENT_MODEL_NAME
2+
TRPC_AGENT_API_KEY=your_api_key
3+
TRPC_AGENT_BASE_URL=your_base_url
4+
TRPC_AGENT_MODEL_NAME=your_model_name

examples/webfetch_tool/README.md

Lines changed: 278 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
""" Agent module for WebFetchTool"""
7+
8+
from trpc_agent_sdk.agents import LlmAgent
9+
from trpc_agent_sdk.models import LLMModel
10+
from trpc_agent_sdk.models import OpenAIModel
11+
from trpc_agent_sdk.tools import WebFetchTool
12+
13+
from .config import get_model_config
14+
from .prompts import INSTRUCTION
15+
16+
17+
def _create_model() -> LLMModel:
18+
"""Create the LLM model used by every demo agent."""
19+
api_key, url, model_name = get_model_config()
20+
return OpenAIModel(model_name=model_name, api_key=api_key, base_url=url)
21+
22+
23+
def create_default_fetch_agent() -> LlmAgent:
24+
"""Build the baseline webfetch agent.
25+
26+
The construction below pins every HTTP-shape knob explicitly so the
27+
example doubles as inline documentation:
28+
29+
- ``timeout`` — lower than the 30s default so transient network
30+
hiccups surface quickly in the demo.
31+
- ``user_agent`` — identifies the demo on the wire so downstream
32+
logs can tell example traffic apart from real deployments.
33+
- ``max_content_length`` — cap returned ``content`` text so the
34+
demo output stays readable even for long pages.
35+
- ``max_response_bytes`` — hard byte budget on the raw wire read
36+
(~1 MiB here) that protects decode / memory budgets before the
37+
char cap kicks in.
38+
- ``follow_redirects`` / ``max_redirects`` — keep the redirect
39+
loop bounded while still handling ``http`` → ``https`` hops.
40+
- ``block_private_network`` — default-on SSRF boundary; pinned
41+
here to make the intent explicit.
42+
"""
43+
web_fetch = WebFetchTool(
44+
timeout=10.0,
45+
user_agent="trpc-agent-python-webfetch-example/1.0",
46+
max_content_length=4000,
47+
max_response_bytes=1 * 1024 * 1024,
48+
follow_redirects=True,
49+
max_redirects=3,
50+
block_private_network=True,
51+
)
52+
return LlmAgent(
53+
name="default_webfetch_assistant",
54+
description="Web-reading assistant that fetches a single URL and summarises its textual content.",
55+
model=_create_model(),
56+
instruction=INSTRUCTION,
57+
tools=[web_fetch],
58+
)
59+
60+
61+
def create_cached_fetch_agent() -> LlmAgent:
62+
"""Build a webfetch agent that enables the in-process LRU cache.
63+
64+
Cache knobs covered:
65+
66+
- ``enable_cache=True`` — opt in to the URL → ``FetchResult`` LRU.
67+
- ``cache_ttl_seconds`` — how long an entry is considered fresh.
68+
- ``cache_max_bytes`` — total byte budget for the cache; entries
69+
larger than this limit are silently skipped.
70+
71+
The demo fetches the same URL twice in a row so the second call
72+
comes back with ``cached=true`` on the tool response.
73+
"""
74+
web_fetch = WebFetchTool(
75+
timeout=10.0,
76+
user_agent="trpc-agent-python-webfetch-example/1.0",
77+
max_content_length=4000,
78+
max_response_bytes=1 * 1024 * 1024,
79+
enable_cache=True,
80+
cache_ttl_seconds=120.0,
81+
cache_max_bytes=2 * 1024 * 1024,
82+
)
83+
return LlmAgent(
84+
name="cached_webfetch_assistant",
85+
description=("Web-reading assistant with an in-process LRU cache so repeated "
86+
"fetches of the same URL skip the network."),
87+
model=_create_model(),
88+
instruction=INSTRUCTION,
89+
tools=[web_fetch],
90+
)
91+
92+
93+
def create_whitelist_fetch_agent() -> LlmAgent:
94+
"""Build a webfetch agent that only permits a closed set of hosts.
95+
96+
Configures ``allowed_domains`` so every non-whitelisted URL is
97+
rejected with ``BLOCKED_URL`` before the HTTP GET is issued. The
98+
matching is subdomain-aware (``www.`` stripped), so ``python.org``
99+
also lets ``docs.python.org`` through.
100+
"""
101+
web_fetch = WebFetchTool(
102+
timeout=10.0,
103+
user_agent="trpc-agent-python-webfetch-example/1.0",
104+
max_content_length=4000,
105+
allowed_domains=["python.org"],
106+
)
107+
return LlmAgent(
108+
name="whitelist_webfetch_assistant",
109+
description=("Web-reading assistant restricted to a domain whitelist; "
110+
"off-list URLs are rejected with BLOCKED_URL."),
111+
model=_create_model(),
112+
instruction=INSTRUCTION,
113+
tools=[web_fetch],
114+
)
115+
116+
117+
def create_ssrf_fetch_agent() -> LlmAgent:
118+
"""Build a webfetch agent that focuses on the SSRF guard."""
119+
web_fetch = WebFetchTool(
120+
timeout=10.0,
121+
user_agent="trpc-agent-python-webfetch-example/1.0",
122+
max_content_length=4000,
123+
follow_redirects=True,
124+
max_redirects=3,
125+
block_private_network=True,
126+
)
127+
return LlmAgent(
128+
name="ssrf_webfetch_assistant",
129+
description=("Web-reading assistant with an SSRF guard that rejects loopback / private / "
130+
"link-local / reserved / multicast / unspecified targets on every hop."),
131+
model=_create_model(),
132+
instruction=INSTRUCTION,
133+
tools=[web_fetch],
134+
)
135+
136+
137+
def create_blocklist_fetch_agent() -> LlmAgent:
138+
"""Build a webfetch agent that rejects a named set of hosts.
139+
140+
Configures ``blocked_domains`` so any URL whose host matches
141+
(subdomain-aware, ``www.`` stripped) is rejected with
142+
``BLOCKED_URL``. Blocks are evaluated *before* the allow list, so a
143+
host present in both lists is still rejected.
144+
"""
145+
web_fetch = WebFetchTool(
146+
timeout=10.0,
147+
user_agent="trpc-agent-python-webfetch-example/1.0",
148+
max_content_length=4000,
149+
blocked_domains=["example.com"],
150+
)
151+
return LlmAgent(
152+
name="blocklist_webfetch_assistant",
153+
description=("Web-reading assistant with a domain blacklist; "
154+
"blacklisted URLs are rejected with BLOCKED_URL."),
155+
model=_create_model(),
156+
instruction=INSTRUCTION,
157+
tools=[web_fetch],
158+
)
159+
160+
161+
default_fetch_agent = create_default_fetch_agent()
162+
cached_fetch_agent = create_cached_fetch_agent()
163+
whitelist_fetch_agent = create_whitelist_fetch_agent()
164+
blocklist_fetch_agent = create_blocklist_fetch_agent()
165+
ssrf_fetch_agent = create_ssrf_fetch_agent()
166+
root_agent = default_fetch_agent
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
""" Agent config module"""
7+
8+
import os
9+
10+
11+
def get_model_config() -> tuple[str, str, str]:
12+
"""Get LLM model config from environment variables."""
13+
api_key = os.getenv('TRPC_AGENT_API_KEY', '')
14+
url = os.getenv('TRPC_AGENT_BASE_URL', '')
15+
model_name = os.getenv('TRPC_AGENT_MODEL_NAME', '')
16+
if not api_key or not url or not model_name:
17+
raise ValueError('''TRPC_AGENT_API_KEY, TRPC_AGENT_BASE_URL,
18+
and TRPC_AGENT_MODEL_NAME must be set in environment variables''')
19+
return api_key, url, model_name
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
""" prompts for agent"""
7+
8+
INSTRUCTION = ("You are a web-reading assistant that answers user questions using the `webfetch` tool.\n"
9+
"\n"
10+
"Workflow:\n"
11+
" 1. When the user gives you an absolute http(s) URL and asks you to read, summarise, or\n"
12+
" quote from it, call `webfetch` ONCE with that exact URL. Do not invent or rewrite the URL.\n"
13+
" 2. If the user asks for only a short excerpt / headline, pass a smaller `max_length`\n"
14+
" (e.g. 500 or 1000) so the tool truncates the response early and keeps the context tight.\n"
15+
" Otherwise omit `max_length` and let the tool-level default apply.\n"
16+
" 3. Use the returned `content` field verbatim to compose your answer; quote short phrases\n"
17+
" directly and summarise the rest in your own words.\n"
18+
" 4. If the tool returns an `error` field (e.g. `BLOCKED_URL`, `SSRF_BLOCKED_URL`, `HTTP_STATUS`,\n"
19+
" `UNSUPPORTED_CONTENT_TYPE`), do NOT guess the page contents — explain the failure briefly.\n"
20+
" - For `BLOCKED_URL`, tell the user which domain allow/block policy rejected the host.\n"
21+
" - For `SSRF_BLOCKED_URL`, tell the user the target resolved to a loopback / private /\n"
22+
" link-local / reserved / multicast / unspecified address and was refused by the SSRF\n"
23+
" guard before any connection was opened.\n"
24+
" 5. If `cached=true` is set on the tool response, mention at the end of your reply that the\n"
25+
" page was served from the in-process cache so the user knows the content may be slightly\n"
26+
" stale (up to the tool's configured TTL).\n"
27+
" 6. After answering, append a `Source:` line with the final `url` from the tool response as a\n"
28+
" markdown link (redirects may have rewritten the URL you were given). Never fabricate URLs.")

0 commit comments

Comments
 (0)