HIL for plan approval

This commit is contained in:
Yiorgis Gozadinos 2025-10-20 14:54:03 +03:00
parent a3a26056ff
commit ebbe668a18
No known key found for this signature in database
3 changed files with 114 additions and 11 deletions

View file

@ -68,30 +68,36 @@ def create_agent(
qa_provider: QA provider for the agent (default: from Config.QA_PROVIDER)
qa_model: Model name to use (default: from Config.QA_MODEL)
"""
print(f"[AGENT SETUP] Creating agent with provider={qa_provider}, model={qa_model}")
agent = Agent(
model=get_model(qa_provider, qa_model),
deps_type=ResearchDeps,
instructions="""You are a research co-pilot powered by haiku.rag.
Your workflow:
1. When user asks a question, IMMEDIATELY call propose_research_plan with the question
2. Wait for user approval before proceeding
3. Once approved, process questions ONE AT A TIME:
Your workflow MUST follow these exact steps in order:
1. Call propose_research_plan with the user's question
2. After propose_research_plan completes, IMMEDIATELY call approve_research_plan (with no arguments)
3. WAIT for approve_research_plan to return:
- If it returns "APPROVED", proceed to step 4
- If it returns "REVISE", ask the user "How would you like me to revise the research plan?" and wait for their response
- Once you receive their revision feedback, revise the plan and go back to step 1
4. Once approved, process questions ONE AT A TIME:
- Call search_question(question_id=0) and WAIT for it to complete
- Then call extract_insights_from_results(question_id=0) and WAIT for it to complete
- Then call search_question(question_id=1) and WAIT for it to complete
- Then call extract_insights_from_results(question_id=1) and WAIT for it to complete
- Then call search_question(question_id=2) and WAIT for it to complete
- Then call extract_insights_from_results(question_id=2) and WAIT for it to complete
4. After all questions are processed, call evaluate_research_confidence
5. Ask user if they want to finalize or continue researching
6. When user approves, call synthesize_final_report
5. After all questions are processed, call evaluate_research_confidence
6. Ask user if they want to finalize or continue researching
7. When user approves, call synthesize_final_report
CRITICAL RULES:
- MANDATORY: Call approve_research_plan immediately after propose_research_plan - NO EXCEPTIONS
- If approve_research_plan returns "REVISE", ask the user for revision feedback naturally in chat
- Call ONE tool at a time - wait for each tool to return before calling the next
- NEVER call extract_insights_from_results until search_question has completed and returned results
- DO NOT explain what you're about to do - just call the tool
- DO NOT say "I'll search for..." or "Let me search..." - just call search_question
- The state updates will show the user what's happening - you don't need to narrate
- Process all 3 questions automatically without asking for approval between them
@ -152,6 +158,7 @@ Return ONLY a JSON array of sub-questions, like: ["Question 1?", "Question 2?",
ctx.deps.state.status = f"Proposed plan with {len(plan)} sub-questions"
print(f"[AGENT] Plan created with {len(plan)} sub-questions")
print("[AGENT] Sending state snapshot to frontend")
print("[AGENT] *** NEXT STEP: Agent should call approve_research_plan ***")
return _as_state_snapshot(ctx)

View file

@ -6,9 +6,6 @@ import {
} from "@copilotkit/runtime";
import type { NextRequest } from "next/server";
// Service adapter for multi-agent support (empty since we only have one agent)
const serviceAdapter = new ExperimentalEmptyAdapter();
// Connect CopilotKit to PydanticAI via HttpAgent
// The HttpAgent creates a bridge between the Next.js frontend and the Python backend
// It communicates with the server created by agent.to_ag_ui()
@ -21,6 +18,9 @@ const runtime = new CopilotRuntime({
},
});
// Service adapter for multi-agent support (empty since we only have one agent)
const serviceAdapter = new ExperimentalEmptyAdapter();
// Next.js API route handler that proxies requests between frontend and backend
export async function POST(request: NextRequest) {
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({

View file

@ -1,9 +1,11 @@
"use client";
import React, { useState } from "react";
import {
CopilotKit,
useCoAgent,
useCoAgentStateRender,
useCopilotAction,
} from "@copilotkit/react-core";
import { CopilotChat } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
@ -94,6 +96,100 @@ function AgentContent() {
// Log state changes
console.log("[FRONTEND] Current state:", state);
// Human-in-the-loop: Request approval for research plan
console.log("[FRONTEND] Registering approve_research_plan action");
useCopilotAction({
name: "approve_research_plan",
description:
"Request user approval for the research plan. Returns 'APPROVED' if approved or 'REVISE' if user wants to revise.",
parameters: [],
renderAndWaitForResponse: ({ respond, status }) => {
console.log(
"[FRONTEND ACTION] renderAndWaitForResponse called",
{ status }
);
return (
<div
style={{
padding: "1.5rem",
background: "white",
borderRadius: "8px",
border: "2px solid #4299e1",
marginBottom: "1rem",
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
}}
>
<h3
style={{
fontSize: "1.25rem",
fontWeight: "bold",
marginBottom: "1rem",
color: "#2d3748",
}}
>
Research Plan Approval
</h3>
<p
style={{
fontSize: "0.875rem",
color: "#4a5568",
marginBottom: "1rem",
}}
>
Please review the research plan in the right pane.
</p>
<div
style={{
display: "flex",
gap: "1rem",
}}
className={status !== "executing" ? "hidden" : ""}
>
<button
type="button"
onClick={() => respond?.("REVISE")}
disabled={status !== "executing"}
style={{
flex: 1,
padding: "0.75rem",
background: "white",
border: "2px solid #e2e8f0",
borderRadius: "6px",
fontSize: "0.875rem",
fontWeight: "600",
cursor: status === "executing" ? "pointer" : "not-allowed",
opacity: status === "executing" ? 1 : 0.5,
}}
>
Revise Plan
</button>
<button
type="button"
onClick={() => respond?.("APPROVED")}
disabled={status !== "executing"}
style={{
flex: 1,
padding: "0.75rem",
background: "#4299e1",
color: "white",
border: "none",
borderRadius: "6px",
fontSize: "0.875rem",
fontWeight: "600",
cursor: status === "executing" ? "pointer" : "not-allowed",
opacity: status === "executing" ? 1 : 0.5,
}}
>
Approve & Start Research
</button>
</div>
</div>
);
},
});
// Render state updates from the research agent
useCoAgentStateRender<ResearchState>({
name: "research_agent",