自定义规划多智能体系统¶
本笔记本将探讨如何通过提示大型语言模型(LLM)来编写、优化并执行计划,利用多个智能体生成报告。
本文并非构建报告生成系统的完整指南,而是旨在为您提供知识和工具,帮助您构建能够规划并协调多个智能体实现目标的健壮系统。
本笔记本假设您已阅读基础智能体工作流笔记本或智能体工作流文档,以及工作流文档。
In [ ]:
Copied!
%pip install llama-index
%pip install llama-index
In [ ]:
Copied!
from llama_index.llms.openai import OpenAI
sub_agent_llm = OpenAI(model="gpt-4.1-mini", api_key="sk-...")
from llama_index.llms.openai import OpenAI
sub_agent_llm = OpenAI(model="gpt-4.1-mini", api_key="sk-...")
系统设计¶
我们的系统将包含三个智能体:
ResearchAgent
:负责在网络上搜索与给定主题相关的信息。WriteAgent
:利用ResearchAgent
获取的信息撰写报告。ReviewAgent
:负责审阅报告并提供反馈意见。
我们将使用一个顶层的大型语言模型(LLM)来手动协调和规划这些智能体的协作,最终完成报告撰写。
虽然系统有多种实现方式,但本项目将采用单一的web_search
工具来执行网络信息检索。
In [ ]:
Copied!
%pip install tavily-python
%pip install tavily-python
In [ ]:
Copied!
from tavily import AsyncTavilyClient
async def search_web(query: str) -> str:
"""Useful for using the web to answer questions."""
client = AsyncTavilyClient(api_key="tvly-...")
return str(await client.search(query))
from tavily import AsyncTavilyClient
async def search_web(query: str) -> str:
"""Useful for using the web to answer questions."""
client = AsyncTavilyClient(api_key="tvly-...")
return str(await client.search(query))
定义好工具后,我们现在可以创建子代理。
如果您使用的LLM支持工具调用功能,可以使用FunctionAgent
类。否则,可以使用ReActAgent
类。
In [ ]:
Copied!
from llama_index.core.agent.workflow import FunctionAgent, ReActAgent
research_agent = FunctionAgent(
name="ResearchAgent",
description="Useful for recording research notes based on a specific prompt.",
system_prompt=(
"You are the ResearchAgent that can search the web for information on a given topic and record notes on the topic. "
"You should output notes on the topic in a structured format."
),
llm=sub_agent_llm,
tools=[search_web],
)
write_agent = FunctionAgent(
name="WriteAgent",
description="Useful for writing a report based on the research notes or revising the report based on feedback.",
system_prompt=(
"You are the WriteAgent that can write a report on a given topic. "
"Your report should be in a markdown format. The content should be grounded in the research notes. "
"Return your markdown report surrounded by <report>...</report> tags."
),
llm=sub_agent_llm,
tools=[],
)
review_agent = FunctionAgent(
name="ReviewAgent",
description="Useful for reviewing a report and providing feedback.",
system_prompt=(
"You are the ReviewAgent that can review the write report and provide feedback. "
"Your review should either approve the current report or request changes to be implemented."
),
llm=sub_agent_llm,
tools=[],
)
from llama_index.core.agent.workflow import FunctionAgent, ReActAgent
research_agent = FunctionAgent(
name="ResearchAgent",
description="Useful for recording research notes based on a specific prompt.",
system_prompt=(
"You are the ResearchAgent that can search the web for information on a given topic and record notes on the topic. "
"You should output notes on the topic in a structured format."
),
llm=sub_agent_llm,
tools=[search_web],
)
write_agent = FunctionAgent(
name="WriteAgent",
description="Useful for writing a report based on the research notes or revising the report based on feedback.",
system_prompt=(
"You are the WriteAgent that can write a report on a given topic. "
"Your report should be in a markdown format. The content should be grounded in the research notes. "
"Return your markdown report surrounded by ... tags."
),
llm=sub_agent_llm,
tools=[],
)
review_agent = FunctionAgent(
name="ReviewAgent",
description="Useful for reviewing a report and providing feedback.",
system_prompt=(
"You are the ReviewAgent that can review the write report and provide feedback. "
"Your review should either approve the current report or request changes to be implemented."
),
llm=sub_agent_llm,
tools=[],
)
定义好每个智能体后,我们还可以编写辅助函数来帮助执行各个智能体的任务。
In [ ]:
Copied!
import re
from llama_index.core.workflow import Context
async def call_research_agent(ctx: Context, prompt: str) -> str:
"""Useful for recording research notes based on a specific prompt."""
result = await research_agent.run(
user_msg=f"Write some notes about the following: {prompt}"
)
state = await ctx.store.get("state")
state["research_notes"].append(str(result))
await ctx.store.set("state", state)
return str(result)
async def call_write_agent(ctx: Context) -> str:
"""Useful for writing a report based on the research notes or revising the report based on feedback."""
state = await ctx.store.get("state")
notes = state.get("research_notes", None)
if not notes:
return "No research notes to write from."
user_msg = f"Write a markdown report from the following notes. Be sure to output the report in the following format: <report>...</report>:\n\n"
# Add the feedback to the user message if it exists
feedback = state.get("review", None)
if feedback:
user_msg += f"<feedback>{feedback}</feedback>\n\n"
# Add the research notes to the user message
notes = "\n\n".join(notes)
user_msg += f"<research_notes>{notes}</research_notes>\n\n"
# Run the write agent
result = await write_agent.run(user_msg=user_msg)
report = re.search(r"<report>(.*)</report>", str(result), re.DOTALL).group(
1
)
state["report_content"] = str(report)
await ctx.store.set("state", state)
return str(report)
async def call_review_agent(ctx: Context) -> str:
"""Useful for reviewing the report and providing feedback."""
state = await ctx.store.get("state")
report = state.get("report_content", None)
if not report:
return "No report content to review."
result = await review_agent.run(
user_msg=f"Review the following report: {report}"
)
state["review"] = result
await ctx.store.set("state", state)
return result
import re
from llama_index.core.workflow import Context
async def call_research_agent(ctx: Context, prompt: str) -> str:
"""Useful for recording research notes based on a specific prompt."""
result = await research_agent.run(
user_msg=f"Write some notes about the following: {prompt}"
)
state = await ctx.store.get("state")
state["research_notes"].append(str(result))
await ctx.store.set("state", state)
return str(result)
async def call_write_agent(ctx: Context) -> str:
"""Useful for writing a report based on the research notes or revising the report based on feedback."""
state = await ctx.store.get("state")
notes = state.get("research_notes", None)
if not notes:
return "No research notes to write from."
user_msg = f"Write a markdown report from the following notes. Be sure to output the report in the following format: ... :\n\n"
# Add the feedback to the user message if it exists
feedback = state.get("review", None)
if feedback:
user_msg += f"{feedback} \n\n"
# Add the research notes to the user message
notes = "\n\n".join(notes)
user_msg += f"{notes} \n\n"
# Run the write agent
result = await write_agent.run(user_msg=user_msg)
report = re.search(r"(.*) ", str(result), re.DOTALL).group(
1
)
state["report_content"] = str(report)
await ctx.store.set("state", state)
return str(report)
async def call_review_agent(ctx: Context) -> str:
"""Useful for reviewing the report and providing feedback."""
state = await ctx.store.get("state")
report = state.get("report_content", None)
if not report:
return "No report content to review."
result = await review_agent.run(
user_msg=f"Review the following report: {report}"
)
state["review"] = result
await ctx.store.set("state", state)
return result
定义规划器工作流¶
为了协调其他智能体进行规划,我们将编写一个自定义工作流,明确编排和规划其他智能体的行为。
当前我们的提示词假设采用顺序执行计划,但未来可以扩展支持并行步骤(这仅涉及更复杂的解析和提示词设计,留给读者作为练习)。
In [ ]:
Copied!
import re
import xml.etree.ElementTree as ET
from pydantic import BaseModel, Field
from typing import Any, Optional
from llama_index.core.llms import ChatMessage
from llama_index.core.workflow import (
Context,
Event,
StartEvent,
StopEvent,
Workflow,
step,
)
PLANNER_PROMPT = """You are a planner chatbot.
Given a user request and the current state, break the solution into ordered <step> blocks. Each step must specify the agent to call and the message to send, e.g.
<plan>
<step agent=\"ResearchAgent\">search for …</step>
<step agent=\"WriteAgent\">draft a report …</step>
...
</plan>
<state>
{state}
</state>
<available_agents>
{available_agents}
</available_agents>
The general flow should be:
- Record research notes
- Write a report
- Review the report
- Write the report again if the review is not positive enough
If the user request does not require any steps, you can skip the <plan> block and respond directly.
"""
class InputEvent(StartEvent):
user_msg: Optional[str] = Field(default=None)
chat_history: list[ChatMessage]
state: Optional[dict[str, Any]] = Field(default=None)
class OutputEvent(StopEvent):
response: str
chat_history: list[ChatMessage]
state: dict[str, Any]
class StreamEvent(Event):
delta: str
class PlanEvent(Event):
step_info: str
# Modelling the plan
class PlanStep(BaseModel):
agent_name: str
agent_input: str
class Plan(BaseModel):
steps: list[PlanStep]
class ExecuteEvent(Event):
plan: Plan
chat_history: list[ChatMessage]
class PlannerWorkflow(Workflow):
llm: OpenAI = OpenAI(
model="o3-mini",
api_key="sk-...",
)
agents: dict[str, FunctionAgent] = {
"ResearchAgent": research_agent,
"WriteAgent": write_agent,
"ReviewAgent": review_agent,
}
@step
async def plan(
self, ctx: Context, ev: InputEvent
) -> ExecuteEvent | OutputEvent:
# Set initial state if it exists
if ev.state:
await ctx.store.set("state", ev.state)
chat_history = ev.chat_history
if ev.user_msg:
user_msg = ChatMessage(
role="user",
content=ev.user_msg,
)
chat_history.append(user_msg)
# Inject the system prompt with state and available agents
state = await ctx.store.get("state")
available_agents_str = "\n".join(
[
f'<agent name="{agent.name}">{agent.description}</agent>'
for agent in self.agents.values()
]
)
system_prompt = ChatMessage(
role="system",
content=PLANNER_PROMPT.format(
state=str(state),
available_agents=available_agents_str,
),
)
# Stream the response from the llm
response = await self.llm.astream_chat(
messages=[system_prompt] + chat_history,
)
full_response = ""
async for chunk in response:
full_response += chunk.delta or ""
if chunk.delta:
ctx.write_event_to_stream(
StreamEvent(delta=chunk.delta),
)
# Parse the response into a plan and decide whether to execute or output
xml_match = re.search(r"(<plan>.*</plan>)", full_response, re.DOTALL)
if not xml_match:
chat_history.append(
ChatMessage(
role="assistant",
content=full_response,
)
)
return OutputEvent(
response=full_response,
chat_history=chat_history,
state=state,
)
else:
xml_str = xml_match.group(1)
root = ET.fromstring(xml_str)
plan = Plan(steps=[])
for step in root.findall("step"):
plan.steps.append(
PlanStep(
agent_name=step.attrib["agent"],
agent_input=step.text.strip() if step.text else "",
)
)
return ExecuteEvent(plan=plan, chat_history=chat_history)
@step
async def execute(self, ctx: Context, ev: ExecuteEvent) -> InputEvent:
chat_history = ev.chat_history
plan = ev.plan
for step in plan.steps:
agent = self.agents[step.agent_name]
agent_input = step.agent_input
ctx.write_event_to_stream(
PlanEvent(
step_info=f'<step agent="{step.agent_name}">{step.agent_input}</step>'
),
)
if step.agent_name == "ResearchAgent":
await call_research_agent(ctx, agent_input)
elif step.agent_name == "WriteAgent":
# Note: we aren't passing the input from the plan since
# we're using the state to drive the write agent
await call_write_agent(ctx)
elif step.agent_name == "ReviewAgent":
await call_review_agent(ctx)
state = await ctx.store.get("state")
chat_history.append(
ChatMessage(
role="user",
content=f"I've completed the previous steps, here's the updated state:\n\n<state>\n{state}\n</state>\n\nDo you need to continue and plan more steps?, If not, write a final response.",
)
)
return InputEvent(
chat_history=chat_history,
)
import re
import xml.etree.ElementTree as ET
from pydantic import BaseModel, Field
from typing import Any, Optional
from llama_index.core.llms import ChatMessage
from llama_index.core.workflow import (
Context,
Event,
StartEvent,
StopEvent,
Workflow,
step,
)
PLANNER_PROMPT = """You are a planner chatbot.
Given a user request and the current state, break the solution into ordered blocks. Each step must specify the agent to call and the message to send, e.g.
search for …
draft a report …
...
{state}
{available_agents}
The general flow should be:
- Record research notes
- Write a report
- Review the report
- Write the report again if the review is not positive enough
If the user request does not require any steps, you can skip the block and respond directly.
"""
class InputEvent(StartEvent):
user_msg: Optional[str] = Field(default=None)
chat_history: list[ChatMessage]
state: Optional[dict[str, Any]] = Field(default=None)
class OutputEvent(StopEvent):
response: str
chat_history: list[ChatMessage]
state: dict[str, Any]
class StreamEvent(Event):
delta: str
class PlanEvent(Event):
step_info: str
# Modelling the plan
class PlanStep(BaseModel):
agent_name: str
agent_input: str
class Plan(BaseModel):
steps: list[PlanStep]
class ExecuteEvent(Event):
plan: Plan
chat_history: list[ChatMessage]
class PlannerWorkflow(Workflow):
llm: OpenAI = OpenAI(
model="o3-mini",
api_key="sk-...",
)
agents: dict[str, FunctionAgent] = {
"ResearchAgent": research_agent,
"WriteAgent": write_agent,
"ReviewAgent": review_agent,
}
@step
async def plan(
self, ctx: Context, ev: InputEvent
) -> ExecuteEvent | OutputEvent:
# Set initial state if it exists
if ev.state:
await ctx.store.set("state", ev.state)
chat_history = ev.chat_history
if ev.user_msg:
user_msg = ChatMessage(
role="user",
content=ev.user_msg,
)
chat_history.append(user_msg)
# Inject the system prompt with state and available agents
state = await ctx.store.get("state")
available_agents_str = "\n".join(
[
f'{agent.description} '
for agent in self.agents.values()
]
)
system_prompt = ChatMessage(
role="system",
content=PLANNER_PROMPT.format(
state=str(state),
available_agents=available_agents_str,
),
)
# Stream the response from the llm
response = await self.llm.astream_chat(
messages=[system_prompt] + chat_history,
)
full_response = ""
async for chunk in response:
full_response += chunk.delta or ""
if chunk.delta:
ctx.write_event_to_stream(
StreamEvent(delta=chunk.delta),
)
# Parse the response into a plan and decide whether to execute or output
xml_match = re.search(r"(.* )", full_response, re.DOTALL)
if not xml_match:
chat_history.append(
ChatMessage(
role="assistant",
content=full_response,
)
)
return OutputEvent(
response=full_response,
chat_history=chat_history,
state=state,
)
else:
xml_str = xml_match.group(1)
root = ET.fromstring(xml_str)
plan = Plan(steps=[])
for step in root.findall("step"):
plan.steps.append(
PlanStep(
agent_name=step.attrib["agent"],
agent_input=step.text.strip() if step.text else "",
)
)
return ExecuteEvent(plan=plan, chat_history=chat_history)
@step
async def execute(self, ctx: Context, ev: ExecuteEvent) -> InputEvent:
chat_history = ev.chat_history
plan = ev.plan
for step in plan.steps:
agent = self.agents[step.agent_name]
agent_input = step.agent_input
ctx.write_event_to_stream(
PlanEvent(
step_info=f'{step.agent_input} '
),
)
if step.agent_name == "ResearchAgent":
await call_research_agent(ctx, agent_input)
elif step.agent_name == "WriteAgent":
# Note: we aren't passing the input from the plan since
# we're using the state to drive the write agent
await call_write_agent(ctx)
elif step.agent_name == "ReviewAgent":
await call_review_agent(ctx)
state = await ctx.store.get("state")
chat_history.append(
ChatMessage(
role="user",
content=f"I've completed the previous steps, here's the updated state:\n\n\n{state}\n \n\nDo you need to continue and plan more steps?, If not, write a final response.",
)
)
return InputEvent(
chat_history=chat_history,
)
运行工作流¶
自定义规划器定义完成后,我们现在可以运行工作流并观察其实际执行效果!
当工作流运行时,我们将实时推送事件流,以便了解底层运行情况。
In [ ]:
Copied!
planner_workflow = PlannerWorkflow(timeout=None)
handler = planner_workflow.run(
user_msg=(
"Write me a report on the history of the internet. "
"Briefly describe the history of the internet, including the development of the internet, the development of the web, "
"and the development of the internet in the 21st century."
),
chat_history=[],
state={
"research_notes": [],
"report_content": "Not written yet.",
"review": "Review required.",
},
)
current_agent = None
current_tool_calls = ""
async for event in handler.stream_events():
if isinstance(event, PlanEvent):
print("Executing plan step: ", event.step_info)
elif isinstance(event, ExecuteEvent):
print("Executing plan: ", event.plan)
result = await handler
planner_workflow = PlannerWorkflow(timeout=None)
handler = planner_workflow.run(
user_msg=(
"Write me a report on the history of the internet. "
"Briefly describe the history of the internet, including the development of the internet, the development of the web, "
"and the development of the internet in the 21st century."
),
chat_history=[],
state={
"research_notes": [],
"report_content": "Not written yet.",
"review": "Review required.",
},
)
current_agent = None
current_tool_calls = ""
async for event in handler.stream_events():
if isinstance(event, PlanEvent):
print("Executing plan step: ", event.step_info)
elif isinstance(event, ExecuteEvent):
print("Executing plan: ", event.plan)
result = await handler
Executing plan step: <step agent="ResearchAgent">Record research notes on the history of the internet, detailing the development of the internet, the emergence and evolution of the web, and progress in the 21st century.</step> Executing plan step: <step agent="WriteAgent">Draft a report based on the recorded research notes about the history of the internet, including its early development, the creation and impact of the web, and milestones in the 21st century.</step> Executing plan step: <step agent="ReviewAgent">Review the generated report and provide feedback to ensure the report meets the request criteria.</step> Executing plan step: <step agent="WriteAgent">Revise the report by incorporating the review suggestions. Specifically, update Section 1 to clarify the role of the Internet Working Group (or IETF) and mention the transition to TCP/IP on January 1, 1983. In Section 2, include a note on the web as a system of interlinked hypertext documents, explain the Browser Wars with references to Netscape Navigator and Internet Explorer, and add that CSS was first proposed in 1996. In Section 3, briefly mention the transformative impact of mobile internet usage and smartphones, as well as outline emerging privacy and security challenges. Add a concluding section summarizing the overall impact of the internet and include references to authoritative sources where applicable.</step>
In [ ]:
Copied!
print(result.response)
print(result.response)
Final Response: No further planning steps are needed. The report on the History of the Internet has been completed and reviewed, with clear suggestions provided for minor enhancements. You can now incorporate those recommendations into your final report if desired.
现在,我们可以在系统中自行检索最终报告。
In [ ]:
Copied!
state = await handler.ctx.store.get("state")
print(state["report_content"])
state = await handler.ctx.store.get("state")
print(state["report_content"])
# History of the Internet ## 1. Development of the Internet The internet's origins trace back to **ARPANET**, developed in 1969 by the U.S. Defense Department's Advanced Research Projects Agency (DARPA) as a military defense communication system during the Cold War. ARPANET initially connected research programs across universities and government institutions, laying the groundwork for packet-switching networks. In the late 1970s, as ARPANET expanded, coordination bodies such as the **International Cooperation Board** and the **Internet Configuration Control Board** were established to manage the growing research community and oversee internet development. The **Internet Working Group (IWG)**, which later evolved into the **Internet Engineering Task Force (IETF)**, played a crucial role in developing protocols and standards. A pivotal milestone occurred on **January 1, 1983**, when ARPANET adopted the **Transmission Control Protocol/Internet Protocol (TCP/IP)** suite, marking the transition from ARPANET to the modern internet. This standardization enabled diverse networks to interconnect seamlessly. During the 1970s and 1980s, **commercial packet networks** such as Telenet and Tymnet emerged, providing broader access to remote computers and facilitating early commercial use of network services. The **National Science Foundation (NSF)** further expanded internet access to the scientific and academic communities through NSFNET, which became a backbone connecting regional networks. By the late 1980s and early 1990s, commercial internet backbones connected through **Network Access Points (NAPs)**, enabling widespread internet connectivity beyond academia and government, setting the stage for the internet's global expansion. ## 2. Emergence and Evolution of the Web The **World Wide Web** was invented in 1989 by **Tim Berners-Lee** while working at CERN. The web introduced a system of interlinked hypertext documents accessed via the internet, revolutionizing information sharing. By December 1990, Berners-Lee developed the foundational technologies of the web: **HTTP (HyperText Transfer Protocol)**, **HTML (HyperText Markup Language)**, the first web browser/editor, the first web server, and the first website. The web became publicly accessible outside CERN by 1991, rapidly gaining popularity. In 1994, Berners-Lee founded the **World Wide Web Consortium (W3C)** at MIT to develop open standards ensuring the web's interoperability and growth. The mid-1990s saw significant technological advancements: - **JavaScript** was introduced in 1995, enabling dynamic and interactive web content. - **Cascading Style Sheets (CSS)** were first proposed in **1996**, allowing separation of content from presentation and enhancing web design flexibility. During this period, the **"Browser Wars"**—a competition primarily between **Netscape Navigator** and **Microsoft Internet Explorer**—spurred rapid innovation and adoption of web technologies, shaping the modern browsing experience. ## 3. Progress in the 21st Century The 21st century witnessed transformative growth in internet technology and usage: - The rise of **broadband**, **fiber-optic networks**, and **5G** technology provided high-speed, reliable connectivity worldwide. - The proliferation of **smartphones** and **mobile internet usage** fundamentally changed how people accessed and interacted with the internet. - **Social media platforms** emerged, revolutionizing communication, social interaction, and information dissemination. - **Cloud computing** transformed data storage and processing by enabling remote access to powerful servers. - The **Internet of Things (IoT)** connected everyday devices to the internet, integrating digital connectivity into physical environments. - Advances in **artificial intelligence**, **blockchain**, and **sensor networks** further expanded internet capabilities and applications. Alongside these advances, privacy and security challenges have become increasingly prominent, prompting ongoing efforts to protect users and data in an interconnected world. ## Conclusion From its origins as a military research project to a global network connecting billions, the internet has profoundly transformed society. The invention of the World Wide Web made information universally accessible, while continuous technological innovations have reshaped communication, commerce, education, and entertainment. As the internet continues to evolve, addressing challenges such as privacy and security remains essential to harnessing its full potential for future generations. --- ### References - Leiner, B. M., Cerf, V. G., Clark, D. D., et al. (1997). A Brief History of the Internet. *ACM SIGCOMM Computer Communication Review*, 39(5), 22–31. - Berners-Lee, T., & Fischetti, M. (1999). *Weaving the Web: The Original Design and Ultimate Destiny of the World Wide Web*. Harper. - World Wide Web Consortium (W3C). (n.d.). About W3C. https://www.w3.org/Consortium/ - Abbate, J. (1999). *Inventing the Internet*. MIT Press. - Internet Society. (n.d.). A Short History of the Internet. https://www.internetsociety.org/internet/history-internet/brief-history-internet/
In [ ]:
Copied!
print(state["review"])
print(state["review"])
The report on the History of the Internet is well-structured, clear, and covers the major milestones and developments comprehensively. It effectively divides the content into logical sections: the development of the internet, the emergence and evolution of the web, and progress in the 21st century. The information is accurate and presented in a concise manner. However, I have a few suggestions to improve clarity and completeness: 1. **Section 1 - Development of the Internet:** - The term "Internet Working Group" is mentioned but could be clarified as the "Internet Working Group (IWG)" or "Internet Engineering Task Force (IETF)" to avoid confusion. - It might be helpful to briefly mention the transition from ARPANET to the modern internet, including the adoption of TCP/IP on January 1, 1983, which is a key milestone. - The role of commercial packet networks could be expanded slightly to mention specific examples or their impact. 2. **Section 2 - Emergence and Evolution of the Web:** - The report correctly credits Tim Berners-Lee but could mention the significance of the web being a system of interlinked hypertext documents accessed via the internet. - The "Browser Wars" could be briefly explained, naming key browsers involved (e.g., Netscape Navigator and Internet Explorer) to provide context. - The introduction of CSS could include the year it was first proposed (1996) for completeness. 3. **Section 3 - Progress in the 21st Century:** - The report covers major technological advances well but could briefly mention the rise of mobile internet usage and smartphones as a transformative factor. - It might be beneficial to note privacy and security challenges that have emerged alongside technological progress. 4. **General:** - Adding references or citations to authoritative sources would strengthen the report's credibility. - Including a brief conclusion summarizing the internet's impact on society could provide a strong closing. Overall, the report is informative and well-written but would benefit from these enhancements to improve depth and clarity. **Recommendation:** Please implement the suggested changes to enhance the report before approval.