Skip to content

Commit 1041a3c

Browse files
committed
chore: add hitl for generic agent
1 parent cfdb761 commit 1041a3c

3 files changed

Lines changed: 76 additions & 7 deletions

File tree

samples/generic-rag-agent/main.py

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from llama_index.core.workflow import (
99
Context,
1010
Event,
11+
HumanResponseEvent,
12+
InputRequiredEvent,
1113
StartEvent,
1214
StopEvent,
1315
Workflow,
@@ -42,8 +44,8 @@
4244
uipath = UiPath()
4345

4446
class CustomStartEvent(StartEvent):
45-
query: str
46-
add_data_to_index: bool
47+
query: str = ""
48+
add_data_to_index: bool = False
4749

4850

4951
class QueryEvent(Event):
@@ -66,6 +68,12 @@ class AgentAnswerEvent(Event):
6668
agent_answer: str
6769

6870

71+
class FormattedAnswerEvent(Event):
72+
"""Event containing the formatted answer awaiting human confirmation."""
73+
formatted_answer: str
74+
iteration_count: int
75+
76+
6977
class OutputEvent(StopEvent):
7078
"""Event representing the final, formatted output."""
7179
output: str
@@ -154,6 +162,8 @@ async def workflow_entrypoint(
154162
self, ctx: Context, ev: CustomStartEvent
155163
) -> QueryEvent | AddDataToIndexEvent:
156164
await ctx.store.set("original_query", ev.query)
165+
await ctx.store.set("iteration_count", 0)
166+
await ctx.store.set("feedback_history", [])
157167

158168
if ev.add_data_to_index:
159169
return AddDataToIndexEvent()
@@ -322,13 +332,22 @@ async def process_query(
322332
@step
323333
async def format_final_answer(
324334
self, ctx: Context, ev: AgentAnswerEvent
325-
) -> OutputEvent:
335+
) -> FormattedAnswerEvent:
326336
"""
327337
Takes the ReAct Agent's raw answer and formats it into the final structure.
328338
"""
329339
original_query = await ctx.store.get("original_query")
340+
iteration_count = await ctx.store.get("iteration_count")
341+
feedback_history = await ctx.store.get("feedback_history", [])
330342
agent_answer = ev.agent_answer
331343

344+
feedback_context = ""
345+
if feedback_history:
346+
feedback_context = "\n\nPrevious feedback from the user:\n"
347+
for i, feedback in enumerate(feedback_history, 1):
348+
feedback_context += f"{i}. {feedback}\n"
349+
feedback_context += "\nPlease address the feedback above in your reformatted answer.\n"
350+
332351
prompt = f"""
333352
You are a final synthesis engine. You are given the user's original query and
334353
a comprehensive answer generated by an intelligent multi-tool agent.
@@ -352,6 +371,7 @@ async def format_final_answer(
352371
If applicable, suggest actionable items or state relevant policies.
353372
354373
Original query: {original_query}
374+
{feedback_context}
355375
356376
Agent's Comprehensive Answer to be Formatted:
357377
---
@@ -364,10 +384,59 @@ async def format_final_answer(
364384

365385
print("Final formatting complete.")
366386

367-
return OutputEvent(
368-
output=response.text,
387+
return FormattedAnswerEvent(
388+
formatted_answer=response.text,
389+
iteration_count=iteration_count
390+
)
391+
392+
@step
393+
async def request_human_confirmation(
394+
self, ctx: Context, ev: FormattedAnswerEvent
395+
) -> OutputEvent | QueryEvent:
396+
"""
397+
Present the formatted answer to the user and request confirmation.
398+
"""
399+
await ctx.store.set("last_formatted_answer", ev.formatted_answer)
400+
401+
print("\n" + "="*80)
402+
print("FORMATTED ANSWER:")
403+
print("="*80)
404+
print(ev.formatted_answer)
405+
print("="*80)
406+
407+
ctx.write_event_to_stream(
408+
InputRequiredEvent(
409+
prefix="\nIs this answer satisfactory? (yes/no or provide feedback): "
410+
)
369411
)
370412

413+
response = await ctx.wait_for_event(HumanResponseEvent)
414+
feedback = response.response.strip().lower()
415+
416+
print(f"Received response: {feedback}")
417+
418+
if feedback == "yes":
419+
print("\nAnswer approved by user.")
420+
return OutputEvent(output=ev.formatted_answer)
421+
422+
max_iterations = 3
423+
iteration_count = ev.iteration_count
424+
425+
if iteration_count >= max_iterations:
426+
print(f"\nMaximum iterations ({max_iterations}) reached.")
427+
return OutputEvent(
428+
output=f"{ev.formatted_answer}\n\n---\nNote: Maximum iteration limit reached. Please refine your query or requirements."
429+
)
430+
431+
feedback_history = await ctx.store.get("feedback_history", [])
432+
feedback_history.append(feedback)
433+
await ctx.store.set("feedback_history", feedback_history)
434+
await ctx.store.set("iteration_count", iteration_count + 1)
435+
436+
print(f"\nFeedback received: '{feedback}'. Re-querying (iteration {iteration_count + 1})...")
437+
438+
return QueryEvent()
439+
371440

372441
agent = AgentWorkflow(timeout=600, verbose=True)
373442

samples/generic-rag-agent/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "generic-rag-agent"
3-
version = "0.0.24"
3+
version = "0.0.27"
44
description = "A generic RAG agent that combines multiple knowledge bases with web search capabilities and MCP tool support"
55
authors = [{ name = "John Doe", email = "john.doe@myemail.com" }]
66
readme = { file = "README.md", content-type = "text/markdown" }

samples/generic-rag-agent/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)