quantalogic 0.2.8__tar.gz → 0.2.10__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.8 → quantalogic-0.2.10}/PKG-INFO +32 -27
- {quantalogic-0.2.8 → quantalogic-0.2.10}/README.md +31 -26
- {quantalogic-0.2.8 → quantalogic-0.2.10}/pyproject.toml +1 -1
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/agent.py +35 -2
- quantalogic-0.2.10/quantalogic/agent_config.py +169 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/coding_agent.py +6 -1
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/generative_model.py +63 -12
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/main.py +62 -18
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/prompts.py +12 -19
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/__init__.py +2 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/llm_tool.py +2 -2
- quantalogic-0.2.10/quantalogic/tools/llm_vision_tool.py +140 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/version.py +1 -1
- quantalogic-0.2.8/quantalogic/agent_config.py +0 -136
- {quantalogic-0.2.8 → quantalogic-0.2.10}/LICENSE +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/__init__.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/event_emitter.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/interactive_text_editor.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/memory.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/model_names.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/print_event.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/server/__init__.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/server/agent_server.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/server/models.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/server/routes.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/server/state.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/server/static/js/event_visualizer.js +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/server/static/js/quantalogic.js +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/server/templates/index.html +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tool_manager.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/agent_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/download_http_file_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/edit_whole_content_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/elixir_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/execute_bash_command_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/input_question_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/language_handlers/__init__.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/language_handlers/c_handler.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/language_handlers/cpp_handler.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/language_handlers/go_handler.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/language_handlers/java_handler.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/language_handlers/javascript_handler.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/language_handlers/python_handler.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/language_handlers/rust_handler.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/language_handlers/scala_handler.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/language_handlers/typescript_handler.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/list_directory_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/markitdown_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/nodejs_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/python_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/read_file_block_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/read_file_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/replace_in_file_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/ripgrep_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/search_definition_names.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/task_complete_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/unified_diff_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/tools/write_file_tool.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/utils/__init__.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/utils/ask_user_validation.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/utils/download_http_file.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/utils/get_coding_environment.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/utils/get_environment.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/utils/get_quantalogic_rules_content.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/utils/git_ls.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/utils/read_file.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/utils/read_http_text_content.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/quantalogic/xml_parser.py +0 -0
- {quantalogic-0.2.8 → quantalogic-0.2.10}/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.10
|
4
4
|
Summary: QuantaLogic ReAct Agents
|
5
5
|
Author: Raphaël MANSUY
|
6
6
|
Author-email: raphael.mansuy@gmail.com
|
@@ -123,8 +123,10 @@ Usage: quantalogic [OPTIONS] COMMAND [ARGS]...
|
|
123
123
|
|
124
124
|
Options:
|
125
125
|
--version Show version information.
|
126
|
-
--model-name TEXT Specify the model to use (litellm format,
|
126
|
+
--model-name TEXT Specify the text model to use (litellm format,
|
127
127
|
e.g. "openrouter/deepseek-chat").
|
128
|
+
--vision-model-name TEXT Specify the vision model to use (litellm format,
|
129
|
+
e.g. "openrouter/A/gpt-4o-mini").
|
128
130
|
--log [info|debug|warning] Set logging level (info/debug/warning).
|
129
131
|
--verbose Enable verbose output.
|
130
132
|
--mode [code|basic|interpreter|full|code-basic]
|
@@ -385,7 +387,7 @@ By integrating these tools into its architecture, QuantaLogic allows agents to p
|
|
385
387
|
| Script Execution | Python Tool, Node.js Tool, Elixir Tool |
|
386
388
|
| File Operations | Read File Tool, Write File Tool, Edit Whole Content Tool, Replace In File Tool |
|
387
389
|
| Code Analysis | Search Definition Names Tool, Ripgrep Tool |
|
388
|
-
| Content Generation | LLM Tool
|
390
|
+
| Content Generation | LLM Tool, LLMVisionTool |
|
389
391
|
| Utility and Management | Download HTTP File Tool, List Directory Tool, Markitdown Tool, Unified Diff Tool |
|
390
392
|
|
391
393
|
---
|
@@ -681,7 +683,30 @@ print("Ripgrep Results:", output)
|
|
681
683
|
|
682
684
|
---
|
683
685
|
|
684
|
-
|
686
|
+
#### 14. LLMVisionTool
|
687
|
+
|
688
|
+
The **LLMVisionTool** enables processing of visual inputs using vision-language models.
|
689
|
+
|
690
|
+
##### Parameters
|
691
|
+
|
692
|
+
| Parameter | Type | Description | Example |
|
693
|
+
|----------------|---------|------------------------------------------------------------------------|--------------------------------------------|
|
694
|
+
| `image_path` | string | Path to the image file to process | `./path/to/image.png` |
|
695
|
+
| `prompt` | string | The question or instruction for the vision model | `Describe the contents of this image` |
|
696
|
+
| `temperature` | float | Sampling temperature between 0.0 and 1.0 | `0.7` |
|
697
|
+
|
698
|
+
##### Example Usage
|
699
|
+
```python
|
700
|
+
vision_tool = LLMVisionTool()
|
701
|
+
response = vision_tool.execute(
|
702
|
+
image_path="./path/to/image.png",
|
703
|
+
prompt="Describe the contents of this image",
|
704
|
+
temperature=0.7
|
705
|
+
)
|
706
|
+
print("Vision Model Response:", response)
|
707
|
+
```
|
708
|
+
|
709
|
+
#### 15. LLM Tool
|
685
710
|
|
686
711
|
The **LLM Tool** generates answers using a specified language model.
|
687
712
|
|
@@ -706,7 +731,7 @@ print("LLM Response:", response)
|
|
706
731
|
|
707
732
|
---
|
708
733
|
|
709
|
-
###
|
734
|
+
### 16. Download HTTP File Tool
|
710
735
|
|
711
736
|
The **Download HTTP File Tool** downloads a file from a specified HTTP URL.
|
712
737
|
|
@@ -726,7 +751,7 @@ print(result)
|
|
726
751
|
|
727
752
|
---
|
728
753
|
|
729
|
-
###
|
754
|
+
### 17. List Directory Tool
|
730
755
|
|
731
756
|
The **List Directory Tool** lists files in a specified directory.
|
732
757
|
|
@@ -745,7 +770,7 @@ print("Directory Files:", result)
|
|
745
770
|
|
746
771
|
---
|
747
772
|
|
748
|
-
###
|
773
|
+
### 18. Markitdown Tool
|
749
774
|
|
750
775
|
The **Markitdown Tool** processes markdown files, possibly for conversion or rendering.
|
751
776
|
|
@@ -762,26 +787,6 @@ result = markitdown_tool.execute(markdown_path="./path/to/file.md")
|
|
762
787
|
print("Processed Markdown Output:", result)
|
763
788
|
```
|
764
789
|
|
765
|
-
---
|
766
|
-
|
767
|
-
### 18. Unified Diff Tool
|
768
|
-
|
769
|
-
The **Unified Diff Tool** generates a unified diff between two texts or files.
|
770
|
-
|
771
|
-
#### Parameters
|
772
|
-
|
773
|
-
| Parameter | Type | Description | Example |
|
774
|
-
|--------------|--------|------------------------------------------------|------------------------|
|
775
|
-
| `original` | string | The original content or file path. | `old_text.txt` |
|
776
|
-
| `updated` | string | The updated content or file path. | `new_text.txt` |
|
777
|
-
|
778
|
-
#### Example Usage
|
779
|
-
```python
|
780
|
-
diff_tool = UnifiedDiffTool()
|
781
|
-
result = diff_tool.execute(original="old_text.txt", updated="new_text.txt")
|
782
|
-
print("Unified Diff Output:", result)
|
783
|
-
```
|
784
|
-
|
785
790
|
|
786
791
|
#### Creating Custom Tools
|
787
792
|
|
@@ -86,8 +86,10 @@ Usage: quantalogic [OPTIONS] COMMAND [ARGS]...
|
|
86
86
|
|
87
87
|
Options:
|
88
88
|
--version Show version information.
|
89
|
-
--model-name TEXT Specify the model to use (litellm format,
|
89
|
+
--model-name TEXT Specify the text model to use (litellm format,
|
90
90
|
e.g. "openrouter/deepseek-chat").
|
91
|
+
--vision-model-name TEXT Specify the vision model to use (litellm format,
|
92
|
+
e.g. "openrouter/A/gpt-4o-mini").
|
91
93
|
--log [info|debug|warning] Set logging level (info/debug/warning).
|
92
94
|
--verbose Enable verbose output.
|
93
95
|
--mode [code|basic|interpreter|full|code-basic]
|
@@ -348,7 +350,7 @@ By integrating these tools into its architecture, QuantaLogic allows agents to p
|
|
348
350
|
| Script Execution | Python Tool, Node.js Tool, Elixir Tool |
|
349
351
|
| File Operations | Read File Tool, Write File Tool, Edit Whole Content Tool, Replace In File Tool |
|
350
352
|
| Code Analysis | Search Definition Names Tool, Ripgrep Tool |
|
351
|
-
| Content Generation | LLM Tool
|
353
|
+
| Content Generation | LLM Tool, LLMVisionTool |
|
352
354
|
| Utility and Management | Download HTTP File Tool, List Directory Tool, Markitdown Tool, Unified Diff Tool |
|
353
355
|
|
354
356
|
---
|
@@ -644,7 +646,30 @@ print("Ripgrep Results:", output)
|
|
644
646
|
|
645
647
|
---
|
646
648
|
|
647
|
-
|
649
|
+
#### 14. LLMVisionTool
|
650
|
+
|
651
|
+
The **LLMVisionTool** enables processing of visual inputs using vision-language models.
|
652
|
+
|
653
|
+
##### Parameters
|
654
|
+
|
655
|
+
| Parameter | Type | Description | Example |
|
656
|
+
|----------------|---------|------------------------------------------------------------------------|--------------------------------------------|
|
657
|
+
| `image_path` | string | Path to the image file to process | `./path/to/image.png` |
|
658
|
+
| `prompt` | string | The question or instruction for the vision model | `Describe the contents of this image` |
|
659
|
+
| `temperature` | float | Sampling temperature between 0.0 and 1.0 | `0.7` |
|
660
|
+
|
661
|
+
##### Example Usage
|
662
|
+
```python
|
663
|
+
vision_tool = LLMVisionTool()
|
664
|
+
response = vision_tool.execute(
|
665
|
+
image_path="./path/to/image.png",
|
666
|
+
prompt="Describe the contents of this image",
|
667
|
+
temperature=0.7
|
668
|
+
)
|
669
|
+
print("Vision Model Response:", response)
|
670
|
+
```
|
671
|
+
|
672
|
+
#### 15. LLM Tool
|
648
673
|
|
649
674
|
The **LLM Tool** generates answers using a specified language model.
|
650
675
|
|
@@ -669,7 +694,7 @@ print("LLM Response:", response)
|
|
669
694
|
|
670
695
|
---
|
671
696
|
|
672
|
-
###
|
697
|
+
### 16. Download HTTP File Tool
|
673
698
|
|
674
699
|
The **Download HTTP File Tool** downloads a file from a specified HTTP URL.
|
675
700
|
|
@@ -689,7 +714,7 @@ print(result)
|
|
689
714
|
|
690
715
|
---
|
691
716
|
|
692
|
-
###
|
717
|
+
### 17. List Directory Tool
|
693
718
|
|
694
719
|
The **List Directory Tool** lists files in a specified directory.
|
695
720
|
|
@@ -708,7 +733,7 @@ print("Directory Files:", result)
|
|
708
733
|
|
709
734
|
---
|
710
735
|
|
711
|
-
###
|
736
|
+
### 18. Markitdown Tool
|
712
737
|
|
713
738
|
The **Markitdown Tool** processes markdown files, possibly for conversion or rendering.
|
714
739
|
|
@@ -725,26 +750,6 @@ result = markitdown_tool.execute(markdown_path="./path/to/file.md")
|
|
725
750
|
print("Processed Markdown Output:", result)
|
726
751
|
```
|
727
752
|
|
728
|
-
---
|
729
|
-
|
730
|
-
### 18. Unified Diff Tool
|
731
|
-
|
732
|
-
The **Unified Diff Tool** generates a unified diff between two texts or files.
|
733
|
-
|
734
|
-
#### Parameters
|
735
|
-
|
736
|
-
| Parameter | Type | Description | Example |
|
737
|
-
|--------------|--------|------------------------------------------------|------------------------|
|
738
|
-
| `original` | string | The original content or file path. | `old_text.txt` |
|
739
|
-
| `updated` | string | The updated content or file path. | `new_text.txt` |
|
740
|
-
|
741
|
-
#### Example Usage
|
742
|
-
```python
|
743
|
-
diff_tool = UnifiedDiffTool()
|
744
|
-
result = diff_tool.execute(original="old_text.txt", updated="new_text.txt")
|
745
|
-
print("Unified Diff Output:", result)
|
746
|
-
```
|
747
|
-
|
748
753
|
|
749
754
|
#### Creating Custom Tools
|
750
755
|
|
@@ -64,6 +64,7 @@ class Agent(BaseModel):
|
|
64
64
|
event_emitter: EventEmitter = EventEmitter()
|
65
65
|
config: AgentConfig
|
66
66
|
task_to_solve: str
|
67
|
+
task_to_solve_summary: str = ""
|
67
68
|
ask_for_user_validation: Callable[[str], bool] = console_ask_for_user_validation
|
68
69
|
last_tool_call: dict[str, Any] = {} # Stores the last tool call information
|
69
70
|
total_tokens: int = 0 # Total tokens in the conversation
|
@@ -137,6 +138,9 @@ class Agent(BaseModel):
|
|
137
138
|
logger.debug(f"Solving task... {task}")
|
138
139
|
self._reset_session(task_to_solve=task, max_iterations=max_iterations)
|
139
140
|
|
141
|
+
# Generate task summary
|
142
|
+
self.task_to_solve_summary = self._generate_task_summary(task)
|
143
|
+
|
140
144
|
# Add system prompt to memory
|
141
145
|
self.memory.add(Message(role="system", content=self.config.system_prompt))
|
142
146
|
|
@@ -166,7 +170,7 @@ class Agent(BaseModel):
|
|
166
170
|
self._update_total_tokens(message_history=self.memory.memory, prompt=current_prompt)
|
167
171
|
|
168
172
|
# Emit event: Task Think Start after updating total tokens
|
169
|
-
self._emit_event("task_think_start")
|
173
|
+
self._emit_event("task_think_start", {"prompt": current_prompt})
|
170
174
|
|
171
175
|
self._compact_memory_if_needed(current_prompt)
|
172
176
|
|
@@ -443,6 +447,10 @@ class Agent(BaseModel):
|
|
443
447
|
"You must analyze this answer and evaluate what to do next to solve the task.\n"
|
444
448
|
"If the step failed, take a step back and rethink your approach.\n"
|
445
449
|
"\n"
|
450
|
+
"--- Task to solve summary ---\n"
|
451
|
+
"\n"
|
452
|
+
f"{self.task_to_solve_summary}"
|
453
|
+
"\n"
|
446
454
|
"--- Format ---\n"
|
447
455
|
"\n"
|
448
456
|
"You MUST respond with exactly two XML blocks formatted in markdown:\n"
|
@@ -546,6 +554,7 @@ class Agent(BaseModel):
|
|
546
554
|
"\n### Tools:\n"
|
547
555
|
"-----------------------------\n"
|
548
556
|
f"{self._get_tools_names_prompt()}\n"
|
557
|
+
"\n"
|
549
558
|
"### Variables:\n"
|
550
559
|
"-----------------------------\n"
|
551
560
|
f"{self._get_variable_prompt()}\n"
|
@@ -574,7 +583,7 @@ class Agent(BaseModel):
|
|
574
583
|
"\n"
|
575
584
|
"Available variables:\n"
|
576
585
|
"\n"
|
577
|
-
f"{', '.join(self.variable_store.keys())}\n"
|
586
|
+
f"{', '.join(self.variable_store.keys())}\n" if len(self.variable_store.keys()) > 0 else "None\n"
|
578
587
|
)
|
579
588
|
return prompt_use_variables
|
580
589
|
|
@@ -619,6 +628,28 @@ class Agent(BaseModel):
|
|
619
628
|
self.memory.memory = memory_copy
|
620
629
|
return summary.response
|
621
630
|
|
631
|
+
def _generate_task_summary(self, content: str) -> str:
|
632
|
+
"""Generate a concise summary of the given content using the generative model.
|
633
|
+
|
634
|
+
Args:
|
635
|
+
content (str): The content to summarize
|
636
|
+
|
637
|
+
Returns:
|
638
|
+
str: Generated summary
|
639
|
+
"""
|
640
|
+
try:
|
641
|
+
prompt = (
|
642
|
+
"Rewrite this task in a precise, dense, and concise manner:\n"
|
643
|
+
f"{content}\n"
|
644
|
+
"Summary should be 2-3 sentences maximum. No extra comments should be added.\n"
|
645
|
+
)
|
646
|
+
result = self.model.generate(prompt=prompt)
|
647
|
+
logger.debug(f"Generated summary: {result.response}")
|
648
|
+
return result.response
|
649
|
+
except Exception as e:
|
650
|
+
logger.error(f"Error generating summary: {str(e)}")
|
651
|
+
return f"Summary generation failed: {str(e)}"
|
652
|
+
|
622
653
|
def _update_session_memory(self, user_content: str, assistant_content: str) -> None:
|
623
654
|
"""
|
624
655
|
Log session messages to memory and emit events.
|
@@ -639,3 +670,5 @@ class Agent(BaseModel):
|
|
639
670
|
"session_add_message",
|
640
671
|
{"role": "assistant", "content": assistant_content},
|
641
672
|
)
|
673
|
+
|
674
|
+
|
@@ -0,0 +1,169 @@
|
|
1
|
+
"""Module for agent configuration and creation."""
|
2
|
+
|
3
|
+
# Standard library imports
|
4
|
+
|
5
|
+
# Local application imports
|
6
|
+
from quantalogic.agent import Agent
|
7
|
+
from quantalogic.coding_agent import create_coding_agent
|
8
|
+
from quantalogic.tools import (
|
9
|
+
AgentTool,
|
10
|
+
DownloadHttpFileTool,
|
11
|
+
EditWholeContentTool,
|
12
|
+
ExecuteBashCommandTool,
|
13
|
+
InputQuestionTool,
|
14
|
+
ListDirectoryTool,
|
15
|
+
LLMTool,
|
16
|
+
LLMVisionTool,
|
17
|
+
MarkitdownTool,
|
18
|
+
NodeJsTool,
|
19
|
+
PythonTool,
|
20
|
+
ReadFileBlockTool,
|
21
|
+
ReadFileTool,
|
22
|
+
ReplaceInFileTool,
|
23
|
+
RipgrepTool,
|
24
|
+
SearchDefinitionNames,
|
25
|
+
TaskCompleteTool,
|
26
|
+
WriteFileTool,
|
27
|
+
)
|
28
|
+
|
29
|
+
MODEL_NAME = "deepseek/deepseek-chat"
|
30
|
+
|
31
|
+
|
32
|
+
def create_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
33
|
+
"""Create an agent with the specified model and tools.
|
34
|
+
|
35
|
+
Args:
|
36
|
+
model_name (str): Name of the model to use
|
37
|
+
vision_model_name (str | None): Name of the vision model to use
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
Agent: An agent with the specified model and tools
|
41
|
+
"""
|
42
|
+
tools = [
|
43
|
+
TaskCompleteTool(),
|
44
|
+
ReadFileTool(),
|
45
|
+
ReadFileBlockTool(),
|
46
|
+
WriteFileTool(),
|
47
|
+
EditWholeContentTool(),
|
48
|
+
InputQuestionTool(),
|
49
|
+
ListDirectoryTool(),
|
50
|
+
ExecuteBashCommandTool(),
|
51
|
+
ReplaceInFileTool(),
|
52
|
+
RipgrepTool(),
|
53
|
+
SearchDefinitionNames(),
|
54
|
+
MarkitdownTool(),
|
55
|
+
LLMTool(model_name=model_name),
|
56
|
+
DownloadHttpFileTool(),
|
57
|
+
]
|
58
|
+
|
59
|
+
if vision_model_name:
|
60
|
+
tools.append(LLMVisionTool(model_name=vision_model_name))
|
61
|
+
|
62
|
+
return Agent(
|
63
|
+
model_name=model_name,
|
64
|
+
tools=tools,
|
65
|
+
)
|
66
|
+
|
67
|
+
|
68
|
+
def create_interpreter_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
69
|
+
"""Create an interpreter agent with the specified model and tools.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
model_name (str): Name of the model to use
|
73
|
+
vision_model_name (str | None): Name of the vision model to use
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
Agent: An interpreter agent with the specified model and tools
|
77
|
+
"""
|
78
|
+
tools = [
|
79
|
+
TaskCompleteTool(),
|
80
|
+
ReadFileTool(),
|
81
|
+
ReadFileBlockTool(),
|
82
|
+
WriteFileTool(),
|
83
|
+
EditWholeContentTool(),
|
84
|
+
InputQuestionTool(),
|
85
|
+
ListDirectoryTool(),
|
86
|
+
ExecuteBashCommandTool(),
|
87
|
+
ReplaceInFileTool(),
|
88
|
+
RipgrepTool(),
|
89
|
+
PythonTool(),
|
90
|
+
NodeJsTool(),
|
91
|
+
SearchDefinitionNames(),
|
92
|
+
MarkitdownTool(),
|
93
|
+
LLMTool(model_name=model_name),
|
94
|
+
DownloadHttpFileTool(),
|
95
|
+
]
|
96
|
+
return Agent(model_name=model_name, tools=tools)
|
97
|
+
|
98
|
+
|
99
|
+
def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
100
|
+
"""Create an agent with the specified model and many tools.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
model_name (str): Name of the model to use
|
104
|
+
vision_model_name (str | None): Name of the vision model to use
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
Agent: An agent with the specified model and tools
|
108
|
+
|
109
|
+
"""
|
110
|
+
tools=[
|
111
|
+
TaskCompleteTool(),
|
112
|
+
ReadFileTool(),
|
113
|
+
ReadFileBlockTool(),
|
114
|
+
WriteFileTool(),
|
115
|
+
EditWholeContentTool(),
|
116
|
+
InputQuestionTool(),
|
117
|
+
ListDirectoryTool(),
|
118
|
+
ExecuteBashCommandTool(),
|
119
|
+
ReplaceInFileTool(),
|
120
|
+
RipgrepTool(),
|
121
|
+
PythonTool(),
|
122
|
+
NodeJsTool(),
|
123
|
+
SearchDefinitionNames(),
|
124
|
+
MarkitdownTool(),
|
125
|
+
LLMTool(model_name=model_name),
|
126
|
+
DownloadHttpFileTool(),
|
127
|
+
]
|
128
|
+
|
129
|
+
if vision_model_name:
|
130
|
+
tools.append(LLMVisionTool(model_name=vision_model_name))
|
131
|
+
|
132
|
+
return Agent(
|
133
|
+
model_name=model_name,
|
134
|
+
tools=tools,
|
135
|
+
)
|
136
|
+
|
137
|
+
|
138
|
+
def create_orchestrator_agent(model_name: str, vision_model_name: str | None = None) -> Agent:
|
139
|
+
"""Create an agent with the specified model and tools.
|
140
|
+
|
141
|
+
Args:
|
142
|
+
model_name (str): Name of the model to use
|
143
|
+
vision_model_name (str | None): Name of the vision model to use
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
Agent: An agent with the specified model and tools
|
147
|
+
"""
|
148
|
+
# Rebuild AgentTool to resolve forward references
|
149
|
+
AgentTool.model_rebuild()
|
150
|
+
|
151
|
+
coding_agent_instance = create_coding_agent(model_name)
|
152
|
+
|
153
|
+
tools = [
|
154
|
+
TaskCompleteTool(),
|
155
|
+
ListDirectoryTool(),
|
156
|
+
ReadFileBlockTool(),
|
157
|
+
RipgrepTool(),
|
158
|
+
SearchDefinitionNames(),
|
159
|
+
LLMTool(model_name=MODEL_NAME),
|
160
|
+
AgentTool(agent=coding_agent_instance, agent_role="software expert", name="coder_agent_tool"),
|
161
|
+
]
|
162
|
+
|
163
|
+
if vision_model_name:
|
164
|
+
tools.append(LLMVisionTool(model_name=vision_model_name))
|
165
|
+
|
166
|
+
return Agent(
|
167
|
+
model_name=model_name,
|
168
|
+
tools=tools,
|
169
|
+
)
|
@@ -5,6 +5,7 @@ from quantalogic.tools import (
|
|
5
5
|
InputQuestionTool,
|
6
6
|
ListDirectoryTool,
|
7
7
|
LLMTool,
|
8
|
+
LLMVisionTool,
|
8
9
|
ReadFileBlockTool,
|
9
10
|
ReadFileTool,
|
10
11
|
ReplaceInFileTool,
|
@@ -17,11 +18,12 @@ from quantalogic.utils import get_coding_environment
|
|
17
18
|
from quantalogic.utils.get_quantalogic_rules_content import get_quantalogic_rules_file_content
|
18
19
|
|
19
20
|
|
20
|
-
def create_coding_agent(model_name: str, basic: bool = False) -> Agent:
|
21
|
+
def create_coding_agent(model_name: str,vision_model_name: str | None = None, basic: bool = False) -> Agent:
|
21
22
|
"""Creates and configures a coding agent with a comprehensive set of tools.
|
22
23
|
|
23
24
|
Args:
|
24
25
|
model_name (str): Name of the language model to use for the agent's core capabilities
|
26
|
+
vision_model_name (str | None): Name of the vision model to use for the agent's core capabilities
|
25
27
|
basic (bool, optional): If True, the agent will be configured with a basic set of tools.
|
26
28
|
|
27
29
|
Returns:
|
@@ -59,6 +61,9 @@ def create_coding_agent(model_name: str, basic: bool = False) -> Agent:
|
|
59
61
|
InputQuestionTool(),
|
60
62
|
]
|
61
63
|
|
64
|
+
if vision_model_name:
|
65
|
+
tools.append(LLMVisionTool(model_name=vision_model_name))
|
66
|
+
|
62
67
|
if not basic:
|
63
68
|
tools.append(
|
64
69
|
LLMTool(
|
@@ -5,16 +5,17 @@ from litellm import completion, exceptions, get_max_tokens, get_model_info, toke
|
|
5
5
|
from loguru import logger
|
6
6
|
from pydantic import BaseModel, Field, field_validator
|
7
7
|
|
8
|
-
MIN_RETRIES =
|
8
|
+
MIN_RETRIES = 1
|
9
9
|
|
10
10
|
|
11
11
|
class Message(BaseModel):
|
12
12
|
"""Represents a message in a conversation with a specific role and content."""
|
13
13
|
|
14
14
|
role: str = Field(..., min_length=1)
|
15
|
-
content: str = Field(..., min_length=1)
|
15
|
+
content: str | dict = Field(..., min_length=1)
|
16
|
+
image_url: str | None = Field(default=None, pattern=r"^https?://")
|
16
17
|
|
17
|
-
@field_validator("role"
|
18
|
+
@field_validator("role")
|
18
19
|
@classmethod
|
19
20
|
def validate_not_empty(cls, v: str) -> str:
|
20
21
|
"""Validate that the field is not empty or whitespace-only."""
|
@@ -22,6 +23,26 @@ class Message(BaseModel):
|
|
22
23
|
raise ValueError("Field cannot be empty or whitespace-only")
|
23
24
|
return v
|
24
25
|
|
26
|
+
@field_validator("content")
|
27
|
+
@classmethod
|
28
|
+
def validate_content(cls, v: str | dict) -> str | dict:
|
29
|
+
"""Validate content based on its type."""
|
30
|
+
if isinstance(v, str):
|
31
|
+
if not v or not v.strip():
|
32
|
+
raise ValueError("Text content cannot be empty or whitespace-only")
|
33
|
+
elif isinstance(v, dict):
|
34
|
+
if not v.get("text") or not v.get("image_url"):
|
35
|
+
raise ValueError("Multimodal content must have both text and image_url")
|
36
|
+
return v
|
37
|
+
|
38
|
+
@field_validator("image_url")
|
39
|
+
@classmethod
|
40
|
+
def validate_image_url(cls, v: str | None) -> str | None:
|
41
|
+
"""Validate image URL format if present."""
|
42
|
+
if v and not v.startswith(("http://", "https://")):
|
43
|
+
raise ValueError("Image URL must start with http:// or https://")
|
44
|
+
return v
|
45
|
+
|
25
46
|
|
26
47
|
class TokenUsage(BaseModel):
|
27
48
|
"""Represents token usage statistics for a language model."""
|
@@ -59,6 +80,7 @@ class GenerativeModel:
|
|
59
80
|
temperature: Sampling temperature between 0 and 1.
|
60
81
|
Defaults to 0.7.
|
61
82
|
"""
|
83
|
+
logger.debug(f"Initializing GenerativeModel with model={model}, temperature={temperature}")
|
62
84
|
self.model = model
|
63
85
|
self.temperature = temperature
|
64
86
|
|
@@ -85,15 +107,16 @@ class GenerativeModel:
|
|
85
107
|
)
|
86
108
|
|
87
109
|
# Retry on specific retriable exceptions
|
88
|
-
def generate_with_history(self, messages_history: list[Message], prompt: str) -> ResponseStats:
|
89
|
-
"""Generate a response with conversation history.
|
110
|
+
def generate_with_history(self, messages_history: list[Message], prompt: str, image_url: str | None = None) -> ResponseStats:
|
111
|
+
"""Generate a response with conversation history and optional image.
|
90
112
|
|
91
|
-
Generates a response based on previous conversation messages
|
92
|
-
|
113
|
+
Generates a response based on previous conversation messages,
|
114
|
+
a new user prompt, and an optional image URL.
|
93
115
|
|
94
116
|
Args:
|
95
117
|
messages_history: Previous conversation messages.
|
96
118
|
prompt: Current user prompt.
|
119
|
+
image_url: Optional image URL for visual queries.
|
97
120
|
|
98
121
|
Returns:
|
99
122
|
Detailed response statistics.
|
@@ -105,7 +128,22 @@ class GenerativeModel:
|
|
105
128
|
Exception: For other unexpected errors.
|
106
129
|
"""
|
107
130
|
messages = [{"role": msg.role, "content": str(msg.content)} for msg in messages_history]
|
108
|
-
|
131
|
+
|
132
|
+
if image_url:
|
133
|
+
messages.append({
|
134
|
+
"role": "user",
|
135
|
+
"content": [
|
136
|
+
{"type": "text", "text": str(prompt)},
|
137
|
+
{
|
138
|
+
"type": "image_url",
|
139
|
+
"image_url": {
|
140
|
+
"url": image_url
|
141
|
+
}
|
142
|
+
}
|
143
|
+
]
|
144
|
+
})
|
145
|
+
else:
|
146
|
+
messages.append({"role": "user", "content": str(prompt)})
|
109
147
|
|
110
148
|
try:
|
111
149
|
logger.debug(f"Generating response for prompt: {prompt}")
|
@@ -140,9 +178,12 @@ class GenerativeModel:
|
|
140
178
|
}
|
141
179
|
|
142
180
|
logger.error("LLM Generation Error: {}", error_details)
|
181
|
+
logger.debug(f"Error details: {error_details}")
|
182
|
+
logger.debug(f"Model: {self.model}, Temperature: {self.temperature}")
|
143
183
|
|
144
184
|
# Handle authentication and permission errors
|
145
185
|
if isinstance(e, self.AUTH_EXCEPTIONS):
|
186
|
+
logger.debug("Authentication error occurred")
|
146
187
|
raise openai.AuthenticationError(
|
147
188
|
f"Authentication failed with provider {error_details['provider']}"
|
148
189
|
) from e
|
@@ -162,7 +203,7 @@ class GenerativeModel:
|
|
162
203
|
# Wrap unknown errors in APIError
|
163
204
|
raise openai.APIError(f"Unexpected error during generation: {str(e)}") from e
|
164
205
|
|
165
|
-
def generate(self, prompt: str) -> ResponseStats:
|
206
|
+
def generate(self, prompt: str, image_url: str | None = None) -> ResponseStats:
|
166
207
|
"""Generate a response without conversation history.
|
167
208
|
|
168
209
|
Generates a response for a single user prompt without
|
@@ -170,11 +211,12 @@ class GenerativeModel:
|
|
170
211
|
|
171
212
|
Args:
|
172
213
|
prompt: User prompt.
|
214
|
+
image_url: Optional image URL for visual queries.
|
173
215
|
|
174
216
|
Returns:
|
175
217
|
Detailed response statistics.
|
176
218
|
"""
|
177
|
-
return self.generate_with_history([], prompt)
|
219
|
+
return self.generate_with_history([], prompt, image_url)
|
178
220
|
|
179
221
|
def get_max_tokens(self) -> int:
|
180
222
|
"""Get the maximum number of tokens that can be generated by the model."""
|
@@ -182,8 +224,11 @@ class GenerativeModel:
|
|
182
224
|
|
183
225
|
def token_counter(self, messages: list[Message]) -> int:
|
184
226
|
"""Count the number of tokens in a list of messages."""
|
227
|
+
logger.debug(f"Counting tokens for {len(messages)} messages using model {self.model}")
|
185
228
|
litellm_messages = [{"role": msg.role, "content": str(msg.content)} for msg in messages]
|
186
|
-
|
229
|
+
token_count = token_counter(model=self.model, messages=litellm_messages)
|
230
|
+
logger.debug(f"Token count: {token_count}")
|
231
|
+
return token_count
|
187
232
|
|
188
233
|
def token_counter_with_history(self, messages_history: list[Message], prompt: str) -> int:
|
189
234
|
"""Count the number of tokens in a list of messages and a prompt."""
|
@@ -193,12 +238,18 @@ class GenerativeModel:
|
|
193
238
|
|
194
239
|
def get_model_info(self) -> dict | None:
|
195
240
|
"""Get information about the model."""
|
241
|
+
logger.debug(f"Retrieving model info for {self.model}")
|
196
242
|
model_info = get_model_info(self.model)
|
197
243
|
|
198
244
|
if not model_info:
|
199
|
-
|
245
|
+
logger.debug("Model info not found, trying without openrouter/ prefix")
|
200
246
|
model_info = get_model_info(self.model.replace("openrouter/", ""))
|
201
247
|
|
248
|
+
if model_info:
|
249
|
+
logger.debug(f"Model info retrieved: {model_info.keys()}")
|
250
|
+
else:
|
251
|
+
logger.debug("No model info available")
|
252
|
+
|
202
253
|
return model_info
|
203
254
|
|
204
255
|
def get_model_max_input_tokens(self) -> int:
|