Skip to content

Commit adff220

Browse files
committed
add duckduckgo tool and improve prompting
1 parent df693df commit adff220

File tree

4 files changed

+197
-66
lines changed

4 files changed

+197
-66
lines changed

templates/types/multiagent/fastapi/app/examples/orchestrator.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,19 @@ def create_orchestrator(chat_history: Optional[List[ChatMessage]] = None):
1111
researcher = create_researcher(chat_history)
1212
writer = FunctionCallingAgent(
1313
name="writer",
14-
role="expert in writing blog posts",
15-
system_prompt="""You are an expert in writing blog posts. You are given a task to write a blog post. Don't make up any information yourself. If you don't have the necessary information to write a blog post, reply "I need information about the topic to write the blog post". If you have all the information needed, write the blog post.""",
14+
role="expert in writing blog posts, need information and images to write a post",
15+
system_prompt="""You are an expert in writing blog posts.
16+
You are given a task to write a blog post. Don't make up any information yourself.
17+
If you don't have the necessary information to write a blog post, reply "I need information about the topic to write the blog post".
18+
If you need to use images, reply "I need images about the topic to write the blog post". Don't use any dummy images made up by you.
19+
If you have all the information needed, write the blog post.""",
1620
chat_history=chat_history,
1721
)
1822
reviewer = FunctionCallingAgent(
1923
name="reviewer",
20-
role="expert in reviewing blog posts",
24+
role="expert in reviewing blog posts, need a written blog post to review",
2125
system_prompt="""You are an expert in reviewing blog posts. You are given a task to review a blog post. Review the post and fix the issues found yourself. You must output a final blog post.
26+
A post must include at lease one valid image, if not, reply "I need images about the topic to write the blog post". An image URL start with example or your website is not valid.
2227
Especially check for logical inconsistencies and proofread the post for grammar and spelling errors.""",
2328
chat_history=chat_history,
2429
)

templates/types/multiagent/fastapi/app/examples/researcher.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import os
22
from typing import List
3-
from llama_index.core.tools import QueryEngineTool, ToolMetadata
3+
44
from app.agents.single import FunctionCallingAgent
55
from app.engine.index import get_index
6-
6+
from app.tools.duckduckgo import get_tools as get_duckduckgo_tools
77
from llama_index.core.chat_engine.types import ChatMessage
8+
from llama_index.core.tools import QueryEngineTool, ToolMetadata
89

910

1011
def get_query_engine_tool() -> QueryEngineTool:
@@ -30,10 +31,11 @@ def get_query_engine_tool() -> QueryEngineTool:
3031

3132

3233
def create_researcher(chat_history: List[ChatMessage]):
34+
duckduckgo_search_tools = get_duckduckgo_tools()
3335
return FunctionCallingAgent(
3436
name="researcher",
35-
tools=[get_query_engine_tool()],
36-
role="expert in retrieving any unknown content",
37-
system_prompt="You are a researcher agent. You are given a researching task. You must use your tools to complete the research.",
37+
tools=[get_query_engine_tool(), *duckduckgo_search_tools],
38+
role="expert in retrieving any unknown content or searching for images from the internet",
39+
system_prompt="You are a researcher agent. You are given a researching task. You must use tools to retrieve information from the knowledge base and search for needed images from the internet for the post.",
3840
chat_history=chat_history,
3941
)
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import logging
12
import os
23
import re
34
from enum import Enum
45
from io import BytesIO
56

7+
from llama_index.core.tools.function_tool import FunctionTool
8+
69
OUTPUT_DIR = "output/tools"
710

811

@@ -11,43 +14,92 @@ class ArtifactType(Enum):
1114
HTML = "html"
1215

1316

14-
HTML_FILE_TEMPLATE = """
17+
COMMON_STYLES = """
18+
body {
19+
font-family: Arial, sans-serif;
20+
line-height: 1.3;
21+
color: #333;
22+
}
23+
h1, h2, h3, h4, h5, h6 {
24+
margin-top: 1em;
25+
margin-bottom: 0.5em;
26+
}
27+
p {
28+
margin-bottom: 0.7em;
29+
}
30+
code {
31+
background-color: #f4f4f4;
32+
padding: 2px 4px;
33+
border-radius: 4px;
34+
}
35+
pre {
36+
background-color: #f4f4f4;
37+
padding: 10px;
38+
border-radius: 4px;
39+
overflow-x: auto;
40+
}
41+
table {
42+
border-collapse: collapse;
43+
width: 100%;
44+
margin-bottom: 1em;
45+
}
46+
th, td {
47+
border: 1px solid #ddd;
48+
padding: 8px;
49+
text-align: left;
50+
}
51+
th {
52+
background-color: #f2f2f2;
53+
font-weight: bold;
54+
}
55+
img {
56+
max-width: 90%;
57+
height: auto;
58+
display: block;
59+
margin: 1em auto;
60+
border-radius: 10px;
61+
}
62+
"""
63+
64+
HTML_SPECIFIC_STYLES = """
65+
body {
66+
max-width: 800px;
67+
margin: 0 auto;
68+
padding: 20px;
69+
}
70+
"""
71+
72+
PDF_SPECIFIC_STYLES = """
73+
@page {
74+
size: letter;
75+
margin: 2cm;
76+
}
77+
body {
78+
font-size: 11pt;
79+
}
80+
h1 { font-size: 18pt; }
81+
h2 { font-size: 16pt; }
82+
h3 { font-size: 14pt; }
83+
h4, h5, h6 { font-size: 12pt; }
84+
pre, code {
85+
font-family: Courier, monospace;
86+
font-size: 0.9em;
87+
}
88+
"""
89+
90+
HTML_TEMPLATE = """
1591
<!DOCTYPE html>
1692
<html lang="en">
1793
<head>
1894
<meta charset="UTF-8">
1995
<meta name="viewport" content="width=device-width, initial-scale=1.0">
2096
<style>
21-
body {{
22-
font-family: Arial, sans-serif;
23-
line-height: 1.3;
24-
color: #333;
25-
max-width: 800px;
26-
margin: 0 auto;
27-
padding: 20px;
28-
}}
29-
h1, h2, h3, h4, h5, h6 {{
30-
margin-top: 1.5em;
31-
margin-bottom: 0.5em;
32-
}}
33-
p {{
34-
margin-bottom: 1em;
35-
}}
36-
code {{
37-
background-color: #f4f4f4;
38-
padding: 2px 4px;
39-
border-radius: 4px;
40-
}}
41-
pre {{
42-
background-color: #f4f4f4;
43-
padding: 10px;
44-
border-radius: 4px;
45-
overflow-x: auto;
46-
}}
97+
{common_styles}
98+
{specific_styles}
4799
</style>
48100
</head>
49101
<body>
50-
{html_content}
102+
{content}
51103
</body>
52104
</html>
53105
"""
@@ -66,10 +118,23 @@ def _generate_html_content(cls, original_content: str) -> str:
66118
"Failed to import required modules. Please install markdown."
67119
)
68120

69-
# Convert markdown to HTML
70-
html_content = markdown.markdown(original_content)
121+
# Convert markdown to HTML with fenced code and table extensions
122+
html_content = markdown.markdown(
123+
original_content, extensions=["fenced_code", "tables"]
124+
)
71125
return html_content
72126

127+
@classmethod
128+
def _generate_html_document(cls, html_content: str) -> str:
129+
"""
130+
Generate a complete HTML document with the given HTML content.
131+
"""
132+
return HTML_TEMPLATE.format(
133+
common_styles=COMMON_STYLES,
134+
specific_styles=HTML_SPECIFIC_STYLES,
135+
content=html_content,
136+
)
137+
73138
@classmethod
74139
def _generate_pdf(cls, html_content: str) -> BytesIO:
75140
"""
@@ -82,35 +147,24 @@ def _generate_pdf(cls, html_content: str) -> BytesIO:
82147
"Failed to import required modules. Please install xhtml2pdf."
83148
)
84149

150+
pdf_html = HTML_TEMPLATE.format(
151+
common_styles=COMMON_STYLES,
152+
specific_styles=PDF_SPECIFIC_STYLES,
153+
content=html_content,
154+
)
155+
85156
buffer = BytesIO()
86157
pdf = pisa.pisaDocument(
87-
BytesIO(html_content.encode("UTF-8")),
88-
buffer,
89-
encoding="UTF-8",
90-
path=".",
91-
link_callback=None,
92-
debug=0,
93-
default_css=None,
94-
xhtml=False,
95-
xml_output=None,
96-
ident=0,
97-
show_error_as_pdf=False,
98-
quiet=True,
99-
capacity=100 * 1024 * 1024,
100-
raise_exception=True,
158+
BytesIO(pdf_html.encode("UTF-8")), buffer, encoding="UTF-8"
101159
)
160+
102161
if pdf.err:
162+
logging.error(f"PDF generation failed: {pdf.err}")
103163
raise ValueError("PDF generation failed")
164+
104165
buffer.seek(0)
105166
return buffer
106167

107-
@classmethod
108-
def _generate_html(cls, html_content: str) -> str:
109-
"""
110-
Generate a complete HTML document with the given HTML content.
111-
"""
112-
return HTML_FILE_TEMPLATE.format(html_content=html_content)
113-
114168
@classmethod
115169
def generate_artifact(
116170
cls, original_content: str, artifact_type: str, file_name: str
@@ -119,10 +173,11 @@ def generate_artifact(
119173
To generate artifact as PDF or HTML file.
120174
Parameters:
121175
original_content: str (markdown style)
122-
artifact_type: str (pdf or html) specify the type of the file format based on the use case
176+
artifact_type: str (pdf or html or image) specify the type of the file format based on the use case
123177
file_name: str (name of the artifact file) must be a valid file name, no extensions needed
124178
Returns:
125-
str (URL to the artifact file): A file URL ready to serve.
179+
str (URL to the artifact file): A file URL ready to serve (pdf or html).
180+
list[str] (URLs to the artifact files): A list of image URLs ready to serve (png).
126181
"""
127182
try:
128183
artifact_type = ArtifactType(artifact_type.lower())
@@ -133,15 +188,12 @@ def generate_artifact(
133188
# Always generate html content first
134189
html_content = cls._generate_html_content(original_content)
135190

136-
# Based on the type of artifact, generate the corresponding file
137191
if artifact_type == ArtifactType.PDF:
138-
content = cls._generate_pdf(cls._generate_html(html_content))
192+
content = cls._generate_pdf(html_content)
139193
file_extension = "pdf"
140194
elif artifact_type == ArtifactType.HTML:
141-
content = BytesIO(cls._generate_html(html_content).encode("utf-8"))
195+
content = BytesIO(cls._generate_html_document(html_content).encode("utf-8"))
142196
file_extension = "html"
143-
else:
144-
raise ValueError(f"Unexpected artifact type: {artifact_type}")
145197

146198
file_name = cls._validate_file_name(file_name)
147199
file_path = os.path.join(OUTPUT_DIR, f"{file_name}.{file_extension}")
@@ -176,3 +228,7 @@ def _validate_file_name(file_name: str) -> str:
176228
return file_name
177229
else:
178230
raise ValueError("File name is not allowed to contain special characters.")
231+
232+
233+
def get_tools(**kwargs):
234+
return [FunctionTool.from_defaults(ArtifactGenerator.generate_artifact)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from llama_index.core.tools.function_tool import FunctionTool
2+
3+
4+
def duckduckgo_search(
5+
query: str,
6+
region: str = "wt-wt",
7+
max_results: int = 10,
8+
):
9+
"""
10+
Use this function to search for any query in DuckDuckGo.
11+
Args:
12+
query (str): The query to search in DuckDuckGo.
13+
region Optional(str): The region to be used for the search in [country-language] convention, ex us-en, uk-en, ru-ru, etc...
14+
max_results Optional(int): The maximum number of results to be returned. Default is 10.
15+
"""
16+
try:
17+
from duckduckgo_search import DDGS
18+
except ImportError:
19+
raise ImportError(
20+
"duckduckgo_search package is required to use this function."
21+
"Please install it by running: `poetry add duckduckgo_search` or `pip install duckduckgo_search`"
22+
)
23+
24+
params = {
25+
"keywords": query,
26+
"region": region,
27+
"max_results": max_results,
28+
}
29+
results = []
30+
with DDGS() as ddg:
31+
results = list(ddg.text(**params))
32+
return results
33+
34+
35+
def duckduckgo_image_search(
36+
query: str,
37+
region: str = "wt-wt",
38+
max_results: int = 10,
39+
):
40+
"""
41+
Use this function to search for images in DuckDuckGo.
42+
Args:
43+
query (str): The query to search in DuckDuckGo.
44+
region Optional(str): The region to be used for the search in [country-language] convention, ex us-en, uk-en, ru-ru, etc...
45+
max_results Optional(int): The maximum number of results to be returned. Default is 10.
46+
"""
47+
try:
48+
from duckduckgo_search import DDGS
49+
except ImportError:
50+
raise ImportError(
51+
"duckduckgo_search package is required to use this function."
52+
"Please install it by running: `poetry add duckduckgo_search` or `pip install duckduckgo_search`"
53+
)
54+
params = {
55+
"keywords": query,
56+
"region": region,
57+
"max_results": max_results,
58+
}
59+
with DDGS() as ddg:
60+
results = list(ddg.images(**params))
61+
return results
62+
63+
64+
def get_tools(**kwargs):
65+
return [
66+
FunctionTool.from_defaults(duckduckgo_search),
67+
FunctionTool.from_defaults(duckduckgo_image_search),
68+
]

0 commit comments

Comments
 (0)