File size: 3,320 Bytes
20b15f3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | import uuid
from fastapi import APIRouter, BackgroundTasks, HTTPException
from langgraph.types import Command
from app.core.logging_config import get_node_logger
from app.modules.intent.graph import graph as intent_graph
from app.modules.intent.schemas import (
IntentResumeRequest,
IntentResumeResponse,
IntentStartRequest,
IntentStartResponse,
)
from app.modules.search.jobs import create_job, run_search_job
router = APIRouter(prefix="/intent", tags=["intent"])
@router.post("/start", response_model=IntentStartResponse)
def start_intent(payload: IntentStartRequest):
"""Runs the intent graph's parallel framing + polish nodes. The graph
hits human_review_node's interrupt() immediately, so this call always
returns the paused state — it never runs to completion on its own."""
thread_id = str(uuid.uuid4())
config = {"configurable": {"thread_id": thread_id}}
logger = get_node_logger(thread_id, "api.intent_start")
logger.info("New research query received (%d chars)", len(payload.user_query))
result = intent_graph.invoke({"user_query": payload.user_query, "run_id": thread_id}, config=config)
if "__interrupt__" not in result:
# Defensive: should never happen given the graph's fixed structure,
# but fail loudly rather than return a malformed response.
logger.error("Intent graph did not pause for review as expected")
raise HTTPException(status_code=500, detail="Intent graph did not pause for review as expected.")
interrupt_payload = result["__interrupt__"][0].value
logger.info("Intent framing complete — paused for human review")
return IntentStartResponse(
thread_id=thread_id,
polished_research_intent=interrupt_payload["polished_research_intent"],
instruction=interrupt_payload["instruction"],
)
@router.post("/resume", response_model=IntentResumeResponse)
def resume_intent(payload: IntentResumeRequest, background_tasks: BackgroundTasks):
"""Resumes the paused intent graph with the human-edited text, then
immediately kicks off the search graph as a background job using the
resulting human_verified_intent — no separate /search/start call needed."""
config = {"configurable": {"thread_id": payload.thread_id}}
logger = get_node_logger(payload.thread_id, "api.intent_resume")
logger.info("Resuming intent graph with human-edited text")
result = intent_graph.invoke(Command(resume=payload.edited_intent), config=config)
if "human_verified_intent" not in result:
logger.error("No paused intent graph found for this thread_id, or it was already resumed")
raise HTTPException(
status_code=404,
detail="No paused intent graph found for this thread_id, or it has already been resumed.",
)
human_verified_intent = result["human_verified_intent"]
job_id = str(uuid.uuid4())
create_job(job_id, payload.thread_id)
logger.info("Human-verified intent finalized — starting search job %s", job_id)
background_tasks.add_task(run_search_job, job_id, human_verified_intent, payload.thread_id)
return IntentResumeResponse(
thread_id=payload.thread_id,
human_verified_intent=human_verified_intent,
job_id=job_id,
status="running",
)
|