HIL for plan approval
This commit is contained in:
parent
a3a26056ff
commit
ebbe668a18
3 changed files with 114 additions and 11 deletions
|
|
@ -68,30 +68,36 @@ def create_agent(
|
||||||
qa_provider: QA provider for the agent (default: from Config.QA_PROVIDER)
|
qa_provider: QA provider for the agent (default: from Config.QA_PROVIDER)
|
||||||
qa_model: Model name to use (default: from Config.QA_MODEL)
|
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(
|
agent = Agent(
|
||||||
model=get_model(qa_provider, qa_model),
|
model=get_model(qa_provider, qa_model),
|
||||||
deps_type=ResearchDeps,
|
deps_type=ResearchDeps,
|
||||||
instructions="""You are a research co-pilot powered by haiku.rag.
|
instructions="""You are a research co-pilot powered by haiku.rag.
|
||||||
|
|
||||||
Your workflow:
|
Your workflow MUST follow these exact steps in order:
|
||||||
1. When user asks a question, IMMEDIATELY call propose_research_plan with the question
|
1. Call propose_research_plan with the user's question
|
||||||
2. Wait for user approval before proceeding
|
2. After propose_research_plan completes, IMMEDIATELY call approve_research_plan (with no arguments)
|
||||||
3. Once approved, process questions ONE AT A TIME:
|
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
|
- 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 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 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 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 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
|
- 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. After all questions are processed, call evaluate_research_confidence
|
||||||
5. Ask user if they want to finalize or continue researching
|
6. Ask user if they want to finalize or continue researching
|
||||||
6. When user approves, call synthesize_final_report
|
7. When user approves, call synthesize_final_report
|
||||||
|
|
||||||
CRITICAL RULES:
|
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
|
- 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
|
- 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 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
|
- 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
|
- 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"
|
ctx.deps.state.status = f"Proposed plan with {len(plan)} sub-questions"
|
||||||
print(f"[AGENT] Plan created with {len(plan)} sub-questions")
|
print(f"[AGENT] Plan created with {len(plan)} sub-questions")
|
||||||
print("[AGENT] Sending state snapshot to frontend")
|
print("[AGENT] Sending state snapshot to frontend")
|
||||||
|
print("[AGENT] *** NEXT STEP: Agent should call approve_research_plan ***")
|
||||||
|
|
||||||
return _as_state_snapshot(ctx)
|
return _as_state_snapshot(ctx)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,6 @@ import {
|
||||||
} from "@copilotkit/runtime";
|
} from "@copilotkit/runtime";
|
||||||
import type { NextRequest } from "next/server";
|
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
|
// Connect CopilotKit to PydanticAI via HttpAgent
|
||||||
// The HttpAgent creates a bridge between the Next.js frontend and the Python backend
|
// 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()
|
// 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
|
// Next.js API route handler that proxies requests between frontend and backend
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
|
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
CopilotKit,
|
CopilotKit,
|
||||||
useCoAgent,
|
useCoAgent,
|
||||||
useCoAgentStateRender,
|
useCoAgentStateRender,
|
||||||
|
useCopilotAction,
|
||||||
} from "@copilotkit/react-core";
|
} from "@copilotkit/react-core";
|
||||||
import { CopilotChat } from "@copilotkit/react-ui";
|
import { CopilotChat } from "@copilotkit/react-ui";
|
||||||
import "@copilotkit/react-ui/styles.css";
|
import "@copilotkit/react-ui/styles.css";
|
||||||
|
|
@ -94,6 +96,100 @@ function AgentContent() {
|
||||||
// Log state changes
|
// Log state changes
|
||||||
console.log("[FRONTEND] Current state:", state);
|
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
|
// Render state updates from the research agent
|
||||||
useCoAgentStateRender<ResearchState>({
|
useCoAgentStateRender<ResearchState>({
|
||||||
name: "research_agent",
|
name: "research_agent",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue