vtk-prompt 0.0.3__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.
- vtk_prompt/__init__.py +25 -0
- vtk_prompt/build_rag_db.py +146 -0
- vtk_prompt/generate_files.py +175 -0
- vtk_prompt/prompt.py +323 -0
- vtk_prompt/prompts/__init__.py +78 -0
- vtk_prompt/prompts/base_context.txt +23 -0
- vtk_prompt/prompts/no_rag_context.txt +4 -0
- vtk_prompt/prompts/python_role.txt +1 -0
- vtk_prompt/prompts/rag_chat_context.txt +28 -0
- vtk_prompt/prompts/rag_context.txt +12 -0
- vtk_prompt/prompts/ui_post_prompt.txt +8 -0
- vtk_prompt/prompts/vtk_xml_context.txt +82 -0
- vtk_prompt/prompts/xml_role.txt +1 -0
- vtk_prompt/rag_chat_wrapper.py +242 -0
- vtk_prompt/test_rag.py +127 -0
- vtk_prompt/vtk_prompt_ui.py +688 -0
- vtk_prompt-0.0.3.dist-info/METADATA +313 -0
- vtk_prompt-0.0.3.dist-info/RECORD +22 -0
- vtk_prompt-0.0.3.dist-info/WHEEL +5 -0
- vtk_prompt-0.0.3.dist-info/entry_points.txt +7 -0
- vtk_prompt-0.0.3.dist-info/licenses/LICENSE +21 -0
- vtk_prompt-0.0.3.dist-info/top_level.txt +1 -0
vtk_prompt/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""VTK-Prompt - CLI tool for generating VTK visualizations using LLMs.
|
|
2
|
+
|
|
3
|
+
This package provides tools to generate VTK Python code and XML files using
|
|
4
|
+
LLMs (Anthropic Claude, OpenAI GPT, or NVIDIA NIM models). It also includes
|
|
5
|
+
Retrieval-Augmented Generation (RAG) capabilities to improve code generation
|
|
6
|
+
by providing relevant examples from the VTK examples corpus.
|
|
7
|
+
|
|
8
|
+
Main components:
|
|
9
|
+
- vtk-prompt: Generate and run VTK Python code
|
|
10
|
+
- gen-vtk-file: Generate VTK XML files
|
|
11
|
+
- vtk-build-rag: Build a RAG database from VTK examples
|
|
12
|
+
- vtk-test-rag: Test the RAG database with queries
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
17
|
+
except ImportError:
|
|
18
|
+
from importlib_metadata import version, PackageNotFoundError
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
__version__ = version("vtk-prompt")
|
|
22
|
+
except PackageNotFoundError:
|
|
23
|
+
__version__ = "unknown"
|
|
24
|
+
__author__ = "Vicente Adolfo Bolea Sanchez"
|
|
25
|
+
__email__ = "vicente.bolea@kitware.com"
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import importlib.util
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def setup_rag_path():
|
|
10
|
+
"""Add rag-components to the Python path.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
The path to the rag-components directory
|
|
14
|
+
"""
|
|
15
|
+
script_dir = Path(__file__).resolve().parent
|
|
16
|
+
project_root = script_dir.parent.parent
|
|
17
|
+
rag_path = str(project_root / "rag-components")
|
|
18
|
+
|
|
19
|
+
if rag_path not in sys.path:
|
|
20
|
+
sys.path.append(rag_path)
|
|
21
|
+
|
|
22
|
+
return rag_path
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def check_dependencies():
|
|
26
|
+
"""Check if required dependencies are installed.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
True if all dependencies are installed, False otherwise
|
|
30
|
+
"""
|
|
31
|
+
required_modules = ["chromadb", "sentence_transformers", "tree_sitter_languages"]
|
|
32
|
+
|
|
33
|
+
for module in required_modules:
|
|
34
|
+
if importlib.util.find_spec(module) is None:
|
|
35
|
+
print(f"Missing required dependency: {module}")
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main():
|
|
42
|
+
"""Build a RAG database from VTK example files."""
|
|
43
|
+
parser = argparse.ArgumentParser(description="Build RAG database for VTK examples")
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--examples-dir",
|
|
46
|
+
type=str,
|
|
47
|
+
default="data/examples",
|
|
48
|
+
help="Directory containing VTK examples (default: data/examples)",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--database",
|
|
52
|
+
type=str,
|
|
53
|
+
default="./db/codesage-codesage-large-v2",
|
|
54
|
+
help="Database path (default: ./db/codesage-codesage-large-v2)",
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--collection-name",
|
|
58
|
+
type=str,
|
|
59
|
+
default="vtk-examples",
|
|
60
|
+
help="Collection name in the database (default: vtk-examples)",
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"--model",
|
|
64
|
+
type=str,
|
|
65
|
+
default="codesage/codesage-large-v2",
|
|
66
|
+
help="Embedding model name (default: codesage/codesage-large-v2)",
|
|
67
|
+
)
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
"--language",
|
|
70
|
+
type=str,
|
|
71
|
+
default="python",
|
|
72
|
+
help="Language of the examples (default: python)",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
args = parser.parse_args()
|
|
76
|
+
|
|
77
|
+
# Check dependencies
|
|
78
|
+
if not check_dependencies():
|
|
79
|
+
print("Please install the required dependencies with:")
|
|
80
|
+
print('pip install -e ".[rag]"')
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
# Setup RAG path
|
|
84
|
+
rag_path = setup_rag_path()
|
|
85
|
+
|
|
86
|
+
# Import populate_db from rag-components
|
|
87
|
+
try:
|
|
88
|
+
sys.path.insert(0, rag_path)
|
|
89
|
+
from populate_db import fill_database
|
|
90
|
+
except ImportError as e:
|
|
91
|
+
print(f"Failed to import from rag-components: {e}")
|
|
92
|
+
print(
|
|
93
|
+
"Make sure the rag-components directory exists and contains the required files."
|
|
94
|
+
)
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
|
|
97
|
+
# Check if examples directory exists
|
|
98
|
+
examples_dir = Path(args.examples_dir)
|
|
99
|
+
if not examples_dir.exists() or not examples_dir.is_dir():
|
|
100
|
+
print(
|
|
101
|
+
f"Error: Examples directory '{args.examples_dir}' does not exist or is not a directory"
|
|
102
|
+
)
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
|
|
105
|
+
# Get all Python files in the examples directory
|
|
106
|
+
files = list(examples_dir.glob("**/*.py"))
|
|
107
|
+
if not files:
|
|
108
|
+
print(f"No Python files found in '{args.examples_dir}'")
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
print(f"Found {len(files)} Python files in '{args.examples_dir}'")
|
|
112
|
+
|
|
113
|
+
# Create database directory if it doesn't exist
|
|
114
|
+
database_dir = Path(args.database).parent
|
|
115
|
+
database_dir.mkdir(parents=True, exist_ok=True)
|
|
116
|
+
|
|
117
|
+
# Build the RAG database
|
|
118
|
+
print(
|
|
119
|
+
f"Building RAG database at '{args.database}' using embedding model '{args.model}'..."
|
|
120
|
+
)
|
|
121
|
+
try:
|
|
122
|
+
fill_database(
|
|
123
|
+
files=files,
|
|
124
|
+
database_path=args.database,
|
|
125
|
+
embedding_model=args.model,
|
|
126
|
+
language=args.language,
|
|
127
|
+
collection_name=args.collection_name,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
print(f"Successfully built RAG database at '{args.database}'")
|
|
131
|
+
print("You can now use the RAG database with vtk-prompt by running:")
|
|
132
|
+
print(
|
|
133
|
+
f'vtk-prompt "your query" -r --database {args.database} '
|
|
134
|
+
f"--collection {args.collection_name}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
print(f"Error building RAG database: {e}")
|
|
139
|
+
import traceback
|
|
140
|
+
|
|
141
|
+
traceback.print_exc()
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
main()
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import os
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
import openai
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
# Import our template system
|
|
11
|
+
from .prompts import (
|
|
12
|
+
get_vtk_xml_context,
|
|
13
|
+
get_xml_role,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VTKXMLGenerator:
|
|
18
|
+
"""OpenAI client for VTK XML file generation."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, api_key=None, base_url=None):
|
|
21
|
+
"""Initialize the VTK XML generator.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
api_key: OpenAI API key (defaults to OPENAI_API_KEY env var)
|
|
25
|
+
base_url: Optional custom API endpoint URL
|
|
26
|
+
"""
|
|
27
|
+
self.api_key = api_key or os.environ.get("OPENAI_API_KEY")
|
|
28
|
+
self.base_url = base_url
|
|
29
|
+
|
|
30
|
+
if not self.api_key:
|
|
31
|
+
raise ValueError(
|
|
32
|
+
"No API key provided. Set OPENAI_API_KEY or pass api_key parameter."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
|
|
36
|
+
|
|
37
|
+
def generate_xml(self, message, model, max_tokens=4000, temperature=0.7):
|
|
38
|
+
"""Generate VTK XML content from a description."""
|
|
39
|
+
examples_path = Path("data/examples/index.json")
|
|
40
|
+
if examples_path.exists():
|
|
41
|
+
_ = " ".join(json.loads(examples_path.read_text()).keys())
|
|
42
|
+
else:
|
|
43
|
+
_ = ""
|
|
44
|
+
|
|
45
|
+
context = get_vtk_xml_context(message)
|
|
46
|
+
|
|
47
|
+
response = self.client.chat.completions.create(
|
|
48
|
+
model=model,
|
|
49
|
+
messages=[
|
|
50
|
+
{"role": "system", "content": get_xml_role()},
|
|
51
|
+
{"role": "user", "content": context},
|
|
52
|
+
],
|
|
53
|
+
max_tokens=max_tokens,
|
|
54
|
+
temperature=temperature,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if hasattr(response, "choices") and len(response.choices) > 0:
|
|
58
|
+
content = response.choices[0].message.content or "No content in response"
|
|
59
|
+
finish_reason = response.choices[0].finish_reason
|
|
60
|
+
|
|
61
|
+
if finish_reason == "length":
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"Output was truncated due to max_tokens limit ({max_tokens}). "
|
|
64
|
+
"Please increase max_tokens."
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return content
|
|
68
|
+
|
|
69
|
+
return "No response generated"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Legacy function wrapper for backwards compatibility
|
|
73
|
+
def openai_query(message, model, api_key, max_tokens, temperature=0.7, base_url=None):
|
|
74
|
+
"""Legacy wrapper for VTK XML generation."""
|
|
75
|
+
generator = VTKXMLGenerator(api_key, base_url)
|
|
76
|
+
return generator.generate_xml(message, model, max_tokens, temperature)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@click.command()
|
|
80
|
+
@click.argument("input_string")
|
|
81
|
+
@click.option(
|
|
82
|
+
"--provider",
|
|
83
|
+
type=click.Choice(["openai", "anthropic", "gemini", "nim"]),
|
|
84
|
+
default="openai",
|
|
85
|
+
help="LLM provider to use",
|
|
86
|
+
)
|
|
87
|
+
@click.option("-m", "--model", default="gpt-4o", help="Model to use for generation")
|
|
88
|
+
@click.option(
|
|
89
|
+
"-t", "--token", required=True, help="API token for the selected provider"
|
|
90
|
+
)
|
|
91
|
+
@click.option("--base-url", help="Base URL for API (auto-detected or custom)")
|
|
92
|
+
@click.option(
|
|
93
|
+
"-k",
|
|
94
|
+
"--max-tokens",
|
|
95
|
+
type=int,
|
|
96
|
+
default=4000,
|
|
97
|
+
help="Maximum number of tokens to generate",
|
|
98
|
+
)
|
|
99
|
+
@click.option(
|
|
100
|
+
"--temperature",
|
|
101
|
+
type=float,
|
|
102
|
+
default=0.7,
|
|
103
|
+
help="Temperature for generation (0.0-2.0)",
|
|
104
|
+
)
|
|
105
|
+
@click.option(
|
|
106
|
+
"-o", "--output", help="Output file path (if not specified, output to stdout)"
|
|
107
|
+
)
|
|
108
|
+
def main(
|
|
109
|
+
input_string, provider, model, token, base_url, max_tokens, temperature, output
|
|
110
|
+
):
|
|
111
|
+
"""Generate VTK XML file content using LLMs.
|
|
112
|
+
|
|
113
|
+
INPUT_STRING: Description of the VTK file to generate
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
# Set default base URLs
|
|
117
|
+
if not base_url:
|
|
118
|
+
base_urls = {
|
|
119
|
+
"anthropic": "https://api.anthropic.com/v1",
|
|
120
|
+
"gemini": "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
121
|
+
"nim": "https://integrate.api.nvidia.com/v1",
|
|
122
|
+
}
|
|
123
|
+
base_url = base_urls.get(provider)
|
|
124
|
+
|
|
125
|
+
# Set default models based on provider
|
|
126
|
+
if model == "gpt-4o":
|
|
127
|
+
default_models = {
|
|
128
|
+
"anthropic": "claude-3-5-sonnet-20241022",
|
|
129
|
+
"gemini": "gemini-1.5-pro",
|
|
130
|
+
"nim": "meta/llama3-70b-instruct",
|
|
131
|
+
}
|
|
132
|
+
model = default_models.get(provider, model)
|
|
133
|
+
|
|
134
|
+
# Initialize the VTK XML generator
|
|
135
|
+
try:
|
|
136
|
+
generator = VTKXMLGenerator(token, base_url)
|
|
137
|
+
except ValueError as e:
|
|
138
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
# Generate the VTK XML content
|
|
142
|
+
try:
|
|
143
|
+
xml_content = generator.generate_xml(
|
|
144
|
+
input_string, model, max_tokens, temperature
|
|
145
|
+
)
|
|
146
|
+
except ValueError as e:
|
|
147
|
+
if "max_tokens" in str(e):
|
|
148
|
+
print(f"\nError: {e}", file=sys.stderr)
|
|
149
|
+
print(f"Current max_tokens: {max_tokens}", file=sys.stderr)
|
|
150
|
+
print("Try increasing with: --max-tokens <higher_number>", file=sys.stderr)
|
|
151
|
+
else:
|
|
152
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
153
|
+
sys.exit(1)
|
|
154
|
+
|
|
155
|
+
# Validate XML structure (basic check)
|
|
156
|
+
if xml_content.strip().startswith("<?xml") and "</VTKFile>" in xml_content:
|
|
157
|
+
# Output to file or stdout
|
|
158
|
+
if output:
|
|
159
|
+
with open(output, "w") as f:
|
|
160
|
+
f.write(xml_content)
|
|
161
|
+
print(f"VTK XML content written to {output}")
|
|
162
|
+
else:
|
|
163
|
+
print(xml_content)
|
|
164
|
+
else:
|
|
165
|
+
print("Warning: Generated content may not be valid VTK XML", file=sys.stderr)
|
|
166
|
+
if output:
|
|
167
|
+
with open(output, "w") as f:
|
|
168
|
+
f.write(xml_content)
|
|
169
|
+
print(f"Content written to {output} (please verify)")
|
|
170
|
+
else:
|
|
171
|
+
print(xml_content)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
main()
|
vtk_prompt/prompt.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import openai
|
|
7
|
+
import click
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
from .prompts import (
|
|
11
|
+
get_no_rag_context,
|
|
12
|
+
get_rag_context,
|
|
13
|
+
get_python_role,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class VTKPromptClient:
|
|
19
|
+
"""OpenAI client for VTK code generation."""
|
|
20
|
+
|
|
21
|
+
collection_name: str = "vtk-examples"
|
|
22
|
+
database_path: str = "./db/codesage-codesage-large-v2"
|
|
23
|
+
verbose: bool = False
|
|
24
|
+
|
|
25
|
+
def validate_code_syntax(self, code_string):
|
|
26
|
+
"""Validate Python code syntax using AST."""
|
|
27
|
+
try:
|
|
28
|
+
ast.parse(code_string)
|
|
29
|
+
return True, None
|
|
30
|
+
except SyntaxError as e:
|
|
31
|
+
return False, f"Syntax error: {e.msg} at line {e.lineno}"
|
|
32
|
+
except Exception as e:
|
|
33
|
+
return False, f"AST parsing error: {str(e)}"
|
|
34
|
+
|
|
35
|
+
def run_code(self, code_string):
|
|
36
|
+
"""Execute VTK code using exec() after AST validation."""
|
|
37
|
+
is_valid, error_msg = self.validate_code_syntax(code_string)
|
|
38
|
+
if not is_valid:
|
|
39
|
+
print(f"Code validation failed: {error_msg}")
|
|
40
|
+
if self.verbose:
|
|
41
|
+
print("Generated code:")
|
|
42
|
+
print(code_string)
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
if self.verbose:
|
|
46
|
+
print(code_string)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
exec(code_string, globals(), {})
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"Error executing code: {e}")
|
|
52
|
+
if not self.verbose:
|
|
53
|
+
print(code_string)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def query(
|
|
57
|
+
self,
|
|
58
|
+
message,
|
|
59
|
+
api_key,
|
|
60
|
+
model="gpt-4o",
|
|
61
|
+
base_url=None,
|
|
62
|
+
max_tokens=1000,
|
|
63
|
+
temperature=0.1,
|
|
64
|
+
top_k=5,
|
|
65
|
+
rag=False,
|
|
66
|
+
retry_attempts=1,
|
|
67
|
+
):
|
|
68
|
+
"""Generate VTK code with optional RAG enhancement and retry logic.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
message: The user query
|
|
72
|
+
api_key: API key for the service
|
|
73
|
+
model: Model name to use
|
|
74
|
+
base_url: API base URL
|
|
75
|
+
max_tokens: Maximum tokens to generate
|
|
76
|
+
temperature: Temperature for generation
|
|
77
|
+
top_k: Number of RAG examples to retrieve
|
|
78
|
+
rag: Whether to use RAG enhancement
|
|
79
|
+
retry_attempts: Number of times to retry if AST validation fails
|
|
80
|
+
"""
|
|
81
|
+
if not api_key:
|
|
82
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
|
83
|
+
|
|
84
|
+
if not api_key:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
"No API key provided. Set OPENAI_API_KEY or pass api_key parameter."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Create client with current parameters
|
|
90
|
+
client = openai.OpenAI(api_key=api_key, base_url=base_url)
|
|
91
|
+
|
|
92
|
+
if rag:
|
|
93
|
+
from .rag_chat_wrapper import (
|
|
94
|
+
check_rag_components_available,
|
|
95
|
+
get_rag_snippets,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if not check_rag_components_available():
|
|
99
|
+
raise ValueError("RAG components not available")
|
|
100
|
+
|
|
101
|
+
rag_snippets = get_rag_snippets(
|
|
102
|
+
message,
|
|
103
|
+
collection_name=self.collection_name,
|
|
104
|
+
database_path=self.database_path,
|
|
105
|
+
top_k=top_k,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if not rag_snippets:
|
|
109
|
+
raise ValueError("Failed to load RAG snippets")
|
|
110
|
+
|
|
111
|
+
context_snippets = "\n\n".join(rag_snippets["code_snippets"])
|
|
112
|
+
context = get_rag_context(message, context_snippets)
|
|
113
|
+
|
|
114
|
+
if self.verbose:
|
|
115
|
+
print("CONTEXT: " + context)
|
|
116
|
+
references = rag_snippets.get("references")
|
|
117
|
+
if references:
|
|
118
|
+
print("Using examples from:")
|
|
119
|
+
for ref in references:
|
|
120
|
+
print(f"- {ref}")
|
|
121
|
+
else:
|
|
122
|
+
context = get_no_rag_context(message)
|
|
123
|
+
if self.verbose:
|
|
124
|
+
print("CONTEXT: " + context)
|
|
125
|
+
|
|
126
|
+
messages = [
|
|
127
|
+
{"role": "system", "content": get_python_role()},
|
|
128
|
+
{"role": "user", "content": context},
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
# Retry loop for AST validation
|
|
132
|
+
for attempt in range(retry_attempts):
|
|
133
|
+
if self.verbose:
|
|
134
|
+
if attempt > 0:
|
|
135
|
+
print(f"Retry attempt {attempt + 1}/{retry_attempts}")
|
|
136
|
+
print(f"Making request with model: {model}, temperature: {temperature}")
|
|
137
|
+
for i, msg in enumerate(messages):
|
|
138
|
+
print(f"Message {i} ({msg['role']}): {msg['content'][:100]}...")
|
|
139
|
+
|
|
140
|
+
response = client.chat.completions.create(
|
|
141
|
+
model=model,
|
|
142
|
+
messages=messages,
|
|
143
|
+
max_tokens=max_tokens,
|
|
144
|
+
temperature=temperature,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if hasattr(response, "choices") and len(response.choices) > 0:
|
|
148
|
+
content = (
|
|
149
|
+
response.choices[0].message.content or "No content in response"
|
|
150
|
+
)
|
|
151
|
+
finish_reason = response.choices[0].finish_reason
|
|
152
|
+
|
|
153
|
+
if finish_reason == "length":
|
|
154
|
+
raise ValueError(
|
|
155
|
+
f"Output was truncated due to max_tokens limit ({max_tokens}). Please increase max_tokens."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
generated_code = None
|
|
159
|
+
if "import vtk" not in content:
|
|
160
|
+
generated_code = "import vtk\n" + content
|
|
161
|
+
else:
|
|
162
|
+
pos = content.find("import vtk")
|
|
163
|
+
if pos != -1:
|
|
164
|
+
generated_code = content[pos:]
|
|
165
|
+
else:
|
|
166
|
+
generated_code = content
|
|
167
|
+
|
|
168
|
+
is_valid, error_msg = self.validate_code_syntax(generated_code)
|
|
169
|
+
if is_valid:
|
|
170
|
+
return generated_code, response.usage
|
|
171
|
+
|
|
172
|
+
elif attempt < retry_attempts - 1: # Don't print on last attempt
|
|
173
|
+
if self.verbose:
|
|
174
|
+
print(f"AST validation failed: {error_msg}. Retrying...")
|
|
175
|
+
# Add error feedback to context for retry
|
|
176
|
+
messages.append({"role": "assistant", "content": content})
|
|
177
|
+
messages.append(
|
|
178
|
+
{
|
|
179
|
+
"role": "user",
|
|
180
|
+
"content": (
|
|
181
|
+
f"The generated code has a syntax error: {error_msg}. "
|
|
182
|
+
"Please fix the syntax and generate valid Python code."
|
|
183
|
+
),
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
# Last attempt failed
|
|
188
|
+
if self.verbose:
|
|
189
|
+
print(f"Final attempt failed AST validation: {error_msg}")
|
|
190
|
+
return (
|
|
191
|
+
generated_code,
|
|
192
|
+
response.usage,
|
|
193
|
+
) # Return anyway, let caller handle
|
|
194
|
+
else:
|
|
195
|
+
if attempt == retry_attempts - 1:
|
|
196
|
+
return "No response generated", response.usage
|
|
197
|
+
|
|
198
|
+
return "No response generated"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@click.command()
|
|
202
|
+
@click.argument("input_string")
|
|
203
|
+
@click.option(
|
|
204
|
+
"--provider",
|
|
205
|
+
type=click.Choice(["openai", "anthropic", "gemini", "nim"]),
|
|
206
|
+
default="openai",
|
|
207
|
+
help="LLM provider to use",
|
|
208
|
+
)
|
|
209
|
+
@click.option("-m", "--model", default="gpt-4o", help="Model name to use")
|
|
210
|
+
@click.option(
|
|
211
|
+
"-k", "--max-tokens", type=int, default=1000, help="Max # of tokens to generate"
|
|
212
|
+
)
|
|
213
|
+
@click.option(
|
|
214
|
+
"--temperature",
|
|
215
|
+
type=float,
|
|
216
|
+
default=0.7,
|
|
217
|
+
help="Temperature for generation (0.0-2.0)",
|
|
218
|
+
)
|
|
219
|
+
@click.option(
|
|
220
|
+
"-t", "--token", required=True, help="API token for the selected provider"
|
|
221
|
+
)
|
|
222
|
+
@click.option("--base-url", help="Base URL for API (auto-detected or custom)")
|
|
223
|
+
@click.option("-r", "--rag", is_flag=True, help="Use RAG to improve code generation")
|
|
224
|
+
@click.option("-v", "--verbose", is_flag=True, help="Show generated source code")
|
|
225
|
+
@click.option("--collection", default="vtk-examples", help="Collection name for RAG")
|
|
226
|
+
@click.option(
|
|
227
|
+
"--database",
|
|
228
|
+
default="./db/codesage-codesage-large-v2",
|
|
229
|
+
help="Database path for RAG",
|
|
230
|
+
)
|
|
231
|
+
@click.option(
|
|
232
|
+
"--top-k", type=int, default=5, help="Number of examples to retrieve from RAG"
|
|
233
|
+
)
|
|
234
|
+
@click.option(
|
|
235
|
+
"--retry-attempts",
|
|
236
|
+
type=int,
|
|
237
|
+
default=1,
|
|
238
|
+
help="Number of times to retry if AST validation fails",
|
|
239
|
+
)
|
|
240
|
+
def main(
|
|
241
|
+
input_string,
|
|
242
|
+
provider,
|
|
243
|
+
model,
|
|
244
|
+
max_tokens,
|
|
245
|
+
temperature,
|
|
246
|
+
token,
|
|
247
|
+
base_url,
|
|
248
|
+
rag,
|
|
249
|
+
verbose,
|
|
250
|
+
collection,
|
|
251
|
+
database,
|
|
252
|
+
top_k,
|
|
253
|
+
retry_attempts,
|
|
254
|
+
):
|
|
255
|
+
"""Generate and execute VTK code using LLMs.
|
|
256
|
+
|
|
257
|
+
INPUT_STRING: The code description to generate VTK code for
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
# Set default base URLs
|
|
261
|
+
if base_url is None:
|
|
262
|
+
base_urls = {
|
|
263
|
+
"anthropic": "https://api.anthropic.com/v1",
|
|
264
|
+
"gemini": "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
265
|
+
"nim": "https://integrate.api.nvidia.com/v1",
|
|
266
|
+
}
|
|
267
|
+
base_url = base_urls.get(provider)
|
|
268
|
+
|
|
269
|
+
# Set default models based on provider
|
|
270
|
+
if model == "gpt-4o":
|
|
271
|
+
default_models = {
|
|
272
|
+
"anthropic": "claude-3-5-sonnet-20241022",
|
|
273
|
+
"gemini": "gemini-1.5-pro",
|
|
274
|
+
"nim": "meta/llama3-70b-instruct",
|
|
275
|
+
}
|
|
276
|
+
model = default_models.get(provider, model)
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
client = VTKPromptClient(
|
|
280
|
+
collection_name=collection,
|
|
281
|
+
database_path=database,
|
|
282
|
+
verbose=verbose,
|
|
283
|
+
)
|
|
284
|
+
generated_code, usage = client.query(
|
|
285
|
+
input_string,
|
|
286
|
+
api_key=token,
|
|
287
|
+
model=model,
|
|
288
|
+
base_url=base_url,
|
|
289
|
+
max_tokens=max_tokens,
|
|
290
|
+
temperature=temperature,
|
|
291
|
+
top_k=top_k,
|
|
292
|
+
rag=rag,
|
|
293
|
+
retry_attempts=retry_attempts,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if verbose and usage is not None:
|
|
297
|
+
print(
|
|
298
|
+
f"Used tokens: input={usage.prompt_tokens} output={usage.completion_tokens}"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
client.run_code(generated_code)
|
|
302
|
+
|
|
303
|
+
except ValueError as e:
|
|
304
|
+
if "RAG components" in str(e):
|
|
305
|
+
print("rag_components not found", file=sys.stderr)
|
|
306
|
+
sys.exit(1)
|
|
307
|
+
elif "Failed to load RAG snippets" in str(e):
|
|
308
|
+
print("failed to load rag snippets", file=sys.stderr)
|
|
309
|
+
sys.exit(2)
|
|
310
|
+
|
|
311
|
+
elif "max_tokens" in str(e):
|
|
312
|
+
print(f"\nError: {e}", file=sys.stderr)
|
|
313
|
+
print(f"Current max_tokens: {max_tokens}", file=sys.stderr)
|
|
314
|
+
print("Try increasing with: --max-tokens <higher_number>", file=sys.stderr)
|
|
315
|
+
sys.exit(3)
|
|
316
|
+
|
|
317
|
+
else:
|
|
318
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
319
|
+
sys.exit(4)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
if __name__ == "__main__":
|
|
323
|
+
main()
|