Skip to content

Commit 9b567d6

Browse files
KludexlaisbscDouweM
authored
docs(gateway): add script to generate Pydantic AI Gateway tab (#3401)
Co-authored-by: Lais Carvalho <[email protected]> Co-authored-by: Douwe Maan <[email protected]> Co-authored-by: Douwe Maan <[email protected]>
1 parent 40e5ee3 commit 9b567d6

File tree

1 file changed

+110
-1
lines changed

1 file changed

+110
-1
lines changed

docs/.hooks/main.py

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
def on_page_markdown(markdown: str, page: Page, config: Config, files: Files) -> str:
1818
"""Called on each file after it is read and before it is converted to HTML."""
19-
markdown = inject_snippets(markdown, (DOCS_ROOT / page.file.src_uri).parent)
19+
relative_path_root = (DOCS_ROOT / page.file.src_uri).parent
20+
markdown = inject_snippets(markdown, relative_path_root)
2021
markdown = replace_uv_python_run(markdown)
2122
markdown = render_examples(markdown)
2223
markdown = render_video(markdown)
24+
markdown = create_gateway_toggle(markdown, relative_path_root)
2325
return markdown
2426

2527

@@ -39,6 +41,7 @@ def on_env(env: Environment, config: Config, files: Files) -> Environment:
3941

4042
def on_post_build(config: Config) -> None:
4143
"""Inject extra CSS into mermaid styles to avoid titles being the same color as the background in dark mode."""
44+
assert bundle_path is not None
4245
if bundle_path.exists():
4346
content = bundle_path.read_text()
4447
content, _ = re.subn(r'}(\.statediagram)', '}.statediagramTitleText{fill:#888}\1', content, count=1)
@@ -115,3 +118,109 @@ def sub_cf_video(m: re.Match[str]) -> str:
115118
></iframe>
116119
</div>
117120
"""
121+
122+
123+
def create_gateway_toggle(markdown: str, relative_path_root: Path) -> str:
124+
"""Transform Python code blocks with Agent() calls to show both Pydantic AI and Gateway versions."""
125+
# Pattern matches Python code blocks with or without attributes, and optional annotation definitions after
126+
# Annotation definitions are numbered list items like "1. Some text" that follow the code block
127+
return re.sub(
128+
r'```py(?:thon)?(?: *\{?([^}\n]*)\}?)?\n(.*?)\n```(\n\n(?:\d+\..+?\n)+?\n)?',
129+
lambda m: transform_gateway_code_block(m, relative_path_root),
130+
markdown,
131+
flags=re.MULTILINE | re.DOTALL,
132+
)
133+
134+
135+
# Models that should get gateway transformation
136+
GATEWAY_MODELS = ('anthropic', 'openai', 'openai-responses', 'openai-chat', 'bedrock', 'google-vertex', 'groq')
137+
138+
139+
def transform_gateway_code_block(m: re.Match[str], relative_path_root: Path) -> str:
140+
"""Transform a single code block to show both versions if it contains Agent() calls."""
141+
attrs = m.group(1) or ''
142+
code = m.group(2)
143+
annotations = m.group(3) or '' # Capture annotation definitions if present
144+
145+
# Simple check: does the code contain both "Agent(" and a quoted string?
146+
if 'Agent(' not in code:
147+
attrs_str = f' {{{attrs}}}' if attrs else ''
148+
return f'```python{attrs_str}\n{code}\n```{annotations}'
149+
150+
# Check if code contains Agent() with a model that should be transformed
151+
# Look for Agent(...'model:...' or Agent(..."model:..."
152+
agent_pattern = r'Agent\((?:(?!["\']).)*([\"\'])([^"\']+)\1'
153+
agent_match = re.search(agent_pattern, code, flags=re.DOTALL)
154+
155+
if not agent_match:
156+
# No Agent() with string literal found
157+
attrs_str = f' {{{attrs}}}' if attrs else ''
158+
return f'```python{attrs_str}\n{code}\n```{annotations}'
159+
160+
model_string = agent_match.group(2)
161+
# Check if model starts with one of the gateway-supported models
162+
should_transform = any(model_string.startswith(f'{model}:') for model in GATEWAY_MODELS)
163+
164+
if not should_transform:
165+
# Model doesn't match gateway models, return original
166+
attrs_str = f' {{{attrs}}}' if attrs else ''
167+
return f'```python{attrs_str}\n{code}\n```{annotations}'
168+
169+
# Transform the code for gateway version
170+
def replace_agent_model(match: re.Match[str]) -> str:
171+
"""Replace model string with gateway/ prefix."""
172+
full_match = match.group(0)
173+
quote = match.group(1)
174+
model = match.group(2)
175+
176+
# Replace the model string while preserving the rest
177+
return full_match.replace(f'{quote}{model}{quote}', f'{quote}gateway/{model}{quote}', 1)
178+
179+
# This pattern finds: "Agent(" followed by anything (lazy), then the first quoted string
180+
gateway_code = re.sub(
181+
agent_pattern,
182+
replace_agent_model,
183+
code,
184+
flags=re.DOTALL,
185+
)
186+
187+
# Build attributes string
188+
docs_path = DOCS_ROOT / 'gateway'
189+
relative_path = docs_path.relative_to(relative_path_root, walk_up=True)
190+
link = f"<a href='{relative_path}' style='float: right;'>Learn about Gateway</a>"
191+
192+
attrs_str = f' {{{attrs}}}' if attrs else ''
193+
194+
if 'title="' in attrs:
195+
gateway_attrs = attrs.replace('title="', f'title="{link} ', 1)
196+
else:
197+
gateway_attrs = attrs + f' title="{link}"'
198+
gateway_attrs_str = f' {{{gateway_attrs}}}'
199+
200+
# Indent code lines for proper markdown formatting within tabs
201+
# Always add 4 spaces to every line (even empty ones) to preserve annotations
202+
code_lines = code.split('\n')
203+
indented_code = '\n'.join(' ' + line for line in code_lines)
204+
205+
gateway_code_lines = gateway_code.split('\n')
206+
indented_gateway_code = '\n'.join(' ' + line for line in gateway_code_lines)
207+
208+
# Indent annotation definitions if present (need to be inside tabs for Material to work)
209+
indented_annotations = ''
210+
if annotations:
211+
# Remove surrounding newlines and indent each line with 4 spaces
212+
annotation_lines = annotations.strip().split('\n')
213+
indented_annotations = '\n\n' + '\n'.join(' ' + line for line in annotation_lines) + '\n\n'
214+
215+
return f"""\
216+
=== "With Pydantic AI Gateway"
217+
218+
```python{gateway_attrs_str}
219+
{indented_gateway_code}
220+
```{indented_annotations}
221+
222+
=== "Directly to Provider API"
223+
224+
```python{attrs_str}
225+
{indented_code}
226+
```{indented_annotations}"""

0 commit comments

Comments
 (0)