quantalogic 0.35.0__py3-none-any.whl → 0.50.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantalogic/__init__.py +0 -4
- quantalogic/agent.py +603 -363
- quantalogic/agent_config.py +233 -46
- quantalogic/agent_factory.py +34 -22
- quantalogic/coding_agent.py +16 -14
- quantalogic/config.py +2 -1
- quantalogic/console_print_events.py +4 -8
- quantalogic/console_print_token.py +2 -2
- quantalogic/docs_cli.py +15 -10
- quantalogic/event_emitter.py +258 -83
- quantalogic/flow/__init__.py +23 -0
- quantalogic/flow/flow.py +595 -0
- quantalogic/flow/flow_extractor.py +672 -0
- quantalogic/flow/flow_generator.py +89 -0
- quantalogic/flow/flow_manager.py +407 -0
- quantalogic/flow/flow_manager_schema.py +169 -0
- quantalogic/flow/flow_yaml.md +419 -0
- quantalogic/generative_model.py +109 -77
- quantalogic/get_model_info.py +5 -5
- quantalogic/interactive_text_editor.py +100 -73
- quantalogic/main.py +17 -21
- quantalogic/model_info_list.py +3 -3
- quantalogic/model_info_litellm.py +14 -14
- quantalogic/prompts.py +2 -1
- quantalogic/{llm.py → quantlitellm.py} +29 -39
- quantalogic/search_agent.py +4 -4
- quantalogic/server/models.py +4 -1
- quantalogic/task_file_reader.py +5 -5
- quantalogic/task_runner.py +20 -20
- quantalogic/tool_manager.py +10 -21
- quantalogic/tools/__init__.py +98 -68
- quantalogic/tools/composio/composio.py +416 -0
- quantalogic/tools/{generate_database_report_tool.py → database/generate_database_report_tool.py} +4 -9
- quantalogic/tools/database/sql_query_tool_advanced.py +261 -0
- quantalogic/tools/document_tools/markdown_to_docx_tool.py +620 -0
- quantalogic/tools/document_tools/markdown_to_epub_tool.py +438 -0
- quantalogic/tools/document_tools/markdown_to_html_tool.py +362 -0
- quantalogic/tools/document_tools/markdown_to_ipynb_tool.py +319 -0
- quantalogic/tools/document_tools/markdown_to_latex_tool.py +420 -0
- quantalogic/tools/document_tools/markdown_to_pdf_tool.py +623 -0
- quantalogic/tools/document_tools/markdown_to_pptx_tool.py +319 -0
- quantalogic/tools/duckduckgo_search_tool.py +2 -4
- quantalogic/tools/finance/alpha_vantage_tool.py +440 -0
- quantalogic/tools/finance/ccxt_tool.py +373 -0
- quantalogic/tools/finance/finance_llm_tool.py +387 -0
- quantalogic/tools/finance/google_finance.py +192 -0
- quantalogic/tools/finance/market_intelligence_tool.py +520 -0
- quantalogic/tools/finance/technical_analysis_tool.py +491 -0
- quantalogic/tools/finance/tradingview_tool.py +336 -0
- quantalogic/tools/finance/yahoo_finance.py +236 -0
- quantalogic/tools/git/bitbucket_clone_repo_tool.py +181 -0
- quantalogic/tools/git/bitbucket_operations_tool.py +326 -0
- quantalogic/tools/git/clone_repo_tool.py +189 -0
- quantalogic/tools/git/git_operations_tool.py +532 -0
- quantalogic/tools/google_packages/google_news_tool.py +480 -0
- quantalogic/tools/grep_app_tool.py +123 -186
- quantalogic/tools/{dalle_e.py → image_generation/dalle_e.py} +37 -27
- quantalogic/tools/jinja_tool.py +6 -10
- quantalogic/tools/language_handlers/__init__.py +22 -9
- quantalogic/tools/list_directory_tool.py +131 -42
- quantalogic/tools/llm_tool.py +45 -15
- quantalogic/tools/llm_vision_tool.py +59 -7
- quantalogic/tools/markitdown_tool.py +17 -5
- quantalogic/tools/nasa_packages/models.py +47 -0
- quantalogic/tools/nasa_packages/nasa_apod_tool.py +232 -0
- quantalogic/tools/nasa_packages/nasa_neows_tool.py +147 -0
- quantalogic/tools/nasa_packages/services.py +82 -0
- quantalogic/tools/presentation_tools/presentation_llm_tool.py +396 -0
- quantalogic/tools/product_hunt/product_hunt_tool.py +258 -0
- quantalogic/tools/product_hunt/services.py +63 -0
- quantalogic/tools/rag_tool/__init__.py +48 -0
- quantalogic/tools/rag_tool/document_metadata.py +15 -0
- quantalogic/tools/rag_tool/query_response.py +20 -0
- quantalogic/tools/rag_tool/rag_tool.py +566 -0
- quantalogic/tools/rag_tool/rag_tool_beta.py +264 -0
- quantalogic/tools/read_html_tool.py +24 -38
- quantalogic/tools/replace_in_file_tool.py +10 -10
- quantalogic/tools/safe_python_interpreter_tool.py +10 -24
- quantalogic/tools/search_definition_names.py +2 -2
- quantalogic/tools/sequence_tool.py +14 -23
- quantalogic/tools/sql_query_tool.py +17 -19
- quantalogic/tools/tool.py +39 -15
- quantalogic/tools/unified_diff_tool.py +1 -1
- quantalogic/tools/utilities/csv_processor_tool.py +234 -0
- quantalogic/tools/utilities/download_file_tool.py +179 -0
- quantalogic/tools/utilities/mermaid_validator_tool.py +661 -0
- quantalogic/tools/utils/__init__.py +1 -4
- quantalogic/tools/utils/create_sample_database.py +24 -38
- quantalogic/tools/utils/generate_database_report.py +74 -82
- quantalogic/tools/wikipedia_search_tool.py +17 -21
- quantalogic/utils/ask_user_validation.py +1 -1
- quantalogic/utils/async_utils.py +35 -0
- quantalogic/utils/check_version.py +3 -5
- quantalogic/utils/get_all_models.py +2 -1
- quantalogic/utils/git_ls.py +21 -7
- quantalogic/utils/lm_studio_model_info.py +9 -7
- quantalogic/utils/python_interpreter.py +113 -43
- quantalogic/utils/xml_utility.py +178 -0
- quantalogic/version_check.py +1 -1
- quantalogic/welcome_message.py +7 -7
- quantalogic/xml_parser.py +0 -1
- {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/METADATA +40 -1
- quantalogic-0.50.0.dist-info/RECORD +148 -0
- quantalogic-0.35.0.dist-info/RECORD +0 -102
- {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/LICENSE +0 -0
- {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/WHEEL +0 -0
- {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,396 @@
|
|
1
|
+
"""Specialized LLM Tool for formatting and structuring content presentations."""
|
2
|
+
|
3
|
+
from typing import Callable, ClassVar, Dict, Literal
|
4
|
+
|
5
|
+
from loguru import logger
|
6
|
+
from pydantic import ConfigDict, Field
|
7
|
+
|
8
|
+
from quantalogic.console_print_token import console_print_token
|
9
|
+
from quantalogic.event_emitter import EventEmitter
|
10
|
+
from quantalogic.generative_model import GenerativeModel, Message
|
11
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
12
|
+
|
13
|
+
|
14
|
+
class PresentationLLMTool(Tool):
|
15
|
+
"""Tool to format and structure content using a language model."""
|
16
|
+
|
17
|
+
model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
|
18
|
+
|
19
|
+
name: str = Field(default="presentation_llm_tool")
|
20
|
+
description: str = Field(
|
21
|
+
default=(
|
22
|
+
"Specializes in formatting and structuring content for presentation. "
|
23
|
+
"Takes raw content and formats it according to specified presentation styles. "
|
24
|
+
"Supports various output formats like markdown, bullet points, technical documentation, etc."
|
25
|
+
)
|
26
|
+
)
|
27
|
+
arguments: list = Field(
|
28
|
+
default=[
|
29
|
+
ToolArgument(
|
30
|
+
name="content",
|
31
|
+
arg_type="string",
|
32
|
+
description="The raw content to be formatted",
|
33
|
+
required=True,
|
34
|
+
example="Here's the raw analysis results and data...",
|
35
|
+
),
|
36
|
+
ToolArgument(
|
37
|
+
name="format_style",
|
38
|
+
arg_type="string",
|
39
|
+
description=(
|
40
|
+
"The desired presentation format. Options: "
|
41
|
+
"'technical_doc', 'executive_summary', 'markdown', "
|
42
|
+
"'bullet_points', 'tutorial', 'api_doc', 'article', "
|
43
|
+
"'html_doc', 'slide_deck', 'code_review', 'release_notes', "
|
44
|
+
"'user_guide', 'research_paper', 'database_doc', 'analytics_report'"
|
45
|
+
),
|
46
|
+
required=True,
|
47
|
+
example="technical_doc",
|
48
|
+
),
|
49
|
+
ToolArgument(
|
50
|
+
name="temperature",
|
51
|
+
arg_type="string",
|
52
|
+
description='Sampling temperature between "0.0" and "1.0"',
|
53
|
+
required=True,
|
54
|
+
default="0.3",
|
55
|
+
example="0.3",
|
56
|
+
),
|
57
|
+
ToolArgument(
|
58
|
+
name="additional_info",
|
59
|
+
arg_type="string",
|
60
|
+
description=(
|
61
|
+
"Optional additional notes or requirements for formatting. "
|
62
|
+
"These will be added as extra instructions to the formatter."
|
63
|
+
),
|
64
|
+
required=False,
|
65
|
+
example="Please make it concise and focus on technical accuracy."
|
66
|
+
),
|
67
|
+
]
|
68
|
+
)
|
69
|
+
|
70
|
+
model_name: str = Field(..., description="The name of the language model to use")
|
71
|
+
system_prompt: str = Field(
|
72
|
+
default=(
|
73
|
+
"You are an expert content formatter and technical writer specializing in creating "
|
74
|
+
"clear, well-structured, and professional presentations. Your role is to take "
|
75
|
+
"raw content and transform it into the requested format while:"
|
76
|
+
"\n- Maintaining accuracy and technical precision"
|
77
|
+
"\n- Ensuring clear hierarchical structure"
|
78
|
+
"\n- Following consistent formatting standards"
|
79
|
+
"\n- Using appropriate professional tone"
|
80
|
+
"\n- Creating logical information flow"
|
81
|
+
"\n- Emphasizing key points effectively"
|
82
|
+
"\n- Adapting detail level to format and audience"
|
83
|
+
"\n- Incorporating relevant examples and references"
|
84
|
+
"\n- Following industry best practices for each format"
|
85
|
+
)
|
86
|
+
)
|
87
|
+
additional_info: str | None = Field(default=None, description="Default additional formatting instructions")
|
88
|
+
on_token: Callable | None = Field(default=None, exclude=True)
|
89
|
+
generative_model: GenerativeModel | None = Field(default=None, exclude=True)
|
90
|
+
event_emitter: EventEmitter | None = Field(default=None, exclude=True)
|
91
|
+
|
92
|
+
FORMAT_PROMPTS: ClassVar[Dict[str, str]] = {
|
93
|
+
"technical_doc": (
|
94
|
+
"Format this as a comprehensive technical documentation with:"
|
95
|
+
"\n- Clear hierarchical sections and subsections"
|
96
|
+
"\n- Properly formatted code examples and technical details"
|
97
|
+
"\n- Implementation details and considerations"
|
98
|
+
"\n- Prerequisites and dependencies"
|
99
|
+
"\n- Troubleshooting guidelines"
|
100
|
+
),
|
101
|
+
"executive_summary": (
|
102
|
+
"Create a concise executive summary that:"
|
103
|
+
"\n- Highlights key points, decisions, and outcomes"
|
104
|
+
"\n- Uses business-friendly language"
|
105
|
+
"\n- Includes actionable insights"
|
106
|
+
"\n- Provides clear recommendations"
|
107
|
+
"\n- Summarizes impact and value"
|
108
|
+
),
|
109
|
+
"markdown": (
|
110
|
+
"Convert this content into well-structured markdown with:"
|
111
|
+
"\n- Appropriate heading levels"
|
112
|
+
"\n- Properly formatted lists and tables"
|
113
|
+
"\n- Code blocks with language specification"
|
114
|
+
"\n- Links and references"
|
115
|
+
"\n- Emphasis and formatting for readability"
|
116
|
+
),
|
117
|
+
"bullet_points": (
|
118
|
+
"Transform this into a clear hierarchical bullet-point format with:"
|
119
|
+
"\n- Main points and key takeaways"
|
120
|
+
"\n- Organized sub-points and details"
|
121
|
+
"\n- Consistent formatting and indentation"
|
122
|
+
"\n- Clear relationships between points"
|
123
|
+
),
|
124
|
+
"tutorial": (
|
125
|
+
"Structure this as a comprehensive tutorial with:"
|
126
|
+
"\n- Clear prerequisites and setup instructions"
|
127
|
+
"\n- Step-by-step guidance with examples"
|
128
|
+
"\n- Common pitfalls and solutions"
|
129
|
+
"\n- Practice exercises or challenges"
|
130
|
+
"\n- Further learning resources"
|
131
|
+
),
|
132
|
+
"api_doc": (
|
133
|
+
"Format this as detailed API documentation including:"
|
134
|
+
"\n- Endpoint descriptions and URLs"
|
135
|
+
"\n- Request/response formats and examples"
|
136
|
+
"\n- Authentication requirements"
|
137
|
+
"\n- Error handling and status codes"
|
138
|
+
"\n- Usage examples and best practices"
|
139
|
+
),
|
140
|
+
"article": (
|
141
|
+
"Format this as an engaging article with:"
|
142
|
+
"\n- Compelling introduction and conclusion"
|
143
|
+
"\n- Clear sections and transitions"
|
144
|
+
"\n- Supporting examples and evidence"
|
145
|
+
"\n- Engaging writing style"
|
146
|
+
"\n- Key takeaways or summary"
|
147
|
+
),
|
148
|
+
"html_doc": (
|
149
|
+
"Format this as HTML documentation with:"
|
150
|
+
"\n- Semantic HTML structure"
|
151
|
+
"\n- Navigation and table of contents"
|
152
|
+
"\n- Code examples with syntax highlighting"
|
153
|
+
"\n- Cross-references and links"
|
154
|
+
"\n- Responsive layout considerations"
|
155
|
+
),
|
156
|
+
"slide_deck": (
|
157
|
+
"Structure this as presentation slides with:"
|
158
|
+
"\n- Clear title and agenda"
|
159
|
+
"\n- One main point per slide"
|
160
|
+
"\n- Supporting visuals or diagrams"
|
161
|
+
"\n- Speaker notes or talking points"
|
162
|
+
"\n- Call to action or next steps"
|
163
|
+
),
|
164
|
+
"code_review": (
|
165
|
+
"Format this as a detailed code review with:"
|
166
|
+
"\n- Code quality assessment"
|
167
|
+
"\n- Performance considerations"
|
168
|
+
"\n- Security implications"
|
169
|
+
"\n- Suggested improvements"
|
170
|
+
"\n- Best practices alignment"
|
171
|
+
),
|
172
|
+
"release_notes": (
|
173
|
+
"Structure this as release notes with:"
|
174
|
+
"\n- Version and date information"
|
175
|
+
"\n- New features and enhancements"
|
176
|
+
"\n- Bug fixes and improvements"
|
177
|
+
"\n- Breaking changes"
|
178
|
+
"\n- Upgrade instructions"
|
179
|
+
),
|
180
|
+
"user_guide": (
|
181
|
+
"Format this as a user guide with:"
|
182
|
+
"\n- Getting started instructions"
|
183
|
+
"\n- Feature explanations and use cases"
|
184
|
+
"\n- Configuration options"
|
185
|
+
"\n- Troubleshooting steps"
|
186
|
+
"\n- FAQs and support information"
|
187
|
+
),
|
188
|
+
"research_paper": (
|
189
|
+
"Structure this as a research paper with:"
|
190
|
+
"\n- Abstract and introduction"
|
191
|
+
"\n- Methodology and approach"
|
192
|
+
"\n- Results and analysis"
|
193
|
+
"\n- Discussion and implications"
|
194
|
+
"\n- References and citations"
|
195
|
+
),
|
196
|
+
"database_doc": (
|
197
|
+
"Format this as a comprehensive database documentation with:"
|
198
|
+
"\n- Schema overview and ER diagrams (in text/ascii format)"
|
199
|
+
"\n- Table descriptions and relationships"
|
200
|
+
"\n- Column details (name, type, constraints, indexes)"
|
201
|
+
"\n- Primary and foreign key relationships"
|
202
|
+
"\n- Common queries and use cases"
|
203
|
+
"\n- Performance considerations"
|
204
|
+
"\n- Data integrity rules"
|
205
|
+
"\n- Security and access control"
|
206
|
+
),
|
207
|
+
"analytics_report": (
|
208
|
+
"Structure this as a data analytics report with:"
|
209
|
+
"\n- Key metrics and KPIs"
|
210
|
+
"\n- Data summary and statistics"
|
211
|
+
"\n- Trend analysis and patterns"
|
212
|
+
"\n- Visual representations (in text/ascii format)"
|
213
|
+
"\n- Correlations and relationships"
|
214
|
+
"\n- Insights and findings"
|
215
|
+
"\n- Recommendations based on data"
|
216
|
+
"\n- Data quality notes"
|
217
|
+
"\n- Methodology and data sources"
|
218
|
+
),
|
219
|
+
}
|
220
|
+
|
221
|
+
def __init__(
|
222
|
+
self,
|
223
|
+
model_name: str,
|
224
|
+
on_token: Callable | None = None,
|
225
|
+
name: str = "presentation_llm_tool",
|
226
|
+
generative_model: GenerativeModel | None = None,
|
227
|
+
event_emitter: EventEmitter | None = None,
|
228
|
+
additional_info: str | None = None,
|
229
|
+
):
|
230
|
+
"""Initialize the Presentation LLM tool.
|
231
|
+
|
232
|
+
Args:
|
233
|
+
model_name (str): Name of the language model to use
|
234
|
+
on_token (Callable | None): Optional callback for token streaming
|
235
|
+
name (str): Name of the tool
|
236
|
+
generative_model (GenerativeModel | None): Optional pre-configured model
|
237
|
+
event_emitter (EventEmitter | None): Optional event emitter
|
238
|
+
additional_info (str | None): Default additional formatting instructions
|
239
|
+
"""
|
240
|
+
super().__init__(
|
241
|
+
**{
|
242
|
+
"model_name": model_name,
|
243
|
+
"on_token": on_token,
|
244
|
+
"name": name,
|
245
|
+
"generative_model": generative_model,
|
246
|
+
"event_emitter": event_emitter,
|
247
|
+
"additional_info": additional_info,
|
248
|
+
}
|
249
|
+
)
|
250
|
+
self.model_post_init(None)
|
251
|
+
|
252
|
+
def model_post_init(self, __context):
|
253
|
+
"""Initialize the generative model after model initialization."""
|
254
|
+
if self.generative_model is None:
|
255
|
+
self.generative_model = GenerativeModel(
|
256
|
+
model=self.model_name,
|
257
|
+
event_emitter=self.event_emitter
|
258
|
+
)
|
259
|
+
logger.debug(f"Initialized PresentationLLMTool with model: {self.model_name}")
|
260
|
+
|
261
|
+
if self.on_token is not None:
|
262
|
+
logger.debug("Setting up event listener for PresentationLLMTool")
|
263
|
+
self.generative_model.event_emitter.on("stream_chunk", self.on_token)
|
264
|
+
|
265
|
+
def execute(
|
266
|
+
self,
|
267
|
+
content: str,
|
268
|
+
format_style: Literal[
|
269
|
+
"technical_doc",
|
270
|
+
"executive_summary",
|
271
|
+
"markdown",
|
272
|
+
"bullet_points",
|
273
|
+
"tutorial",
|
274
|
+
"api_doc",
|
275
|
+
"article",
|
276
|
+
"html_doc",
|
277
|
+
"slide_deck",
|
278
|
+
"code_review",
|
279
|
+
"release_notes",
|
280
|
+
"user_guide",
|
281
|
+
"research_paper",
|
282
|
+
"database_doc",
|
283
|
+
"analytics_report",
|
284
|
+
],
|
285
|
+
temperature: str = "0.3",
|
286
|
+
additional_info: str | None = None,
|
287
|
+
) -> str:
|
288
|
+
"""Execute the tool to format the content according to the specified style.
|
289
|
+
|
290
|
+
Args:
|
291
|
+
content (str): The raw content to be formatted
|
292
|
+
format_style (str): The desired presentation format
|
293
|
+
temperature (str): Sampling temperature, defaults to "0.3"
|
294
|
+
additional_info (str | None): Optional additional notes or requirements for formatting.
|
295
|
+
If not provided, will use the default additional_info set during initialization.
|
296
|
+
Example: "Please make it concise and focus on technical accuracy."
|
297
|
+
|
298
|
+
Returns:
|
299
|
+
str: The formatted content
|
300
|
+
|
301
|
+
Raises:
|
302
|
+
ValueError: If temperature is invalid or format_style is not supported
|
303
|
+
"""
|
304
|
+
try:
|
305
|
+
temp = float(temperature)
|
306
|
+
if not (0.0 <= temp <= 1.0):
|
307
|
+
raise ValueError("Temperature must be between 0 and 1.")
|
308
|
+
except ValueError as ve:
|
309
|
+
logger.error(f"Invalid temperature value: {temperature}")
|
310
|
+
raise ValueError(f"Invalid temperature value: {temperature}") from ve
|
311
|
+
|
312
|
+
if format_style not in self.FORMAT_PROMPTS:
|
313
|
+
raise ValueError(f"Unsupported format style: {format_style}")
|
314
|
+
|
315
|
+
format_prompt = self.FORMAT_PROMPTS[format_style]
|
316
|
+
|
317
|
+
# Use provided additional_info or fall back to default
|
318
|
+
info_to_use = additional_info if additional_info is not None else self.additional_info
|
319
|
+
|
320
|
+
# Add additional formatting instructions if provided
|
321
|
+
additional_instructions = ""
|
322
|
+
if info_to_use:
|
323
|
+
additional_instructions = f"\n\nAdditional requirements:\n{info_to_use}"
|
324
|
+
|
325
|
+
messages_history = [
|
326
|
+
Message(role="system", content=self.system_prompt),
|
327
|
+
Message(
|
328
|
+
role="user",
|
329
|
+
content=(
|
330
|
+
f"{format_prompt}\n{additional_instructions}\n\n"
|
331
|
+
f"Here's the content to format:\n\n{content}"
|
332
|
+
),
|
333
|
+
),
|
334
|
+
]
|
335
|
+
|
336
|
+
is_streaming = self.on_token is not None
|
337
|
+
|
338
|
+
if self.generative_model:
|
339
|
+
self.generative_model.temperature = temp
|
340
|
+
|
341
|
+
try:
|
342
|
+
result = self.generative_model.generate_with_history(
|
343
|
+
messages_history=messages_history,
|
344
|
+
prompt="", # Prompt is already included in messages_history
|
345
|
+
streaming=is_streaming
|
346
|
+
)
|
347
|
+
|
348
|
+
if is_streaming:
|
349
|
+
response = ""
|
350
|
+
for chunk in result:
|
351
|
+
response += chunk
|
352
|
+
else:
|
353
|
+
response = result.response
|
354
|
+
|
355
|
+
logger.debug("Successfully formatted content")
|
356
|
+
return response
|
357
|
+
except Exception as e:
|
358
|
+
logger.error(f"Error formatting content: {e}")
|
359
|
+
raise Exception(f"Error formatting content: {e}") from e
|
360
|
+
else:
|
361
|
+
raise ValueError("Generative model not initialized")
|
362
|
+
|
363
|
+
|
364
|
+
if __name__ == "__main__":
|
365
|
+
# Example usage
|
366
|
+
formatter = PresentationLLMTool(
|
367
|
+
model_name="openrouter/openai/gpt-4o-mini",
|
368
|
+
on_token=console_print_token
|
369
|
+
)
|
370
|
+
|
371
|
+
raw_content = """
|
372
|
+
Analysis results:
|
373
|
+
- Found 3 critical bugs in the authentication system
|
374
|
+
- Performance improved by 45% after optimization
|
375
|
+
- New API endpoints added for user management
|
376
|
+
- Database queries optimized
|
377
|
+
- Added unit tests for core functions
|
378
|
+
"""
|
379
|
+
|
380
|
+
# Format as technical documentation
|
381
|
+
tech_doc = formatter.execute(
|
382
|
+
content=raw_content,
|
383
|
+
format_style="technical_doc",
|
384
|
+
temperature="0.3"
|
385
|
+
)
|
386
|
+
print("\nTechnical Documentation Format:")
|
387
|
+
print(tech_doc)
|
388
|
+
|
389
|
+
# Format as bullet points
|
390
|
+
bullets = formatter.execute(
|
391
|
+
content=raw_content,
|
392
|
+
format_style="bullet_points",
|
393
|
+
temperature="0.3"
|
394
|
+
)
|
395
|
+
print("\nBullet Points Format:")
|
396
|
+
print(bullets)
|
@@ -0,0 +1,258 @@
|
|
1
|
+
"""Product Hunt API tool.
|
2
|
+
|
3
|
+
This tool provides access to Product Hunt's GraphQL API to retrieve information about
|
4
|
+
products, posts, collections, and more.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from datetime import datetime
|
8
|
+
from typing import Any, Dict, List
|
9
|
+
|
10
|
+
from loguru import logger
|
11
|
+
|
12
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
13
|
+
|
14
|
+
from .services import ProductHuntService
|
15
|
+
|
16
|
+
|
17
|
+
class ProductHuntTool(Tool):
|
18
|
+
"""Tool for accessing Product Hunt's GraphQL API.
|
19
|
+
|
20
|
+
Features:
|
21
|
+
- Search for products
|
22
|
+
- Get product details
|
23
|
+
- Get posts by date
|
24
|
+
- Get collections
|
25
|
+
- Get topics
|
26
|
+
"""
|
27
|
+
|
28
|
+
name: str = "product_hunt_tool"
|
29
|
+
description: str = (
|
30
|
+
"Product Hunt API tool for retrieving information about products, "
|
31
|
+
"posts, collections, and more using GraphQL API."
|
32
|
+
)
|
33
|
+
arguments: List[ToolArgument] = [
|
34
|
+
ToolArgument(
|
35
|
+
name="query_type",
|
36
|
+
arg_type="string",
|
37
|
+
description="Type of query to execute (posts, product, search, collections)",
|
38
|
+
required=True,
|
39
|
+
example="posts",
|
40
|
+
),
|
41
|
+
ToolArgument(
|
42
|
+
name="search_query",
|
43
|
+
arg_type="string",
|
44
|
+
description="Search query for products/posts",
|
45
|
+
required=False,
|
46
|
+
example="AI tools",
|
47
|
+
),
|
48
|
+
ToolArgument(
|
49
|
+
name="date",
|
50
|
+
arg_type="string",
|
51
|
+
description="Date for posts query (YYYY-MM-DD format)",
|
52
|
+
required=False,
|
53
|
+
example="2024-02-24",
|
54
|
+
),
|
55
|
+
ToolArgument(
|
56
|
+
name="first",
|
57
|
+
arg_type="int",
|
58
|
+
description="Number of items to return (max 20)",
|
59
|
+
required=False,
|
60
|
+
default="10",
|
61
|
+
example="5",
|
62
|
+
),
|
63
|
+
]
|
64
|
+
|
65
|
+
def __init__(self):
|
66
|
+
"""Initialize the Product Hunt tool."""
|
67
|
+
super().__init__()
|
68
|
+
self.service = ProductHuntService()
|
69
|
+
|
70
|
+
def execute(self, **kwargs) -> str:
|
71
|
+
"""Execute the Product Hunt API tool with provided arguments.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
**kwargs: Tool arguments including query_type and other parameters
|
75
|
+
|
76
|
+
Returns:
|
77
|
+
Formatted string containing the API response data
|
78
|
+
|
79
|
+
Raises:
|
80
|
+
ValueError: If required arguments are missing
|
81
|
+
RuntimeError: If the API request fails
|
82
|
+
"""
|
83
|
+
query_type = kwargs.get("query_type")
|
84
|
+
if not query_type:
|
85
|
+
raise ValueError("query_type is required")
|
86
|
+
|
87
|
+
try:
|
88
|
+
if query_type == "posts":
|
89
|
+
return self._get_posts(kwargs)
|
90
|
+
elif query_type == "search":
|
91
|
+
return self._search_products(kwargs)
|
92
|
+
elif query_type == "collections":
|
93
|
+
return self._get_collections(kwargs)
|
94
|
+
else:
|
95
|
+
raise ValueError(f"Unsupported query_type: {query_type}")
|
96
|
+
except Exception as e:
|
97
|
+
logger.error(f"Product Hunt tool execution failed: {str(e)}")
|
98
|
+
raise RuntimeError(f"Tool execution failed: {str(e)}")
|
99
|
+
|
100
|
+
def _get_posts(self, params: Dict[str, Any]) -> str:
|
101
|
+
query = """
|
102
|
+
query Posts($first: Int, $postedAfter: DateTime, $postedBefore: DateTime) {
|
103
|
+
posts(first: $first, postedAfter: $postedAfter, postedBefore: $postedBefore) {
|
104
|
+
edges {
|
105
|
+
node {
|
106
|
+
id
|
107
|
+
name
|
108
|
+
tagline
|
109
|
+
description
|
110
|
+
url
|
111
|
+
votesCount
|
112
|
+
website
|
113
|
+
thumbnail {
|
114
|
+
url
|
115
|
+
}
|
116
|
+
topics {
|
117
|
+
edges {
|
118
|
+
node {
|
119
|
+
name
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
"""
|
128
|
+
|
129
|
+
variables = {
|
130
|
+
"first": min(int(params.get("first", 10)), 20),
|
131
|
+
}
|
132
|
+
|
133
|
+
if params.get("date"):
|
134
|
+
try:
|
135
|
+
date = datetime.strptime(params["date"], "%Y-%m-%d")
|
136
|
+
variables["postedAfter"] = date.replace(hour=0, minute=0, second=0).isoformat()
|
137
|
+
variables["postedBefore"] = date.replace(hour=23, minute=59, second=59).isoformat()
|
138
|
+
except ValueError as e:
|
139
|
+
raise ValueError(f"Invalid date format. Use YYYY-MM-DD: {str(e)}")
|
140
|
+
|
141
|
+
result = self.service.execute_query(query, variables)
|
142
|
+
posts = result.get("data", {}).get("posts", {}).get("edges", [])
|
143
|
+
|
144
|
+
if not posts:
|
145
|
+
return "No posts found"
|
146
|
+
|
147
|
+
formatted_posts = []
|
148
|
+
for post in posts:
|
149
|
+
node = post["node"]
|
150
|
+
topics = [t["node"]["name"] for t in node.get("topics", {}).get("edges", [])]
|
151
|
+
formatted_posts.append(
|
152
|
+
f"• {node['name']}\n"
|
153
|
+
f" {node['tagline']}\n"
|
154
|
+
f" Topics: {', '.join(topics)}\n"
|
155
|
+
f" Votes: {node['votesCount']}\n"
|
156
|
+
f" URL: {node['url']}\n"
|
157
|
+
)
|
158
|
+
|
159
|
+
return "\n".join(formatted_posts)
|
160
|
+
|
161
|
+
def _search_products(self, params: Dict[str, Any]) -> str:
|
162
|
+
query = """
|
163
|
+
query Search($query: String!, $first: Int) {
|
164
|
+
search(query: $query, first: $first, types: [POST]) {
|
165
|
+
edges {
|
166
|
+
node {
|
167
|
+
... on Post {
|
168
|
+
id
|
169
|
+
name
|
170
|
+
tagline
|
171
|
+
description
|
172
|
+
url
|
173
|
+
votesCount
|
174
|
+
topics {
|
175
|
+
edges {
|
176
|
+
node {
|
177
|
+
name
|
178
|
+
}
|
179
|
+
}
|
180
|
+
}
|
181
|
+
}
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
}
|
186
|
+
"""
|
187
|
+
|
188
|
+
if not params.get("search_query"):
|
189
|
+
raise ValueError("search_query is required for search")
|
190
|
+
|
191
|
+
variables = {
|
192
|
+
"query": params["search_query"],
|
193
|
+
"first": min(int(params.get("first", 10)), 20),
|
194
|
+
}
|
195
|
+
|
196
|
+
result = self.service.execute_query(query, variables)
|
197
|
+
products = result.get("data", {}).get("search", {}).get("edges", [])
|
198
|
+
|
199
|
+
if not products:
|
200
|
+
return "No products found"
|
201
|
+
|
202
|
+
formatted_products = []
|
203
|
+
for product in products:
|
204
|
+
node = product["node"]
|
205
|
+
topics = [t["node"]["name"] for t in node.get("topics", {}).get("edges", [])]
|
206
|
+
formatted_products.append(
|
207
|
+
f"• {node['name']}\n"
|
208
|
+
f" {node['tagline']}\n"
|
209
|
+
f" Topics: {', '.join(topics)}\n"
|
210
|
+
f" Votes: {node['votesCount']}\n"
|
211
|
+
f" URL: {node['url']}\n"
|
212
|
+
)
|
213
|
+
|
214
|
+
return "\n".join(formatted_products)
|
215
|
+
|
216
|
+
|
217
|
+
def _get_collections(self, params: Dict[str, Any]) -> str:
|
218
|
+
query = """
|
219
|
+
query Collections($first: Int) {
|
220
|
+
collections(first: $first) {
|
221
|
+
edges {
|
222
|
+
node {
|
223
|
+
id
|
224
|
+
name
|
225
|
+
description
|
226
|
+
url
|
227
|
+
productsCount
|
228
|
+
posts {
|
229
|
+
totalCount
|
230
|
+
}
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
}
|
235
|
+
"""
|
236
|
+
|
237
|
+
variables = {
|
238
|
+
"first": min(int(params.get("first", 10)), 20),
|
239
|
+
}
|
240
|
+
|
241
|
+
result = self.service.execute_query(query, variables)
|
242
|
+
collections = result.get("data", {}).get("collections", {}).get("edges", [])
|
243
|
+
|
244
|
+
if not collections:
|
245
|
+
return "No collections found"
|
246
|
+
|
247
|
+
formatted_collections = []
|
248
|
+
for collection in collections:
|
249
|
+
node = collection["node"]
|
250
|
+
formatted_collections.append(
|
251
|
+
f"• {node['name']}\n"
|
252
|
+
f" Products: {node['productsCount']}\n"
|
253
|
+
f" Posts: {node['posts']['totalCount']}\n"
|
254
|
+
f" {node['description']}\n"
|
255
|
+
f" URL: {node['url']}\n"
|
256
|
+
)
|
257
|
+
|
258
|
+
return "\n".join(formatted_collections)
|