Databricks Gemini Workflow Agents
Databricks Gemini Workflow Agents
genai
[Link]()
import os
import sys
import time
import json
from [Link] import WorkspaceClient
from databricks_openai import UCFunctionToolkit, DatabricksFunctionClient
from google import genai
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class GeminiChat:
"""
Unified Gemini wrapper for LangGraph agents.
Includes:
- safe retry logic
- prompt sanitization
- JSON-safe output
"""
# Attempt 3 retries
for attempt in range(3):
try:
response =
self.model_serving_client.[Link](model=self.llm_endpoint,
messages=prompt,response_format={"type": "json_object"})
class Result:
content = [Link][0].[Link]
return Result()
except Exception as e:
print(f"[Gemini Error] attempt {attempt+1}: {e}")
[Link](1.5)
# ------------------------------
# General / Router Info
# ------------------------------
user_request: Optional[str]
task_type: Optional[str] # "entity_resolution", "data_quality", "governance",
"combined"
# ------------------------------
# Clean Slate (Entity Resolution)
# ------------------------------
schema_profile: Optional[Dict[str, Any]]
candidate_pairs: Optional[List[Dict[str, Any]]]
match_scores: Optional[List[Dict[str, Any]]]
dedup_results: Optional[List[Dict[str, Any]]]
external_lookup_data: Optional[Dict[str, Any]]
feedback_notes: Optional[List[str]]
# ------------------------------
# Watchtower (Data Quality)
# ------------------------------
dq_profile: Optional[Dict[str, Any]]
dq_scores: Optional[Dict[str, float]]
dq_anomalies: Optional[List[Dict[str, Any]]]
remediation_suggestions: Optional[List[str]]
# ------------------------------
# Sentinel (Governance & Compliance)
# ------------------------------
policy_violations: Optional[List[Dict[str, Any]]]
governance_alerts: Optional[List[str]]
audit_log: Optional[List[Dict[str, Any]]]
# ------------------------------
# Internal Agent Communication (A2A)
# ------------------------------
agent_messages: Optional[List[Dict[str, Any]]]
# ------------------------------
# Error Handling
# ------------------------------
errors: Optional[List[str]]
import json, re
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class DedupAgent:
"""
Clean Slate Agent #3:
Uses LLM logic to identify duplicate entities,
looping until no duplicates remain.
"""
def __init__(self):
[Link] = GeminiChat(LLM_ENDPOINT_NAME)
def _extract_prompt(self):
marker = "## 3. Deduplication / Merge Agent Prompt"
section = self.prompt_file.split(marker)[1]
user_text = [Link]("```")[1]
sys_text = [Link]("```")[0]
match = [Link](r"\*\*Role:\*\*\s*(.+?)(?=\n\*\*|$)", sys_text,
[Link])
if match:
role_text = [Link](1).strip()
prompt_text = f"{role_text}```{user_text}"
return prompt_text.strip()
template = self._extract_prompt()
role_text = [Link]("```")[0]
user_text = [Link]("```")[1].replace(
"{{records}}", [Link](records, indent=2)
).replace(
"{{match_results}}", [Link](match_results, indent=2)
)
prompt = [
{"role": "system", "content": role_text},
{"role": "user", "content": user_text},
]
response = [Link](prompt)
output = [Link]
try:
parsed = [Link](output)
except Exception:
parsed = {
"error": "Invalid JSON from DedupAgent",
"raw_output": output
}
# Logging
[Link]("agent_messages", []).append({
"agent": "DedupAgent",
"action": "entity_deduplication",
"info": "Deduplication step completed"
})
return state
class FeedbackAgent:
"""
Clean Slate Agent #4:
Stores SME / automated feedback into workflow state.
def __init__(self):
pass
state["feedback"].append(auto_feedback)
# Log activity
[Link]("agent_messages", []).append({
"agent": "FeedbackAgent",
"action": "store_feedback",
"info": "Feedback stage completed"
})
return state
import json, re
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class MatchingAgent:
"""
Clean Slate Agent #2:
Performs pairwise record matching using LLM reasoning.
Reads needed values from state instead of function arguments.
"""
def __init__(self):
[Link] = GeminiChat(LLM_ENDPOINT_NAME)
def _extract_prompt(self):
marker = "## 2. Matching Agent Prompt"
section = self.prompt_file.split(marker)[1]
user_text = [Link]("```")[1]
sys_text = [Link]("```")[0]
match = [Link](r"\*\*Role:\*\*\s*(.+?)(?=\n\*\*|$)", sys_text,
[Link])
if match:
role_text = [Link](1).strip()
prompt_text = f"{role_text}```{user_text}"
return prompt_text.strip()
# Prepare prompt
template = self._extract_prompt()
role_text = [Link]("```")[0]
user_text = [Link]("```")[1].replace(
"{{records}}",
[Link](records, indent=2)
)
prompt = [
{"role": "system", "content": role_text},
{"role": "user", "content": user_text},
]
# Call Gemini
response = [Link](prompt)
output = [Link]
try:
parsed = [Link](output)
except Exception:
parsed = {
"error": "Invalid JSON from MatchingAgent",
"raw_output": output
}
# Save results
state["match_results"] = parsed
# Log action
[Link]("agent_messages", []).append({
"agent": "MatchingAgent",
"action": "run_matching",
"info": "Record matching complete"
})
return state
import json, re
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class SchemaProfilerAgent:
"""
Clean Slate Agent #1:
Profiles dataset schema using LLM reasoning.
Reads rows from state["input_records"].
"""
def __init__(self):
# Centralized model loading:
# Only gemini_chat.py controls which model is used.
[Link] = GeminiChat(LLM_ENDPOINT_NAME) # no direct model name inside
the agent
def _extract_prompt(self):
marker = "## 1. Schema Profiler Agent Prompt"
section = self.prompt_file.split(marker)[1]
user_text = [Link]("```")[1]
sys_text = [Link]("```")[0]
match = [Link](r"\*\*Role:\*\*\s*(.+?)(?=\n\*\*|$)", sys_text,
[Link])
if match:
role_text = [Link](1).strip()
prompt_text = f"{role_text}```{user_text}"
return prompt_text.strip()
template = self._extract_prompt()
role_text = [Link]("```")[0]
user_text = [Link]("```")[1].replace(
"{{records}}",
[Link](rows, indent=2)
)
prompt = [
{"role": "system", "content": role_text},
{"role": "user", "content": user_text},
]
# Invoke Gemini
response = [Link](prompt)
output = [Link]
# Log activity
[Link]("agent_messages", []).append({
"agent": "SchemaProfilerAgent",
"action": "profile_schema",
"info": "Schema profile completed"
})
return state
import json, re
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class AlertAgent:
"""
Sentinel Agent #3:
Generates alerts based on governance_report, policy_results,
anomalies, and dq_scores.
Outputs:
state["alerts"]
"""
def __init__(self):
[Link] = GeminiChat(LLM_ENDPOINT_NAME)
try:
with open("prompts/sentinel_prompts.md", "r", encoding="utf-8") as f:
self.prompt_file = [Link]()
except FileNotFoundError:
print("\n[ERROR] Missing prompts/sentinel_prompts.md!")
self.prompt_file = ""
def _extract_prompt(self):
marker = "## 3. Alerting Agent Prompt"
section = self.prompt_file.split(marker)[1]
user_text = [Link]("```")[1]
sys_text = [Link]("```")[0]
match = [Link](r"\*\*Role:\*\*\s*(.+?)(?=\n\*\*|$)", sys_text,
[Link])
if match:
role_text = [Link](1).strip()
prompt_text = f"{role_text}```{user_text}"
return prompt_text.strip()
template = self._extract_prompt()
role_text = [Link]("```")[0]
user_text = (
template
.split("```")[1]
.replace("{{policy_results}}", [Link](policies, indent=2))
.replace("{{governance_report}}", [Link](governance, indent=2))
.replace("{{anomalies}}", [Link](anomalies, indent=2))
.replace("{{dq_scores}}", [Link](dq_scores, indent=2))
)
prompt = [
{"role": "system", "content": role_text},
{"role": "user", "content": user_text},
]
response = [Link](prompt)
output = [Link]
try:
parsed = [Link](output)
except Exception:
parsed = {"error": "Invalid JSON from AlertAgent", "raw_output":
output}
state["alerts"] = parsed
[Link]("agent_messages", []).append({
"agent": "AlertAgent",
"action": "alert_generation",
"info": "Alerts generated"
})
return state
import json, re
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class AuditSummaryAgent:
"""
Sentinel Agent #4:
Produces an audit summary of the entire pipeline execution.
Reads:
- schema_profile
- match_summary
- dq_profile
- anomalies
- dq_scores
- remediation_plan
- governance_report
- alerts
Writes:
state["audit_summary"]
"""
def __init__(self):
[Link] = GeminiChat(LLM_ENDPOINT_NAME)
try:
with open("prompts/sentinel_prompts.md", "r", encoding="utf-8") as f:
self.prompt_file = [Link]()
except FileNotFoundError:
print("\n[ERROR] Missing prompts/sentinel_prompts.md!")
self.prompt_file = ""
def _extract_prompt(self):
marker = "## 4. Audit Agent Prompt"
section = self.prompt_file.split(marker)[1]
if "```" not in section:
print(f"[Warning] Code block missing: {marker}")
return "You are the AuditSummaryAgent. Produce a JSON audit summary."
user_text = [Link]("```")[1]
sys_text = [Link]("```")[0]
match = [Link](r"\*\*Role:\*\*\s*(.+?)(?=\n\*\*|$)", sys_text,
[Link])
if match:
role_text = [Link](1).strip()
prompt_text = f"{role_text}```{user_text}"
return prompt_text.strip()
template = self._extract_prompt()
role_text = [Link]("```")[0]
user_text = [Link]("```")[1].replace(
"{{pipeline_outputs}}", [Link](collected, indent=2)
)
prompt = [
{"role": "system", "content": role_text},
{"role": "user", "content": user_text},
]
response = [Link](prompt)
output = [Link]
try:
parsed = [Link](output)
except Exception:
parsed = {
"error": "Invalid JSON from AuditSummaryAgent",
"raw_output": output
}
state["audit_summary"] = parsed
[Link]("agent_messages", []).append({
"agent": "AuditSummaryAgent",
"action": "audit_complete",
"info": "Full pipeline audit summary generated"
})
return state
import json, re
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class GovernanceCheckerAgent:
"""
Sentinel Agent #2:
Performs governance compliance checking by combining:
- policies
- dq_profile
- entity resolution results (er_results)
Produces:
state["governance_report"]
"""
def __init__(self):
[Link] = GeminiChat(LLM_ENDPOINT_NAME)
def _extract_prompt(self):
"""
Extract the governance checking prompt section.
Falls back to a safe default if markers are missing.
"""
section = self.prompt_file.split(marker)[1]
user_text = [Link]("```")[1]
sys_text = [Link]("```")[0]
match = [Link](r"\*\*Role:\*\*\s*(.+?)(?=\n\*\*|$)", sys_text,
[Link])
if match:
role_text = [Link](1).strip()
prompt_text = f"{role_text}```{user_text}"
return prompt_text.strip()
template = self._extract_prompt()
role_text = [Link]("```")[0]
user_text = ([Link]("```")[1]
.replace("{{policies}}", [Link](policies, indent=2))
.replace("{{dq_profile}}", [Link](dq_profile, indent=2))
.replace("{{er_results}}", [Link](er_results, indent=2))
)
prompt = [
{"role": "system", "content": role_text},
{"role": "user", "content": user_text},
]
# Call Gemini
response = [Link](prompt)
output = [Link]
# Save result
state["governance_report"] = parsed
# Log activity
[Link]("agent_messages", []).append({
"agent": "GovernanceCheckerAgent",
"action": "governance_eval",
"info": "Governance compliance evaluated"
})
return state
import json, re
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class PolicyAgent:
"""
Sentinel Agent #1:
Checks data against defined governance policies.
Produces:
state["policy_results"]
"""
def __init__(self):
[Link] = GeminiChat(LLM_ENDPOINT_NAME)
try:
with open("prompts/sentinel_prompts.md", "r", encoding="utf-8") as f:
self.prompt_file = [Link]()
except FileNotFoundError:
print("\n[ERROR] Missing prompts/sentinel_prompts.md!")
self.prompt_file = ""
def _extract_prompt(self):
"""
Extract the policy evaluation prompt from sentinel_prompts.md.
If the marker or code block is missing, fallback to safe default.
"""
section = self.prompt_file.split(marker)[1]
user_text = [Link]("```")[1]
sys_text = [Link]("```")[0]
match = [Link](r"\*\*Role:\*\*\s*(.+?)(?=\n\*\*|$)", sys_text,
[Link])
if match:
role_text = [Link](1).strip()
prompt_text = f"{role_text}```{user_text}"
return prompt_text.strip()
template = self._extract_prompt()
role_text = [Link]("```")[0]
user_text = ([Link]("```")[1]
.replace("{{records}}", [Link](safe_rows,
indent=2))
.replace("{{policies}}", [Link](policies,
indent=2))
)
prompt = [
{"role": "system", "content": role_text},
{"role": "user", "content": user_text},
]
response = [Link](prompt)
output = [Link]
# Parse JSON
try:
parsed = [Link](output)
except Exception:
parsed = {
"error": "Invalid JSON from PolicyAgent",
"raw_output": output
}
# Save results
state["policy_results"] = parsed
# Logging
[Link]("agent_messages", []).append({
"agent": "PolicyAgent",
"action": "policy_check",
"info": "Policy evaluation completed"
})
return state
import json, re
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class AnomalyReasonerAgent:
"""
Watchtower Agent #2:
Performs anomaly detection using LLM reasoning.
Accepts only the workflow state and produces:
state["anomalies"]
"""
def __init__(self):
[Link] = GeminiChat(LLM_ENDPOINT_NAME)
def _extract_prompt(self):
"""
Extract anomaly detection prompt from markdown file.
If markers or code blocks are missing, fallback.
"""
marker = "## 3. Anomaly Reasoner Agent Prompt"
section = self.prompt_file.split(marker)[1]
user_text = [Link]("```")[1]
sys_text = [Link]("```")[0]
match = [Link](r"\*\*Role:\*\*\s*(.+?)(?=\n\*\*|$)", sys_text,
[Link])
if match:
role_text = [Link](1).strip()
prompt_text = f"{role_text}```{user_text}"
return prompt_text.strip()
def run(self, state: WorkflowState):
"""
LangGraph-compatible:
- Reads input records from state
- Generates anomaly summary
"""
records = [Link]("input_records", [])
template = self._extract_prompt()
role_text = [Link]("```")[0]
user_text = [Link]("```")[1].replace(
"{{records}}",
[Link](safe_rows, indent=2)
)
prompt = [
{"role": "system", "content": role_text},
{"role": "user", "content": user_text},
]
response = [Link](prompt)
output = [Link]
# Log activity
[Link]("agent_messages", []).append({
"agent": "AnomalyReasonerAgent",
"action": "anomaly_detection",
"info": "Anomaly detection completed"
})
return state
import json, re
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class DQProfileReasonerAgent:
"""
Watchtower Agent #1:
Performs Data Quality (DQ) profiling using LLM reasoning.
Reads records from state["input_records"] and produces:
state["dq_profile"]
Includes robust prompt extraction fallback for missing prompt markers.
"""
def __init__(self):
[Link] = GeminiChat(LLM_ENDPOINT_NAME)
def _extract_prompt(self):
"""
Extract the DQ Profile Reasoner prompt from the markdown file.
If markers or code blocks are missing, fallback to a safe default.
"""
prompt_text = f"{role_text}```{user_text}"
return prompt_text.strip()
template = self._extract_prompt()
prompt = [
{"role": "system", "content": role_text},
{"role": "user", "content": user_text},
]
return state
import json, re
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class DQScoringAgent:
"""
Watchtower Agent #3:
Combines DQ Profile + Anomalies to compute an overall
DQ score and classification.
Produces state["dq_scores"].
"""
def __init__(self):
[Link] = GeminiChat(LLM_ENDPOINT_NAME)
try:
with open("prompts/watchtower_prompts.md", "r", encoding="utf-8") as f:
self.prompt_file = [Link]()
except FileNotFoundError:
print("\n[ERROR] Missing prompts/watchtower_prompts.md!")
self.prompt_file = ""
def _extract_prompt(self):
"""
Extracts prompt for DQ scoring.
If missing, fallback.
"""
section = self.prompt_file.split(marker)[1]
user_text = [Link]("```")[1]
sys_text = [Link]("```")[0]
match = [Link](r"\*\*Role:\*\*\s*(.+?)(?=\n\*\*|$)", sys_text,
[Link])
if match:
role_text = [Link](1).strip()
prompt_text = f"{role_text}```{user_text}"
return prompt_text.strip()
template = self._extract_prompt()
role_text = [Link]("```")[0]
user_text = ([Link]("```")[1]
.replace("{{dq_profile}}", [Link](dq_profile,
indent=2))
.replace("{{anomalies}}", [Link](anomalies,
indent=2))
)
prompt = [
{"role": "system", "content": role_text},
{"role": "user", "content": user_text},
]
# Gemini call
response = [Link](prompt)
output = [Link]
# Save results
state["dq_scores"] = parsed
# Logging
[Link]("agent_messages", []).append({
"agent": "DQScoringAgent",
"action": "dq_scoring",
"info": "DQ scoring completed"
})
return state
import json, re
LLM_ENDPOINT_NAME = "databricks-gemini-2-5-flash"
class RemediationAgent:
"""
Watchtower Agent #4:
Suggests remediation actions based on:
- detected anomalies
- computed DQ scores
Produces state["remediation_plan"].
"""
def __init__(self):
[Link] = GeminiChat(LLM_ENDPOINT_NAME)
try:
with open("prompts/watchtower_prompts.md", "r", encoding="utf-8") as f:
self.prompt_file = [Link]()
except FileNotFoundError:
print("\n[ERROR] Missing prompts/watchtower_prompts.md!")
self.prompt_file = ""
def _extract_prompt(self):
"""
Extract remediation prompt.
Uses fallback if the marker or code block is missing.
"""
section = self.prompt_file.split(marker)[1]
user_text = [Link]("```")[1]
sys_text = [Link]("```")[0]
match = [Link](r"\*\*Role:\*\*\s*(.+?)(?=\n\*\*|$)", sys_text,
[Link])
if match:
role_text = [Link](1).strip()
prompt_text = f"{role_text}```{user_text}"
return prompt_text.strip()
template = self._extract_prompt()
role_text = [Link]("```")[0]
user_text = ([Link]("```")[1]
.replace("{{anomalies}}", [Link](anomalies, indent=2))
.replace("{{dq_scores}}", [Link](dq_scores, indent=2))
)
prompt = [
{"role": "system", "content": role_text},
{"role": "user", "content": user_text},
]
response = [Link](prompt)
output = [Link]
# Save output
state["remediation_plan"] = parsed
[Link]("agent_messages", []).append({
"agent": "RemediationAgent",
"action": "remediation_generated",
"info": "Remediation plan created"
})
return state
class RouterAgent:
"""
Router Agent:
- Reads user_request from state
- Classifies the task type
- Updates state.task_type accordingly
"""
@staticmethod
def classify_task(state: WorkflowState) -> WorkflowState:
user_request = ([Link]("user_request") or "").lower()
else:
# When unclear, fall back to combined mode
state["task_type"] = "combined"
state["agent_messages"].append(message_entry)
return state
def build_graph():
graph = StateGraph(WorkflowState)
# ---------------------------------------------------
# Register nodes
# ---------------------------------------------------
# Router
router = RouterAgent()
graph.add_node("router", router.classify_task)
graph.add_node("schema_profiler", schema_profiler.run)
graph.add_node("match_agent", match_agent.run)
graph.add_node("dedup_agent", dedup_agent.run)
graph.add_node("feedback_agent", feedback_agent.run)
graph.add_node("dq_profile_agent", dq_profile_agent.run)
graph.add_node("anomaly_reasoner", anomaly_reasoner.run)
graph.add_node("dq_scoring_agent", dq_scoring_agent.run)
graph.add_node("remediation_agent", remediation_agent.run)
# Sentinel (Governance)
policy_agent = PolicyAgent()
governance_checker = GovernanceCheckerAgent()
alert_agent = AlertAgent()
audit_agent = AuditSummaryAgent()
graph.add_node("policy_agent", policy_agent.run)
graph.add_node("governance_checker", governance_checker.run)
graph.add_node("alert_agent", alert_agent.run)
graph.add_node("audit_agent", audit_agent.run)
# ---------------------------------------------------
# Entry point
# ---------------------------------------------------
graph.set_entry_point("router")
# ---------------------------------------------------
# CONDITIONAL ROUTING (Router → Subsystems)
# ---------------------------------------------------
graph.add_conditional_edges(
"router",
lambda state: state["task_type"],
{
"entity_resolution": "schema_profiler",
"data_quality": "dq_profile_agent",
"governance": "policy_agent",
"combined": "schema_profiler" # full pipeline begins here
}
)
# ---------------------------------------------------
# ENTITY RESOLUTION PATH (with Loop)
# ---------------------------------------------------
# Schema → Matching
graph.add_edge("schema_profiler", "match_agent")
# Matching → Dedup
graph.add_edge("match_agent", "dedup_agent")
graph.add_conditional_edges(
"dedup_agent",
dedup_loop_condition,
{
"loop": "match_agent",
"finish": "feedback_agent"
}
)
graph.add_conditional_edges(
"feedback_agent",
er_exit_condition,
{
"combined": "dq_profile_agent",
"audit": "audit_agent"
}
)
# ---------------------------------------------------
# DATA QUALITY PATH
# ---------------------------------------------------
graph.add_edge("dq_profile_agent", "anomaly_reasoner")
graph.add_edge("anomaly_reasoner", "dq_scoring_agent")
graph.add_edge("dq_scoring_agent", "remediation_agent")
graph.add_conditional_edges(
"remediation_agent",
dq_exit_condition,
{
"governance": "policy_agent",
"audit": "audit_agent"
}
)
# ---------------------------------------------------
# GOVERNANCE PATH
# ---------------------------------------------------
graph.add_edge("policy_agent", "governance_checker")
graph.add_edge("governance_checker", "alert_agent")
graph.add_edge("alert_agent", "audit_agent")
# ---------------------------------------------------
# FINAL END
# ---------------------------------------------------
graph.add_edge("audit_agent", END)
return [Link]()
def run_pipeline(
user_request: str,
records: list = None,
policies: dict = None,
feedback: str = None
):
"""
Runs any of the supported modes:
- entity_resolution
- data_quality
- governance
- combined (ER → DQ → Governance → Audit)
"""
# -----------------------------------
# Build graph
# -----------------------------------
graph = build_graph()
# -----------------------------------
# Initialize workflow state
# -----------------------------------
state = WorkflowState()
state["user_request"] = user_request
# -----------------------------------
# RUN THE GRAPH
# -----------------------------------
final_state = [Link](state)
return final_state
# =================================================
# TEST RUNNER
# =================================================
# -----------------------------------
# TEST DATASET (Records)
# -----------------------------------
#sample_records = [
# {"name": "John Doe", "email": "john@[Link]", "phone": "1234567890",
"age": 32},
# {"name": "Jon Doe", "email": "jon.d@[Link]", "phone": "1234567890",
"age": 32},
# {"name": "Jane Doe", "email": "jane@example", "phone": "", "age": None}
#]
table_data = [Link]("`hackthon-aethermind`.ingestion.table_a")
sample_records = [[Link]() for row in table_data.collect()]
# -----------------------------------
# TEST GOVERNANCE POLICIES
# -----------------------------------
sample_policies = {
"pii_columns": ["name", "email", "phone"],
"required_columns": ["name", "email"],
"rules": [
{"rule": "pii_not_null", "columns": ["name", "email"]},
{"rule": "email_validity", "columns": ["email"]},
{"rule": "phone_validity", "columns": ["phone"]},
{"rule": "non_empty_string", "columns": ["name"]},
{"rule": "completeness_threshold", "threshold": 0.70},
{"rule": "freshness_limit_days", "limit": 60}
]
}
# -----------------------------------
# RUN FULL PIPELINE (ER → DQ → Governance)
# -----------------------------------
result = run_pipeline(
user_request="data_quality", # FULL WORKFLOW
records=sample_records, # Dataset to test
policies=sample_policies # Governance rules
)
# -----------------------------------
# PRINT FINAL STATE
# -----------------------------------
import json
print("\n================ FINAL STATE ================\n")
print([Link](result, indent=2, default=str))