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.
Files changed (107) hide show
  1. quantalogic/__init__.py +0 -4
  2. quantalogic/agent.py +603 -363
  3. quantalogic/agent_config.py +233 -46
  4. quantalogic/agent_factory.py +34 -22
  5. quantalogic/coding_agent.py +16 -14
  6. quantalogic/config.py +2 -1
  7. quantalogic/console_print_events.py +4 -8
  8. quantalogic/console_print_token.py +2 -2
  9. quantalogic/docs_cli.py +15 -10
  10. quantalogic/event_emitter.py +258 -83
  11. quantalogic/flow/__init__.py +23 -0
  12. quantalogic/flow/flow.py +595 -0
  13. quantalogic/flow/flow_extractor.py +672 -0
  14. quantalogic/flow/flow_generator.py +89 -0
  15. quantalogic/flow/flow_manager.py +407 -0
  16. quantalogic/flow/flow_manager_schema.py +169 -0
  17. quantalogic/flow/flow_yaml.md +419 -0
  18. quantalogic/generative_model.py +109 -77
  19. quantalogic/get_model_info.py +5 -5
  20. quantalogic/interactive_text_editor.py +100 -73
  21. quantalogic/main.py +17 -21
  22. quantalogic/model_info_list.py +3 -3
  23. quantalogic/model_info_litellm.py +14 -14
  24. quantalogic/prompts.py +2 -1
  25. quantalogic/{llm.py → quantlitellm.py} +29 -39
  26. quantalogic/search_agent.py +4 -4
  27. quantalogic/server/models.py +4 -1
  28. quantalogic/task_file_reader.py +5 -5
  29. quantalogic/task_runner.py +20 -20
  30. quantalogic/tool_manager.py +10 -21
  31. quantalogic/tools/__init__.py +98 -68
  32. quantalogic/tools/composio/composio.py +416 -0
  33. quantalogic/tools/{generate_database_report_tool.py → database/generate_database_report_tool.py} +4 -9
  34. quantalogic/tools/database/sql_query_tool_advanced.py +261 -0
  35. quantalogic/tools/document_tools/markdown_to_docx_tool.py +620 -0
  36. quantalogic/tools/document_tools/markdown_to_epub_tool.py +438 -0
  37. quantalogic/tools/document_tools/markdown_to_html_tool.py +362 -0
  38. quantalogic/tools/document_tools/markdown_to_ipynb_tool.py +319 -0
  39. quantalogic/tools/document_tools/markdown_to_latex_tool.py +420 -0
  40. quantalogic/tools/document_tools/markdown_to_pdf_tool.py +623 -0
  41. quantalogic/tools/document_tools/markdown_to_pptx_tool.py +319 -0
  42. quantalogic/tools/duckduckgo_search_tool.py +2 -4
  43. quantalogic/tools/finance/alpha_vantage_tool.py +440 -0
  44. quantalogic/tools/finance/ccxt_tool.py +373 -0
  45. quantalogic/tools/finance/finance_llm_tool.py +387 -0
  46. quantalogic/tools/finance/google_finance.py +192 -0
  47. quantalogic/tools/finance/market_intelligence_tool.py +520 -0
  48. quantalogic/tools/finance/technical_analysis_tool.py +491 -0
  49. quantalogic/tools/finance/tradingview_tool.py +336 -0
  50. quantalogic/tools/finance/yahoo_finance.py +236 -0
  51. quantalogic/tools/git/bitbucket_clone_repo_tool.py +181 -0
  52. quantalogic/tools/git/bitbucket_operations_tool.py +326 -0
  53. quantalogic/tools/git/clone_repo_tool.py +189 -0
  54. quantalogic/tools/git/git_operations_tool.py +532 -0
  55. quantalogic/tools/google_packages/google_news_tool.py +480 -0
  56. quantalogic/tools/grep_app_tool.py +123 -186
  57. quantalogic/tools/{dalle_e.py → image_generation/dalle_e.py} +37 -27
  58. quantalogic/tools/jinja_tool.py +6 -10
  59. quantalogic/tools/language_handlers/__init__.py +22 -9
  60. quantalogic/tools/list_directory_tool.py +131 -42
  61. quantalogic/tools/llm_tool.py +45 -15
  62. quantalogic/tools/llm_vision_tool.py +59 -7
  63. quantalogic/tools/markitdown_tool.py +17 -5
  64. quantalogic/tools/nasa_packages/models.py +47 -0
  65. quantalogic/tools/nasa_packages/nasa_apod_tool.py +232 -0
  66. quantalogic/tools/nasa_packages/nasa_neows_tool.py +147 -0
  67. quantalogic/tools/nasa_packages/services.py +82 -0
  68. quantalogic/tools/presentation_tools/presentation_llm_tool.py +396 -0
  69. quantalogic/tools/product_hunt/product_hunt_tool.py +258 -0
  70. quantalogic/tools/product_hunt/services.py +63 -0
  71. quantalogic/tools/rag_tool/__init__.py +48 -0
  72. quantalogic/tools/rag_tool/document_metadata.py +15 -0
  73. quantalogic/tools/rag_tool/query_response.py +20 -0
  74. quantalogic/tools/rag_tool/rag_tool.py +566 -0
  75. quantalogic/tools/rag_tool/rag_tool_beta.py +264 -0
  76. quantalogic/tools/read_html_tool.py +24 -38
  77. quantalogic/tools/replace_in_file_tool.py +10 -10
  78. quantalogic/tools/safe_python_interpreter_tool.py +10 -24
  79. quantalogic/tools/search_definition_names.py +2 -2
  80. quantalogic/tools/sequence_tool.py +14 -23
  81. quantalogic/tools/sql_query_tool.py +17 -19
  82. quantalogic/tools/tool.py +39 -15
  83. quantalogic/tools/unified_diff_tool.py +1 -1
  84. quantalogic/tools/utilities/csv_processor_tool.py +234 -0
  85. quantalogic/tools/utilities/download_file_tool.py +179 -0
  86. quantalogic/tools/utilities/mermaid_validator_tool.py +661 -0
  87. quantalogic/tools/utils/__init__.py +1 -4
  88. quantalogic/tools/utils/create_sample_database.py +24 -38
  89. quantalogic/tools/utils/generate_database_report.py +74 -82
  90. quantalogic/tools/wikipedia_search_tool.py +17 -21
  91. quantalogic/utils/ask_user_validation.py +1 -1
  92. quantalogic/utils/async_utils.py +35 -0
  93. quantalogic/utils/check_version.py +3 -5
  94. quantalogic/utils/get_all_models.py +2 -1
  95. quantalogic/utils/git_ls.py +21 -7
  96. quantalogic/utils/lm_studio_model_info.py +9 -7
  97. quantalogic/utils/python_interpreter.py +113 -43
  98. quantalogic/utils/xml_utility.py +178 -0
  99. quantalogic/version_check.py +1 -1
  100. quantalogic/welcome_message.py +7 -7
  101. quantalogic/xml_parser.py +0 -1
  102. {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/METADATA +40 -1
  103. quantalogic-0.50.0.dist-info/RECORD +148 -0
  104. quantalogic-0.35.0.dist-info/RECORD +0 -102
  105. {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/LICENSE +0 -0
  106. {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/WHEEL +0 -0
  107. {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)