In the Advertising and AdTech industry, structured data historical Click-Through Rates (CTR), Cost-Per-Click (CPC), impression volumes, and bid prices—is the foundation of media buying. But structured data is inherently backward-looking. It tells you what did happen, not what will happen when the market shifts. To build truly predictive intelligence, we must answer a critical question: What unstructured context actually improves forecasting performance in AdTech? Through enterprise implementations, we’ve found that four specific types of unstructured context consistently move the needle on forecasting accuracy:
Real-Time Cultural & News Sentiment: Shifts in public mood that impact brand safety and audience receptiveness, directly affecting CTR.
Competitor Intelligence: Unstructured press releases, earnings calls, or social chatter indicating a competitor’s aggressive new product launch or discounting, which spikes CPC.
Platform Algorithm & Policy Shifts: Subtle updates to ad exchange rules, privacy policy changes, or auction dynamics that alter bid competitiveness.
Creative Resonance & Fatigue Signals: Semantic analysis of audience comments and engagement patterns that indicate a creative is losing cultural relevance before structured metrics officially drop.
When we integrate these unstructured signals via Retrieval-Augmented Generation (RAG) into a multi-agent predictive workflow, we transition from reactive dashboard monitoring to proactive campaign optimization.
Here is an end-to-end guide on how to build this for an AdTech platform using LangGraph, complete with state management and persistent memory.
The Use Case: Proactive Budget Reallocation for an EV Launch
The Scenario: An AdTech platform is managing a programmatic campaign for a major automotive brand launching a new Electric Vehicle (EV). The campaign has been running for two weeks.
The Problem: Structured forecasting models predict a steady CPC of $2.50 and a CTR of 1.2% for the next week. However, the models are blind to a breaking news story about a major competitor’s battery recall, combined with a viral social media trend favoring the client's specific battery technology.
The Predictive RAG Solution: Instead of waiting for the CPC to actually spike or the CTR to drop, a proactive multi-agent system detects the initial performance deviation. It uses RAG to pull unstructured context (news, social sentiment, competitor press releases), recalibrates the forecast, and automatically recommends a massive budget reallocation and a creative pivot to capitalize on the moment.
![444]()
The Multi-Agent Workflow
We orchestrate this using LangGraph to manage the flow of state and persistent memory across three specialized agents:
The Context Sentinel (RAG Agent): Monitors the campaign's structured performance against the baseline forecast. When a deviation is detected, it queries the vector database for unstructured context (news, social sentiment, competitor activity) relevant to the brand and campaign.
The Performance Oracle (Predictive Agent): Ingests the current structured metrics and the unstructured RAG context. It uses an LLM to reason through how these external factors will impact future auction dynamics and audience behavior, outputting a revised CPC/CTR forecast.
The Media Strategist (Action & Memory Agent): Takes the revised forecast and queries long-term memory for historical lessons (e.g., "How did we handle competitor recalls in Q3?"). It formulates a concrete action plan for budget reallocation and creative adjustments.
Code Implementation
Below is the complete Python implementation using LangGraph.
1. Setup and State Definition
We define a unified state that passes between agents, ensuring every node has access to both structured metrics and unstructured context.
from typing import TypedDict, List, Dict, Any
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import HumanMessage, AIMessage
# Define the shared state for the AdTech predictive workflow
class AdTechState(TypedDict):
campaign_id: str
brand: str
# Structured data inputs (from the ad server/DSP)
current_cpc: float
current_ctr: float
spend_pace: float # 1.0 = on track, >1.0 = overspending
# RAG outputs
unstructured_context: List[str]
# Predictive outputs
forecasted_cpc: float
forecasted_ctr: float
# Action outputs
action_plan: str
# Memory context
historical_lessons: str
messages: List[Any]
2. Mock Tools for RAG and Data Retrieval
In a production environment, these functions would interface with your vector database (e.g., Pinecone, Milvus) for RAG, and your data warehouse for historical memory.
def retrieve_market_context(brand: str, campaign_id: str) -> List[str]:
"""Mock RAG retrieval for unstructured market context."""
# In production: Semantic search for news, social sentiment, and competitor PR
# related to {brand} and {campaign_id} over the last 48 hours.
return [
"NEWS: Major competitor 'AutoRival' issued a voluntary recall for their EV battery packs due to overheating concerns.",
"SOCIAL SENTIMENT: Viral TikTok trend (#SafeDriveEV) praising {brand}'s specific LFP battery technology as the safest on the market (5M views).",
"PLATFORM UPDATE: Ad exchange 'AdNet' announced a temporary freeze on auto-category CPC bids due to high volatility, effectively lowering auction density."
]
def retrieve_historical_memory(brand: str, event_type: str) -> str:
"""Mock retrieval of past campaign strategies from long-term memory."""
return f"MEMORY: During the 'SedanX' recall in Q3 last year, we shifted 40% of the budget from Search to YouTube pre-roll to capitalize on video engagement. Lesson: Video creative outperforms static display during high-emotion news cycles."
3. Defining the Agent Nodes
Each node reads from the AdTechState, performs its specialized task, and returns an updated state dictionary.
def context_sentinel(state: AdTechState) -> Dict[str, Any]:
"""Agent 1: Detects performance deviation and retrieves causal context via RAG."""
brand = state["brand"]
campaign_id = state["campaign_id"]
print(f"[Sentinel] Performance deviation detected for {campaign_id}. Fetching market context...")
context = retrieve_market_context(brand, campaign_id)
return {
"unstructured_context": context,
"messages": state["messages"] + [AIMessage(content="Market context retrieved. Passing to Oracle.")]
}
def performance_oracle(state: AdTechState) -> Dict[str, Any]:
"""Agent 2: Combines structured metrics with unstructured context to forecast."""
context = "\n".join(state["unstructured_context"])
current_cpc = state["current_cpc"]
current_ctr = state["current_ctr"]
print(f"[Oracle] Analyzing current CPC (${current_cpc}) and CTR ({current_ctr}%) against market context...")
# Simulated LLM reasoning for predictive math
# Prompt: "Given current CPC is {current_cpc} and CTR is {current_ctr}, and context is {context},
# forecast the CPC and CTR for the next 7 days."
# Logic: Competitor recall + lower auction density = CPC drops. Viral trend = CTR spikes.
forecasted_cpc = current_cpc * 0.65 # 35% drop in CPC due to lower competition
forecasted_ctr = current_ctr * 1.8 # 80% increase in CTR due to viral sentiment
return {
"forecasted_cpc": forecasted_cpc,
"forecasted_ctr": forecasted_ctr,
"messages": state["messages"] + [AIMessage(content=f"Forecast updated: CPC ${forecasted_cpc:.2f}, CTR {forecasted_ctr:.2f}%. Passing to Strategist.")]
}
def media_strategist(state: AdTechState) -> Dict[str, Any]:
"""Agent 3: Formulates action plan using predictions and historical memory."""
forecasted_cpc = state["forecasted_cpc"]
forecasted_ctr = state["forecasted_ctr"]
brand = state["brand"]
# Retrieve historical lessons using the memory saver context
historical_lessons = retrieve_historical_memory(brand, "competitor_recall")
print(f"[Strategist] Formulating media strategy considering past lessons...")
# Simulated LLM call for strategy formulation
# Prompt: "Forecasted CPC is ${forecasted_cpc} and CTR is {forecasted_ctr}%.
# Based on {historical_lessons}, what is the optimal budget and creative action?"
action = (
f"1. BUDGET REALLOCATION: Shift 40% of daily budget from Programmatic Display to YouTube Pre-Roll. "
f"The forecasted CPC drop (${forecasted_cpc:.2f}) allows us to buy more impressions at a lower cost. "
f"2. CREATIVE PIVOT: Immediately swap static display banners for UGC-style video creatives highlighting "
f"the LFP battery safety features to capitalize on the #SafeDriveEV viral trend. "
f"3. KEYWORD TARGETING: Pause bidding on 'EV battery life' and increase bids on 'safest EV'."
)
return {
"action_plan": action,
"historical_lessons": historical_lessons,
"messages": state["messages"] + [AIMessage(content="Media strategy finalized. Workflow complete.")]
}
4. Graph Construction and Execution
We wire the nodes into a LangGraph StateGraph and attach the MemorySaver to ensure the Strategist can access historical context.
def build_adtech_graph():
# Initialize the graph with the defined state
workflow = StateGraph(AdTechState)
# Add nodes
workflow.add_node("context_sentinel", context_sentinel)
workflow.add_node("performance_oracle", performance_oracle)
workflow.add_node("media_strategist", media_strategist)
# Define the entry point
workflow.set_entry_point("context_sentinel")
# Define edges (the flow of the predictive workflow)
workflow.add_edge("context_sentinel", "performance_oracle")
workflow.add_edge("performance_oracle", "media_strategist")
workflow.add_edge("media_strategist", END)
# Initialize Persistent Memory
# In production, use SqliteSaver or PostgresSaver for enterprise durability
memory = MemorySaver()
# Compile the graph
app = workflow.compile(checkpointer=memory)
return app
# --- Execution ---
if __name__ == "__main__":
app = build_adtech_graph()
# Configuration for the thread (Memory isolation per campaign)
config = {"configurable": {"thread_id": "ev-launch-campaign-001"}}
# Initial trigger state (simulating an automated alert from the DSP)
initial_state = {
"campaign_id": "EV-LAUNCH-Q3",
"brand": "VoltMotors",
"current_cpc": 2.50,
"current_ctr": 1.20,
"spend_pace": 0.95, # Slightly under pacing
"unstructured_context": [],
"forecasted_cpc": 0.0,
"forecasted_ctr": 0.0,
"action_plan": "",
"historical_lessons": "",
"messages": [HumanMessage(content="Performance deviation detected. Initiating predictive workflow.")]
}
print("--- Starting AdTech Predictive Intelligence Workflow ---\n")
# Invoke the graph
final_state = app.invoke(initial_state, config)
print("\n--- Workflow Complete ---")
print(f"Forecasted CPC: ${final_state['forecasted_cpc']:.2f}")
print(f"Forecasted CTR: {final_state['forecasted_ctr']:.2f}%")
print(f"\nAction Plan:\n{final_state['action_plan']}")
The ROI of Predictive RAG in AdTech
By integrating unstructured context into a predictive multi-agent workflow, AdTech platforms can fundamentally change how they manage media spend:
Capturing the "Why" Behind the Metrics: Structured data tells you CPC is rising. RAG tells you why (e.g., a competitor just launched an aggressive bid strategy). This allows the Oracle agent to accurately forecast if the CPC spike is a temporary anomaly or a new baseline.
Institutional Media Buying Memory: By utilizing LangGraph's persistent memory, the Strategist agent captures the tribal knowledge of senior media buyers. It remembers that during high-emotion news cycles, video outperforms display, ensuring the AI doesn't make the same sub-optimal media mix recommendations twice.
Automating the "Golden Hour": In AdTech, the first 24 hours of a cultural trend or competitor misstep dictate the ROI of the campaign. Predictive RAG automates the detection, context gathering, and strategic pivoting within minutes, securing the most efficient impressions before the broader market reacts and drives up auction prices.
In the hyper-competitive, millisecond-latency world of programmatic advertising, the ability to forecast the future by reading the unstructured present is the ultimate competitive advantage.