quantalogic 0.2.21__tar.gz → 0.2.23__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.
- {quantalogic-0.2.21 → quantalogic-0.2.23}/PKG-INFO +4 -1
- {quantalogic-0.2.21 → quantalogic-0.2.23}/README.md +1 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/pyproject.toml +3 -1
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/agent.py +1 -1
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/agent_config.py +16 -1
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/coding_agent.py +2 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/generative_model.py +76 -2
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/main.py +2 -2
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/__init__.py +4 -0
- quantalogic-0.2.23/quantalogic/tools/dalle_e.py +270 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/read_file_tool.py +3 -3
- quantalogic-0.2.23/quantalogic/tools/read_html_tool.py +303 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/LICENSE +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/__init__.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/console_print_events.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/console_print_token.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/docs_cli.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/event_emitter.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/interactive_text_editor.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/memory.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/model_names.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/prompts.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/search_agent.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/server/__init__.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/server/agent_server.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/server/models.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/server/routes.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/server/state.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/server/static/js/event_visualizer.js +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/server/static/js/quantalogic.js +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/server/templates/index.html +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tool_manager.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/agent_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/download_http_file_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/duckduckgo_search_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/edit_whole_content_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/elixir_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/execute_bash_command_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/input_question_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/jinja_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/__init__.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/c_handler.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/cpp_handler.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/go_handler.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/java_handler.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/javascript_handler.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/python_handler.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/rust_handler.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/scala_handler.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/typescript_handler.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/list_directory_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/llm_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/llm_vision_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/markitdown_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/nodejs_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/python_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/read_file_block_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/replace_in_file_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/ripgrep_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/search_definition_names.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/serpapi_search_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/task_complete_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/unified_diff_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/wikipedia_search_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/write_file_tool.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/utils/__init__.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/utils/ask_user_validation.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/utils/check_version.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/utils/download_http_file.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/utils/get_coding_environment.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/utils/get_environment.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/utils/get_quantalogic_rules_content.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/utils/git_ls.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/utils/read_file.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/utils/read_http_text_content.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/version.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/xml_parser.py +0 -0
- {quantalogic-0.2.21 → quantalogic-0.2.23}/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.
|
3
|
+
Version: 0.2.23
|
4
4
|
Summary: QuantaLogic ReAct Agents
|
5
5
|
Author: Raphaël MANSUY
|
6
6
|
Author-email: raphael.mansuy@gmail.com
|
@@ -8,6 +8,7 @@ Requires-Python: >=3.12,<4.0
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
9
9
|
Classifier: Programming Language :: Python :: 3.12
|
10
10
|
Classifier: Programming Language :: Python :: 3.13
|
11
|
+
Requires-Dist: beautifulsoup4 (>=4.12.3,<5.0.0)
|
11
12
|
Requires-Dist: boto3 (>=1.35.86,<2.0.0)
|
12
13
|
Requires-Dist: click (>=8.1.8,<9.0.0)
|
13
14
|
Requires-Dist: duckduckgo-search (>=7.2.1,<8.0.0)
|
@@ -18,6 +19,7 @@ Requires-Dist: jinja2 (>=3.1.5,<4.0.0)
|
|
18
19
|
Requires-Dist: litellm (>=1.56.4,<2.0.0)
|
19
20
|
Requires-Dist: llmlingua (>=0.2.2,<0.3.0)
|
20
21
|
Requires-Dist: loguru (>=0.7.3,<0.8.0)
|
22
|
+
Requires-Dist: markdownify (>=0.14.1,<0.15.0)
|
21
23
|
Requires-Dist: markitdown (>=0.0.1a3,<0.0.2)
|
22
24
|
Requires-Dist: mkdocs-git-revision-date-localized-plugin (>=1.2.0,<2.0.0)
|
23
25
|
Requires-Dist: mkdocs-macros-plugin (>=1.0.4,<2.0.0)
|
@@ -66,6 +68,7 @@ The `cli` version include coding capabilities comparable to Aider.
|
|
66
68
|
|
67
69
|

|
68
70
|
|
71
|
+
|
69
72
|
[HowTo Guide](./docs/howto/howto.md)
|
70
73
|
|
71
74
|
## Why QuantaLogic?
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "quantalogic"
|
3
|
-
version = "0.2.
|
3
|
+
version = "0.2.23"
|
4
4
|
description = "QuantaLogic ReAct Agents"
|
5
5
|
authors = ["Raphaël MANSUY <raphael.mansuy@gmail.com>"]
|
6
6
|
readme = "README.md"
|
@@ -47,6 +47,8 @@ mkdocstrings-python = "^1.7.0"
|
|
47
47
|
pymdown-extensions = "^10.3.1"
|
48
48
|
llmlingua = "^0.2.2"
|
49
49
|
jinja2 = "^3.1.5"
|
50
|
+
beautifulsoup4 = "^4.12.3"
|
51
|
+
markdownify = "^0.14.1"
|
50
52
|
|
51
53
|
[tool.poetry.scripts]
|
52
54
|
quantalogic = "quantalogic.main:cli"
|
@@ -483,7 +483,7 @@ class Agent(BaseModel):
|
|
483
483
|
if len(response) > MAX_RESPONSE_LENGTH:
|
484
484
|
response_display = response[:MAX_RESPONSE_LENGTH]
|
485
485
|
response_display += (
|
486
|
-
f"... content was
|
486
|
+
f"... content was truncated full content available by interpolation in variable {variable_name}"
|
487
487
|
)
|
488
488
|
|
489
489
|
# Format the response message
|
@@ -3,8 +3,9 @@
|
|
3
3
|
# Standard library imports
|
4
4
|
|
5
5
|
# Local application imports
|
6
|
+
from dotenv import load_dotenv
|
7
|
+
|
6
8
|
from quantalogic.agent import Agent
|
7
|
-
from quantalogic.coding_agent import create_coding_agent
|
8
9
|
from quantalogic.console_print_token import console_print_token
|
9
10
|
from quantalogic.tools import (
|
10
11
|
AgentTool,
|
@@ -14,6 +15,7 @@ from quantalogic.tools import (
|
|
14
15
|
ExecuteBashCommandTool,
|
15
16
|
InputQuestionTool,
|
16
17
|
ListDirectoryTool,
|
18
|
+
LLMImageGenerationTool,
|
17
19
|
LLMTool,
|
18
20
|
LLMVisionTool,
|
19
21
|
MarkitdownTool,
|
@@ -21,6 +23,7 @@ from quantalogic.tools import (
|
|
21
23
|
PythonTool,
|
22
24
|
ReadFileBlockTool,
|
23
25
|
ReadFileTool,
|
26
|
+
ReadHTMLTool,
|
24
27
|
ReplaceInFileTool,
|
25
28
|
RipgrepTool,
|
26
29
|
SearchDefinitionNames,
|
@@ -29,6 +32,8 @@ from quantalogic.tools import (
|
|
29
32
|
WriteFileTool,
|
30
33
|
)
|
31
34
|
|
35
|
+
load_dotenv()
|
36
|
+
|
32
37
|
MODEL_NAME = "deepseek/deepseek-chat"
|
33
38
|
|
34
39
|
|
@@ -66,6 +71,12 @@ def create_agent(
|
|
66
71
|
MarkitdownTool(),
|
67
72
|
LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
|
68
73
|
DownloadHttpFileTool(),
|
74
|
+
LLMImageGenerationTool(
|
75
|
+
provider="dall-e",
|
76
|
+
model_name="openai/dall-e-3",
|
77
|
+
on_token=console_print_token if not no_stream else None
|
78
|
+
),
|
79
|
+
ReadHTMLTool()
|
69
80
|
]
|
70
81
|
|
71
82
|
if vision_model_name:
|
@@ -115,6 +126,7 @@ def create_interpreter_agent(
|
|
115
126
|
MarkitdownTool(),
|
116
127
|
LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
|
117
128
|
DownloadHttpFileTool(),
|
129
|
+
ReadHTMLTool(),
|
118
130
|
]
|
119
131
|
return Agent(
|
120
132
|
model_name=model_name,
|
@@ -163,6 +175,7 @@ def create_full_agent(
|
|
163
175
|
DownloadHttpFileTool(),
|
164
176
|
WikipediaSearchTool(),
|
165
177
|
DuckDuckGoSearchTool(),
|
178
|
+
ReadHTMLTool(),
|
166
179
|
]
|
167
180
|
|
168
181
|
if vision_model_name:
|
@@ -209,8 +222,10 @@ def create_basic_agent(
|
|
209
222
|
WriteFileTool(),
|
210
223
|
EditWholeContentTool(),
|
211
224
|
ReplaceInFileTool(),
|
225
|
+
InputQuestionTool(),
|
212
226
|
ExecuteBashCommandTool(),
|
213
227
|
LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
|
228
|
+
ReadHTMLTool(),
|
214
229
|
]
|
215
230
|
|
216
231
|
if vision_model_name:
|
@@ -11,6 +11,7 @@ from quantalogic.tools import (
|
|
11
11
|
LLMVisionTool,
|
12
12
|
ReadFileBlockTool,
|
13
13
|
ReadFileTool,
|
14
|
+
ReadHTMLTool,
|
14
15
|
ReplaceInFileTool,
|
15
16
|
RipgrepTool,
|
16
17
|
SearchDefinitionNames,
|
@@ -74,6 +75,7 @@ def create_coding_agent(
|
|
74
75
|
InputQuestionTool(),
|
75
76
|
DuckDuckGoSearchTool(),
|
76
77
|
JinjaTool(),
|
78
|
+
ReadHTMLTool()
|
77
79
|
]
|
78
80
|
|
79
81
|
if vision_model_name:
|
@@ -1,10 +1,12 @@
|
|
1
1
|
"""Generative model module for AI-powered text generation."""
|
2
2
|
|
3
3
|
import functools
|
4
|
+
from typing import Dict, Any, Optional, List
|
5
|
+
from datetime import datetime
|
4
6
|
|
5
7
|
import litellm
|
6
8
|
import openai
|
7
|
-
from litellm import completion, exceptions, get_max_tokens, get_model_info, token_counter
|
9
|
+
from litellm import completion, exceptions, get_max_tokens, get_model_info, token_counter, image_generation
|
8
10
|
from loguru import logger
|
9
11
|
from pydantic import BaseModel, Field, field_validator
|
10
12
|
|
@@ -70,10 +72,12 @@ class ResponseStats(BaseModel):
|
|
70
72
|
usage: TokenUsage
|
71
73
|
model: str
|
72
74
|
finish_reason: str | None = None
|
75
|
+
data: List[Dict[str, Any]] | None = None
|
76
|
+
created: str | None = None
|
73
77
|
|
74
78
|
|
75
79
|
class GenerativeModel:
|
76
|
-
"""Generative model for AI-powered text generation
|
80
|
+
"""Generative model for AI-powered text generation and image generation."""
|
77
81
|
|
78
82
|
def __init__(
|
79
83
|
self,
|
@@ -311,3 +315,73 @@ class GenerativeModel:
|
|
311
315
|
except Exception as e:
|
312
316
|
logger.error(f"Error getting max output tokens for {self.model}: {e}")
|
313
317
|
return None
|
318
|
+
|
319
|
+
def generate_image(self, prompt: str, params: Dict[str, Any]) -> ResponseStats:
|
320
|
+
"""Generate an image using the specified model and parameters.
|
321
|
+
|
322
|
+
Args:
|
323
|
+
prompt: Text description of the image to generate
|
324
|
+
params: Dictionary of parameters for image generation including:
|
325
|
+
- model: Name of the image generation model
|
326
|
+
- size: Size of the generated image
|
327
|
+
- quality: Quality level (DALL-E only)
|
328
|
+
- style: Style preference (DALL-E only)
|
329
|
+
- response_format: Format of the response (url/base64)
|
330
|
+
- negative_prompt: What to avoid in the image (SD only)
|
331
|
+
- cfg_scale: Classifier Free Guidance scale (SD only)
|
332
|
+
|
333
|
+
Returns:
|
334
|
+
ResponseStats containing the image generation results
|
335
|
+
|
336
|
+
Raises:
|
337
|
+
Exception: If there's an error during image generation
|
338
|
+
"""
|
339
|
+
try:
|
340
|
+
logger.debug(f"Generating image with params: {params}")
|
341
|
+
|
342
|
+
# Ensure prompt is in params
|
343
|
+
generation_params = {**params}
|
344
|
+
generation_params["prompt"] = prompt
|
345
|
+
|
346
|
+
# Call litellm's image generation function
|
347
|
+
response = image_generation(
|
348
|
+
model=generation_params.pop("model"),
|
349
|
+
**generation_params
|
350
|
+
)
|
351
|
+
|
352
|
+
# Convert response data to list of dictionaries with string values
|
353
|
+
if hasattr(response, "data"):
|
354
|
+
data = []
|
355
|
+
for img in response.data:
|
356
|
+
img_data = {}
|
357
|
+
if hasattr(img, "url"):
|
358
|
+
img_data["url"] = str(img.url)
|
359
|
+
if hasattr(img, "b64_json"):
|
360
|
+
img_data["b64_json"] = str(img.b64_json)
|
361
|
+
if hasattr(img, "revised_prompt"):
|
362
|
+
img_data["revised_prompt"] = str(img.revised_prompt)
|
363
|
+
data.append(img_data)
|
364
|
+
else:
|
365
|
+
data = [{"url": str(response.url)}]
|
366
|
+
|
367
|
+
# Convert timestamp to ISO format string
|
368
|
+
if hasattr(response, "created"):
|
369
|
+
try:
|
370
|
+
created = datetime.fromtimestamp(response.created).isoformat()
|
371
|
+
except (TypeError, ValueError):
|
372
|
+
created = str(response.created)
|
373
|
+
else:
|
374
|
+
created = None
|
375
|
+
|
376
|
+
# Convert response to our ResponseStats format
|
377
|
+
return ResponseStats(
|
378
|
+
response="", # Empty for image generation
|
379
|
+
usage=TokenUsage(prompt_tokens=0, completion_tokens=0, total_tokens=0),
|
380
|
+
model=str(params["model"]),
|
381
|
+
data=data,
|
382
|
+
created=created
|
383
|
+
)
|
384
|
+
|
385
|
+
except Exception as e:
|
386
|
+
logger.error(f"Error in image generation: {str(e)}")
|
387
|
+
raise
|
@@ -28,11 +28,11 @@ from quantalogic.agent import Agent # noqa: E402
|
|
28
28
|
# Local application imports
|
29
29
|
from quantalogic.agent_config import ( # noqa: E402
|
30
30
|
MODEL_NAME,
|
31
|
-
|
31
|
+
create_basic_agent,
|
32
32
|
create_full_agent,
|
33
33
|
create_interpreter_agent,
|
34
|
-
create_basic_agent,
|
35
34
|
)
|
35
|
+
from quantalogic.coding_agent import create_coding_agent # noqa: E402
|
36
36
|
from quantalogic.interactive_text_editor import get_multiline_input # noqa: E402
|
37
37
|
from quantalogic.search_agent import create_search_agent # noqa: E402
|
38
38
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""Tools for the QuantaLogic agent."""
|
2
2
|
|
3
3
|
from .agent_tool import AgentTool
|
4
|
+
from .dalle_e import LLMImageGenerationTool
|
4
5
|
from .download_http_file_tool import DownloadHttpFileTool
|
5
6
|
from .duckduckgo_search_tool import DuckDuckGoSearchTool
|
6
7
|
from .edit_whole_content_tool import EditWholeContentTool
|
@@ -16,6 +17,7 @@ from .nodejs_tool import NodeJsTool
|
|
16
17
|
from .python_tool import PythonTool
|
17
18
|
from .read_file_block_tool import ReadFileBlockTool
|
18
19
|
from .read_file_tool import ReadFileTool
|
20
|
+
from .read_html_tool import ReadHTMLTool
|
19
21
|
from .replace_in_file_tool import ReplaceInFileTool
|
20
22
|
from .ripgrep_tool import RipgrepTool
|
21
23
|
from .search_definition_names import SearchDefinitionNames
|
@@ -53,4 +55,6 @@ __all__ = [
|
|
53
55
|
"DownloadHttpFileTool",
|
54
56
|
"EditWholeContentTool",
|
55
57
|
"JinjaTool",
|
58
|
+
"LLMImageGenerationTool",
|
59
|
+
"ReadHTMLTool"
|
56
60
|
]
|
@@ -0,0 +1,270 @@
|
|
1
|
+
"""LLM Image Generation Tool for creating images using DALL-E or Stable Diffusion via AWS Bedrock."""
|
2
|
+
|
3
|
+
import datetime
|
4
|
+
import json
|
5
|
+
from enum import Enum
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Any, Dict, Optional
|
8
|
+
|
9
|
+
import requests
|
10
|
+
from loguru import logger
|
11
|
+
from pydantic import ConfigDict, Field
|
12
|
+
from tenacity import retry, stop_after_attempt, wait_exponential
|
13
|
+
|
14
|
+
from quantalogic.generative_model import GenerativeModel
|
15
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
16
|
+
|
17
|
+
|
18
|
+
class ImageProvider(str, Enum):
|
19
|
+
"""Supported image generation providers."""
|
20
|
+
DALLE = "dall-e"
|
21
|
+
STABLE_DIFFUSION = "stable-diffusion"
|
22
|
+
|
23
|
+
PROVIDER_CONFIGS = {
|
24
|
+
ImageProvider.DALLE: {
|
25
|
+
"model_name": "dall-e-3",
|
26
|
+
"sizes": ["1024x1024", "1024x1792", "1792x1024"],
|
27
|
+
"qualities": ["standard", "hd"],
|
28
|
+
"styles": ["vivid", "natural"],
|
29
|
+
},
|
30
|
+
ImageProvider.STABLE_DIFFUSION: {
|
31
|
+
#"model_name": "anthropic.claude-3-sonnet-20240229",
|
32
|
+
"model_name": "amazon.titan-image-generator-v1",
|
33
|
+
"sizes": ["1024x1024"], # Bedrock SD supported size
|
34
|
+
"qualities": ["standard"], # SD quality is controlled by cfg_scale
|
35
|
+
"styles": ["none"], # Style is controlled through prompting
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
class LLMImageGenerationTool(Tool):
|
40
|
+
"""Tool for generating images using DALL-E or Stable Diffusion."""
|
41
|
+
|
42
|
+
model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
|
43
|
+
|
44
|
+
name: str = Field(default="llm_image_generation_tool")
|
45
|
+
description: str = Field(
|
46
|
+
default=(
|
47
|
+
"Generate images using DALL-E or Stable Diffusion. "
|
48
|
+
"Supports different sizes, styles, and quality settings."
|
49
|
+
)
|
50
|
+
)
|
51
|
+
arguments: list = Field(
|
52
|
+
default=[
|
53
|
+
ToolArgument(
|
54
|
+
name="prompt",
|
55
|
+
arg_type="string",
|
56
|
+
description="Text description of the image to generate",
|
57
|
+
required=True,
|
58
|
+
example="A serene Japanese garden with a red maple tree",
|
59
|
+
),
|
60
|
+
ToolArgument(
|
61
|
+
name="provider",
|
62
|
+
arg_type="string",
|
63
|
+
description="Image generation provider (dall-e or stable-diffusion)",
|
64
|
+
required=False,
|
65
|
+
default="dall-e",
|
66
|
+
example="dall-e",
|
67
|
+
),
|
68
|
+
ToolArgument(
|
69
|
+
name="size",
|
70
|
+
arg_type="string",
|
71
|
+
description="Size of the generated image",
|
72
|
+
required=False,
|
73
|
+
default="1024x1024",
|
74
|
+
example="1024x1024",
|
75
|
+
),
|
76
|
+
ToolArgument(
|
77
|
+
name="quality",
|
78
|
+
arg_type="string",
|
79
|
+
description="Quality level for DALL-E",
|
80
|
+
required=False,
|
81
|
+
default="standard",
|
82
|
+
example="standard",
|
83
|
+
),
|
84
|
+
ToolArgument(
|
85
|
+
name="style",
|
86
|
+
arg_type="string",
|
87
|
+
description="Style preference for DALL-E",
|
88
|
+
required=False,
|
89
|
+
default="vivid",
|
90
|
+
example="vivid",
|
91
|
+
),
|
92
|
+
ToolArgument(
|
93
|
+
name="negative_prompt",
|
94
|
+
arg_type="string",
|
95
|
+
description="What to avoid in the image (Stable Diffusion only)",
|
96
|
+
required=False,
|
97
|
+
default="",
|
98
|
+
example="blurry, low quality",
|
99
|
+
),
|
100
|
+
ToolArgument(
|
101
|
+
name="cfg_scale",
|
102
|
+
arg_type="string",
|
103
|
+
description="Classifier Free Guidance scale (Stable Diffusion only)",
|
104
|
+
required=False,
|
105
|
+
default="7.5",
|
106
|
+
example="7.5",
|
107
|
+
),
|
108
|
+
]
|
109
|
+
)
|
110
|
+
|
111
|
+
provider: ImageProvider = Field(default=ImageProvider.DALLE)
|
112
|
+
model_name: str = Field(default=PROVIDER_CONFIGS[ImageProvider.DALLE]["model_name"])
|
113
|
+
output_dir: Path = Field(default=Path("generated_images"))
|
114
|
+
generative_model: Optional[GenerativeModel] = Field(default=None)
|
115
|
+
|
116
|
+
def model_post_init(self, __context):
|
117
|
+
"""Initialize after model creation."""
|
118
|
+
if self.generative_model is None:
|
119
|
+
self.generative_model = GenerativeModel(model=self.model_name)
|
120
|
+
logger.debug(f"Initialized LLMImageGenerationTool with model: {self.model_name}")
|
121
|
+
|
122
|
+
# Create output directory if it doesn't exist
|
123
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
124
|
+
|
125
|
+
def _validate_dalle_params(self, size: str, quality: str, style: str) -> None:
|
126
|
+
"""Validate DALL-E specific parameters."""
|
127
|
+
if size not in PROVIDER_CONFIGS[ImageProvider.DALLE]["sizes"]:
|
128
|
+
raise ValueError(f"Invalid size for DALL-E. Must be one of: {PROVIDER_CONFIGS[ImageProvider.DALLE]['sizes']}")
|
129
|
+
if quality not in PROVIDER_CONFIGS[ImageProvider.DALLE]["qualities"]:
|
130
|
+
raise ValueError(f"Invalid quality for DALL-E. Must be one of: {PROVIDER_CONFIGS[ImageProvider.DALLE]['qualities']}")
|
131
|
+
if style not in PROVIDER_CONFIGS[ImageProvider.DALLE]["styles"]:
|
132
|
+
raise ValueError(f"Invalid style for DALL-E. Must be one of: {PROVIDER_CONFIGS[ImageProvider.DALLE]['styles']}")
|
133
|
+
|
134
|
+
def _validate_sd_params(self, size: str, cfg_scale: float) -> None:
|
135
|
+
"""Validate Stable Diffusion specific parameters."""
|
136
|
+
if size not in PROVIDER_CONFIGS[ImageProvider.STABLE_DIFFUSION]["sizes"]:
|
137
|
+
raise ValueError(f"Invalid size for Stable Diffusion. Must be one of: {PROVIDER_CONFIGS[ImageProvider.STABLE_DIFFUSION]['sizes']}")
|
138
|
+
if not 1.0 <= cfg_scale <= 20.0:
|
139
|
+
raise ValueError("cfg_scale must be between 1.0 and 20.0")
|
140
|
+
|
141
|
+
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
|
142
|
+
def _save_image(self, image_url: str, filename: str) -> Path:
|
143
|
+
"""Download and save image locally with retry logic."""
|
144
|
+
try:
|
145
|
+
response = requests.get(image_url, timeout=30)
|
146
|
+
response.raise_for_status()
|
147
|
+
|
148
|
+
file_path = self.output_dir / filename
|
149
|
+
file_path.write_bytes(response.content)
|
150
|
+
logger.info(f"Image saved successfully at: {file_path}")
|
151
|
+
return file_path
|
152
|
+
|
153
|
+
except Exception as e:
|
154
|
+
logger.error(f"Error saving image: {e}")
|
155
|
+
raise
|
156
|
+
|
157
|
+
def _save_metadata(self, metadata: Dict[str, Any]) -> None:
|
158
|
+
"""Save image metadata to JSON file."""
|
159
|
+
try:
|
160
|
+
metadata_path = self.output_dir / f"{metadata['filename']}.json"
|
161
|
+
with open(metadata_path, 'w') as f:
|
162
|
+
json.dump(metadata, f, indent=2)
|
163
|
+
logger.info(f"Metadata saved successfully at: {metadata_path}")
|
164
|
+
except Exception as e:
|
165
|
+
logger.error(f"Error saving metadata: {e}")
|
166
|
+
raise
|
167
|
+
|
168
|
+
def execute(
|
169
|
+
self,
|
170
|
+
prompt: str,
|
171
|
+
provider: str = "dall-e",
|
172
|
+
size: str = "1024x1024",
|
173
|
+
quality: str = "standard",
|
174
|
+
style: str = "vivid",
|
175
|
+
negative_prompt: str = "",
|
176
|
+
cfg_scale: str = "7.5",
|
177
|
+
) -> str:
|
178
|
+
"""Execute the tool to generate an image based on the prompt.
|
179
|
+
|
180
|
+
Args:
|
181
|
+
prompt: Text description of the image to generate
|
182
|
+
provider: Provider to use (dall-e or stable-diffusion)
|
183
|
+
size: Size of the generated image
|
184
|
+
quality: Quality level for DALL-E
|
185
|
+
style: Style preference for DALL-E
|
186
|
+
negative_prompt: What to avoid in the image (Stable Diffusion only)
|
187
|
+
cfg_scale: Classifier Free Guidance scale (Stable Diffusion only)
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
Path to the locally saved image
|
191
|
+
"""
|
192
|
+
try:
|
193
|
+
provider_enum = ImageProvider(provider.lower())
|
194
|
+
|
195
|
+
# Convert cfg_scale to float only if it's not empty and we're using Stable Diffusion
|
196
|
+
cfg_scale_float = float(cfg_scale) if cfg_scale and provider_enum == ImageProvider.STABLE_DIFFUSION else None
|
197
|
+
|
198
|
+
# Validate parameters based on provider
|
199
|
+
if provider_enum == ImageProvider.DALLE:
|
200
|
+
self._validate_dalle_params(size, quality, style)
|
201
|
+
params = {
|
202
|
+
"model": PROVIDER_CONFIGS[provider_enum]["model_name"],
|
203
|
+
"size": size,
|
204
|
+
"quality": quality,
|
205
|
+
"style": style,
|
206
|
+
"response_format": "url"
|
207
|
+
}
|
208
|
+
else: # Stable Diffusion
|
209
|
+
if cfg_scale_float is None:
|
210
|
+
cfg_scale_float = 7.5 # Default value
|
211
|
+
self._validate_sd_params(size, cfg_scale_float)
|
212
|
+
params = {
|
213
|
+
"model": PROVIDER_CONFIGS[provider_enum]["model_name"],
|
214
|
+
"negative_prompt": negative_prompt,
|
215
|
+
"cfg_scale": cfg_scale_float,
|
216
|
+
"size": size,
|
217
|
+
"response_format": "url"
|
218
|
+
}
|
219
|
+
|
220
|
+
# Generate image
|
221
|
+
logger.info(f"Generating image with {provider} using params: {params}")
|
222
|
+
response = self.generative_model.generate_image(
|
223
|
+
prompt=prompt,
|
224
|
+
params=params
|
225
|
+
)
|
226
|
+
|
227
|
+
# Extract image data from response
|
228
|
+
if not response.data:
|
229
|
+
raise ValueError("No image data in response")
|
230
|
+
|
231
|
+
image_data = response.data[0] # First image from the response
|
232
|
+
image_url = str(image_data.get("url", ""))
|
233
|
+
revised_prompt = str(image_data.get("revised_prompt", prompt))
|
234
|
+
|
235
|
+
if not image_url:
|
236
|
+
raise ValueError("No image URL in response")
|
237
|
+
|
238
|
+
# Save image locally
|
239
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
240
|
+
filename = f"{provider}_{timestamp}.png"
|
241
|
+
local_path = self._save_image(image_url, filename)
|
242
|
+
|
243
|
+
# Save metadata
|
244
|
+
metadata = {
|
245
|
+
"filename": str(filename),
|
246
|
+
"prompt": str(prompt),
|
247
|
+
"revised_prompt": str(revised_prompt),
|
248
|
+
"provider": str(provider),
|
249
|
+
"model": str(response.model),
|
250
|
+
"created": str(response.created or ""),
|
251
|
+
"parameters": {k: str(v) for k, v in {**params, "prompt": prompt}.items()},
|
252
|
+
"image_url": str(image_url),
|
253
|
+
"local_path": str(local_path)
|
254
|
+
}
|
255
|
+
self._save_metadata(metadata)
|
256
|
+
|
257
|
+
logger.info(f"Image generated and saved at: {local_path}")
|
258
|
+
return str(local_path)
|
259
|
+
|
260
|
+
except Exception as e:
|
261
|
+
logger.error(f"Error generating image with {provider}: {e}")
|
262
|
+
raise Exception(f"Error generating image with {provider}: {e}") from e
|
263
|
+
|
264
|
+
|
265
|
+
if __name__ == "__main__":
|
266
|
+
# Example usage
|
267
|
+
tool = LLMImageGenerationTool()
|
268
|
+
prompt = "A serene Japanese garden with a red maple tree"
|
269
|
+
image_path = tool.execute(prompt=prompt)
|
270
|
+
print(f"Image saved at: {image_path}")
|
@@ -14,7 +14,7 @@ class ReadFileTool(Tool):
|
|
14
14
|
|
15
15
|
name: str = "read_file_tool"
|
16
16
|
description: str = (
|
17
|
-
f"Reads a local file
|
17
|
+
f"Reads a local file content and returns its content."
|
18
18
|
f"Cut to {MAX_LINES} first lines.\n"
|
19
19
|
"Don't use on HTML files and large files."
|
20
20
|
"Prefer to use read file block tool to don't fill the memory."
|
@@ -23,9 +23,9 @@ class ReadFileTool(Tool):
|
|
23
23
|
ToolArgument(
|
24
24
|
name="file_path",
|
25
25
|
arg_type="string",
|
26
|
-
description="The path to the file
|
26
|
+
description="The path to the file to read.",
|
27
27
|
required=True,
|
28
|
-
example="/path/to/file.txt
|
28
|
+
example="/path/to/file.txt",
|
29
29
|
),
|
30
30
|
]
|
31
31
|
|
@@ -0,0 +1,303 @@
|
|
1
|
+
import os
|
2
|
+
import random
|
3
|
+
import time
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
import requests
|
7
|
+
from bs4 import BeautifulSoup
|
8
|
+
from loguru import logger
|
9
|
+
from pydantic import BaseModel, Field, field_validator
|
10
|
+
|
11
|
+
# Ensure that markdownify is installed: pip install markdownify
|
12
|
+
try:
|
13
|
+
from markdownify import markdownify as md
|
14
|
+
except ImportError:
|
15
|
+
logger.error("Missing dependency: markdownify. Install it using 'pip install markdownify'")
|
16
|
+
raise
|
17
|
+
|
18
|
+
# Assuming Tool and ToolArgument are properly defined in quantalogic.tools.tool
|
19
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
20
|
+
|
21
|
+
# User-Agent list to mimic different browsers
|
22
|
+
USER_AGENTS = [
|
23
|
+
# Chrome on Windows
|
24
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
|
25
|
+
" Chrome/91.0.4472.124 Safari/537.36",
|
26
|
+
# Chrome on macOS
|
27
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)"
|
28
|
+
" Chrome/91.0.4472.124 Safari/537.36",
|
29
|
+
# Firefox on Windows
|
30
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0",
|
31
|
+
# Firefox on macOS
|
32
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:89.0) Gecko/20100101 Firefox/89.0",
|
33
|
+
# Safari on macOS
|
34
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)"
|
35
|
+
" Version/14.1.1 Safari/605.1.15"
|
36
|
+
]
|
37
|
+
|
38
|
+
# Additional headers to mimic real browser requests
|
39
|
+
ADDITIONAL_HEADERS = {
|
40
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;"
|
41
|
+
"q=0.9,image/webp,*/*;q=0.8",
|
42
|
+
"Accept-Language": "en-US,en;q=0.5",
|
43
|
+
"Upgrade-Insecure-Requests": "1",
|
44
|
+
"DNT": "1", # Do Not Track
|
45
|
+
"Connection": "keep-alive",
|
46
|
+
"Cache-Control": "max-age=0"
|
47
|
+
}
|
48
|
+
|
49
|
+
class ReadHTMLTool(Tool):
|
50
|
+
"""Tool for reading HTML content from files or URLs in specified line ranges."""
|
51
|
+
|
52
|
+
class Arguments(BaseModel):
|
53
|
+
source: str = Field(
|
54
|
+
...,
|
55
|
+
description="The file path or URL to read HTML from",
|
56
|
+
example="https://example.com or ./example.html"
|
57
|
+
)
|
58
|
+
convert: Optional[str] = Field(
|
59
|
+
"text",
|
60
|
+
description="Convert input to 'text' (Markdown) or 'html' no conversion. Default is 'text'",
|
61
|
+
example="'text' or 'html'"
|
62
|
+
)
|
63
|
+
line_start: Optional[int] = Field(
|
64
|
+
1,
|
65
|
+
description="The starting line number (1-based index). Default: 1",
|
66
|
+
ge=1,
|
67
|
+
example="1"
|
68
|
+
)
|
69
|
+
line_end: Optional[int] = Field(
|
70
|
+
300,
|
71
|
+
description="The ending line number (1-based index). Default: 300",
|
72
|
+
ge=1,
|
73
|
+
example="300"
|
74
|
+
)
|
75
|
+
|
76
|
+
@field_validator('convert')
|
77
|
+
def validate_convert(cls, v):
|
78
|
+
if v not in ["text", "html"]:
|
79
|
+
raise ValueError("Convert must be either 'text' or 'html'")
|
80
|
+
return v
|
81
|
+
|
82
|
+
@field_validator('line_end')
|
83
|
+
def validate_line_end(cls, v, values):
|
84
|
+
if 'line_start' in values and v < values['line_start']:
|
85
|
+
raise ValueError("line_end must be greater than or equal to line_start")
|
86
|
+
return v
|
87
|
+
|
88
|
+
name: str = "read_html_tool"
|
89
|
+
description: str = (
|
90
|
+
"Reads HTML content from either a file path or URL in specified line ranges. "
|
91
|
+
"Returns parsed HTML content using BeautifulSoup or converts it to Markdown. "
|
92
|
+
"Allows reading specific portions of HTML files by defining start and end lines."
|
93
|
+
)
|
94
|
+
arguments: list = [
|
95
|
+
ToolArgument(
|
96
|
+
name="source",
|
97
|
+
arg_type="string",
|
98
|
+
description="The file path or URL to read HTML from",
|
99
|
+
required=True,
|
100
|
+
example="https://example.com or ./example.html"
|
101
|
+
),
|
102
|
+
ToolArgument(
|
103
|
+
name="convert",
|
104
|
+
arg_type="string",
|
105
|
+
description="Convert input to 'text' (Markdown) or 'html'. Default is 'text'",
|
106
|
+
default='text',
|
107
|
+
required=False,
|
108
|
+
example="'text' or 'html'"
|
109
|
+
),
|
110
|
+
ToolArgument(
|
111
|
+
name="line_start",
|
112
|
+
arg_type="int",
|
113
|
+
description="The starting line number (1-based index). Default: 1",
|
114
|
+
required=False,
|
115
|
+
example="1"
|
116
|
+
),
|
117
|
+
ToolArgument(
|
118
|
+
name="line_end",
|
119
|
+
arg_type="int",
|
120
|
+
description="The ending line number (1-based index). Default: 300",
|
121
|
+
required=False,
|
122
|
+
example="300"
|
123
|
+
)
|
124
|
+
]
|
125
|
+
|
126
|
+
def validate_source(self, source: str) -> bool:
|
127
|
+
"""Validate if source is a valid file path or URL."""
|
128
|
+
if os.path.isfile(source):
|
129
|
+
return True
|
130
|
+
try:
|
131
|
+
result = requests.utils.urlparse(source)
|
132
|
+
return all([result.scheme, result.netloc])
|
133
|
+
except (requests.exceptions.RequestException, ValueError) as e:
|
134
|
+
# Log the specific exception for debugging
|
135
|
+
logger.debug(f"URL validation failed: {e}")
|
136
|
+
return False
|
137
|
+
|
138
|
+
def read_from_file(self, file_path: str) -> str:
|
139
|
+
"""Read HTML content from a file."""
|
140
|
+
try:
|
141
|
+
with open(file_path, encoding='utf-8') as file:
|
142
|
+
return file.read()
|
143
|
+
except Exception as e:
|
144
|
+
logger.error(f"Error reading file: {e}")
|
145
|
+
raise ValueError(f"Error reading file: {e}")
|
146
|
+
|
147
|
+
def read_from_url(self, url: str) -> str:
|
148
|
+
"""Read HTML content from a URL with randomized User-Agent and headers."""
|
149
|
+
try:
|
150
|
+
# Randomize User-Agent
|
151
|
+
headers = ADDITIONAL_HEADERS.copy()
|
152
|
+
headers["User-Agent"] = random.choice(USER_AGENTS)
|
153
|
+
|
154
|
+
# Add a small random delay to mimic human behavior
|
155
|
+
time.sleep(random.uniform(0.5, 2.0))
|
156
|
+
|
157
|
+
# Use a timeout to prevent hanging
|
158
|
+
response = requests.get(
|
159
|
+
url,
|
160
|
+
headers=headers,
|
161
|
+
timeout=10,
|
162
|
+
allow_redirects=True
|
163
|
+
)
|
164
|
+
response.raise_for_status()
|
165
|
+
return response.text
|
166
|
+
except requests.RequestException as e:
|
167
|
+
logger.error(f"Error fetching URL {url}: {e}")
|
168
|
+
raise ValueError(f"Error fetching URL: {e}")
|
169
|
+
|
170
|
+
def parse_html(self, html_content: str) -> BeautifulSoup:
|
171
|
+
"""Parse HTML content using BeautifulSoup."""
|
172
|
+
try:
|
173
|
+
return BeautifulSoup(html_content, 'html.parser')
|
174
|
+
except Exception as e:
|
175
|
+
logger.error(f"Error parsing HTML: {e}")
|
176
|
+
raise ValueError(f"Error parsing HTML: {e}")
|
177
|
+
|
178
|
+
def read_source(self, source: str) -> str:
|
179
|
+
"""Read entire content from source."""
|
180
|
+
if os.path.isfile(source):
|
181
|
+
return self.read_from_file(source)
|
182
|
+
else:
|
183
|
+
return self.read_from_url(source)
|
184
|
+
|
185
|
+
def _convert_content(self, content: str, convert_type: str) -> str:
|
186
|
+
"""
|
187
|
+
Convert content based on the specified type.
|
188
|
+
|
189
|
+
Args:
|
190
|
+
content (str): The input content to convert
|
191
|
+
convert_type (str): The type of conversion to perform
|
192
|
+
|
193
|
+
Returns:
|
194
|
+
str: Converted content
|
195
|
+
"""
|
196
|
+
if convert_type == "text":
|
197
|
+
# Convert HTML to Markdown using markdownify
|
198
|
+
try:
|
199
|
+
markdown_content = md(content, heading_style="ATX")
|
200
|
+
return markdown_content
|
201
|
+
except Exception as e:
|
202
|
+
logger.error(f"Error converting HTML to Markdown: {e}")
|
203
|
+
raise ValueError(f"Error converting HTML to Markdown: {e}")
|
204
|
+
|
205
|
+
if convert_type == "html":
|
206
|
+
# Ensure content is valid HTML
|
207
|
+
try:
|
208
|
+
soup = BeautifulSoup(content, 'html.parser')
|
209
|
+
return soup.prettify()
|
210
|
+
except Exception as e:
|
211
|
+
logger.error(f"Error prettifying HTML: {e}")
|
212
|
+
raise ValueError(f"Error prettifying HTML: {e}")
|
213
|
+
|
214
|
+
return content
|
215
|
+
|
216
|
+
def execute(self, source: str, convert: Optional[str] = 'text',
|
217
|
+
line_start: int = 1, line_end: int = 300) -> str:
|
218
|
+
"""Execute the tool to read and parse HTML content in specified line ranges."""
|
219
|
+
logger.debug(f"Executing read_html_tool with source: {source}")
|
220
|
+
|
221
|
+
line_start = int(line_start)
|
222
|
+
line_end = int(line_end)
|
223
|
+
|
224
|
+
if not self.validate_source(source):
|
225
|
+
logger.warning(f"Invalid source: {source}")
|
226
|
+
return f"Invalid source: {source}"
|
227
|
+
|
228
|
+
try:
|
229
|
+
# Step 1: Read entire content from source
|
230
|
+
raw_content = self.read_source(source)
|
231
|
+
|
232
|
+
# Step 2: Convert content
|
233
|
+
converted_content = self._convert_content(raw_content, convert)
|
234
|
+
|
235
|
+
# Step 3: Split converted content into lines
|
236
|
+
lines = converted_content.splitlines()
|
237
|
+
total_lines = len(lines)
|
238
|
+
|
239
|
+
# Step 4: Adjust line_end if it exceeds total_lines
|
240
|
+
adjusted_end_line = min(line_end, total_lines)
|
241
|
+
|
242
|
+
# Step 5: Slice lines based on line_start and adjusted_end_line
|
243
|
+
sliced_lines = lines[line_start - 1: adjusted_end_line]
|
244
|
+
sliced_content = "\n".join(sliced_lines)
|
245
|
+
|
246
|
+
# Step 6: Calculate actual_end_line based on lines returned
|
247
|
+
if sliced_lines:
|
248
|
+
actual_end_line = line_start + len(sliced_lines) - 1
|
249
|
+
else:
|
250
|
+
actual_end_line = line_start
|
251
|
+
|
252
|
+
# Step 7: Determine if this is the last block
|
253
|
+
is_last_block = actual_end_line >= total_lines
|
254
|
+
|
255
|
+
# Step 8: Calculate total lines returned
|
256
|
+
total_lines_returned = len(sliced_lines)
|
257
|
+
|
258
|
+
# Prepare detailed output
|
259
|
+
result = [
|
260
|
+
f"==== Source: {source} ====",
|
261
|
+
f"==== Lines: {line_start} - {actual_end_line} of {total_lines} ====",
|
262
|
+
"==== Block Detail ====",
|
263
|
+
f"Start Line: {line_start}",
|
264
|
+
f"End Line: {actual_end_line}",
|
265
|
+
f"Total Lines Returned: {total_lines_returned}",
|
266
|
+
f"Is Last Block: {'Yes' if is_last_block else 'No'}",
|
267
|
+
"==== Content ====",
|
268
|
+
sliced_content,
|
269
|
+
"==== End of Block ===="
|
270
|
+
]
|
271
|
+
|
272
|
+
return "\n".join(result)
|
273
|
+
|
274
|
+
except Exception as e:
|
275
|
+
logger.error(f"Unexpected error processing source {source}: {e}")
|
276
|
+
return f"Unexpected error: {e}"
|
277
|
+
|
278
|
+
|
279
|
+
if __name__ == "__main__":
|
280
|
+
tool = ReadHTMLTool()
|
281
|
+
|
282
|
+
# Since to_markdown() is not defined, we'll comment it out.
|
283
|
+
# print(tool.to_markdown())
|
284
|
+
|
285
|
+
# Test with a known working URL
|
286
|
+
try:
|
287
|
+
result = tool.execute(source="https://www.quantalogic.app", line_start=1, line_end=100)
|
288
|
+
print("URL Test Result:")
|
289
|
+
print(result)
|
290
|
+
except Exception as e:
|
291
|
+
print(f"URL Test Failed: {e}")
|
292
|
+
|
293
|
+
# Test with local file (if available)
|
294
|
+
try:
|
295
|
+
local_file = os.path.join(os.path.dirname(__file__), "test.html")
|
296
|
+
if os.path.exists(local_file):
|
297
|
+
result = tool.execute(source=local_file, line_start=1, line_end=1000)
|
298
|
+
print("Local File Test Result:")
|
299
|
+
print(result)
|
300
|
+
else:
|
301
|
+
print("No local test file found.")
|
302
|
+
except Exception as e:
|
303
|
+
print(f"Local File Test Failed: {e}")
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/cpp_handler.py
RENAMED
File without changes
|
File without changes
|
{quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/java_handler.py
RENAMED
File without changes
|
{quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/javascript_handler.py
RENAMED
File without changes
|
{quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/python_handler.py
RENAMED
File without changes
|
{quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/rust_handler.py
RENAMED
File without changes
|
{quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/scala_handler.py
RENAMED
File without changes
|
{quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/tools/language_handlers/typescript_handler.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{quantalogic-0.2.21 → quantalogic-0.2.23}/quantalogic/utils/get_quantalogic_rules_content.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|