|
|
""" |
|
|
Student Agent for Text Adventure Games |
|
|
|
|
|
This is your submission file. Implement the StudentAgent class to play |
|
|
text adventure games using the MCP server you also implement. |
|
|
|
|
|
Your agent should: |
|
|
1. Connect to the MCP server via the provided client |
|
|
2. Use the ReAct pattern (Thought -> Action -> Observation) |
|
|
3. Call MCP tools to interact with the game |
|
|
4. Maximize the game score within the step limit |
|
|
|
|
|
Required method: |
|
|
async def run(self, client, game, max_steps, seed, verbose) -> RunResult |
|
|
|
|
|
The 'client' is a FastMCP Client already connected to your MCP server. |
|
|
Use it to call tools like: await client.call_tool("play_action", {"action": "look"}) |
|
|
|
|
|
Tips: |
|
|
- Start by looking around and understanding your environment |
|
|
- Keep track of visited locations to avoid loops |
|
|
- Pick up useful items (lamp, sword, etc.) |
|
|
- The seed parameter should be used to set your LLM's seed for reproducibility |
|
|
""" |
|
|
|
|
|
import json |
|
|
import os |
|
|
import re |
|
|
from dataclasses import dataclass, field |
|
|
from typing import Optional |
|
|
|
|
|
from dotenv import load_dotenv |
|
|
from huggingface_hub import InferenceClient |
|
|
|
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LLM_MODEL = "Qwen/Qwen2.5-72B-Instruct" |
|
|
|
|
|
|
|
|
_hf_token = os.getenv("HF_TOKEN") |
|
|
if not _hf_token: |
|
|
raise ValueError("HF_TOKEN not found. Set it in your .env file.") |
|
|
|
|
|
LLM_CLIENT = InferenceClient(token=_hf_token) |
|
|
|
|
|
|
|
|
def call_llm(prompt: str, system_prompt: str, seed: int, max_tokens: int = 300) -> str: |
|
|
""" |
|
|
Call the LLM with the given prompt. Use this function in your agent. |
|
|
|
|
|
Args: |
|
|
prompt: The user prompt (current game state, history, etc.) |
|
|
system_prompt: The system prompt (instructions for the agent) |
|
|
seed: Random seed for reproducibility |
|
|
max_tokens: Maximum tokens in response (default: 300) |
|
|
|
|
|
Returns: |
|
|
The LLM's response text |
|
|
|
|
|
Example: |
|
|
response = call_llm( |
|
|
prompt="You are in a forest. What do you do?", |
|
|
system_prompt=SYSTEM_PROMPT, |
|
|
seed=42, |
|
|
) |
|
|
""" |
|
|
messages = [ |
|
|
{"role": "system", "content": system_prompt}, |
|
|
{"role": "user", "content": prompt}, |
|
|
] |
|
|
|
|
|
response = LLM_CLIENT.chat.completions.create( |
|
|
model=LLM_MODEL, |
|
|
messages=messages, |
|
|
temperature=0.0, |
|
|
max_tokens=max_tokens, |
|
|
seed=seed, |
|
|
) |
|
|
|
|
|
return response.choices[0].message.content |
|
|
|
|
|
|
|
|
@dataclass |
|
|
class RunResult: |
|
|
"""Result of running the agent. Do not modify this class.""" |
|
|
final_score: int |
|
|
max_score: int |
|
|
moves: int |
|
|
locations_visited: set[str] |
|
|
game_completed: bool |
|
|
error: Optional[str] = None |
|
|
history: list[tuple[str, str, str]] = field(default_factory=list) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SYSTEM_PROMPT = """You are playing a classic text adventure game. |
|
|
|
|
|
GOAL: Explore the world, solve puzzles, and maximize your score. |
|
|
|
|
|
AVAILABLE TOOLS (use via MCP): |
|
|
- play_action: Execute a game command (north, take lamp, open mailbox, etc.) |
|
|
- memory: Get current game state and history (if implemented) |
|
|
- inventory: Check what you're carrying (if implemented) |
|
|
|
|
|
VALID GAME COMMANDS for play_action: |
|
|
- Movement: north, south, east, west, up, down, enter, exit |
|
|
- Objects: take <item>, drop <item>, open <thing>, close <thing>, examine <thing> |
|
|
- Other: look, inventory, read <thing>, turn on lamp |
|
|
|
|
|
RESPOND IN THIS EXACT FORMAT (no markdown): |
|
|
THOUGHT: <your reasoning about what to do next> |
|
|
TOOL: <tool_name> |
|
|
ARGS: <JSON arguments, e.g., {"action": "look"}> |
|
|
|
|
|
Example: |
|
|
THOUGHT: I should look around to see where I am. |
|
|
TOOL: play_action |
|
|
ARGS: {"action": "look"} |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class StudentAgent: |
|
|
""" |
|
|
Your ReAct agent implementation. |
|
|
|
|
|
TODO: |
|
|
1. Implement the run() method with the ReAct loop |
|
|
2. Parse LLM responses to extract tool calls |
|
|
3. Track state and avoid loops |
|
|
|
|
|
Use the provided call_llm() function to interact with the LLM. |
|
|
""" |
|
|
|
|
|
def __init__(self): |
|
|
"""Initialize your agent here.""" |
|
|
|
|
|
|
|
|
|
|
|
pass |
|
|
|
|
|
async def run( |
|
|
self, |
|
|
client, |
|
|
game: str, |
|
|
max_steps: int, |
|
|
seed: int, |
|
|
verbose: bool = False, |
|
|
) -> RunResult: |
|
|
""" |
|
|
Run the agent for a game session. |
|
|
|
|
|
Args: |
|
|
client: FastMCP Client connected to your MCP server |
|
|
game: Name of the game being played (e.g., "zork1") |
|
|
max_steps: Maximum number of steps to take |
|
|
seed: Random seed for reproducibility (use for LLM calls) |
|
|
verbose: Whether to print detailed output |
|
|
|
|
|
Returns: |
|
|
RunResult with final score and statistics |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
locations_visited = set() |
|
|
history = [] |
|
|
final_score = 0 |
|
|
moves = 0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return RunResult( |
|
|
final_score=final_score, |
|
|
max_score=350, |
|
|
moves=moves, |
|
|
locations_visited=locations_visited, |
|
|
game_completed=False, |
|
|
history=history, |
|
|
) |
|
|
|
|
|
def _build_prompt(self, observation: str, history: list) -> str: |
|
|
""" |
|
|
Build the prompt for the LLM. |
|
|
|
|
|
TODO: Implement this to create effective prompts |
|
|
""" |
|
|
|
|
|
pass |
|
|
|
|
|
def _parse_response(self, response: str) -> tuple[str, str, dict]: |
|
|
""" |
|
|
Parse LLM response to extract thought, tool name, and arguments. |
|
|
|
|
|
TODO: Implement robust parsing |
|
|
|
|
|
Returns: |
|
|
Tuple of (thought, tool_name, args_dict) |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pass |
|
|
|
|
|
def _call_llm(self, prompt: str, system_prompt: str, seed: int) -> str: |
|
|
""" |
|
|
Call the LLM with the given prompt. |
|
|
|
|
|
This is a convenience wrapper - you can also use call_llm() directly. |
|
|
""" |
|
|
return call_llm(prompt, system_prompt, seed) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_agent(): |
|
|
"""Test the agent locally.""" |
|
|
from fastmcp import Client |
|
|
|
|
|
|
|
|
server_path = "mcp_server.py" |
|
|
|
|
|
agent = StudentAgent() |
|
|
|
|
|
async with Client(server_path) as client: |
|
|
result = await agent.run( |
|
|
client=client, |
|
|
game="zork1", |
|
|
max_steps=10, |
|
|
seed=42, |
|
|
verbose=True, |
|
|
) |
|
|
|
|
|
print(f"\nFinal Score: {result.final_score}") |
|
|
print(f"Moves: {result.moves}") |
|
|
print(f"Locations: {result.locations_visited}") |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
import asyncio |
|
|
asyncio.run(test_agent()) |
|
|
|