MIRAI Event Forecasting Agent
Zero to Hero: Converting a Complex Academic Paper Implementation into Google ADK
Introduction
Welcome to the MIRAI Agent tutorial! Here, we break down how to migrate a highly complex academic international relations reasoning system into Google's Agent Development Kit (ADK).
The original MIRAI paper used heavy manual Python loops to
parse CSV files and process prompts sequentially. Our ADK implementation transforms this into a
beautiful parallel process. The master orchestrator fires off three highly specialized geopolitical
analysts (News, Events, and Policy) at the exact same time using the
ParallelAgent.
Once they finish compiling their specialized worldview chunks, a Chief Forecaster synthesizes all of it to lock down a final JSON timeline prediction.
Project Structure
We split our application up into elegant, manageable pieces. Here is what the folder structure looks like:
/mirai_agent/
βββ __init__.py # πͺ Required package declaration
βββ agent.py # π§ The Logic: Parallel Analysts + Sequential Chief Forecaster
βββ tools.py # π οΈ The Hooks: Fetch GDELT mock data
βββ prompts.py # π The Instructions: Specialized intelligence roles
βββ .env # π Keys pointing to Gemini 2.5 Flash
βββ run_agent.sh # π ADK Web UI launcher
Step 2: External Functions
Our agents constantly need connection to real-world data (simulating the massive GDELT database) to be useful. We define three functions serving as mock endpoints to pass data into the LLM:
# MIRAI Agent Tools
import random
def get_news_articles(actor1: str, actor2: str) -> dict:
"""Fetches the latest mock news articles involving both actor1 and actor2."""
news_pool = [
f"{actor1} and {actor2} sign a new trade agreement, fostering economic cooperation.",
f"Tensions rise as {actor1} condemns {actor2}'s recent military exercises.",
f"{actor1} and {actor2} agree to collaborate on climate change initiatives.",
f"Border disputes between {actor1} and {actor2} lead to minor skirmishes.",
f"{actor1} imposes tariffs on imports from {actor2}."
]
return {"status": "success", "articles": random.sample(news_pool, 2)}
def count_historical_events(actor1: str, actor2: str) -> dict:
"""Counts the number of historical interactions between actor1 and actor2 by relation type."""
return {
"status": "success",
"actor1": actor1,
"actor2": actor2,
"events_summary": {
"cooperative_events": random.randint(10, 50),
"conflict_events": random.randint(5, 30),
"neutral_events": random.randint(20, 100)
}
}
def get_cameo_code_description(code: str) -> dict:
"""Looks up the description of a given CAMEO relation code."""
cameo_db = {
"01": "Make public statement",
"04": "Consult",
"08": "Yield",
"10": "Cooperate",
"12": "Reject",
"16": "Reduce relationship",
"19": "Fight"
}
desc = cameo_db.get(str(code).zfill(2)[:2], "Unknown relation code")
return {"status": "success", "code": code, "description": desc}
Step 3: State Injection and Personas
The Chief Forecaster isn't performing the data lookup itselfβit acts as the supervisor analyzing the
reports handed up by its specialized workers. Using ADK's powerful prompt templating, we inject
{news_report}, {event_report}, and {policy_report} explicitly
into the Chief's context window.
NEWS_ANALYST_PROMPT = """
Act like an expert international relations news analyst.
Task: Review the recent news articles involving the two entities (actor1 and actor2) provided by the user.
Focus: Determine the trajectory of their relationship based on public media.
Recommendation: Provide a brief summary of the narrative surrounding their relationship based on the news events.
"""
EVENT_ANALYST_PROMPT = """
Act like an expert quantitative geopolitical analyst.
Task: Review the historical event counts (cooperative vs conflict) between the two entities provided by the user.
Focus: Determine if their base interactions lean historically towards cooperation or conflict.
Recommendation: Provide a statistical summary and describe the historical baseline of their relationship.
"""
POLICY_ANALYST_PROMPT = """
Act like a senior diplomatic strategy advisor.
Task: Consider the geopolitical context and known CAMEO codes of recent interactions if queried.
Focus: Analyze the broader structural relationship between these entities.
Recommendation: Provide strategic context on what their structural relationship implies for future actions.
"""
CHIEF_FORECASTER_PROMPT = """
Act as the Chief Event Forecaster. You will receive advice from 3 sub-agents evaluating two entities:
1. News Analyst (Media sentiment and recent events)
2. Event Analyst (Historical quantitative interactions)
3. Policy Analyst (Structural geopolitical relationship)
Task: Combine their insights with the user's query about the future relations between actor1 and actor2.
Your goal is to forecast the most likely future relation CAMEO code class (e.g., 01, 04, 10, 19).
Return ONLY a valid JSON object matching this exact schema:
{{
"forecasted_cameo_code": "The 2-digit CAMEO code (e.g., '01', '10', '19')",
"forecasted_relation": "A brief name for the relation (e.g., 'Cooperate', 'Fight')",
"confidence_score": ,
"reasoning": "A concise explanation of your forecast integrating the 3 reports."
}}
---
### NEWS ANALYST REPORT
{news_report}
---
### EVENT ANALYST REPORT
{event_report}
---
### POLICY ANALYST REPORT
{policy_report}
"""
{news_report} is magically populated later because
we assign output_key="news_report" to the news_analyst agent configuration in
ADK. The framework's state machine handles passing all the strings natively!
Step 4: The Parallel Pipeline
By nesting a `ParallelAgent` inside a `SequentialAgent`, we can guarantee that the Chief Forecaster waits for the lower-level analysis to finish, but we simultaneously minimize latency since the news, event counting, and policy analysis all execute concurrently.
# agent.py
import os
from dotenv import load_dotenv
current_dir = os.path.dirname(os.path.abspath(__file__))
env_path = os.path.join(current_dir, ".env")
load_dotenv(dotenv_path=env_path)
from google.adk.agents import LlmAgent, ParallelAgent, SequentialAgent
from .tools import get_news_articles, count_historical_events, get_cameo_code_description
from .prompts import NEWS_ANALYST_PROMPT, EVENT_ANALYST_PROMPT, POLICY_ANALYST_PROMPT, CHIEF_FORECASTER_PROMPT
# --- WORKERS (The International Relations Analysts) ---
news_analyst = LlmAgent(
name="news_analyst",
model="gemini-2.5-flash",
description="Analyzes news media involving the entities.",
instruction=NEWS_ANALYST_PROMPT,
tools=[get_news_articles],
output_key="news_report"
)
event_analyst = LlmAgent(
name="event_analyst",
model="gemini-2.5-flash",
description="Analyzes historical event counts (GDELT data) between entities.",
instruction=EVENT_ANALYST_PROMPT,
tools=[count_historical_events],
output_key="event_report"
)
policy_analyst = LlmAgent(
name="policy_analyst",
model="gemini-2.5-flash",
description="Provides structural geopolitical context and CAMEO code translations.",
instruction=POLICY_ANALYST_PROMPT,
tools=[get_cameo_code_description],
output_key="policy_report"
)
# --- THE PARALLEL PANEL ---
# Analysts evaluate the relationship context all at the same time
analyst_floor = ParallelAgent(
name="analyst_floor",
description="Runs all geopolitical analyses concurrently to increase efficiency.",
sub_agents=[news_analyst, event_analyst, policy_analyst]
)
# --- THE SYNTHESIZER ---
# Chief Forecaster uses the insights provided by the three subagents to form a prediction.
chief_forecaster = LlmAgent(
name="chief_forecaster",
model="gemini-2.5-flash",
description="Makes the final forecast of future relations between two countries based on analyst reports.",
instruction=CHIEF_FORECASTER_PROMPT,
output_key="final_forecast"
)
# --- THE ORCHESTRATOR ---
root_agent = SequentialAgent(
name="mirai_agent",
description="A multi-agent system for forecasting international relations events inspired by MIRAI.",
sub_agents=[analyst_floor, chief_forecaster]
)
if __name__ == "__main__":
from google.adk.cli.adk_run import serve_agent_cli
import asyncio
print("Launching MIRAI Agent in Terminal Mode...")
asyncio.run(serve_agent_cli(root_agent))
Step 5: Firing it up
Once you are executing the agent with `adk web` or Python directly, try feeding it these test cases to invoke different geopolitical outcomes:
- Testing Conflict Prediction:
"Please forecast the future relation trajectory between actor1=IND (India) and actor2=PAK (Pakistan). Historically, they have had many border disputes and neutral interactions. Recently, there has been an uptick in military exercises near the border. What CAMEO code relation should we expect next month?" - Testing Cooperative Prediction:
"Analyze the future relationship between actor1=USA (United States) and actor2=GBR (United Kingdom). They have a long history of deep cooperation. Recently, news indicates they are signing new climate change initiatives and trade agreements. Forecast their future relations."
Final Result
Below is a screenshot demonstrating an ADK agent output. In this case, our MIRAI Agent generates structured JSON recommendations tailored entirely based on local data retrieval!
