14.5 Service Layer Implementation
This section will introduce the implementation of core services in detail, including PlanningService, SummarizationService, ReportingService, and SearchService. These services are the bridge connecting Agents and tools, responsible for specific business logic.
14.5.1 Task Planning Service
PlanningService is responsible for calling the research planning Agent to decompose the research topic into subtasks. This is the first and most critical step of the entire research process.
(1) Implementation Approach
Its core responsibilities are:
- Build planning Prompt: Build Prompt based on research topic and current date
- Call planning Agent: Call TODO Planner Agent to generate subtask list
- Parse JSON response: Extract JSON-format subtask list from Agent's response
- Validate subtask format: Ensure each subtask contains required fields (title, intent, query)
import re
import json
from typing import List, Callable, Optional
from datetime import datetime
from hello_agents import HelloAgentsLLM
from hello_agents import ToolAwareSimpleAgent
from models import TodoItem, SummaryState
from prompts import todo_planner_instructions
class PlanningService:
"""Task planning service"""
def __init__(
self,
llm: HelloAgentsLLM,
tool_call_listener: Optional[Callable] = None
):
self._llm = llm
self._tool_call_listener = tool_call_listener
# Create planning Agent
self._agent = ToolAwareSimpleAgent(
name="TODO Planner",
system_prompt="You are a research planning expert, skilled at decomposing complex research topics into clear subtasks.",
llm=llm,
tool_call_listener=tool_call_listener
)
def plan_todo_list(self, state: SummaryState) -> List[TodoItem]:
"""Plan TODO list
Args:
state: Research state, containing research topic
Returns:
Subtask list
"""
# Build Prompt
prompt = todo_planner_instructions.format(
current_date=self._get_current_date(),
research_topic=state.research_topic,
)
# Call Agent
response = self._agent.run(prompt)
# Parse JSON
tasks_payload = self._extract_tasks(response)
# Validate and create TodoItem
todo_items = []
for idx, item in enumerate(tasks_payload, start=1):
# Validate required fields
if not all(key in item for key in ["title", "intent", "query"]):
raise ValueError(f"Task {idx} is missing required fields")
task = TodoItem(
id=idx,
title=item["title"],
intent=item["intent"],
query=item["query"],
)
todo_items.append(task)
return todo_items
def _get_current_date(self) -> str:
"""Get current date"""
return datetime.now().strftime("%Y-%m-%d")
def _extract_tasks(self, response: str) -> List[dict]:
"""Extract JSON from Agent response
The Agent's response may contain extra text, such as:
"Okay, I will plan the following tasks for you:\n[{...}, {...}]\nThese tasks cover..."
We need to extract the JSON part.
"""
# Method 1: Use regex to extract JSON array
json_match = re.search(r'\[.*\]', response, re.DOTALL)
if json_match:
json_str = json_match.group(0)
try:
return json.loads(json_str)
except json.JSONDecodeError as e:
raise ValueError(f"JSON parsing failed: {e}")
# Method 2: If no JSON array is found, try to parse the entire response directly
try:
return json.loads(response)
except json.JSONDecodeError:
raise ValueError("Unable to extract JSON from response")(2) JSON Parsing and Validation
The JSON returned by the Agent may contain extra text or format errors, so we need robust parsing logic:
Common Issues:
- Contains extra text: Agent may add explanatory text before and after JSON
- Format errors: JSON may be missing quotes, commas, etc.
- Missing fields: Some subtasks may be missing required fields
Solutions:
- Use regex: Extract JSON part
- Multiple parsing strategies: First try to extract JSON array, then try to parse directly
- Field validation: Ensure each subtask contains required fields
Example:
# Agent response example 1: Contains extra text
response1 = """
Okay, I will plan the following tasks for you:
[
{
"title": "What is a multimodal model",
"intent": "Understand basic concepts",
"query": "multimodal model definition"
},
{
"title": "Latest multimodal models",
"intent": "Understand technical status",
"query": "latest multimodal models 2024"
}
]
These tasks cover the basic information and core projects of the Datawhale organization.
"""
# Extract JSON
tasks1 = service._extract_tasks(response1)
# Result: [{"title": "Basic information about Datawhale", ...}, ...]
# Agent response example 2: Pure JSON
response2 = """
[
{"title": "Basic information about Datawhale", "intent": "Understand organizational positioning", "query": "Datawhale organization introduction"},
{"title": "Main projects of Datawhale", "intent": "Understand core content", "query": "Datawhale projects tutorials 2024"}
]
"""
# Extract JSON
tasks2 = service._extract_tasks(response2)
# Result: [{"title": "What is a multimodal model", ...}, ...](3) Planning Quality Assessment
A good plan should meet the following criteria:
- Comprehensive coverage: Cover all important aspects of the topic
- Clear logic: Clear logical relationships between subtasks
- Precise queries: Search queries can accurately find relevant materials
- Appropriate quantity: 3-5 subtasks
We can add an evaluation method:
def evaluate_plan(self, todo_items: List[TodoItem]) -> dict:
"""Evaluate planning quality
Returns:
Evaluation results, including score and suggestions
"""
score = 100
suggestions = []
# Check quantity
if len(todo_items) < 3:
score -= 20
suggestions.append("Too few subtasks, may miss important information")
elif len(todo_items) > 5:
score -= 10
suggestions.append("Too many subtasks, may have redundancy")
# Check query quality
for task in todo_items:
if len(task.query.split()) < 2:
score -= 10
suggestions.append(f"Query for task '{task.title}' is too simple")
# Check logical relationships
# (More complex logic checks can be added here)
return {
"score": score,
"suggestions": suggestions
}14.5.2 Summarization Service
SummarizationService is responsible for calling the task summarization Agent to summarize search results. This is the core link of the research process and determines the quality of the research.
Its responsibilities are:
- Format search results: Format search results into readable text
- Build summarization Prompt: Build Prompt based on task information and search results
- Call summarization Agent: Call Task Summarizer Agent to generate summary
- Extract source citations: Extract source citations from summary
Core code:
from typing import List, Callable, Optional, Tuple
from hello_agents import HelloAgentsLLM
from hello_agents import ToolAwareSimpleAgent
from models import TodoItem
from prompts import task_summarizer_instructions
class SummarizationService:
"""Summarization service"""
def __init__(
self,
llm: HelloAgentsLLM,
tool_call_listener: Optional[Callable] = None
):
self._llm = llm
self._tool_call_listener = tool_call_listener
# Create summarization Agent
self._agent = ToolAwareSimpleAgent(
name="Task Summarizer",
system_prompt="You are a task summarization expert, skilled at extracting key information from search results.",
llm=llm,
tool_call_listener=tool_call_listener
)
def summarize_task(
self,
task: TodoItem,
search_results: List[dict]
) -> Tuple[str, List[str]]:
"""Summarize task
Args:
task: Task information
search_results: Search results list
Returns:
(Summary text, source URL list)
"""
# Format search results
formatted_sources = self._format_sources(search_results)
# Build Prompt
prompt = task_summarizer_instructions.format(
task_title=task.title,
task_intent=task.intent,
task_query=task.query,
search_results=formatted_sources,
)
# Call Agent
summary = self._agent.run(prompt)
# Extract source URLs
source_urls = [result["url"] for result in search_results]
return summary, source_urls
def _format_sources(self, search_results: List[dict]) -> str:
"""Format search results
Format search results into readable text, including:
- Serial number
- Title
- URL
- Snippet
"""
formatted = []
for idx, result in enumerate(search_results, start=1):
formatted.append(
f"[{idx}] {result['title']}\n"
f"URL: {result['url']}\n"
f"Snippet: {result['snippet']}\n"
)
return "\n".join(formatted)Report Structure Design
The final report should include the following parts: