quantalogic 0.2.8__py3-none-any.whl → 0.2.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantalogic/agent.py +34 -3
- quantalogic/agent_config.py +101 -68
- quantalogic/coding_agent.py +7 -2
- quantalogic/generative_model.py +62 -12
- quantalogic/main.py +108 -25
- quantalogic/print_event.py +3 -1
- quantalogic/prompts.py +12 -19
- quantalogic/server/agent_server.py +13 -13
- quantalogic/server/state.py +5 -5
- quantalogic/tool_manager.py +4 -4
- quantalogic/tools/__init__.py +2 -0
- quantalogic/tools/agent_tool.py +1 -1
- quantalogic/tools/elixir_tool.py +1 -1
- quantalogic/tools/llm_tool.py +14 -5
- quantalogic/tools/llm_vision_tool.py +127 -0
- quantalogic/tools/nodejs_tool.py +2 -2
- quantalogic/tools/python_tool.py +2 -2
- quantalogic/tools/replace_in_file_tool.py +3 -3
- quantalogic/tools/ripgrep_tool.py +3 -3
- quantalogic/tools/tool.py +3 -3
- quantalogic/tools/unified_diff_tool.py +1 -0
- quantalogic/tools/write_file_tool.py +1 -0
- quantalogic/utils/check_version.py +37 -0
- quantalogic/utils/download_http_file.py +4 -4
- quantalogic/utils/read_http_text_content.py +2 -2
- quantalogic/version.py +3 -2
- {quantalogic-0.2.8.dist-info → quantalogic-0.2.12.dist-info}/METADATA +33 -27
- {quantalogic-0.2.8.dist-info → quantalogic-0.2.12.dist-info}/RECORD +31 -29
- {quantalogic-0.2.8.dist-info → quantalogic-0.2.12.dist-info}/LICENSE +0 -0
- {quantalogic-0.2.8.dist-info → quantalogic-0.2.12.dist-info}/WHEEL +0 -0
- {quantalogic-0.2.8.dist-info → quantalogic-0.2.12.dist-info}/entry_points.txt +0 -0
quantalogic/agent.py
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
"""Enhanced QuantaLogic agent implementing the ReAct framework."""
|
2
2
|
|
3
|
-
import os
|
4
|
-
import sys
|
5
3
|
from collections.abc import Callable
|
6
4
|
from datetime import datetime
|
7
5
|
from typing import Any
|
@@ -64,6 +62,7 @@ class Agent(BaseModel):
|
|
64
62
|
event_emitter: EventEmitter = EventEmitter()
|
65
63
|
config: AgentConfig
|
66
64
|
task_to_solve: str
|
65
|
+
task_to_solve_summary: str = ""
|
67
66
|
ask_for_user_validation: Callable[[str], bool] = console_ask_for_user_validation
|
68
67
|
last_tool_call: dict[str, Any] = {} # Stores the last tool call information
|
69
68
|
total_tokens: int = 0 # Total tokens in the conversation
|
@@ -137,6 +136,9 @@ class Agent(BaseModel):
|
|
137
136
|
logger.debug(f"Solving task... {task}")
|
138
137
|
self._reset_session(task_to_solve=task, max_iterations=max_iterations)
|
139
138
|
|
139
|
+
# Generate task summary
|
140
|
+
self.task_to_solve_summary = self._generate_task_summary(task)
|
141
|
+
|
140
142
|
# Add system prompt to memory
|
141
143
|
self.memory.add(Message(role="system", content=self.config.system_prompt))
|
142
144
|
|
@@ -166,7 +168,7 @@ class Agent(BaseModel):
|
|
166
168
|
self._update_total_tokens(message_history=self.memory.memory, prompt=current_prompt)
|
167
169
|
|
168
170
|
# Emit event: Task Think Start after updating total tokens
|
169
|
-
self._emit_event("task_think_start")
|
171
|
+
self._emit_event("task_think_start", {"prompt": current_prompt})
|
170
172
|
|
171
173
|
self._compact_memory_if_needed(current_prompt)
|
172
174
|
|
@@ -443,6 +445,10 @@ class Agent(BaseModel):
|
|
443
445
|
"You must analyze this answer and evaluate what to do next to solve the task.\n"
|
444
446
|
"If the step failed, take a step back and rethink your approach.\n"
|
445
447
|
"\n"
|
448
|
+
"--- Task to solve summary ---\n"
|
449
|
+
"\n"
|
450
|
+
f"{self.task_to_solve_summary}"
|
451
|
+
"\n"
|
446
452
|
"--- Format ---\n"
|
447
453
|
"\n"
|
448
454
|
"You MUST respond with exactly two XML blocks formatted in markdown:\n"
|
@@ -546,6 +552,7 @@ class Agent(BaseModel):
|
|
546
552
|
"\n### Tools:\n"
|
547
553
|
"-----------------------------\n"
|
548
554
|
f"{self._get_tools_names_prompt()}\n"
|
555
|
+
"\n"
|
549
556
|
"### Variables:\n"
|
550
557
|
"-----------------------------\n"
|
551
558
|
f"{self._get_variable_prompt()}\n"
|
@@ -575,6 +582,8 @@ class Agent(BaseModel):
|
|
575
582
|
"Available variables:\n"
|
576
583
|
"\n"
|
577
584
|
f"{', '.join(self.variable_store.keys())}\n"
|
585
|
+
if len(self.variable_store.keys()) > 0
|
586
|
+
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.
|
quantalogic/agent_config.py
CHANGED
@@ -13,6 +13,7 @@ from quantalogic.tools import (
|
|
13
13
|
InputQuestionTool,
|
14
14
|
ListDirectoryTool,
|
15
15
|
LLMTool,
|
16
|
+
LLMVisionTool,
|
16
17
|
MarkitdownTool,
|
17
18
|
NodeJsTool,
|
18
19
|
PythonTool,
|
@@ -28,109 +29,141 @@ from quantalogic.tools import (
|
|
28
29
|
MODEL_NAME = "deepseek/deepseek-chat"
|
29
30
|
|
30
31
|
|
31
|
-
def create_agent(model_name) -> Agent:
|
32
|
+
def create_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
32
33
|
"""Create an agent with the specified model and tools.
|
33
34
|
|
34
35
|
Args:
|
35
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
|
36
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
|
+
|
37
62
|
return Agent(
|
38
63
|
model_name=model_name,
|
39
|
-
tools=
|
40
|
-
TaskCompleteTool(),
|
41
|
-
ReadFileTool(),
|
42
|
-
ReadFileBlockTool(),
|
43
|
-
WriteFileTool(),
|
44
|
-
EditWholeContentTool(),
|
45
|
-
InputQuestionTool(),
|
46
|
-
ListDirectoryTool(),
|
47
|
-
ExecuteBashCommandTool(),
|
48
|
-
ReplaceInFileTool(),
|
49
|
-
RipgrepTool(),
|
50
|
-
SearchDefinitionNames(),
|
51
|
-
MarkitdownTool(),
|
52
|
-
LLMTool(model_name=model_name),
|
53
|
-
DownloadHttpFileTool(),
|
54
|
-
],
|
64
|
+
tools=tools,
|
55
65
|
)
|
56
66
|
|
57
67
|
|
58
|
-
def create_interpreter_agent(model_name: str) -> Agent:
|
68
|
+
def create_interpreter_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
59
69
|
"""Create an interpreter agent with the specified model and tools.
|
60
70
|
|
61
71
|
Args:
|
62
72
|
model_name (str): Name of the model to use
|
63
|
-
|
64
|
-
return Agent(
|
65
|
-
model_name=model_name,
|
66
|
-
tools=[
|
67
|
-
TaskCompleteTool(),
|
68
|
-
ReadFileTool(),
|
69
|
-
ReadFileBlockTool(),
|
70
|
-
WriteFileTool(),
|
71
|
-
EditWholeContentTool(),
|
72
|
-
InputQuestionTool(),
|
73
|
-
ListDirectoryTool(),
|
74
|
-
ExecuteBashCommandTool(),
|
75
|
-
ReplaceInFileTool(),
|
76
|
-
RipgrepTool(),
|
77
|
-
PythonTool(),
|
78
|
-
NodeJsTool(),
|
79
|
-
SearchDefinitionNames(),
|
80
|
-
DownloadHttpFileTool(),
|
81
|
-
],
|
82
|
-
)
|
83
|
-
|
73
|
+
vision_model_name (str | None): Name of the vision model to use
|
84
74
|
|
85
|
-
|
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:
|
86
100
|
"""Create an agent with the specified model and many tools.
|
87
101
|
|
88
102
|
Args:
|
89
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
|
+
|
90
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
|
+
|
91
132
|
return Agent(
|
92
133
|
model_name=model_name,
|
93
|
-
tools=
|
94
|
-
TaskCompleteTool(),
|
95
|
-
ReadFileTool(),
|
96
|
-
ReadFileBlockTool(),
|
97
|
-
WriteFileTool(),
|
98
|
-
EditWholeContentTool(),
|
99
|
-
InputQuestionTool(),
|
100
|
-
ListDirectoryTool(),
|
101
|
-
ExecuteBashCommandTool(),
|
102
|
-
ReplaceInFileTool(),
|
103
|
-
RipgrepTool(),
|
104
|
-
PythonTool(),
|
105
|
-
NodeJsTool(),
|
106
|
-
SearchDefinitionNames(),
|
107
|
-
MarkitdownTool(),
|
108
|
-
LLMTool(model_name=model_name),
|
109
|
-
DownloadHttpFileTool(),
|
110
|
-
],
|
134
|
+
tools=tools,
|
111
135
|
)
|
112
136
|
|
113
137
|
|
114
|
-
def create_orchestrator_agent(model_name: str) -> Agent:
|
138
|
+
def create_orchestrator_agent(model_name: str, vision_model_name: str | None = None) -> Agent:
|
115
139
|
"""Create an agent with the specified model and tools.
|
116
140
|
|
117
141
|
Args:
|
118
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
|
119
147
|
"""
|
120
148
|
# Rebuild AgentTool to resolve forward references
|
121
149
|
AgentTool.model_rebuild()
|
122
150
|
|
123
151
|
coding_agent_instance = create_coding_agent(model_name)
|
124
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
|
+
|
125
166
|
return Agent(
|
126
167
|
model_name=model_name,
|
127
|
-
tools=
|
128
|
-
TaskCompleteTool(),
|
129
|
-
ListDirectoryTool(),
|
130
|
-
ReadFileBlockTool(),
|
131
|
-
RipgrepTool(),
|
132
|
-
SearchDefinitionNames(),
|
133
|
-
LLMTool(model_name=MODEL_NAME),
|
134
|
-
AgentTool(agent=coding_agent_instance, agent_role="software expert", name="coder_agent_tool"),
|
135
|
-
],
|
168
|
+
tools=tools,
|
136
169
|
)
|
quantalogic/coding_agent.py
CHANGED
@@ -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,12 +61,15 @@ 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(
|
65
70
|
model_name=model_name,
|
66
71
|
system_prompt="You are a software expert, your role is to answer coding questions.",
|
67
|
-
name="coding_consultant", # Handles implementation-level coding questions
|
72
|
+
name="coding_consultant", # Handles implementation-level coding questions
|
68
73
|
)
|
69
74
|
)
|
70
75
|
tools.append(
|
quantalogic/generative_model.py
CHANGED
@@ -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,18 @@ class GenerativeModel:
|
|
85
107
|
)
|
86
108
|
|
87
109
|
# Retry on specific retriable exceptions
|
88
|
-
def generate_with_history(
|
89
|
-
|
110
|
+
def generate_with_history(
|
111
|
+
self, messages_history: list[Message], prompt: str, image_url: str | None = None
|
112
|
+
) -> ResponseStats:
|
113
|
+
"""Generate a response with conversation history and optional image.
|
90
114
|
|
91
|
-
Generates a response based on previous conversation messages
|
92
|
-
|
115
|
+
Generates a response based on previous conversation messages,
|
116
|
+
a new user prompt, and an optional image URL.
|
93
117
|
|
94
118
|
Args:
|
95
119
|
messages_history: Previous conversation messages.
|
96
120
|
prompt: Current user prompt.
|
121
|
+
image_url: Optional image URL for visual queries.
|
97
122
|
|
98
123
|
Returns:
|
99
124
|
Detailed response statistics.
|
@@ -105,7 +130,19 @@ class GenerativeModel:
|
|
105
130
|
Exception: For other unexpected errors.
|
106
131
|
"""
|
107
132
|
messages = [{"role": msg.role, "content": str(msg.content)} for msg in messages_history]
|
108
|
-
|
133
|
+
|
134
|
+
if image_url:
|
135
|
+
messages.append(
|
136
|
+
{
|
137
|
+
"role": "user",
|
138
|
+
"content": [
|
139
|
+
{"type": "text", "text": str(prompt)},
|
140
|
+
{"type": "image_url", "image_url": {"url": image_url}},
|
141
|
+
],
|
142
|
+
}
|
143
|
+
)
|
144
|
+
else:
|
145
|
+
messages.append({"role": "user", "content": str(prompt)})
|
109
146
|
|
110
147
|
try:
|
111
148
|
logger.debug(f"Generating response for prompt: {prompt}")
|
@@ -140,9 +177,12 @@ class GenerativeModel:
|
|
140
177
|
}
|
141
178
|
|
142
179
|
logger.error("LLM Generation Error: {}", error_details)
|
180
|
+
logger.debug(f"Error details: {error_details}")
|
181
|
+
logger.debug(f"Model: {self.model}, Temperature: {self.temperature}")
|
143
182
|
|
144
183
|
# Handle authentication and permission errors
|
145
184
|
if isinstance(e, self.AUTH_EXCEPTIONS):
|
185
|
+
logger.debug("Authentication error occurred")
|
146
186
|
raise openai.AuthenticationError(
|
147
187
|
f"Authentication failed with provider {error_details['provider']}"
|
148
188
|
) from e
|
@@ -162,7 +202,7 @@ class GenerativeModel:
|
|
162
202
|
# Wrap unknown errors in APIError
|
163
203
|
raise openai.APIError(f"Unexpected error during generation: {str(e)}") from e
|
164
204
|
|
165
|
-
def generate(self, prompt: str) -> ResponseStats:
|
205
|
+
def generate(self, prompt: str, image_url: str | None = None) -> ResponseStats:
|
166
206
|
"""Generate a response without conversation history.
|
167
207
|
|
168
208
|
Generates a response for a single user prompt without
|
@@ -170,11 +210,12 @@ class GenerativeModel:
|
|
170
210
|
|
171
211
|
Args:
|
172
212
|
prompt: User prompt.
|
213
|
+
image_url: Optional image URL for visual queries.
|
173
214
|
|
174
215
|
Returns:
|
175
216
|
Detailed response statistics.
|
176
217
|
"""
|
177
|
-
return self.generate_with_history([], prompt)
|
218
|
+
return self.generate_with_history([], prompt, image_url)
|
178
219
|
|
179
220
|
def get_max_tokens(self) -> int:
|
180
221
|
"""Get the maximum number of tokens that can be generated by the model."""
|
@@ -182,8 +223,11 @@ class GenerativeModel:
|
|
182
223
|
|
183
224
|
def token_counter(self, messages: list[Message]) -> int:
|
184
225
|
"""Count the number of tokens in a list of messages."""
|
226
|
+
logger.debug(f"Counting tokens for {len(messages)} messages using model {self.model}")
|
185
227
|
litellm_messages = [{"role": msg.role, "content": str(msg.content)} for msg in messages]
|
186
|
-
|
228
|
+
token_count = token_counter(model=self.model, messages=litellm_messages)
|
229
|
+
logger.debug(f"Token count: {token_count}")
|
230
|
+
return token_count
|
187
231
|
|
188
232
|
def token_counter_with_history(self, messages_history: list[Message], prompt: str) -> int:
|
189
233
|
"""Count the number of tokens in a list of messages and a prompt."""
|
@@ -193,12 +237,18 @@ class GenerativeModel:
|
|
193
237
|
|
194
238
|
def get_model_info(self) -> dict | None:
|
195
239
|
"""Get information about the model."""
|
240
|
+
logger.debug(f"Retrieving model info for {self.model}")
|
196
241
|
model_info = get_model_info(self.model)
|
197
242
|
|
198
243
|
if not model_info:
|
199
|
-
|
244
|
+
logger.debug("Model info not found, trying without openrouter/ prefix")
|
200
245
|
model_info = get_model_info(self.model.replace("openrouter/", ""))
|
201
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
|
+
|
202
252
|
return model_info
|
203
253
|
|
204
254
|
def get_model_max_input_tokens(self) -> int:
|