Skip to content

Commit 4c2fd93

Browse files
committed
Add OpenAI 2048 example and fix agent loop
1 parent 3e3e5c2 commit 4c2fd93

File tree

3 files changed

+156
-11
lines changed

3 files changed

+156
-11
lines changed

examples/openai_2048.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#!/usr/bin/env python3
2+
"""
3+
OpenAI Chat Agent playing Text 2048
4+
5+
This example demonstrates using the OpenAIChatAgent with the text-2048 environment.
6+
It shows how to:
7+
- Initialize an OpenAI client with the openai_chat agent
8+
- Configure the text-2048 environment
9+
- Run the agent to play the game
10+
11+
Requirements:
12+
- pip install openai
13+
- export OPENAI_API_KEY="your-api-key" # Or set OPENAI_BASE_URL for custom endpoints
14+
15+
Environment Variables:
16+
- OPENAI_BASE_URL: Custom OpenAI-compatible API endpoint (optional)
17+
- OPENAI_API_KEY: API key for authentication
18+
"""
19+
20+
import asyncio
21+
import os
22+
from openai import AsyncOpenAI
23+
import hud
24+
from hud.agents.openai_chat_generic import GenericOpenAIChatAgent
25+
from hud.clients import MCPClient
26+
from hud.datasets import Task
27+
28+
from hud.agents.misc import ResponseAgent
29+
30+
31+
async def main():
32+
# Initialize OpenAI client with environment variables
33+
base_url = os.getenv("OPENAI_BASE_URL") # Optional custom endpoint
34+
api_key = os.getenv("OPENAI_API_KEY", "EMPTY") # Default to "EMPTY" for local servers
35+
36+
openai_client = AsyncOpenAI(
37+
base_url=base_url, # None will use default OpenAI endpoint
38+
api_key=api_key,
39+
)
40+
41+
# Configure the text-2048 environment
42+
mcp_config = {
43+
"local": {
44+
"command": "docker",
45+
"args": ["run", "--rm", "-i", "hudevals/hud-text-2048:latest"]
46+
}
47+
}
48+
49+
# Define the task with game setup and evaluation
50+
task = Task(
51+
prompt="""Play the 2048 game strategically.
52+
53+
Tips for high scores:
54+
- Keep your highest tile in a corner (preferably bottom-right)
55+
- Build tiles in descending order from that corner
56+
- Avoid moving up unless absolutely necessary
57+
- Try to keep tiles of similar values adjacent
58+
59+
Use the 'move' tool with directions: up, down, left, or right.
60+
Aim for the highest possible score!""",
61+
mcp_config=mcp_config,
62+
setup_tool={"name": "setup","arguments": {"name": "board", "arguments": {"board_size": 4}},}, # type: ignore
63+
evaluate_tool={"name": "evaluate", "arguments": {"name": "max_number", "arguments": {}}}, # type: ignore
64+
)
65+
66+
# Initialize MCP client
67+
client = MCPClient(mcp_config=task.mcp_config)
68+
69+
# Create OpenAI agent with the text-2048 game tools
70+
agent = GenericOpenAIChatAgent(
71+
mcp_client=client,
72+
openai_client=openai_client,
73+
model_name="Qwen/Qwen2.5-3B-Instruct",
74+
allowed_tools=["move"],
75+
parallel_tool_calls=False,
76+
response_agent=ResponseAgent(),
77+
system_prompt="""You are an expert 2048 game player.
78+
Make strategic moves to achieve the highest score possible.
79+
Always analyze the board state before making a move.""",
80+
)
81+
82+
agent.metadata = {}
83+
84+
# Run the game with tracing
85+
with hud.trace("OpenAI 2048 Game"):
86+
try:
87+
print("🎮 Starting 2048 game with OpenAI agent...")
88+
print(f"🤖 Model: {agent.model_name}")
89+
print("="*50)
90+
91+
# Run the task with unlimited steps (game ends when no moves available)
92+
result = await agent.run(task, max_steps=-1)
93+
94+
# Display results
95+
print("="*50)
96+
print(f"✅ Game completed!")
97+
print(f"🏆 Final Score/Max Tile: {result.reward}")
98+
if result.info:
99+
print(f"📊 Game Stats: {result.info}")
100+
101+
# Display conversation history
102+
print("🗣️ Conversation History:")
103+
for i, msg in enumerate(agent.conversation_history):
104+
print(f" {i+1} : {msg}")
105+
print("-"*30)
106+
107+
except Exception as e:
108+
print(f"❌ Error during game: {e}")
109+
finally:
110+
await client.shutdown()
111+
112+
113+
if __name__ == "__main__":
114+
# Check for API configuration
115+
if not os.getenv("OPENAI_API_KEY") and not os.getenv("OPENAI_BASE_URL"):
116+
print("⚠️ Please configure OpenAI API access:")
117+
print(" For OpenAI API: export OPENAI_API_KEY='your-api-key'")
118+
print(" For local/custom endpoints: export OPENAI_BASE_URL='your-custom-endpoint'")
119+
exit(1)
120+
121+
# Display configuration
122+
if os.getenv("OPENAI_BASE_URL"):
123+
print(f"🔗 Using endpoint: {os.getenv('OPENAI_BASE_URL')}")
124+
else:
125+
print("🔗 Using default OpenAI API endpoint")
126+
127+
asyncio.run(main())

hud/agents/misc/response_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ async def determine_response(self, agent_message: str) -> ResponseType:
5454
"""
5555
try:
5656
response = await self.client.chat.completions.create(
57-
model="gpt-4o",
57+
model="gpt-5-nano",
5858
messages=[
5959
{"role": "system", "content": self.system_prompt},
6060
{

hud/agents/openai_chat_generic.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import mcp.types as types
2323

24+
from hud import instrument
2425
from hud.types import AgentResponse, MCPToolCall, MCPToolResult
2526

2627
from .base import MCPAgent
@@ -52,6 +53,7 @@ def __init__(
5253
self.model_name = model_name
5354
self.parallel_tool_calls = parallel_tool_calls
5455
self.logprobs = logprobs
56+
self.conversation_history = []
5557

5658
@staticmethod
5759
def _oai_to_mcp(tool_call: Any) -> MCPToolCall: # type: ignore[valid-type]
@@ -64,9 +66,7 @@ def _oai_to_mcp(tool_call: Any) -> MCPToolCall: # type: ignore[valid-type]
6466

6567
async def get_system_messages(self) -> list[Any]:
6668
"""Get system messages for OpenAI."""
67-
return [
68-
{"role": "system", "content": self.system_prompt},
69-
]
69+
return [{"role": "system", "content": self.system_prompt}]
7070

7171
async def format_blocks(self, blocks: list[types.ContentBlock]) -> list[Any]:
7272
"""Format blocks for OpenAI."""
@@ -96,8 +96,14 @@ def get_tool_schemas(self) -> list[dict]:
9696
openai_tools.append(openai_tool)
9797
return openai_tools
9898

99+
@instrument(
100+
span_type="agent",
101+
record_args=False,
102+
record_result=True,
103+
)
99104
async def get_response(self, messages: list[Any]) -> AgentResponse:
100105
"""Send chat request to OpenAI and convert the response."""
106+
101107
# Convert MCP tool schemas to OpenAI format
102108
mcp_schemas = self.get_tool_schemas()
103109

@@ -111,6 +117,19 @@ async def get_response(self, messages: list[Any]) -> AgentResponse:
111117

112118
choice = response.choices[0]
113119
msg = choice.message
120+
121+
assistant_msg: dict[str, Any] = {"role": "assistant"}
122+
123+
if msg.content:
124+
assistant_msg["content"] = msg.content
125+
126+
if msg.tool_calls:
127+
assistant_msg["tool_calls"] = msg.tool_calls
128+
129+
messages.append(assistant_msg)
130+
131+
# Store the complete conversation history
132+
self.conversation_history = messages.copy()
114133

115134
tool_calls = []
116135
if msg.tool_calls:
@@ -144,11 +163,10 @@ async def format_tool_results(
144163
for c in res.content
145164
if hasattr(c, "text")
146165
)
147-
rendered.append(
148-
{
149-
"role": "tool",
150-
"tool_call_id": call.id,
151-
"content": content or "", # Ensure content is never None
152-
}
153-
)
166+
tool_msg = {
167+
"role": "tool",
168+
"tool_call_id": call.id,
169+
"content": content or "", # Ensure content is never None
170+
}
171+
rendered.append(tool_msg)
154172
return rendered

0 commit comments

Comments
 (0)