quantalogic 0.2.14__tar.gz → 0.2.15__tar.gz

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 (74) hide show
  1. {quantalogic-0.2.14 → quantalogic-0.2.15}/PKG-INFO +2 -1
  2. {quantalogic-0.2.14 → quantalogic-0.2.15}/pyproject.toml +2 -1
  3. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/agent_config.py +4 -0
  4. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/generative_model.py +43 -15
  5. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/search_agent.py +14 -3
  6. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/__init__.py +2 -0
  7. quantalogic-0.2.15/quantalogic/tools/duckduckgo_search_tool.py +214 -0
  8. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/markitdown_tool.py +36 -33
  9. {quantalogic-0.2.14 → quantalogic-0.2.15}/LICENSE +0 -0
  10. {quantalogic-0.2.14 → quantalogic-0.2.15}/README.md +0 -0
  11. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/__init__.py +0 -0
  12. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/agent.py +0 -0
  13. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/coding_agent.py +0 -0
  14. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/event_emitter.py +0 -0
  15. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/interactive_text_editor.py +0 -0
  16. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/main.py +0 -0
  17. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/memory.py +0 -0
  18. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/model_names.py +0 -0
  19. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/print_event.py +0 -0
  20. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/prompts.py +0 -0
  21. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/server/__init__.py +0 -0
  22. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/server/agent_server.py +0 -0
  23. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/server/models.py +0 -0
  24. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/server/routes.py +0 -0
  25. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/server/state.py +0 -0
  26. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/server/static/js/event_visualizer.js +0 -0
  27. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/server/static/js/quantalogic.js +0 -0
  28. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/server/templates/index.html +0 -0
  29. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tool_manager.py +0 -0
  30. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/agent_tool.py +0 -0
  31. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/download_http_file_tool.py +0 -0
  32. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/edit_whole_content_tool.py +0 -0
  33. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/elixir_tool.py +0 -0
  34. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/execute_bash_command_tool.py +0 -0
  35. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/input_question_tool.py +0 -0
  36. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/language_handlers/__init__.py +0 -0
  37. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/language_handlers/c_handler.py +0 -0
  38. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/language_handlers/cpp_handler.py +0 -0
  39. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/language_handlers/go_handler.py +0 -0
  40. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/language_handlers/java_handler.py +0 -0
  41. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/language_handlers/javascript_handler.py +0 -0
  42. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/language_handlers/python_handler.py +0 -0
  43. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/language_handlers/rust_handler.py +0 -0
  44. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/language_handlers/scala_handler.py +0 -0
  45. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/language_handlers/typescript_handler.py +0 -0
  46. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/list_directory_tool.py +0 -0
  47. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/llm_tool.py +0 -0
  48. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/llm_vision_tool.py +0 -0
  49. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/nodejs_tool.py +0 -0
  50. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/python_tool.py +0 -0
  51. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/read_file_block_tool.py +0 -0
  52. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/read_file_tool.py +0 -0
  53. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/replace_in_file_tool.py +0 -0
  54. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/ripgrep_tool.py +0 -0
  55. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/search_definition_names.py +0 -0
  56. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/serpapi_search_tool.py +0 -0
  57. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/task_complete_tool.py +0 -0
  58. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/tool.py +0 -0
  59. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/unified_diff_tool.py +0 -0
  60. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/wikipedia_search_tool.py +0 -0
  61. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/tools/write_file_tool.py +0 -0
  62. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/utils/__init__.py +0 -0
  63. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/utils/ask_user_validation.py +0 -0
  64. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/utils/check_version.py +0 -0
  65. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/utils/download_http_file.py +0 -0
  66. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/utils/get_coding_environment.py +0 -0
  67. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/utils/get_environment.py +0 -0
  68. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/utils/get_quantalogic_rules_content.py +0 -0
  69. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/utils/git_ls.py +0 -0
  70. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/utils/read_file.py +0 -0
  71. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/utils/read_http_text_content.py +0 -0
  72. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/version.py +0 -0
  73. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/xml_parser.py +0 -0
  74. {quantalogic-0.2.14 → quantalogic-0.2.15}/quantalogic/xml_tool_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantalogic
3
- Version: 0.2.14
3
+ Version: 0.2.15
4
4
  Summary: QuantaLogic ReAct Agents
5
5
  Author: Raphaël MANSUY
6
6
  Author-email: raphael.mansuy@gmail.com
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.12
10
10
  Classifier: Programming Language :: Python :: 3.13
11
11
  Requires-Dist: boto3 (>=1.35.86,<2.0.0)
12
12
  Requires-Dist: click (>=8.1.8,<9.0.0)
13
+ Requires-Dist: duckduckgo-search (>=7.2.1,<8.0.0)
13
14
  Requires-Dist: fastapi (>=0.115.6,<0.116.0)
14
15
  Requires-Dist: google-auth (>=2.20.0,<3.0.0)
15
16
  Requires-Dist: google-search-results (>=2.4.2,<3.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "quantalogic"
3
- version = "0.2.14"
3
+ version = "0.2.15"
4
4
  description = "QuantaLogic ReAct Agents"
5
5
  authors = ["Raphaël MANSUY <raphael.mansuy@gmail.com>"]
6
6
  readme = "README.md"
@@ -36,6 +36,7 @@ toml = "^0.10.2"
36
36
  types-requests = "^2.32.0.20241016"
37
37
  google-search-results = "^2.4.2"
38
38
  serpapi = "^0.1.5"
39
+ duckduckgo-search = "^7.2.1"
39
40
 
40
41
  [tool.poetry.scripts]
41
42
  quantalogic = "quantalogic.main:cli"
@@ -24,6 +24,8 @@ from quantalogic.tools import (
24
24
  SearchDefinitionNames,
25
25
  TaskCompleteTool,
26
26
  WriteFileTool,
27
+ DuckDuckGoSearchTool,
28
+ WikipediaSearchTool,
27
29
  )
28
30
 
29
31
  MODEL_NAME = "deepseek/deepseek-chat"
@@ -124,6 +126,8 @@ def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
124
126
  MarkitdownTool(),
125
127
  LLMTool(model_name=model_name),
126
128
  DownloadHttpFileTool(),
129
+ WikipediaSearchTool(),
130
+ DuckDuckGoSearchTool(),
127
131
  ]
128
132
 
129
133
  if vision_model_name:
@@ -1,5 +1,7 @@
1
1
  """Generative model module for AI-powered text generation."""
2
2
 
3
+ import functools
4
+
3
5
  import openai
4
6
  from litellm import completion, exceptions, get_max_tokens, get_model_info, token_counter
5
7
  from loguru import logger
@@ -83,6 +85,7 @@ class GenerativeModel:
83
85
  logger.debug(f"Initializing GenerativeModel with model={model}, temperature={temperature}")
84
86
  self.model = model
85
87
  self.temperature = temperature
88
+ self._get_model_info_cached = functools.lru_cache(maxsize=32)(self._get_model_info_impl)
86
89
 
87
90
  # Define retriable exceptions based on LiteLLM's exception mapping
88
91
  RETRIABLE_EXCEPTIONS = (
@@ -235,21 +238,46 @@ class GenerativeModel:
235
238
  litellm_messages.append({"role": "user", "content": str(prompt)})
236
239
  return token_counter(model=self.model, messages=litellm_messages)
237
240
 
238
- def get_model_info(self) -> dict | None:
239
- """Get information about the model."""
240
- logger.debug(f"Retrieving model info for {self.model}")
241
- model_info = get_model_info(self.model)
242
-
243
- if not model_info:
244
- logger.debug("Model info not found, trying without openrouter/ prefix")
245
- model_info = get_model_info(self.model.replace("openrouter/", ""))
246
-
247
- if model_info:
248
- logger.debug(f"Model info retrieved: {model_info.keys()}")
249
- else:
250
- logger.debug("No model info available")
251
-
252
- return model_info
241
+ def _get_model_info_impl(self, model_name: str) -> dict:
242
+ """Get information about the model with prefix fallback logic.
243
+
244
+ Attempts to find model info by progressively removing provider prefixes.
245
+ Raises ValueError if no valid model configuration is found.
246
+ Results are cached to improve performance.
247
+
248
+ Example:
249
+ openrouter/openai/gpt-4o-mini → openai/gpt-4o-mini → gpt-4o-mini
250
+ """
251
+ original_model = model_name
252
+
253
+ while True:
254
+ try:
255
+ logger.debug(f"Attempting to retrieve model info for: {model_name}")
256
+ model_info = get_model_info(model_name)
257
+ if model_info:
258
+ logger.debug(f"Found model info for {model_name}: {model_info}")
259
+ return model_info
260
+ except Exception:
261
+ pass
262
+
263
+ # Try removing one prefix level
264
+ parts = model_name.split('/')
265
+ if len(parts) <= 1:
266
+ break
267
+ model_name = '/'.join(parts[1:])
268
+
269
+ error_msg = f"Could not find model info for {original_model} after trying: {self.model} → {model_name}"
270
+ logger.error(error_msg)
271
+ raise ValueError(error_msg)
272
+
273
+ def get_model_info(self, model_name: str = None) -> dict:
274
+ """Get cached information about the model.
275
+
276
+ If no model name is provided, uses the current model.
277
+ """
278
+ if model_name is None:
279
+ model_name = self.model
280
+ return self._get_model_info_cached(model_name)
253
281
 
254
282
  def get_model_max_input_tokens(self) -> int:
255
283
  """Get the maximum number of input tokens for the model."""
@@ -1,16 +1,26 @@
1
1
  from quantalogic.agent import Agent
2
- from quantalogic.tools import InputQuestionTool, SerpApiSearchTool, TaskCompleteTool, WikipediaSearchTool, ReadFileBlockTool,ReadFileTool, MarkitdownTool, RipgrepTool
2
+ from quantalogic.tools import (
3
+ InputQuestionTool,
4
+ SerpApiSearchTool,
5
+ DuckDuckGoSearchTool,
6
+ TaskCompleteTool,
7
+ WikipediaSearchTool,
8
+ ReadFileBlockTool,
9
+ ReadFileTool,
10
+ MarkitdownTool,
11
+ RipgrepTool
12
+ )
3
13
 
4
14
 
5
15
  def create_search_agent(model_name: str) -> Agent:
6
- """Creates and configures a search agent with web and knowledge search tools.
16
+ """Creates and configures a search agent with web, knowledge, and privacy-focused search tools.
7
17
 
8
18
  Args:
9
19
  model_name (str): Name of the language model to use for the agent's core capabilities
10
20
 
11
21
  Returns:
12
22
  Agent: A fully configured search agent instance with:
13
- - Web search capabilities (SerpAPI)
23
+ - Web search capabilities (SerpAPI, DuckDuckGo)
14
24
  - Knowledge search capabilities (Wikipedia)
15
25
  - Basic interaction tools
16
26
  """
@@ -22,6 +32,7 @@ def create_search_agent(model_name: str) -> Agent:
22
32
  tools = [
23
33
  # Search tools
24
34
  SerpApiSearchTool(), # Web search capabilities
35
+ DuckDuckGoSearchTool(), # Privacy-focused web search
25
36
  WikipediaSearchTool(), # Knowledge search capabilities
26
37
  # Basic interaction tools
27
38
  TaskCompleteTool(), # Marks task completion
@@ -18,6 +18,7 @@ from .replace_in_file_tool import ReplaceInFileTool
18
18
  from .ripgrep_tool import RipgrepTool
19
19
  from .search_definition_names import SearchDefinitionNames
20
20
  from .serpapi_search_tool import SerpApiSearchTool
21
+ from .duckduckgo_search_tool import DuckDuckGoSearchTool
21
22
  from .task_complete_tool import TaskCompleteTool
22
23
  from .tool import Tool, ToolArgument
23
24
  from .unified_diff_tool import UnifiedDiffTool
@@ -27,6 +28,7 @@ from .write_file_tool import WriteFileTool
27
28
  __all__ = [
28
29
  "WikipediaSearchTool",
29
30
  "SerpApiSearchTool",
31
+ "DuckDuckGoSearchTool",
30
32
  "Tool",
31
33
  "ToolArgument",
32
34
  "TaskCompleteTool",
@@ -0,0 +1,214 @@
1
+ """Tool for interacting with DuckDuckGo for search results."""
2
+
3
+ from duckduckgo_search import DDGS
4
+
5
+ from quantalogic.tools.tool import Tool, ToolArgument
6
+
7
+
8
+ class DuckDuckGoSearchTool(Tool):
9
+ """Tool for retrieving search results from DuckDuckGo.
10
+
11
+ This tool provides a convenient interface to DuckDuckGo's search capabilities,
12
+ supporting multiple search types and structured JSON output.
13
+
14
+ Example usage:
15
+ ```python
16
+ tool = DuckDuckGoSearchTool()
17
+ results = tool.execute(
18
+ query="machine learning",
19
+ search_type="text",
20
+ max_results=10,
21
+ region="us-en",
22
+ safesearch="moderate"
23
+ )
24
+ print(results)
25
+ ```
26
+
27
+ The tool handles:
28
+ - Query validation
29
+ - API error handling
30
+ - Multiple search types (text, images, videos, news)
31
+ - Scope filtering (region, safesearch, timelimit)
32
+ - JSON result formatting
33
+ """
34
+
35
+ name: str = "duckduckgo_tool"
36
+ description: str = (
37
+ "Retrieves search results from DuckDuckGo. "
38
+ "Provides structured output of search results."
39
+ )
40
+ arguments: list = [
41
+ ToolArgument(
42
+ name="query",
43
+ arg_type="string",
44
+ description="The search query to execute",
45
+ required=True,
46
+ example="machine learning",
47
+ ),
48
+ ToolArgument(
49
+ name="max_results",
50
+ arg_type="int",
51
+ description="Maximum number of results to retrieve (1-50)",
52
+ required=True,
53
+ default="10",
54
+ example="20",
55
+ ),
56
+ ToolArgument(
57
+ name="search_type",
58
+ arg_type="string",
59
+ description="Type of search to perform (text, images, videos, news)",
60
+ required=False,
61
+ default="text",
62
+ example="images",
63
+ ),
64
+ ToolArgument(
65
+ name="region",
66
+ arg_type="string",
67
+ description="Region for search results (e.g., 'wt-wt', 'us-en')",
68
+ required=False,
69
+ default="wt-wt",
70
+ example="us-en",
71
+ ),
72
+ ToolArgument(
73
+ name="safesearch",
74
+ arg_type="string",
75
+ description="Safesearch level ('on', 'moderate', 'off')",
76
+ required=False,
77
+ default="moderate",
78
+ example="moderate",
79
+ ),
80
+ ToolArgument(
81
+ name="timelimit",
82
+ arg_type="string",
83
+ description="Time limit for results (e.g., 'd' for day, 'w' for week)",
84
+ required=False,
85
+ default=None,
86
+ example="d",
87
+ ),
88
+ ]
89
+
90
+ def execute(
91
+ self,
92
+ query: str,
93
+ max_results: int = 10,
94
+ search_type: str = "text",
95
+ region: str = "wt-wt",
96
+ safesearch: str = "moderate",
97
+ timelimit: str = None,
98
+ ) -> str:
99
+ """Execute a search query using DuckDuckGo and return results.
100
+
101
+ Args:
102
+ query: The search query to execute
103
+ max_results: Maximum number of results to retrieve (1-50)
104
+ search_type: Type of search to perform (text, images, videos, news)
105
+ region: Region for search results (e.g., "wt-wt", "us-en")
106
+ safesearch: Safesearch level ("on", "moderate", "off")
107
+ timelimit: Time limit for results (e.g., "d" for day, "w" for week)
108
+
109
+ Returns:
110
+ Pretty-printed JSON string of search results.
111
+
112
+ Raises:
113
+ ValueError: If any parameter is invalid
114
+ RuntimeError: If search fails
115
+ """
116
+ # Handle empty string parameters by setting to defaults
117
+ query = str(query) if query else query
118
+ search_type = search_type if search_type else "text"
119
+ region = region if region else "wt-wt"
120
+ safesearch = safesearch if safesearch else "moderate"
121
+ timelimit = timelimit if timelimit else None
122
+
123
+ # Validate and convert query
124
+ if not query:
125
+ raise ValueError("Query must be a non-empty string")
126
+ try:
127
+ query = str(query)
128
+ except (TypeError, ValueError) as e:
129
+ raise ValueError(f"Query must be convertible to string: {str(e)}")
130
+
131
+ # Validate and convert max_results
132
+ try:
133
+ max_results = int(max_results)
134
+ if max_results < 1 or max_results > 50:
135
+ raise ValueError("Number of results must be between 1 and 50")
136
+ except (TypeError, ValueError) as e:
137
+ raise ValueError(f"Invalid number of results: {str(e)}")
138
+
139
+ # Validate search_type
140
+ if search_type not in ["text", "images", "videos", "news"]:
141
+ raise ValueError("search_type must be one of: text, images, videos, news")
142
+
143
+ # Validate safesearch
144
+ if safesearch not in ["on", "moderate", "off"]:
145
+ raise ValueError("safesearch must be one of: on, moderate, off")
146
+
147
+ try:
148
+ ddgs = DDGS()
149
+
150
+ # Perform the appropriate search based on search_type
151
+ if search_type == "text":
152
+ results = ddgs.text(
153
+ keywords=query,
154
+ region=region,
155
+ safesearch=safesearch,
156
+ timelimit=timelimit,
157
+ max_results=max_results,
158
+ )
159
+ elif search_type == "images":
160
+ results = ddgs.images(
161
+ keywords=query,
162
+ region=region,
163
+ safesearch=safesearch,
164
+ timelimit=timelimit,
165
+ max_results=max_results,
166
+ )
167
+ elif search_type == "videos":
168
+ results = ddgs.videos(
169
+ keywords=query,
170
+ region=region,
171
+ safesearch=safesearch,
172
+ timelimit=timelimit,
173
+ max_results=max_results,
174
+ )
175
+ elif search_type == "news":
176
+ results = ddgs.news(
177
+ keywords=query,
178
+ region=region,
179
+ safesearch=safesearch,
180
+ timelimit=timelimit,
181
+ max_results=max_results,
182
+ )
183
+
184
+ # Return pretty-printed JSON
185
+ import json
186
+ return json.dumps(results, indent=4, ensure_ascii=False)
187
+
188
+ except Exception as e:
189
+ raise RuntimeError(f"Search failed: {str(e)}")
190
+
191
+
192
+ def main():
193
+ """Demonstrate DuckDuckGoSearchTool functionality."""
194
+ try:
195
+ tool = DuckDuckGoSearchTool()
196
+
197
+ # Test basic search functionality
198
+ print("Testing DuckDuckGoSearchTool with sample query...")
199
+ results = tool.execute(query="Python programming", max_results=3)
200
+ print(results)
201
+
202
+ # Test error handling
203
+ print("\nTesting error handling with invalid query...")
204
+ try:
205
+ tool.execute(query="")
206
+ except ValueError as e:
207
+ print(f"Caught expected ValueError: {e}")
208
+
209
+ except Exception as e:
210
+ print(f"Error in main: {e}")
211
+
212
+
213
+ if __name__ == "__main__":
214
+ main()
@@ -47,50 +47,53 @@ class MarkitdownTool(Tool):
47
47
  Returns:
48
48
  str: The Markdown content or a success message.
49
49
  """
50
- # Handle tilde expansion for local paths
51
- if file_path.startswith("~"):
52
- file_path = os.path.expanduser(file_path)
53
-
54
- # Handle URL paths
55
- if file_path.startswith(("http://", "https://")):
56
- try:
57
- # Create a temporary file
58
- with tempfile.NamedTemporaryFile(delete=False) as temp_file:
59
- temp_path = temp_file.name
60
- # Download the file from URL
61
- download_http_file(file_path, temp_path)
62
- # Use the temporary file path for conversion
63
- file_path = temp_path
64
- is_temp_file = True
65
- except Exception as e:
66
- return f"Error downloading file from URL: {str(e)}"
67
- else:
68
- is_temp_file = False
69
-
70
50
  try:
71
- from markitdown import MarkItDown
51
+ # Handle URL paths first
52
+ if file_path.startswith(("http://", "https://")):
53
+ try:
54
+ with tempfile.NamedTemporaryFile(delete=False) as temp_file:
55
+ temp_path = temp_file.name
56
+ download_http_file(file_path, temp_path)
57
+ file_path = temp_path
58
+ is_temp_file = True
59
+ except Exception as e:
60
+ return f"Error downloading file from URL: {str(e)}"
61
+ else:
62
+ is_temp_file = False
63
+ # Handle local paths
64
+ if file_path.startswith("~"):
65
+ file_path = os.path.expanduser(file_path)
66
+ if not os.path.isabs(file_path):
67
+ file_path = os.path.abspath(file_path)
68
+
69
+ # Verify file exists
70
+ if not os.path.exists(file_path):
71
+ return f"Error: File not found at path: {file_path}"
72
72
 
73
+ from markitdown import MarkItDown
73
74
  md = MarkItDown()
74
75
  result = md.convert(file_path)
75
76
 
76
77
  if output_file_path:
78
+ # Ensure output directory exists
79
+ output_dir = os.path.dirname(output_file_path)
80
+ if output_dir and not os.path.exists(output_dir):
81
+ os.makedirs(output_dir)
82
+
77
83
  with open(output_file_path, "w", encoding="utf-8") as f:
78
84
  f.write(result.text_content)
79
- output_message = f"Markdown content successfully written to {output_file_path}"
80
- else:
81
- # Truncate content if it exceeds MAX_LINES
82
- lines = result.text_content.splitlines()
83
- if len(lines) > MAX_LINES:
84
- truncated_content = "\n".join(lines[:MAX_LINES])
85
- output_message = f"Markdown content truncated to {MAX_LINES} lines:\n{truncated_content}"
86
- else:
87
- output_message = result.text_content
88
-
89
- return output_message
85
+ return f"Markdown content successfully written to {output_file_path}"
86
+
87
+ # Handle content truncation
88
+ lines = result.text_content.splitlines()
89
+ if len(lines) > MAX_LINES:
90
+ truncated_content = "\n".join(lines[:MAX_LINES])
91
+ return f"Markdown content truncated to {MAX_LINES} lines:\n{truncated_content}"
92
+ return result.text_content
93
+
90
94
  except Exception as e:
91
95
  return f"Error converting file to Markdown: {str(e)}"
92
96
  finally:
93
- # Clean up temporary file if it was created
94
97
  if is_temp_file and os.path.exists(file_path):
95
98
  os.remove(file_path)
96
99
 
File without changes
File without changes