diff --git a/.gitignore b/.gitignore
index 27e0605..98b0d18 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,8 @@ venv/
.idea/
.vscode/
.DS_Store
+
+# Brand asset working files (downloaded by scripts/render_screenshot.py at runtime)
+docs/assets/FTSystemMono-*.ttf
+docs/assets/parallel-symbol-*.png
+docs/assets/parallel-logo-*.png
diff --git a/README.md b/README.md
index 2e529ea..e1f1dff 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,20 @@
+
+
+
+
# parallel-google-adk
+[](https://pypi.org/project/parallel-google-adk/)
+[](https://pypi.org/project/parallel-google-adk/)
+[](LICENSE)
+
Google [Agent Development Kit](https://adk.dev) (ADK) tools and plugin for [Parallel](https://parallel.ai) — grounded web search, clean extraction, and cited deep research with structured output.
## Install
```bash
-pip install parallel-google-adk
-export PARALLEL_API_KEY=your-key-here # get one at https://platform.parallel.ai
+uv add parallel-google-adk # or: pip install parallel-google-adk
+export PARALLEL_API_KEY=your-key-here # get one at https://platform.parallel.ai
```
## Quickstart
@@ -72,7 +80,9 @@ This package is the typed-FunctionTool path for users who want fine-grained sche
## Examples
-See [`examples/research_agent.py`](examples/research_agent.py) for a runnable demo.
+See [`examples/research_agent.py`](examples/research_agent.py) for a runnable demo. A run looks like this:
+
+
## Development
diff --git a/docs/assets/parallel.png b/docs/assets/parallel.png
new file mode 100644
index 0000000..4bb12af
Binary files /dev/null and b/docs/assets/parallel.png differ
diff --git a/docs/assets/research_agent.png b/docs/assets/research_agent.png
new file mode 100644
index 0000000..4ad40f1
Binary files /dev/null and b/docs/assets/research_agent.png differ
diff --git a/docs/screenshot.txt b/docs/screenshot.txt
index 96b6f5e..840da23 100644
--- a/docs/screenshot.txt
+++ b/docs/screenshot.txt
@@ -2,15 +2,7 @@ $ python research_agent.py
>>> What does Parallel's Search API do? Cite parallel.ai in one sentence.
-Parallel’s Search API is a tool designed specifically for AI agents and LLMs to perform natural language web searches and retrieve structured, token-efficient data. Unlike traditional search engines that rely on keywords, it processes a "natural language objective" to return ranked URLs with extended excerpts optimized for model consumption, reducing hallucinations and improving accuracy in complex research tasks ([parallel.ai](https://docs.parallel.ai/search/search-quickstart)).
-
-According to the developer documentation: "The Search API takes a natural language objective and returns relevant excerpts optimized for LLMs, replacing multiple keyword searches with a single call for broad or complex queries" ([parallel.ai](https://docs.parallel.ai/search/search-quickstart)).
-
-### Key Features
-* **Search Modes:** Offers specialized modes including **one-shot** (comprehensive), **agentic** (concise for multi-step reasoning), and **fast** (latency-sensitive) ([parallel.ai](https://docs.parallel.ai/search/modes)).
-* **LLM Optimization:** Excerpts are curated to fit efficiently into a model's context window, lowering token costs while maintaining high relevance ([parallel.ai](https://parallel.ai/)).
-* **Accuracy Benchmarks:** Parallel claims its enterprise deep research API achieves up to 48% accuracy on complex benchmarks, significantly outperforming the native browsing capabilities of standard LLMs ([parallel.ai](https://parallel.ai/)).
-* **Evidence-Based:** Every output provides verifiability and provenance, ensuring AI agents can cite their sources accurately ([parallel.ai](https://parallel.ai/)).
+Parallel's Search API is a web search engine designed specifically for AI agents that streamlines the search, scrape, and extraction pipeline into a single API call using declarative semantic search to provide token-efficient results ([parallel.ai](https://parallel.ai/products/search)).
--- Parallel call trace (1 calls) ---
- {"tool": "_web_search", "latency_s": 1.4036233751103282, "citation_count": 5}
+ {"tool": "web_search", "latency_s": 2.6487630419433117, "citation_count": 5}
diff --git a/scripts/render_screenshot.py b/scripts/render_screenshot.py
new file mode 100644
index 0000000..71528d1
--- /dev/null
+++ b/scripts/render_screenshot.py
@@ -0,0 +1,173 @@
+"""Render docs/screenshot.txt to a brand-styled PNG for the adk-docs PR.
+
+Reads docs/screenshot.txt, lays it out in FT System Mono on Off-white using
+Parallel's brand palette (per assets.parallel.ai/llms.txt style guide), and
+writes docs/assets/research_agent.png.
+
+Fonts and intermediate brand assets are downloaded on demand from
+assets.parallel.ai (gitignored). The committed outputs are docs/assets/parallel.png
+(the symbol logo) and docs/assets/research_agent.png (the rendered screenshot).
+
+Run:
+ .venv/bin/python scripts/render_screenshot.py
+"""
+
+from __future__ import annotations
+
+import sys
+import urllib.request
+from pathlib import Path
+
+from PIL import Image, ImageDraw, ImageFont
+
+ROOT = Path(__file__).resolve().parent.parent
+SRC = ROOT / "docs" / "screenshot.txt"
+DST = ROOT / "docs" / "assets" / "research_agent.png"
+
+# Parallel brand palette (assets.parallel.ai/llms.txt).
+OFF_WHITE = "#fcfcfa"
+INDEX_BLACK = "#1d1b16"
+NEURAL = "#d8d0bf"
+SIGNAL = "#fb631b"
+
+FONT_REGULAR = ROOT / "docs" / "assets" / "FTSystemMono-Regular.ttf"
+FONT_BOLD = ROOT / "docs" / "assets" / "FTSystemMono-Bold.ttf"
+
+FONT_URLS = {
+ FONT_REGULAR: "https://assets.parallel.ai/FTSystemMono-Regular.ttf",
+ FONT_BOLD: "https://assets.parallel.ai/FTSystemMono-Bold.ttf",
+}
+
+
+def ensure_fonts() -> None:
+ """Download the brand fonts if they aren't already cached locally.
+
+ assets.parallel.ai's CDN rejects the default urllib User-Agent (403);
+ pass a browser-style UA so the request goes through.
+ """
+ for path, url in FONT_URLS.items():
+ if path.exists():
+ continue
+ path.parent.mkdir(parents=True, exist_ok=True)
+ print(f"fetching {url}", file=sys.stderr)
+ req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
+ with urllib.request.urlopen(req) as resp, path.open("wb") as fp:
+ fp.write(resp.read())
+
+# Layout.
+WIDTH = 1400
+PADDING_X = 70
+PADDING_Y = 60
+FONT_SIZE = 22
+LINE_HEIGHT = int(FONT_SIZE * 1.55)
+WRAP_WIDTH = 78 # chars
+
+# Window-chrome dots (subtle, no labels).
+DOT_RADIUS = 7
+DOT_GAP = 22
+
+
+def wrap(text: str, width: int) -> list[str]:
+ """Word-wrap a single line of text to a max char width, preserving runs."""
+ if len(text) <= width:
+ return [text]
+ out: list[str] = []
+ line = ""
+ for word in text.split(" "):
+ if not line:
+ line = word
+ elif len(line) + 1 + len(word) <= width:
+ line = f"{line} {word}"
+ else:
+ out.append(line)
+ line = word
+ if line:
+ out.append(line)
+ return out
+
+
+def color_for_line(line: str) -> str:
+ stripped = line.strip()
+ if stripped.startswith("$ "):
+ return SIGNAL
+ if stripped.startswith(">>>"):
+ return SIGNAL
+ if stripped.startswith("---"):
+ return NEURAL
+ if stripped.startswith("{"):
+ return NEURAL
+ return INDEX_BLACK
+
+
+def font_for_line(line: str, regular: ImageFont.FreeTypeFont, bold: ImageFont.FreeTypeFont) -> ImageFont.FreeTypeFont:
+ s = line.strip()
+ if s.startswith("$ ") or s.startswith(">>>"):
+ return bold
+ return regular
+
+
+def main() -> int:
+ if not SRC.exists():
+ print(f"missing: {SRC}", file=sys.stderr)
+ return 1
+ ensure_fonts()
+
+ raw = SRC.read_text().splitlines()
+
+ # Wrap long lines once for layout.
+ lines: list[str] = []
+ for line in raw:
+ if not line:
+ lines.append("")
+ else:
+ lines.extend(wrap(line, WRAP_WIDTH))
+
+ regular = ImageFont.truetype(str(FONT_REGULAR), FONT_SIZE)
+ bold = ImageFont.truetype(str(FONT_BOLD), FONT_SIZE)
+
+ # Compute height: window-chrome row + padding + text + padding.
+ chrome_height = PADDING_Y - 10
+ text_height = max(LINE_HEIGHT * len(lines), 100)
+ height = chrome_height + PADDING_Y + text_height + PADDING_Y
+
+ img = Image.new("RGB", (WIDTH, height), OFF_WHITE)
+ draw = ImageDraw.Draw(img)
+
+ # Window chrome dots.
+ cy = chrome_height // 2 + 18
+ cx = PADDING_X + DOT_RADIUS
+ for i in range(3):
+ x = cx + i * DOT_GAP
+ draw.ellipse(
+ [(x - DOT_RADIUS, cy - DOT_RADIUS), (x + DOT_RADIUS, cy + DOT_RADIUS)],
+ outline=NEURAL,
+ width=2,
+ )
+
+ # Hairline separator between chrome and text.
+ sep_y = chrome_height + PADDING_Y // 2
+ draw.line(
+ [(PADDING_X, sep_y), (WIDTH - PADDING_X, sep_y)],
+ fill=NEURAL,
+ width=1,
+ )
+
+ # Text.
+ y = sep_y + PADDING_Y // 2
+ for line in lines:
+ if line == "":
+ y += LINE_HEIGHT
+ continue
+ font = font_for_line(line, regular, bold)
+ color = color_for_line(line)
+ draw.text((PADDING_X, y), line, font=font, fill=color)
+ y += LINE_HEIGHT
+
+ DST.parent.mkdir(parents=True, exist_ok=True)
+ img.save(DST, optimize=True)
+ print(f"wrote {DST} ({img.size[0]}x{img.size[1]})")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())