I Built a Live-API Economic Research Agent (FRED, Census, HUD & Real Estate Yields)

[Agent Garden] Solving LLM Hallucinations, Math Drift, and Obscure Identifiers via ADK 2.0

Written by Enrique Chan, Delta AI Technical Deployment Lead


Organizations want to deploy Large Language Models (LLMs) to automate high-stakes workflows like supply-chain planning, commercial underwriting, and cost-of-living index analysis. But when developers try to use out-of-the-box (OOTB) LLMs for these tasks, they inevitably hit three enterprise blockers:

  1. Factual Hallucinations: OOTB LLMs generate plausible-sounding statistics instead of pulling live, verified records.
  2. Math Drift: Compounding financial formulas, regressions, and yields drift or fail entirely when the model attempts to execute mathematical calculations inside its prompt context.
  3. Obscure Identifier Barriers: Public and enterprise databases often require cryptic parameters—like 5-digit county FIPS codes or custom inventory IDs—that human users don’t know and standard models cannot dynamically map.

To bridge the gap between creative text generators and mathematically precise analysts, developers are moving toward using Agent Development Kit (ADK).

In this post, we’ll outline the technical blueprint for resolving these three limitations using patterns developed for our Economic Research Agent, deployed live on the Google Cloud Agent Garden platform.

:clapper_board: Want to see it in action first? Check out the Economic Research Agent: First-Person Screencast Walkthrough on YouTube to see the agent query FRED, Census, HUD, and compute MLS Cap Rates in real-time.

The Core Architecture: Tool-Grounded Planning Engine

The ADK allows developers to wrap Gemini in an orchestrated framework containing three core components: the Planning Engine (Gemini 3.5), Grounded Skills (typed Python tools), and the Execution Context.

Let’s look at exactly how this architecture solves each of the three LLM limitations.


Pattern 1. Solving Hallucinations: Strict Tool Grounding

Rather than allowing Gemini to generate macro statistics from its pre-trained weights, we use the ADK to bind it to typed, validated Python tools. By explicitly declaring schemas and docstrings, the agent’s behavior shifts from retrieval-by-generation to retrieval-by-execution.

Here is a snippet showing how to define an ADK tool with strict input validation:

from vertexai.preview.reasoning_engines import AdkApp

# Define the tool with explicit typing and docstrings for the LLM planner
def fetch_fmr_rent_rate(fips_code: str, year: int = 2024) -> dict:
   """Fetches the 2-Bedroom Fair Market Rent (FMR) from HUD for a given county FIPS code.
  
   Args:
       fips_code: The 5-digit US county FIPS identifier (e.g. '48453').
       year: The target calendar year (defaults to 2024).
   """
   # Force strict parameter checking
   if not fips_code.isdigit() or len(fips_code) != 5:
       raise ValueError("Invalid FIPS code format. Must be 5 digits.")
      
   api_url = f"https://www.huduser.gov/portal/datasets/fmr/fmr2024/api/fmr/{fips_code}"
   # Secure API invocation logic here...
   return {"fips": fips_code, "fmr_2br": 1450, "year": year}

By binding this tool to the agent via AdkApp(tools=[fetch_fmr_rent_rate]), the model is restricted from making up numbers. If the data isn’t returned by the tool, the planner must inform the user rather than hallucinating.


Pattern 2. Solving Math Drift: Isolated Runtimes

When an LLM is asked to perform multi-step math—such as calculating a 30-year amortized commercial yield or running an Ordinary Least Squares (OLS) regression—it executes calculations sequentially through text generation. This leads to math drift, where compounding errors accumulate in the prompt.

The Solution: Outsource mathematical computations entirely to a sandboxed Python execution context running standard numeric libraries (pandas, numpy, statsmodels).

Instead of letting the LLM calculate numbers, the agent compiles data vectors, passes them to a secure tool, and executes code deterministically:

import pandas as pd
import statsmodels.api as sm

def run_econometric_regression(x_vector: list, y_vector: list) -> str:
   """Executes a formal Ordinary Least Squares (OLS) regression on two data vectors in the sandbox.
  
   Args:
       x_vector: Independent variable series (e.g. Unionization Density).
       y_vector: Dependent variable series (e.g. Weekly Wage Growth).
   """
   df = pd.DataFrame({"X": x_vector, "Y": y_vector})
   X = sm.add_constant(df["X"])
   model = sm.OLS(df["Y"], X).fit()
  
   # Return statistical validation summary (p-values, R-squared, coefficients)
   return model.summary().as_text()

By isolating math to Python’s numeric runtime, we ensure 100% calculation accuracy, letting the LLM focus solely on translating the output regression summary into a business brief.


Pattern 3. Solving Obscure Identifiers: Geo-Context Alignment

Enterprise databases require precise keys. For example, HUD APIs require county FIPS codes (48453), while Census APIs require tract codes. A human user will never type: “What is the AMI in FIPS 48453?”. They will ask: “What is the AMI in Austin?”.

If the LLM tries to guess FIPS codes on-the-fly, it will frequently map them incorrectly.

The Solution: Embed geo-context alignment mapping directly into the tool’s resolver layer. The LLM handles the natural language intent (“Austin”), while the tool layer dynamically maps it to the target database key:

# Hardcoded or database-backed lookup table inside the tool layer
CITY_TO_COUNTY_FIPS = {
   "austin": "48453",
   "raleigh": "37183",
   "charlotte": "37119",
   "seattle": "53033",
   "orlando": "12095"
}

def fetch_housing_affordability(city: str) -> dict:
   """Retrieves HUD housing metrics for a target US city name."""
   clean_city = city.strip().lower()
   fips_code = CITY_TO_COUNTY_FIPS.get(clean_city)
  
   if not fips_code:
       return {"error": f"City '{city}' is not mapped to a county FIPS."}
      
   # Forward the mapped FIPS identifier to the HUD API tool
   return fetch_fmr_rent_rate(fips_code)

This resolver decoupling keeps the planner from breaking, aligning human queries with structured system constraints.


Continuous Governance: Evaluating at Scale

To guarantee that code revisions don’t reintroduce drift or parameter mapping errors, ADK includes a robust programmatic evaluation suite.

Before deploying to the Vertex AI Reasoning Engine, developers can execute batch evaluations on custom test suites to evaluate agent performance over time using LLM-as-a-judge:

# Programmatically verify agent output quality against golden datasets
from vertexai.preview.reasoning_engines import AdkEvaluator

evaluator = AdkEvaluator()
results = evaluator.evaluate(
   agent=my_deployed_agent,
   dataset="tests/eval/evalsets/wow_stress_test.evalset.json",
   metrics=["relevance", "completeness"]
)
print(f"Agent Relevance Score: {results.relevance.mean()}")

Try it Yourself: The Deployed Sourcing Lab Console

To demonstrate these patterns in a production-ready user interface, we built and deployed an interactive single-page application hosted live on Firebase.

The console showcases 17 pre-mapped strategic queries—allowing you to click, run, and inspect the grounded outputs in real-time:

:backhand_index_pointing_right: Test the Live Console: https://economic-research.web.app


Get Started & Source Code

All files, agent runtime definitions, and standalone packages are open-sourced:

3 Likes