Skip to content

Instantly share code, notes, and snippets.

@netgfx
Created November 23, 2025 20:48
Show Gist options
  • Select an option

  • Save netgfx/47e51ba2ade83ce21689e89f4329d081 to your computer and use it in GitHub Desktop.

Select an option

Save netgfx/47e51ba2ade83ce21689e89f4329d081 to your computer and use it in GitHub Desktop.
Hybrid System with Action Planning & Grounding Validation
// server.js - Hybrid System with Action Planning & Grounding Validation
import express from "express";
import dotenv from "dotenv";
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
import { StateGraph, START, END } from "@langchain/langgraph";
import axios from "axios";
dotenv.config();
const app = express();
app.use(express.json());
// --- CONFIGURATION ---
const PORT = 3000;
const PARALLEL_API_KEY = process.env.PARALLEL_API_KEY;
const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY;
// LLM for generating plans and answers
const model = new ChatGoogleGenerativeAI({
model: "gemini-2.5-flash",
temperature: 0.3,
apiKey: GOOGLE_API_KEY,
});
console.log("\n" + "=".repeat(70));
console.log("πŸ”„ HYBRID SYSTEM - Action Planning Architecture");
console.log("=".repeat(70));
console.log("Flow: Plan β†’ Ground β†’ Validate β†’ Execute β†’ Verify");
console.log("=".repeat(70) + "\n");
// --- STATE DEFINITION ---
const graphState = {
userQuery: null,
// Planning Phase
actionPlan: null, // LLM's proposed actions to answer query
requiredFacts: null, // Facts that need grounding
// Grounding Phase
groundedFacts: null, // Facts verified via web search
groundingIssues: null, // Facts that failed verification
// Validation Phase
planValid: false, // Is the plan grounded in reality?
corrections: null, // Suggested fixes for invalid plan
// Execution Phase
finalAnswer: null, // Answer after applying corrections
confidence: 0, // Deterministic confidence score
// Output
finalJson: null,
sources: [],
};
// ========================================
// NODE 1: ACTION PLANNER
// ========================================
async function planActions(state) {
console.log(`\n[${"1".padStart(2, "0")}] πŸ“‹ ACTION PLANNER`);
console.log(`Query: "${state.userQuery}"`);
const prompt = `
You are a query analyzer. Break down how you would answer this query into verifiable steps.
USER QUERY: "${state.userQuery}"
TASK: Output a JSON plan with:
1. "actions": Step-by-step actions you'll take
2. "required_facts": Facts that MUST be verified via external sources
3. "reasoning": Why these facts need verification
EXAMPLES:
Query: "What is Dyno?"
{
"actions": [
"Determine which 'Dyno' entity the user is asking about",
"Check if it's Heroku Dyno (container technology)",
"Check if it's Dyno Nobel (explosives company)",
"Check if it's Dynojet (dynamometer)"
],
"required_facts": [
"Existence of Heroku Dyno and its purpose",
"Existence of Dyno Nobel and its industry",
"Existence of Dynojet and its product type"
],
"reasoning": "Ambiguous term - multiple entities exist with this name. Must verify which ones are real and what they do."
}
Query: "What is the latest version of React?"
{
"actions": [
"Search for React's current stable release",
"Verify the version number from official sources",
"Confirm release date if possible"
],
"required_facts": [
"Current React version number (as of November 2025)",
"Source: Official React documentation or npm registry"
],
"reasoning": "Version information changes frequently and my training data may be outdated. Must verify from current sources."
}
Query: "What is the Flux Capacitor pattern in microservices?"
{
"actions": [
"Search for 'Flux Capacitor pattern' in microservices literature",
"Check if this is a recognized architectural pattern",
"Verify existence in authoritative sources"
],
"required_facts": [
"Existence of Flux Capacitor pattern in software architecture",
"Documentation or papers describing this pattern"
],
"reasoning": "Unfamiliar pattern name - could be a hallucination or fictional reference. Must verify it exists in technical literature."
}
Now analyze this query:
USER QUERY: "${state.userQuery}"
Return ONLY valid JSON (no markdown):
`;
const response = await model.invoke(prompt);
const planJson = response.content.replace(/```json|```/g, "").trim();
const plan = JSON.parse(planJson);
console.log(`Actions planned: ${plan.actions.length}`);
plan.actions.forEach((action, i) => {
console.log(` ${i + 1}. ${action}`);
});
console.log(`\nFacts requiring verification: ${plan.required_facts.length}`);
plan.required_facts.forEach((fact, i) => {
console.log(` ${i + 1}. ${fact}`);
});
return {
actionPlan: plan,
requiredFacts: plan.required_facts,
};
}
// ========================================
// NODE 2: FACT GROUNDER
// ========================================
async function groundFacts(state) {
console.log(`\n[${"2".padStart(2, "0")}] 🌐 FACT GROUNDER`);
console.log(
`Verifying ${state.requiredFacts.length} facts via web search...`
);
const grounded = [];
const issues = [];
for (const fact of state.requiredFacts) {
console.log(`\n Searching: "${fact}"`);
try {
const response = await axios.post(
"https://api.parallel.ai/v1beta/search",
{
mode: "one-shot",
objective: fact,
search_queries: [fact],
max_results: 3,
},
{
headers: {
"x-api-key": PARALLEL_API_KEY,
"Content-Type": "application/json",
"parallel-beta": "search-extract-2025-10-10",
},
timeout: 15000,
}
);
const results = response.data.results || [];
if (results.length === 0) {
console.log(` ❌ NO RESULTS - likely does not exist`);
issues.push({
fact,
issue: "NOT_FOUND",
evidence: "No web search results found for this claim",
});
} else {
const sources = results.map((r) => ({
title: r.title,
url: r.url || r.link,
content: r.excerpt || r.content || r.snippet || "(title only)",
}));
console.log(` βœ“ Found ${results.length} sources`);
results.forEach((r) => console.log(` - ${r.title}`));
grounded.push({
fact,
status: "VERIFIED",
sources,
});
}
} catch (error) {
console.log(` ⚠️ ERROR: ${error.message}`);
issues.push({
fact,
issue: "SEARCH_FAILED",
evidence: error.message,
});
}
}
console.log(`\nGrounding Results:`);
console.log(` βœ“ Verified: ${grounded.length}`);
console.log(` ❌ Issues: ${issues.length}`);
return {
groundedFacts: grounded,
groundingIssues: issues,
};
}
// ========================================
// NODE 3: PLAN VALIDATOR (Deterministic!)
// ========================================
async function validatePlan(state) {
console.log(`\n[${"3".padStart(2, "0")}] βœ… PLAN VALIDATOR (Deterministic)`);
const { groundedFacts, groundingIssues } = state;
// DETERMINISTIC VALIDATION RULES
const totalFacts = groundedFacts.length + groundingIssues.length;
const verifiedCount = groundedFacts.length;
const verificationRate = verifiedCount / totalFacts;
console.log(`Verification Rate: ${(verificationRate * 100).toFixed(1)}%`);
let planValid = false;
let confidence = 0;
const corrections = [];
// RULE 1: If >80% facts verified β†’ VALID
if (verificationRate >= 0.8) {
planValid = true;
confidence = 0.85 + (verificationRate - 0.8) * 0.5; // 0.85-0.95
console.log(`βœ“ VALID - High verification rate`);
}
// RULE 2: If 50-80% verified β†’ PARTIAL (need corrections)
else if (verificationRate >= 0.5) {
planValid = false;
confidence = 0.5 + (verificationRate - 0.5) * 0.5; // 0.5-0.65
console.log(`⚠️ PARTIAL - Some facts unverified, need corrections`);
groundingIssues.forEach((issue) => {
if (issue.issue === "NOT_FOUND") {
corrections.push({
problem: `Fact not found: "${issue.fact}"`,
recommendation: "Remove this claim or find alternative evidence",
severity: "HIGH",
});
}
});
}
// RULE 3: If <50% verified β†’ INVALID
else {
planValid = false;
confidence = 0.1 + verificationRate * 0.3; // 0.1-0.25
console.log(`❌ INVALID - Too many unverified facts`);
corrections.push({
problem: `Only ${(verificationRate * 100).toFixed(
0
)}% of facts could be verified`,
recommendation:
"Query is likely based on hallucinated or outdated information. Cannot provide reliable answer.",
severity: "CRITICAL",
});
}
// DETERMINISTIC CONFIDENCE CALCULATION
// Based on: verification rate + source quality + source count
const sourceQuality = groundedFacts.reduce((score, g) => {
const hasAuthoritative = g.sources.some(
(s) =>
s.url &&
(s.url.includes("wikipedia.org") ||
s.url.includes("github.com") ||
s.url.includes(".gov") ||
s.url.includes(".edu") ||
s.url.includes("npmjs.com") ||
s.url.includes("python.org"))
);
return score + (hasAuthoritative ? 1 : 0);
}, 0);
const qualityBonus = (sourceQuality / groundedFacts.length) * 0.1;
confidence = Math.min(0.95, confidence + qualityBonus);
console.log(`Confidence Score: ${confidence.toFixed(2)} (deterministic)`);
console.log(` - Verification rate: ${(verificationRate * 100).toFixed(0)}%`);
console.log(
` - Authoritative sources: ${sourceQuality}/${groundedFacts.length}`
);
if (corrections.length > 0) {
console.log(`\nCorrections needed:`);
corrections.forEach((c, i) => {
console.log(` ${i + 1}. [${c.severity}] ${c.problem}`);
console.log(` β†’ ${c.recommendation}`);
});
}
return {
planValid,
confidence,
corrections: corrections.length > 0 ? corrections : null,
};
}
// ========================================
// NODE 4: ANSWER EXECUTOR (with corrections)
// ========================================
async function executeAnswer(state) {
console.log(`\n[${"4".padStart(2, "0")}] 🎯 ANSWER EXECUTOR`);
if (!state.planValid && state.corrections) {
const criticalIssues = state.corrections.filter(
(c) => c.severity === "CRITICAL"
);
if (criticalIssues.length > 0) {
console.log(
`❌ Cannot execute - critical issues prevent reliable answer`
);
return {
finalAnswer: null,
finalJson: {
entity_name: "Unknown",
category: "UNKNOWN",
creation_date: null,
confidence_score: state.confidence,
validation_status: "REJECTED",
error: "Plan validation failed - facts could not be verified",
issues: state.corrections,
sources: [],
},
};
}
}
// Build context from grounded facts
const context = state.groundedFacts
.map((g) => {
const sourceText = g.sources
.map((s) => `Title: ${s.title}\nURL: ${s.url}\nContent: ${s.content}`)
.join("\n");
return `VERIFIED FACT: ${g.fact}\n\nEVIDENCE:\n${sourceText}`;
})
.join("\n\n" + "=".repeat(50) + "\n\n");
const corrections = state.corrections || [];
const correctionText =
corrections.length > 0
? `\n\nWARNINGS/CORRECTIONS:\n${corrections
.map((c) => `- ${c.problem}\n Fix: ${c.recommendation}`)
.join("\n")}`
: "";
const prompt = `
You are answering a user query using ONLY verified facts from web sources.
USER QUERY: "${state.userQuery}"
VERIFIED FACTS (from web search):
${context}${correctionText}
INSTRUCTIONS:
- Base your answer ONLY on the verified facts above
- DO NOT make claims that aren't supported by the evidence
- If multiple entities match the query (e.g., "Dyno"), mention the most relevant one(s)
- Be concise but accurate
- If facts are limited, acknowledge uncertainty
Provide a clear, factual answer (2-3 sentences):
`;
const response = await model.invoke(prompt);
const answer = response.content.trim();
console.log(`βœ“ Generated answer: "${answer.substring(0, 100)}..."`);
return {
finalAnswer: answer,
};
}
// ========================================
// NODE 5: OUTPUT STRUCTURER
// ========================================
async function structureOutput(state) {
console.log(`\n[${"5".padStart(2, "0")}] πŸ“¦ OUTPUT STRUCTURER`);
if (!state.finalAnswer) {
console.log(`Using pre-built rejection response`);
return {}; // Already set in executeAnswer
}
const allSources = state.groundedFacts
.flatMap((g) => g.sources.map((s) => s.url))
.filter(Boolean);
const prompt = `
Convert this answer into structured JSON.
ANSWER: "${state.finalAnswer}"
QUERY: "${state.userQuery}"
CONFIDENCE: ${state.confidence}
Extract:
- entity_name: Main entity being discussed
- category: SOFTWARE | HARDWARE | COMPANY | PERSON | CONCEPT | PLACE | OTHER
- creation_date: YYYY-MM-DD if mentioned, else null
- validation_status: Use "ACCEPTED" (plan was valid with minor/no corrections) or "CORRECTED" (plan had issues but was fixed)
Return ONLY JSON (no markdown):
{
"entity_name": "...",
"category": "...",
"creation_date": "...",
"confidence_score": ${state.confidence},
"validation_status": "...",
"answer": "${state.finalAnswer.replace(/"/g, '\\"')}"
}
`;
const response = await model.invoke(prompt);
const jsonText = response.content.replace(/```json|```/g, "").trim();
const structured = JSON.parse(jsonText);
structured.sources = allSources;
structured.verification_rate = `${(
(state.groundedFacts.length /
(state.groundedFacts.length + state.groundingIssues.length)) *
100
).toFixed(0)}%`;
if (state.corrections && state.corrections.length > 0) {
structured.corrections_applied = state.corrections.map((c) => c.problem);
}
console.log(`βœ“ Structured output created`);
console.log(` Category: ${structured.category}`);
console.log(` Confidence: ${structured.confidence_score}`);
console.log(` Sources: ${allSources.length}`);
return {
finalJson: structured,
sources: allSources,
};
}
// ========================================
// BUILD LANGGRAPH
// ========================================
const workflow = new StateGraph({ channels: graphState })
.addNode("planner", planActions)
.addNode("grounder", groundFacts)
.addNode("validator", validatePlan)
.addNode("executor", executeAnswer)
.addNode("structurer", structureOutput)
.addEdge(START, "planner")
.addEdge("planner", "grounder")
.addEdge("grounder", "validator")
.addEdge("validator", "executor")
.addEdge("executor", "structurer")
.addEdge("structurer", END);
const appFlow = workflow.compile();
console.log("βœ“ LangGraph compiled successfully\n");
// ========================================
// API ENDPOINT
// ========================================
app.post("/query", async (req, res) => {
const { question } = req.body;
console.log("\n" + "=".repeat(70));
console.log(`πŸ” NEW REQUEST: "${question}"`);
console.log("=".repeat(70));
try {
const result = await appFlow.invoke({ userQuery: question });
console.log("\n" + "=".repeat(70));
console.log("βœ… FINAL OUTPUT:");
console.log(JSON.stringify(result.finalJson, null, 2));
console.log("=".repeat(70) + "\n");
if (result.finalJson.validation_status === "REJECTED") {
return res.status(422).json(result.finalJson);
}
res.json(result.finalJson);
} catch (error) {
console.error("\n❌ ERROR:", error.message);
console.error(error.stack);
res.status(500).json({
error: "System failure",
details: error.message,
});
}
});
app.listen(PORT, () => {
console.log("=".repeat(70));
console.log(`πŸš€ Hybrid System running on port ${PORT}`);
console.log("=".repeat(70));
console.log("\nArchitecture:");
console.log(" Plan β†’ Ground β†’ Validate β†’ Execute β†’ Structure");
console.log("\nDeterministic Validation:");
console.log(" βœ“ Fact verification rate");
console.log(" βœ“ Source quality scoring");
console.log(" βœ“ No LLM self-validation");
console.log("\nEndpoint: POST http://localhost:3000/query");
console.log(' Body: {"question": "your query"}');
console.log("=".repeat(70) + "\n");
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment