Simplify research verbose logging

This commit is contained in:
Yiorgis Gozadinos 2025-09-19 09:44:46 +03:00
parent 5f102c8475
commit ccaae7cca1
No known key found for this signature in database
3 changed files with 74 additions and 90 deletions

View file

@ -70,14 +70,17 @@ from haiku.rag.client import HaikuRAG
from haiku.rag.research import ResearchOrchestrator from haiku.rag.research import ResearchOrchestrator
client = HaikuRAG(path_to_db) client = HaikuRAG(path_to_db)
orchestrator = ResearchOrchestrator(provider="openai", model="gpt-4o-mini") orchestrator = ResearchOrchestrator(
provider="ollama",
model="gpt-oss",
verbose=True
)
report = await orchestrator.conduct_research( report = await orchestrator.conduct_research(
question="What are the main drivers and recent trends of global temperature anomalies since 1990?", question="What are the main drivers and recent trends of global temperature anomalies since 1990?",
client=client, client=client,
max_iterations=2, max_iterations=2,
confidence_threshold=0.8, confidence_threshold=0.8,
verbose=False,
) )
print(report.title) print(report.title)

View file

@ -86,7 +86,7 @@ class HaikuRAGApp:
async with HaikuRAG(db_path=self.db_path) as client: async with HaikuRAG(db_path=self.db_path) as client:
try: try:
# Create orchestrator with default config or fallback to QA # Create orchestrator with default config or fallback to QA
orchestrator = ResearchOrchestrator() orchestrator = ResearchOrchestrator(verbose=verbose)
if verbose: if verbose:
self.console.print( self.console.print(
@ -100,8 +100,6 @@ class HaikuRAGApp:
question=question, question=question,
client=client, client=client,
max_iterations=max_iterations, max_iterations=max_iterations,
verbose=verbose,
console=self.console if verbose else None,
) )
# Display the report # Display the report

View file

@ -31,7 +31,10 @@ class ResearchOrchestrator(BaseResearchAgent[ResearchPlan]):
"""Orchestrator agent that coordinates the research workflow.""" """Orchestrator agent that coordinates the research workflow."""
def __init__( def __init__(
self, provider: str | None = Config.RESEARCH_PROVIDER, model: str | None = None self,
provider: str | None = Config.RESEARCH_PROVIDER,
model: str | None = None,
verbose: bool = False,
): ):
# Use provided values or fall back to config defaults # Use provided values or fall back to config defaults
provider = provider or Config.RESEARCH_PROVIDER or Config.QA_PROVIDER provider = provider or Config.RESEARCH_PROVIDER or Config.QA_PROVIDER
@ -49,6 +52,10 @@ class ResearchOrchestrator(BaseResearchAgent[ResearchPlan]):
provider, model provider, model
) )
self.synthesis_agent: SynthesisAgent = SynthesisAgent(provider, model) self.synthesis_agent: SynthesisAgent = SynthesisAgent(provider, model)
if verbose:
self._console = Console()
self.verbose = verbose
def get_system_prompt(self) -> str: def get_system_prompt(self) -> str:
return ORCHESTRATOR_PROMPT return ORCHESTRATOR_PROMPT
@ -73,14 +80,30 @@ class ResearchOrchestrator(BaseResearchAgent[ResearchPlan]):
} }
return format_as_xml(context_data, root_tag="research_context") return format_as_xml(context_data, root_tag="research_context")
def _should_stop_research(
self,
evaluation_result: AgentRunResult[EvaluationResult],
confidence_threshold: float,
) -> bool:
"""Determine if research should stop based on evaluation."""
result = evaluation_result.output
return result.is_sufficient and result.confidence_score >= confidence_threshold
def _log(self, line="", rule=False):
if not self._console:
return
if rule:
self._console.rule(line)
else:
self._console.print(line)
async def conduct_research( async def conduct_research(
self, self,
question: str, question: str,
client: Any, client: Any,
max_iterations: int = 3, max_iterations: int = 3,
confidence_threshold: float = 0.8, confidence_threshold: float = 0.8,
verbose: bool = False,
console: Console | None = None,
) -> ResearchReport: ) -> ResearchReport:
"""Conduct comprehensive research on a question. """Conduct comprehensive research on a question.
@ -100,16 +123,10 @@ class ResearchOrchestrator(BaseResearchAgent[ResearchPlan]):
context = ResearchContext(original_question=question) context = ResearchContext(original_question=question)
deps = ResearchDependencies(client=client, context=context) deps = ResearchDependencies(client=client, context=context)
# Use provided console or create a new one
console = console or Console() if verbose else None
# Create initial research plan # Create initial research plan
# Run a simple presearch survey to summarize KB context # Run a simple presearch survey to summarize KB context
if console: self._log("\n[bold cyan]📋 Creating research plan...[/bold cyan]")
console.print("\n[bold cyan]📋 Creating research plan...[/bold cyan]") self._log("\n[bold cyan]🔎 Presearch: summarizing KB context...[/bold cyan]")
console.print(
"\n[bold cyan]🔎 Presearch: summarizing KB context...[/bold cyan]"
)
presearch_result = await self.presearch_agent.run(question, deps=deps) presearch_result = await self.presearch_agent.run(question, deps=deps)
@ -126,28 +143,23 @@ class ResearchOrchestrator(BaseResearchAgent[ResearchPlan]):
) )
context.sub_questions = plan_result.output.sub_questions context.sub_questions = plan_result.output.sub_questions
if console: self._log("\n[bold green]✅ Research Plan Created:[/bold green]")
console.print("\n[bold green]✅ Research Plan Created:[/bold green]") self._log(f" [bold]Main Question:[/bold] {plan_result.output.main_question}")
console.print( self._log(" [bold]Sub-questions:[/bold]")
f" [bold]Main Question:[/bold] {plan_result.output.main_question}"
)
console.print(" [bold]Sub-questions:[/bold]")
for i, sq in enumerate(plan_result.output.sub_questions, 1): for i, sq in enumerate(plan_result.output.sub_questions, 1):
console.print(f" {i}. {sq}") self._log(f" {i}. {sq}")
console.print()
# Execute research iterations # Execute research iterations
for iteration in range(max_iterations): for iteration in range(max_iterations):
if console: self._log(
console.rule( f"[bold yellow]🔄 Iteration {iteration + 1}/{max_iterations}[/bold yellow]",
f"[bold yellow]🔄 Iteration {iteration + 1}/{max_iterations}[/bold yellow]" rule=True,
) )
# Check if we have questions to search # Check if we have questions to search
if not context.sub_questions: if not context.sub_questions:
# No more questions to explore # No more questions to explore
if console: self._log(
console.print(
"[yellow]No more questions to explore. Concluding research.[/yellow]" "[yellow]No more questions to explore. Concluding research.[/yellow]"
) )
break break
@ -156,12 +168,11 @@ class ResearchOrchestrator(BaseResearchAgent[ResearchPlan]):
questions_to_search = context.sub_questions questions_to_search = context.sub_questions
# Search phase - answer all questions in this iteration # Search phase - answer all questions in this iteration
if console: self._log(
console.print(
f"\n[bold cyan]🔍 Searching & Answering {len(questions_to_search)} questions:[/bold cyan]" f"\n[bold cyan]🔍 Searching & Answering {len(questions_to_search)} questions:[/bold cyan]"
) )
for i, q in enumerate(questions_to_search, 1): for i, q in enumerate(questions_to_search, 1):
console.print(f" {i}. {q}") self._log(f" {i}. {q}")
# Run searches for all questions and remove answered ones # Run searches for all questions and remove answered ones
answered_questions = [] answered_questions = []
@ -169,27 +180,22 @@ class ResearchOrchestrator(BaseResearchAgent[ResearchPlan]):
try: try:
await self.search_agent.run(search_question, deps=deps) await self.search_agent.run(search_question, deps=deps)
except Exception as e: # pragma: no cover - defensive except Exception as e: # pragma: no cover - defensive
if console: self._log(
console.print(
f"\n [red]×[/red] Omitting failed question: {search_question} ({e})" f"\n [red]×[/red] Omitting failed question: {search_question} ({e})"
) )
finally: finally:
answered_questions.append(search_question) answered_questions.append(search_question)
if console and context.qa_responses: if self._console and context.qa_responses:
# Show the last QA response (which should be for this question) # Show the last QA response (which should be for this question)
latest_qa = context.qa_responses[-1] latest_qa = context.qa_responses[-1]
answer_preview = ( answer_preview = (
latest_qa.answer[:150] + "..." latest_qa.answer[:150] + ""
if len(latest_qa.answer) > 150 if len(latest_qa.answer) > 150
else latest_qa.answer else latest_qa.answer
) )
console.print( self._log(f"\n [green]✓[/green] {search_question}")
f"\n [green]✓[/green] {search_question[:50]}..." self._log(f" {answer_preview}")
if len(search_question) > 50
else f"\n [green]✓[/green] {search_question}"
)
console.print(f" {answer_preview}")
# Remove answered questions from the list # Remove answered questions from the list
for question in answered_questions: for question in answered_questions:
@ -197,8 +203,7 @@ class ResearchOrchestrator(BaseResearchAgent[ResearchPlan]):
context.sub_questions.remove(question) context.sub_questions.remove(question)
# Analysis and Evaluation phase # Analysis and Evaluation phase
if console: self._log(
console.print(
"\n[bold cyan]📊 Analyzing and evaluating research progress...[/bold cyan]" "\n[bold cyan]📊 Analyzing and evaluating research progress...[/bold cyan]"
) )
@ -215,19 +220,14 @@ Evaluate the research progress for the original question and identify any remain
deps=deps, deps=deps,
) )
if console and evaluation_result.output:
output = evaluation_result.output output = evaluation_result.output
if output.key_insights: if output.key_insights:
console.print(" [bold]Key insights:[/bold]") self._log(" [bold]Key insights:[/bold]")
for insight in output.key_insights: for insight in output.key_insights:
console.print(f"{insight}") self._log(f"{insight}")
console.print( self._log(f" Confidence: [yellow]{output.confidence_score:.1%}[/yellow]")
f" Confidence: [yellow]{output.confidence_score:.1%}[/yellow]" status = "[green]Yes[/green]" if output.is_sufficient else "[red]No[/red]"
) self._log(f" Sufficient: {status}")
status = (
"[green]Yes[/green]" if output.is_sufficient else "[red]No[/red]"
)
console.print(f" Sufficient: {status}")
# Store insights # Store insights
for insight in evaluation_result.output.key_insights: for insight in evaluation_result.output.key_insights:
@ -240,17 +240,13 @@ Evaluate the research progress for the original question and identify any remain
# Check if research is sufficient # Check if research is sufficient
if self._should_stop_research(evaluation_result, confidence_threshold): if self._should_stop_research(evaluation_result, confidence_threshold):
if console: self._log(
console.print(
f"\n[bold green]✅ Stopping research:[/bold green] {evaluation_result.output.reasoning}" f"\n[bold green]✅ Stopping research:[/bold green] {evaluation_result.output.reasoning}"
) )
break break
# Generate final report # Generate final report
if console: self._log("\n[bold cyan]📝 Generating final research report...[/bold cyan]")
console.print(
"\n[bold cyan]📝 Generating final research report...[/bold cyan]"
)
# Format context for the synthesis agent # Format context for the synthesis agent
final_context_xml = self._format_context_for_prompt(context) final_context_xml = self._format_context_for_prompt(context)
@ -264,19 +260,6 @@ Create a detailed report that synthesizes all findings into a coherent response.
synthesis_prompt, deps=deps synthesis_prompt, deps=deps
) )
if console: self._log("[bold green]✅ Research complete![/bold green]")
console.print("[bold green]✅ Research complete![/bold green]")
return report_result.output return report_result.output
def _should_stop_research(
self,
evaluation_result: AgentRunResult[EvaluationResult],
confidence_threshold: float,
) -> bool:
"""Determine if research should stop based on evaluation."""
result = evaluation_result.output
# Stop if the agent indicates sufficient information AND confidence exceeds threshold
return result.is_sufficient and result.confidence_score >= confidence_threshold