Skip to content

Commit c9cf371

Browse files
jdchawla29Parth220
andauthored
Add custom agent example using HUD Gateway for inference (#223)
* feat: add custom agent example using HUD Gateway for inference * Fix system message format in custom agent example * docs: hud gateway custom agent docs --------- Co-authored-by: Parth A. Patel <parthpatel0220@gmail.com>
1 parent 4a8687b commit c9cf371

File tree

2 files changed

+247
-0
lines changed

2 files changed

+247
-0
lines changed

docs/gateway/index.mdx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,14 @@ response = await client.chat.completions.create(
108108
## Rate Limits
109109
HUD Gateway automatically handles key rotation and rate limiting across our pool of enterprise keys. If you hit rate limits with your own keys (BYOK), you will receive a 429 error directly from the provider.
110110

111+
## Building Custom Agents with Tracing
112+
113+
For a complete example of building a custom agent that uses HUD Gateway with full tracing support, see the [custom agent example](https://github.com/hud-evals/hud-python/blob/main/examples/05_custom_agent.py).
114+
115+
This example demonstrates:
116+
- Using the `@instrument` decorator to capture inference traces
117+
- Building a custom `MCPAgent` with HUD Gateway
118+
- Automatic token usage and latency tracking
119+
120+
View your traces on the [HUD Dashboard](https://hud.ai/home).
121+

examples/05_custom_agent.py

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
"""
2+
Example: Custom agent using HUD Gateway for inference.
3+
4+
This demonstrates building a custom MCPAgent that:
5+
1. Uses the HUD Gateway (https://inference.hud.ai) for inference
6+
2. Has instrumented get_response() for tracing
7+
3. Works with any model available via the gateway
8+
9+
Usage:
10+
HUD_API_KEY=sk-hud-... python examples/custom_gateway_agent.py
11+
"""
12+
13+
import asyncio
14+
import json
15+
import os
16+
from typing import Any
17+
18+
import mcp.types as types
19+
from openai import AsyncOpenAI
20+
21+
from hud import instrument
22+
from hud.agents.base import MCPAgent
23+
from hud.datasets import Task
24+
from hud.settings import settings
25+
from hud.types import AgentResponse, MCPToolCall, MCPToolResult
26+
27+
28+
class MyAgent(MCPAgent):
29+
"""
30+
Custom agent that uses HUD Gateway for inference.
31+
32+
The HUD Gateway (https://inference.hud.ai) provides:
33+
- Unified access to Anthropic, OpenAI, Gemini, OpenRouter models
34+
- Automatic billing via HUD credits
35+
- No need for individual provider API keys
36+
37+
All inference calls are traced via @instrument decorator.
38+
"""
39+
40+
def __init__(
41+
self,
42+
checkpoint_name: str = "anthropic/claude-sonnet-4-5-20250929",
43+
max_tokens: int = 4096,
44+
temperature: float = 0.7,
45+
**kwargs: Any,
46+
) -> None:
47+
super().__init__(**kwargs)
48+
49+
self.checkpoint_name = checkpoint_name
50+
self.max_tokens = max_tokens
51+
self.temperature = temperature
52+
53+
# Validate API key
54+
if not settings.api_key:
55+
raise ValueError("HUD_API_KEY is required for HUD Gateway access")
56+
57+
# Create OpenAI-compatible client pointing to HUD Gateway
58+
self.client = AsyncOpenAI(
59+
base_url=settings.hud_gateway_url, # https://inference.hud.ai
60+
api_key=settings.api_key,
61+
)
62+
63+
async def get_system_messages(self) -> list[dict[str, Any]]:
64+
"""Return system prompt formatted for OpenAI chat API."""
65+
system_text = self.system_prompt or "You are a helpful assistant."
66+
return [{"role": "system", "content": system_text}]
67+
68+
def get_tool_schemas(self) -> list[dict[str, Any]]:
69+
"""Convert MCP tools to OpenAI function format."""
70+
tools = self.get_available_tools()
71+
return [
72+
{
73+
"type": "function",
74+
"function": {
75+
"name": tool.name,
76+
"description": tool.description or "",
77+
"parameters": tool.inputSchema,
78+
},
79+
}
80+
for tool in tools
81+
]
82+
83+
@instrument(
84+
span_type="agent",
85+
record_args=False,
86+
record_result=True,
87+
)
88+
async def get_response(self, messages: list[Any]) -> AgentResponse:
89+
"""
90+
Get response from model via HUD Gateway.
91+
92+
This method is instrumented with @hud.instrument to automatically:
93+
- Create a span for this inference call
94+
- Record the response for tracing
95+
- Track token usage and latency
96+
"""
97+
tools = self.get_tool_schemas()
98+
99+
try:
100+
response = await self.client.chat.completions.create(
101+
model=self.checkpoint_name,
102+
messages=messages,
103+
tools=tools if tools else None, # type: ignore
104+
max_tokens=self.max_tokens,
105+
temperature=self.temperature,
106+
)
107+
except Exception as e:
108+
self.console.error_log(f"Gateway inference error: {e}")
109+
return AgentResponse(
110+
content=f"Error: {e}",
111+
tool_calls=[],
112+
done=True,
113+
isError=True,
114+
raw=None,
115+
)
116+
117+
choice = response.choices[0]
118+
msg = choice.message
119+
120+
# Log usage info
121+
if response.usage:
122+
self.console.info_log(
123+
f"Tokens: {response.usage.prompt_tokens} prompt, "
124+
f"{response.usage.completion_tokens} completion"
125+
)
126+
127+
# Build assistant message for history
128+
assistant_msg: dict[str, Any] = {"role": "assistant"}
129+
if msg.content:
130+
assistant_msg["content"] = msg.content
131+
if msg.tool_calls:
132+
assistant_msg["tool_calls"] = [
133+
{
134+
"id": tc.id,
135+
"type": "function",
136+
"function": {"name": tc.function.name, "arguments": tc.function.arguments}, # type: ignore[union-attr]
137+
}
138+
for tc in msg.tool_calls
139+
]
140+
messages.append(assistant_msg)
141+
142+
# Parse tool calls
143+
tool_calls = []
144+
if msg.tool_calls:
145+
for tc in msg.tool_calls:
146+
try:
147+
args = json.loads(tc.function.arguments) # type: ignore[union-attr]
148+
except json.JSONDecodeError:
149+
args = {}
150+
tool_calls.append(
151+
MCPToolCall(id=tc.id, name=tc.function.name, arguments=args) # type: ignore[union-attr]
152+
)
153+
154+
return AgentResponse(
155+
content=msg.content or "",
156+
tool_calls=tool_calls,
157+
done=choice.finish_reason == "stop" and not tool_calls,
158+
isError=False,
159+
raw=response,
160+
)
161+
162+
async def format_blocks(self, blocks: list[types.ContentBlock]) -> list[Any]:
163+
"""Format content blocks into OpenAI message format."""
164+
content_parts = []
165+
for block in blocks:
166+
if isinstance(block, types.TextContent):
167+
content_parts.append({"type": "text", "text": block.text})
168+
elif isinstance(block, types.ImageContent):
169+
content_parts.append(
170+
{
171+
"type": "image_url",
172+
"image_url": {"url": f"data:{block.mimeType};base64,{block.data}"},
173+
}
174+
)
175+
return [{"role": "user", "content": content_parts}]
176+
177+
async def format_tool_results(
178+
self, tool_calls: list[MCPToolCall], tool_results: list[MCPToolResult]
179+
) -> list[Any]:
180+
"""Format tool results for the model."""
181+
messages = []
182+
for tc, result in zip(tool_calls, tool_results):
183+
content = ""
184+
if result.content:
185+
for block in result.content:
186+
if isinstance(block, types.TextContent):
187+
content += block.text
188+
messages.append(
189+
{
190+
"role": "tool",
191+
"tool_call_id": tc.id,
192+
"content": content or "Tool executed successfully",
193+
}
194+
)
195+
return messages
196+
197+
198+
async def main():
199+
"""Example usage of MyAgent."""
200+
201+
# Create agent with Claude via Gateway
202+
agent = MyAgent(
203+
checkpoint_name="anthropic/claude-sonnet-4-5-20250929",
204+
max_tokens=2048,
205+
temperature=0.5,
206+
verbose=True,
207+
)
208+
209+
# Define a task with HUD MCP environment
210+
task = Task(
211+
prompt="Go to example.com and tell me the page title",
212+
mcp_config={
213+
"hud": {
214+
"url": "https://mcp.hud.ai/v3/mcp",
215+
"headers": {
216+
"Authorization": f"Bearer {os.environ.get('HUD_API_KEY', '')}",
217+
"Mcp-Image": "hudpython/hud-remote-browser:latest",
218+
},
219+
}
220+
},
221+
)
222+
223+
# Run the agent - traces are automatically captured
224+
print("Running agent with HUD Gateway inference...")
225+
result = await agent.run(task, max_steps=5)
226+
227+
print("\n=== Results ===")
228+
print(f"Done: {result.done}")
229+
print(f"Reward: {result.reward}")
230+
print(f"Steps: {len(result)}")
231+
232+
# View traces at https://hud.ai/home
233+
234+
235+
if __name__ == "__main__":
236+
asyncio.run(main())

0 commit comments

Comments
 (0)