import litellm
from rich.console import Console
from rich.panel import Panel
from rich.markdown import Markdown
from rich.table import Table
from rich.box import SIMPLE
from wiki_run_engine import WikiRunEnvironment
from langsmith import traceable
import os
from openai import OpenAI
from langsmith.wrappers import wrap_openai
openai_client = wrap_openai(OpenAI())
class WikiRunAgent:
def __init__(self, wiki_data_path, model="gemini/gemini-2.5-pro-exp-03-25"):
self.env = WikiRunEnvironment(wiki_data_path)
self.model = model
self.console = Console()
@traceable(name="WikiRun Game")
def run_game(self, start_article=None, target_article=None):
"""Play the WikiRun game with LLM agent"""
state = self.env.reset(start_article, target_article)
self.console.print(Panel(f"[bold cyan]Starting WikiRun![/bold cyan]"))
self.console.print(f"[bold green]Starting at:[/bold green] {state['current_article']}")
self.console.print(f"[bold red]Target:[/bold red] {state['target_article']}\n")
while not state['is_complete']:
# Display current game status
self._display_game_status(state)
# Get LLM's choice
choice = self._get_llm_choice(state)
self.console.print(f"\n[bold yellow]Agent chooses:[/bold yellow] {choice}")
# Process the choice
available_links = self._get_available_links(state['available_links'])
if not available_links:
self.console.print("[bold red]No available links to choose from![/bold red]")
break
try:
# If choice is a number
idx = int(choice) - 1
if 0 <= idx < len(available_links):
next_article = available_links[idx]
self.console.print(f"[bold cyan]Moving to:[/bold cyan] {next_article}\n")
state, message = self.env.step(next_article)
if message:
self.console.print(f"[bold]{message}[/bold]")
else:
self.console.print("[bold red]Invalid choice. Trying again.[/bold red]\n")
except ValueError:
self.console.print("[bold red]Invalid choice format. Trying again.[/bold red]\n")
self.console.print(Panel(f"[bold green]Game completed in {state['steps_taken']} steps[/bold green]"))
self.console.print(f"[bold]Path:[/bold] {' → '.join(state['path_taken'])}")
return state
def _display_game_status(self, state):
"""Display current game status with rich formatting"""
# Display current article
self.console.print(Panel(f"[bold cyan]{state['current_article']}[/bold cyan]",
expand=False,
border_style="cyan"))
# Display article links
self.console.print("[bold green]Available Links:[/bold green]")
self._display_links(state['available_links'])
# Display path so far
self.console.print(f"\n[bold yellow]Steps taken:[/bold yellow] {state['steps_taken']}")
if state['path_taken']:
self.console.print(f"[bold yellow]Path so far:[/bold yellow] {' → '.join(state['path_taken'])}")
def _display_links(self, links):
"""Display links in a nicely formatted table"""
table = Table(show_header=False, box=SIMPLE)
table.add_column("Number", style="dim")
table.add_column("Link", style="green")
table.add_column("Available", style="bold")
for i, link in enumerate(links):
# Check if link is available
is_available = link in self.env.wiki_data
status = "[green]✓[/green]" if is_available else "[red]✗[/red]"
color = "green" if is_available else "red"
table.add_row(
f"{i+1}",
f"[{color}]{link}[/{color}]",
status
)
self.console.print(table)
def _get_available_links(self, links):
"""Filter links to only those available in the wiki data"""
return [link for link in links if link in self.env.wiki_data]
@traceable(name="LLM Decision")
def _get_llm_choice(self, state):
"""Ask LLM for next move"""
current = state['current_article']
target = state['target_article']
all_links = state['available_links']
available_links = self._get_available_links(all_links)
path_so_far = state['path_taken']
# Create prompt with relevant context (not the full article)
prompt = f"""You are playing WikiRun, trying to navigate from one Wikipedia article to another using only links.
Current article: {current}
Target article: {target}
Available links (numbered):
{self._format_links(available_links)}
Your path so far: {' -> '.join(path_so_far)}
Think about which link is most likely to lead you toward the target article.
First, think step by step about your strategy.
Then output your choice as a number in this format: N where N is the link number.
"""
# Call LLM via litellm with langsmith tracing
response = litellm.completion(
model=self.model,
messages=[{"role": "user", "content": prompt}],
# metadata={
# "current_article": current,
# "target_article": target,
# "available_links": available_links,
# "steps_taken": state['steps_taken'],
# "path_so_far": path_so_far
# }
)
# Extract the choice from response
content = response.choices[0].message.content
self.console.print(Panel(Markdown(content), title="[bold]Agent Thinking[/bold]", border_style="yellow"))
# Extract choice using format N
import re
choice_match = re.search(r'(\d+)', content)
if choice_match:
return choice_match.group(1)
else:
# Fallback: try to find any number in the response
numbers = re.findall(r'\d+', content)
if numbers:
for num in numbers:
if 1 <= int(num) <= len(available_links):
return num
# Default to first link if no valid choice found
return "1" if available_links else "0"
def _format_links(self, links):
"""Format the list of links for the prompt"""
return "\n".join([f"{i+1}. {link}" for i, link in enumerate(links)])
def setup_langsmith():
"""Print instructions for setting up LangSmith tracing"""
console = Console()
console.print(Panel("[bold yellow]LangSmith Setup Instructions[/bold yellow]"))
console.print("To enable LangSmith tracing, set the following environment variables:")
console.print("[bold]export LANGSMITH_API_KEY='your-api-key'[/bold]")
console.print("Get your API key from: https://smith.langchain.com/settings")
console.print("Once set, your WikiRun agent will log traces to your LangSmith dashboard")
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
console = Console()
console.print("[bold red]Please provide the path to Wikipedia data[/bold red]")
console.print("Usage: python agent.py ")
sys.exit(1)
# Remind about LangSmith setup
if not os.environ.get("LANGSMITH_API_KEY"):
setup_langsmith()
wiki_data_path = sys.argv[1]
agent = WikiRunAgent(wiki_data_path)
agent.run_game(start_article="Peanut", target_article="Silicon Valley")
# agent.run_game(start_article="Silicon Valley", target_article="Peanut")