ara-cli 0.1.9.77__py3-none-any.whl → 0.1.10.8__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.
Potentially problematic release.
This version of ara-cli might be problematic. Click here for more details.
- ara_cli/__init__.py +18 -2
- ara_cli/__main__.py +245 -66
- ara_cli/ara_command_action.py +128 -63
- ara_cli/ara_config.py +201 -177
- ara_cli/ara_subcommands/__init__.py +0 -0
- ara_cli/ara_subcommands/autofix.py +26 -0
- ara_cli/ara_subcommands/chat.py +27 -0
- ara_cli/ara_subcommands/classifier_directory.py +16 -0
- ara_cli/ara_subcommands/common.py +100 -0
- ara_cli/ara_subcommands/create.py +75 -0
- ara_cli/ara_subcommands/delete.py +22 -0
- ara_cli/ara_subcommands/extract.py +22 -0
- ara_cli/ara_subcommands/fetch_templates.py +14 -0
- ara_cli/ara_subcommands/list.py +65 -0
- ara_cli/ara_subcommands/list_tags.py +25 -0
- ara_cli/ara_subcommands/load.py +48 -0
- ara_cli/ara_subcommands/prompt.py +136 -0
- ara_cli/ara_subcommands/read.py +47 -0
- ara_cli/ara_subcommands/read_status.py +20 -0
- ara_cli/ara_subcommands/read_user.py +20 -0
- ara_cli/ara_subcommands/reconnect.py +27 -0
- ara_cli/ara_subcommands/rename.py +22 -0
- ara_cli/ara_subcommands/scan.py +14 -0
- ara_cli/ara_subcommands/set_status.py +22 -0
- ara_cli/ara_subcommands/set_user.py +22 -0
- ara_cli/ara_subcommands/template.py +16 -0
- ara_cli/artefact_autofix.py +214 -28
- ara_cli/artefact_creator.py +5 -8
- ara_cli/artefact_deleter.py +2 -4
- ara_cli/artefact_fuzzy_search.py +13 -6
- ara_cli/artefact_lister.py +29 -55
- ara_cli/artefact_models/artefact_data_retrieval.py +23 -0
- ara_cli/artefact_models/artefact_model.py +106 -25
- ara_cli/artefact_models/artefact_templates.py +23 -13
- ara_cli/artefact_models/epic_artefact_model.py +11 -2
- ara_cli/artefact_models/feature_artefact_model.py +56 -1
- ara_cli/artefact_models/userstory_artefact_model.py +15 -3
- ara_cli/artefact_reader.py +4 -5
- ara_cli/artefact_renamer.py +6 -2
- ara_cli/artefact_scan.py +2 -2
- ara_cli/chat.py +594 -219
- ara_cli/chat_agent/__init__.py +0 -0
- ara_cli/chat_agent/agent_communicator.py +62 -0
- ara_cli/chat_agent/agent_process_manager.py +211 -0
- ara_cli/chat_agent/agent_status_manager.py +73 -0
- ara_cli/chat_agent/agent_workspace_manager.py +76 -0
- ara_cli/commands/__init__.py +0 -0
- ara_cli/commands/command.py +7 -0
- ara_cli/commands/extract_command.py +15 -0
- ara_cli/commands/load_command.py +65 -0
- ara_cli/commands/load_image_command.py +34 -0
- ara_cli/commands/read_command.py +117 -0
- ara_cli/completers.py +144 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/error_handler.py +134 -0
- ara_cli/file_classifier.py +3 -2
- ara_cli/file_loaders/__init__.py +0 -0
- ara_cli/file_loaders/binary_file_loader.py +33 -0
- ara_cli/file_loaders/document_file_loader.py +34 -0
- ara_cli/file_loaders/document_reader.py +245 -0
- ara_cli/file_loaders/document_readers.py +233 -0
- ara_cli/file_loaders/file_loader.py +50 -0
- ara_cli/file_loaders/file_loaders.py +123 -0
- ara_cli/file_loaders/image_processor.py +89 -0
- ara_cli/file_loaders/markdown_reader.py +75 -0
- ara_cli/file_loaders/text_file_loader.py +187 -0
- ara_cli/global_file_lister.py +51 -0
- ara_cli/prompt_extractor.py +214 -87
- ara_cli/prompt_handler.py +508 -146
- ara_cli/tag_extractor.py +54 -24
- ara_cli/template_loader.py +245 -0
- ara_cli/template_manager.py +14 -4
- ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
- ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
- ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
- ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
- ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
- ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
- ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
- ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
- ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
- ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
- ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
- ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
- ara_cli/update_config_prompt.py +7 -1
- ara_cli/version.py +1 -1
- ara_cli-0.1.10.8.dist-info/METADATA +241 -0
- {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/RECORD +104 -59
- tests/test_ara_command_action.py +66 -52
- tests/test_ara_config.py +200 -279
- tests/test_artefact_autofix.py +361 -5
- tests/test_artefact_lister.py +52 -132
- tests/test_artefact_scan.py +1 -1
- tests/test_chat.py +2009 -603
- tests/test_file_classifier.py +23 -0
- tests/test_file_creator.py +3 -5
- tests/test_global_file_lister.py +131 -0
- tests/test_prompt_handler.py +746 -0
- tests/test_tag_extractor.py +19 -13
- tests/test_template_loader.py +192 -0
- tests/test_template_manager.py +5 -4
- ara_cli/ara_command_parser.py +0 -536
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
- ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
- ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
- ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
- ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
- ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
- ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
- ara_cli-0.1.9.77.dist-info/METADATA +0 -18
- {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/top_level.txt +0 -0
ara_cli/prompt_handler.py
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import base64
|
|
2
|
-
import litellm
|
|
3
|
-
from ara_cli.classifier import Classifier
|
|
4
|
-
from ara_cli.artefact_creator import ArtefactCreator
|
|
5
|
-
from ara_cli.template_manager import TemplatePathManager
|
|
6
|
-
from ara_cli.ara_config import ConfigManager, LLMConfigItem
|
|
7
|
-
from ara_cli.file_lister import generate_markdown_listing
|
|
8
2
|
from os.path import exists, join
|
|
9
3
|
import os
|
|
10
4
|
from os import makedirs
|
|
@@ -13,109 +7,350 @@ import re
|
|
|
13
7
|
import shutil
|
|
14
8
|
import glob
|
|
15
9
|
import logging
|
|
10
|
+
import warnings
|
|
11
|
+
from io import StringIO
|
|
12
|
+
from contextlib import redirect_stderr
|
|
13
|
+
from langfuse import Langfuse
|
|
14
|
+
from langfuse.api.resources.commons.errors import Error as LangfuseError, NotFoundError
|
|
15
|
+
import litellm
|
|
16
|
+
from ara_cli.classifier import Classifier
|
|
17
|
+
from ara_cli.artefact_creator import ArtefactCreator
|
|
18
|
+
from ara_cli.template_manager import TemplatePathManager
|
|
19
|
+
from ara_cli.ara_config import ConfigManager
|
|
20
|
+
from ara_cli.file_lister import generate_markdown_listing
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
class LLMSingleton:
|
|
19
24
|
_instance = None
|
|
20
|
-
|
|
25
|
+
_default_model = None
|
|
26
|
+
_extraction_model = None
|
|
27
|
+
langfuse = None
|
|
21
28
|
|
|
22
|
-
def __init__(self,
|
|
29
|
+
def __init__(self, default_model_id, extraction_model_id):
|
|
23
30
|
config = ConfigManager().get_config()
|
|
24
|
-
|
|
31
|
+
default_config_data = config.llm_config.get(str(default_model_id))
|
|
25
32
|
|
|
26
|
-
if not
|
|
27
|
-
raise ValueError(
|
|
33
|
+
if not default_config_data:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
f"No configuration found for the default model: {default_model_id}"
|
|
36
|
+
)
|
|
37
|
+
self.default_config_params = default_config_data.model_dump(exclude_none=True)
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
extraction_config_data = config.llm_config.get(str(extraction_model_id))
|
|
40
|
+
if not extraction_config_data:
|
|
41
|
+
raise ValueError(
|
|
42
|
+
f"No configuration found for the extraction model: {extraction_model_id}"
|
|
43
|
+
)
|
|
44
|
+
self.extraction_config_params = extraction_config_data.model_dump(
|
|
45
|
+
exclude_none=True
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
langfuse_public_key = os.getenv("ARA_CLI_LANGFUSE_PUBLIC_KEY")
|
|
49
|
+
langfuse_secret_key = os.getenv("ARA_CLI_LANGFUSE_SECRET_KEY")
|
|
50
|
+
langfuse_host = os.getenv("LANGFUSE_HOST")
|
|
51
|
+
|
|
52
|
+
captured_stderr = StringIO()
|
|
53
|
+
with redirect_stderr(captured_stderr):
|
|
54
|
+
self.langfuse = Langfuse(
|
|
55
|
+
public_key=langfuse_public_key,
|
|
56
|
+
secret_key=langfuse_secret_key,
|
|
57
|
+
host=langfuse_host,
|
|
58
|
+
)
|
|
33
59
|
|
|
60
|
+
# Check if there was an authentication error
|
|
61
|
+
stderr_output = captured_stderr.getvalue()
|
|
62
|
+
if "Authentication error" in stderr_output:
|
|
63
|
+
warnings.warn(
|
|
64
|
+
"Invalid Langfuse credentials - prompt tracing disabled and using default prompts. "
|
|
65
|
+
"Set environment variables 'ARA_CLI_LANGFUSE_PUBLIC_KEY', 'ARA_CLI_LANGFUSE_SECRET_KEY', "
|
|
66
|
+
"'LANGFUSE_HOST' and restart application to use Langfuse capabilities",
|
|
67
|
+
UserWarning,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
LLMSingleton._default_model = default_model_id
|
|
71
|
+
LLMSingleton._extraction_model = extraction_model_id
|
|
34
72
|
LLMSingleton._instance = self
|
|
35
73
|
|
|
36
74
|
@classmethod
|
|
37
75
|
def get_instance(cls):
|
|
38
76
|
if cls._instance is None:
|
|
39
77
|
config = ConfigManager().get_config()
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
78
|
+
default_model = config.default_llm
|
|
79
|
+
if not default_model:
|
|
80
|
+
if not config.llm_config:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
"No LLM configurations are defined in the configuration file."
|
|
83
|
+
)
|
|
84
|
+
default_model = next(iter(config.llm_config))
|
|
85
|
+
|
|
86
|
+
extraction_model = getattr(config, "extraction_llm", default_model)
|
|
87
|
+
if not extraction_model:
|
|
88
|
+
extraction_model = default_model
|
|
89
|
+
|
|
90
|
+
cls(default_model, extraction_model)
|
|
91
|
+
return cls._instance
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def get_config_by_purpose(cls, purpose="default"):
|
|
95
|
+
"""
|
|
96
|
+
purpose= 'default' or 'extraction'
|
|
97
|
+
"""
|
|
98
|
+
instance = cls.get_instance()
|
|
99
|
+
if purpose == "extraction":
|
|
100
|
+
return instance.extraction_config_params.copy()
|
|
101
|
+
return instance.default_config_params.copy()
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def set_default_model(cls, model_name):
|
|
105
|
+
"""Sets the default language model for the current session."""
|
|
106
|
+
cls.get_instance()
|
|
107
|
+
if model_name == cls._default_model:
|
|
108
|
+
return cls._instance
|
|
109
|
+
cls(model_name, cls._extraction_model)
|
|
46
110
|
return cls._instance
|
|
47
111
|
|
|
48
112
|
@classmethod
|
|
49
|
-
def
|
|
50
|
-
|
|
113
|
+
def set_extraction_model(cls, model_name):
|
|
114
|
+
"""Sets the extraction language model for the current session."""
|
|
115
|
+
cls.get_instance()
|
|
116
|
+
if model_name == cls._extraction_model:
|
|
51
117
|
return cls._instance
|
|
52
|
-
cls(model_name)
|
|
53
|
-
print(f"Language model switched to '{model_name}'")
|
|
118
|
+
cls(cls._default_model, model_name)
|
|
54
119
|
return cls._instance
|
|
55
120
|
|
|
56
121
|
@classmethod
|
|
57
|
-
def
|
|
122
|
+
def get_default_model(cls):
|
|
123
|
+
"""Gets the default model name stored in the singleton instance."""
|
|
124
|
+
if cls._instance is None:
|
|
125
|
+
cls.get_instance()
|
|
126
|
+
return cls._default_model
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def get_extraction_model(cls):
|
|
130
|
+
"""Gets the extraction model name stored in the singleton instance."""
|
|
58
131
|
if cls._instance is None:
|
|
59
132
|
cls.get_instance()
|
|
60
|
-
return cls.
|
|
133
|
+
return cls._extraction_model
|
|
61
134
|
|
|
62
135
|
|
|
63
136
|
def write_string_to_file(filename, string, mode):
|
|
64
|
-
with open(filename, mode, encoding=
|
|
137
|
+
with open(filename, mode, encoding="utf-8") as file:
|
|
65
138
|
file.write(f"\n{string}\n")
|
|
66
139
|
return file
|
|
67
140
|
|
|
68
141
|
|
|
69
142
|
def read_string_from_file(path):
|
|
70
|
-
with open(path,
|
|
143
|
+
with open(path, "r", encoding="utf-8") as file:
|
|
71
144
|
text = file.read()
|
|
72
145
|
return text
|
|
73
146
|
|
|
74
147
|
|
|
75
|
-
def
|
|
76
|
-
|
|
148
|
+
def _is_valid_message(message: dict) -> bool:
|
|
149
|
+
"""
|
|
150
|
+
Checks if a message in a prompt is valid (i.e., not empty).
|
|
151
|
+
It handles both string content and list content (for multimodal inputs).
|
|
152
|
+
"""
|
|
153
|
+
content = message.get("content")
|
|
154
|
+
|
|
155
|
+
if isinstance(content, str):
|
|
156
|
+
return content.strip() != ""
|
|
157
|
+
|
|
158
|
+
if isinstance(content, list):
|
|
159
|
+
# For multimodal content, check if there's at least one non-empty text part.
|
|
160
|
+
return any(
|
|
161
|
+
item.get("type") == "text" and item.get("text", "").strip() != ""
|
|
162
|
+
for item in content
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return False
|
|
166
|
+
|
|
77
167
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
168
|
+
def _norm(p: str) -> str:
|
|
169
|
+
"""Normalize slashes and collapse .. segments."""
|
|
170
|
+
return os.path.normpath(p) if p else p
|
|
81
171
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
172
|
+
|
|
173
|
+
def resolve_existing_path(rel_or_abs_path: str, anchor_dir: str) -> str:
|
|
174
|
+
"""
|
|
175
|
+
Resolve a potentially relative path to an existing absolute path.
|
|
176
|
+
|
|
177
|
+
Strategy:
|
|
178
|
+
- If already absolute and exists -> return it.
|
|
179
|
+
- Else, try from the anchor_dir.
|
|
180
|
+
- Else, walk up parent directories from anchor_dir and try joining at each level.
|
|
181
|
+
- If nothing is found, return the normalized original (will fail later with clear message).
|
|
182
|
+
"""
|
|
183
|
+
if not rel_or_abs_path:
|
|
184
|
+
return rel_or_abs_path
|
|
185
|
+
|
|
186
|
+
candidate = _norm(rel_or_abs_path)
|
|
187
|
+
|
|
188
|
+
if os.path.isabs(candidate) and os.path.exists(candidate):
|
|
189
|
+
return candidate
|
|
190
|
+
|
|
191
|
+
anchor_dir = os.path.abspath(anchor_dir or os.getcwd())
|
|
192
|
+
|
|
193
|
+
# Try from anchor dir directly
|
|
194
|
+
direct = _norm(os.path.join(anchor_dir, candidate))
|
|
195
|
+
if os.path.exists(direct):
|
|
196
|
+
return direct
|
|
197
|
+
|
|
198
|
+
# Walk parents
|
|
199
|
+
cur = anchor_dir
|
|
200
|
+
prev = None
|
|
201
|
+
while cur and cur != prev:
|
|
202
|
+
test = _norm(os.path.join(cur, candidate))
|
|
203
|
+
if os.path.exists(test):
|
|
204
|
+
return test
|
|
205
|
+
prev = cur
|
|
206
|
+
cur = os.path.dirname(cur)
|
|
207
|
+
|
|
208
|
+
# Give back normalized candidate; open() will raise, but at least path is clean
|
|
209
|
+
return candidate
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def send_prompt(prompt, purpose="default"):
|
|
213
|
+
"""Prepares and sends a prompt to the LLM, streaming the response."""
|
|
214
|
+
chat_instance = LLMSingleton.get_instance()
|
|
215
|
+
config_parameters = chat_instance.get_config_by_purpose(purpose)
|
|
216
|
+
model_info = config_parameters.get("model", "unknown_model")
|
|
217
|
+
|
|
218
|
+
with LLMSingleton.get_instance().langfuse.start_as_current_span(
|
|
219
|
+
name="send_prompt"
|
|
220
|
+
) as span:
|
|
221
|
+
span.update_trace(
|
|
222
|
+
input={"prompt": prompt, "purpose": purpose, "model": model_info}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
config_parameters.pop("provider", None)
|
|
226
|
+
|
|
227
|
+
filtered_prompt = [msg for msg in prompt if _is_valid_message(msg)]
|
|
228
|
+
|
|
229
|
+
completion = litellm.completion(
|
|
230
|
+
**config_parameters, messages=filtered_prompt, stream=True
|
|
231
|
+
)
|
|
232
|
+
response_text = ""
|
|
233
|
+
try:
|
|
234
|
+
for chunk in completion:
|
|
235
|
+
chunk_content = chunk.choices[0].delta.content
|
|
236
|
+
if chunk_content:
|
|
237
|
+
response_text += chunk_content
|
|
238
|
+
yield chunk
|
|
239
|
+
|
|
240
|
+
# Update Langfuse span with success output
|
|
241
|
+
span.update(
|
|
242
|
+
output={
|
|
243
|
+
"success": True,
|
|
244
|
+
"response_length": len(response_text),
|
|
245
|
+
"response": response_text,
|
|
246
|
+
}
|
|
90
247
|
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
# Update Langfuse span with error details
|
|
251
|
+
span.update(output={"error": str(e)}, level="ERROR")
|
|
252
|
+
raise
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def describe_image(image_path: str) -> str:
|
|
256
|
+
"""
|
|
257
|
+
Send an image to the LLM and get a text description.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
image_path: Path to the image file
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Text description of the image
|
|
264
|
+
"""
|
|
265
|
+
with LLMSingleton.get_instance().langfuse.start_as_current_span(
|
|
266
|
+
name="ara-cli/describe-image"
|
|
267
|
+
) as span:
|
|
268
|
+
span.update_trace(input={"image_path": image_path})
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
langfuse_prompt = LLMSingleton.get_instance().langfuse.get_prompt(
|
|
272
|
+
"ara-cli/describe-image"
|
|
273
|
+
)
|
|
274
|
+
describe_image_prompt = (
|
|
275
|
+
langfuse_prompt.prompt if langfuse_prompt.prompt else None
|
|
276
|
+
)
|
|
277
|
+
except (LangfuseError, NotFoundError, Exception) as e:
|
|
278
|
+
logging.info(f"Could not fetch Langfuse prompt: {e}")
|
|
279
|
+
describe_image_prompt = None
|
|
280
|
+
|
|
281
|
+
# Fallback to default prompt if Langfuse prompt is not available
|
|
282
|
+
if not describe_image_prompt:
|
|
283
|
+
logging.info("Using default describe-image prompt.")
|
|
284
|
+
describe_image_prompt = (
|
|
285
|
+
"Please describe this image in detail. If it contains text, transcribe it exactly. "
|
|
286
|
+
"If it's a diagram or chart, explain its structure and content. If it's a photo or illustration, "
|
|
287
|
+
"describe what you see."
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Resolve and read the image
|
|
291
|
+
resolved_image_path = resolve_existing_path(image_path, os.getcwd())
|
|
292
|
+
with open(resolved_image_path, "rb") as image_file:
|
|
293
|
+
base64_image = base64.b64encode(image_file.read()).decode("utf-8")
|
|
294
|
+
|
|
295
|
+
# Determine image type
|
|
296
|
+
image_extension = os.path.splitext(resolved_image_path)[1].lower()
|
|
297
|
+
mime_type = {
|
|
298
|
+
".png": "image/png",
|
|
299
|
+
".jpg": "image/jpeg",
|
|
300
|
+
".jpeg": "image/jpeg",
|
|
301
|
+
".gif": "image/gif",
|
|
302
|
+
".bmp": "image/bmp",
|
|
303
|
+
}.get(image_extension, "image/png")
|
|
304
|
+
|
|
305
|
+
# Create message with image
|
|
306
|
+
message = {
|
|
307
|
+
"role": "user",
|
|
308
|
+
"content": [
|
|
309
|
+
{
|
|
310
|
+
"type": "text",
|
|
311
|
+
"text": describe_image_prompt,
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"type": "image_url",
|
|
315
|
+
"image_url": {"url": f"data:{mime_type};base64,{base64_image}"},
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
# Get response from LLM using the extraction model purpose
|
|
321
|
+
response_text = ""
|
|
322
|
+
for chunk in send_prompt([message], purpose="extraction"):
|
|
323
|
+
chunk_content = chunk.choices[0].delta.content
|
|
324
|
+
if chunk_content:
|
|
325
|
+
response_text += chunk_content
|
|
326
|
+
|
|
327
|
+
response_text = response_text.strip()
|
|
328
|
+
|
|
329
|
+
span.update(
|
|
330
|
+
output={
|
|
331
|
+
"success": True,
|
|
332
|
+
"description_length": len(response_text),
|
|
333
|
+
"response": response_text,
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
return response_text
|
|
105
338
|
|
|
106
339
|
|
|
107
340
|
def append_headings(classifier, param, heading_name):
|
|
108
341
|
sub_directory = Classifier.get_sub_directory(classifier)
|
|
109
342
|
|
|
110
|
-
artefact_data_path =
|
|
343
|
+
artefact_data_path = _norm(
|
|
344
|
+
f"ara/{sub_directory}/{param}.data/{classifier}.prompt_log.md"
|
|
345
|
+
)
|
|
111
346
|
|
|
112
347
|
# Check if the file exists, and if not, create an empty file
|
|
113
348
|
if not os.path.exists(artefact_data_path):
|
|
114
|
-
with open(artefact_data_path,
|
|
349
|
+
with open(artefact_data_path, "w", encoding="utf-8") as file:
|
|
115
350
|
file.write("")
|
|
116
351
|
|
|
117
352
|
content = read_string_from_file(artefact_data_path)
|
|
118
|
-
pattern = r
|
|
353
|
+
pattern = r"## {}_(\d+)".format(heading_name)
|
|
119
354
|
matches = findall(pattern, content)
|
|
120
355
|
|
|
121
356
|
max_number = 1
|
|
@@ -123,27 +358,27 @@ def append_headings(classifier, param, heading_name):
|
|
|
123
358
|
max_number = max(map(int, matches)) + 1
|
|
124
359
|
heading = f"## {heading_name}_{max_number}"
|
|
125
360
|
|
|
126
|
-
write_string_to_file(artefact_data_path, heading,
|
|
361
|
+
write_string_to_file(artefact_data_path, heading, "a")
|
|
127
362
|
|
|
128
363
|
|
|
129
364
|
def write_prompt_result(classifier, param, text):
|
|
130
365
|
sub_directory = Classifier.get_sub_directory(classifier)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
write_string_to_file(artefact_data_path, text,
|
|
366
|
+
artefact_data_path = _norm(
|
|
367
|
+
f"ara/{sub_directory}/{param}.data/{classifier}.prompt_log.md"
|
|
368
|
+
)
|
|
369
|
+
write_string_to_file(artefact_data_path, text, "a")
|
|
135
370
|
|
|
136
371
|
|
|
137
372
|
def prompt_data_directory_creation(classifier, parameter):
|
|
138
373
|
sub_directory = Classifier.get_sub_directory(classifier)
|
|
139
|
-
prompt_data_path = f"ara/{sub_directory}/{parameter}.data/prompt.data"
|
|
374
|
+
prompt_data_path = _norm(f"ara/{sub_directory}/{parameter}.data/prompt.data")
|
|
140
375
|
if not exists(prompt_data_path):
|
|
141
376
|
makedirs(prompt_data_path)
|
|
142
377
|
return prompt_data_path
|
|
143
378
|
|
|
144
379
|
|
|
145
380
|
def get_file_content(path):
|
|
146
|
-
with open(path,
|
|
381
|
+
with open(path, "r", encoding="utf-8") as file:
|
|
147
382
|
return file.read()
|
|
148
383
|
|
|
149
384
|
|
|
@@ -151,14 +386,25 @@ def initialize_prompt_templates(classifier, parameter):
|
|
|
151
386
|
prompt_data_path = prompt_data_directory_creation(classifier, parameter)
|
|
152
387
|
prompt_log_path = os.path.dirname(prompt_data_path)
|
|
153
388
|
|
|
154
|
-
template_path = os.path.join(os.path.dirname(__file__),
|
|
389
|
+
template_path = os.path.join(os.path.dirname(__file__), "templates")
|
|
155
390
|
artefact_creator = ArtefactCreator()
|
|
156
|
-
artefact_creator.create_artefact_prompt_files(
|
|
391
|
+
artefact_creator.create_artefact_prompt_files(
|
|
392
|
+
prompt_log_path, template_path, classifier
|
|
393
|
+
)
|
|
157
394
|
|
|
158
395
|
generate_config_prompt_template_file(prompt_data_path, "config.prompt_templates.md")
|
|
159
396
|
|
|
160
397
|
# Mark the relevant artefact in the givens list
|
|
161
|
-
generate_config_prompt_givens_file(
|
|
398
|
+
generate_config_prompt_givens_file(
|
|
399
|
+
prompt_data_path,
|
|
400
|
+
"config.prompt_givens.md",
|
|
401
|
+
artefact_to_mark=f"{parameter}.{classifier}",
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Only once (was duplicated before)
|
|
405
|
+
generate_config_prompt_global_givens_file(
|
|
406
|
+
prompt_data_path, "config.prompt_global_givens.md"
|
|
407
|
+
)
|
|
162
408
|
|
|
163
409
|
|
|
164
410
|
def write_template_files_to_config(template_type, config_file, base_template_path):
|
|
@@ -170,14 +416,14 @@ def write_template_files_to_config(template_type, config_file, base_template_pat
|
|
|
170
416
|
|
|
171
417
|
def load_selected_prompt_templates(classifier, parameter):
|
|
172
418
|
sub_directory = Classifier.get_sub_directory(classifier)
|
|
173
|
-
prompt_data_path = f"ara/{sub_directory}/{parameter}.data/prompt.data"
|
|
419
|
+
prompt_data_path = _norm(f"ara/{sub_directory}/{parameter}.data/prompt.data")
|
|
174
420
|
config_file_path = os.path.join(prompt_data_path, "config.prompt_templates.md")
|
|
175
421
|
|
|
176
422
|
if not os.path.exists(config_file_path):
|
|
177
423
|
print("WARNING: config.prompt_templates.md does not exist.")
|
|
178
424
|
return
|
|
179
425
|
|
|
180
|
-
with open(config_file_path,
|
|
426
|
+
with open(config_file_path, "r", encoding="utf-8") as config_file:
|
|
181
427
|
content = config_file.read()
|
|
182
428
|
|
|
183
429
|
global_base_template_path = TemplatePathManager.get_template_base_path()
|
|
@@ -213,7 +459,9 @@ def find_files_with_endings(directory, endings):
|
|
|
213
459
|
# Create an empty dictionary to store files according to their endings
|
|
214
460
|
files_by_ending = {ending: [] for ending in endings}
|
|
215
461
|
|
|
216
|
-
files = [
|
|
462
|
+
files = [
|
|
463
|
+
f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))
|
|
464
|
+
]
|
|
217
465
|
# Walk through the files list
|
|
218
466
|
for file in files:
|
|
219
467
|
# Check each file to see if it ends with one of the specified endings
|
|
@@ -248,75 +496,114 @@ def move_and_copy_files(source_path, prompt_data_path, prompt_archive_path):
|
|
|
248
496
|
|
|
249
497
|
# Move all existing files with the same ending to the prompt_archive_path
|
|
250
498
|
for existing_file in glob.glob(glob_pattern):
|
|
251
|
-
archived_file_path = os.path.join(
|
|
499
|
+
archived_file_path = os.path.join(
|
|
500
|
+
prompt_archive_path, os.path.basename(existing_file)
|
|
501
|
+
)
|
|
252
502
|
shutil.move(existing_file, archived_file_path)
|
|
253
|
-
print(
|
|
254
|
-
|
|
503
|
+
print(
|
|
504
|
+
f"Moved existing prompt-module: {os.path.basename(existing_file)} to prompt.archive"
|
|
505
|
+
)
|
|
506
|
+
|
|
255
507
|
# Copy the source_path file to the prompt_data_path directory
|
|
256
508
|
target_path = os.path.join(prompt_data_path, file_name)
|
|
257
509
|
shutil.copy(source_path, target_path)
|
|
258
510
|
print(f"Loaded new prompt-module: {os.path.basename(target_path)}")
|
|
259
511
|
|
|
260
512
|
else:
|
|
261
|
-
print(
|
|
513
|
+
print(
|
|
514
|
+
f"File name {file_name} does not end with one of the specified patterns, skipping move and copy."
|
|
515
|
+
)
|
|
262
516
|
else:
|
|
263
517
|
print(f"WARNING: template {source_path} does not exist.")
|
|
264
518
|
|
|
265
519
|
|
|
266
520
|
def extract_and_load_markdown_files(md_prompt_file_path):
|
|
267
521
|
"""
|
|
268
|
-
Extracts markdown files paths based on checked items and constructs proper paths
|
|
522
|
+
Extracts markdown files paths based on checked items and constructs proper paths
|
|
523
|
+
respecting markdown header hierarchy. **Returns normalized relative paths**
|
|
524
|
+
(not resolved), and resolution happens later relative to the config file dir.
|
|
269
525
|
"""
|
|
270
526
|
header_stack = []
|
|
271
527
|
path_accumulator = []
|
|
272
|
-
with open(md_prompt_file_path,
|
|
528
|
+
with open(md_prompt_file_path, "r", encoding="utf-8") as file:
|
|
273
529
|
for line in file:
|
|
274
|
-
if line.strip().startswith(
|
|
275
|
-
level = line.count(
|
|
276
|
-
header = line.strip().strip(
|
|
530
|
+
if line.strip().startswith("#"):
|
|
531
|
+
level = line.count("#")
|
|
532
|
+
header = line.strip().strip("#").strip()
|
|
277
533
|
# Adjust the stack based on the current header level
|
|
278
534
|
current_depth = len(header_stack)
|
|
279
535
|
if level <= current_depth:
|
|
280
|
-
header_stack = header_stack[:level-1]
|
|
536
|
+
header_stack = header_stack[: level - 1]
|
|
281
537
|
header_stack.append(header)
|
|
282
|
-
elif
|
|
283
|
-
relative_path = line.split(
|
|
284
|
-
|
|
285
|
-
|
|
538
|
+
elif "[x]" in line:
|
|
539
|
+
relative_path = line.split("]")[-1].strip()
|
|
540
|
+
# Use os.path.join for OS-safe joining, then normalize
|
|
541
|
+
full_rel_path = os.path.join(*header_stack, relative_path) if header_stack else relative_path
|
|
542
|
+
path_accumulator.append(_norm(full_rel_path))
|
|
286
543
|
return path_accumulator
|
|
287
544
|
|
|
288
545
|
|
|
289
546
|
def load_givens(file_path):
|
|
290
|
-
|
|
547
|
+
"""
|
|
548
|
+
Reads marked givens from a config markdown and returns:
|
|
549
|
+
- combined markdown content (including code fences / images)
|
|
550
|
+
- a list of image data dicts for the multimodal message
|
|
551
|
+
Paths inside the markdown are resolved robustly relative to the config file directory (and its parents).
|
|
552
|
+
"""
|
|
553
|
+
content = ""
|
|
291
554
|
image_data_list = []
|
|
292
555
|
markdown_items = extract_and_load_markdown_files(file_path)
|
|
293
556
|
|
|
557
|
+
if not markdown_items:
|
|
558
|
+
return "", []
|
|
559
|
+
|
|
560
|
+
content = "### GIVENS\n\n"
|
|
561
|
+
|
|
562
|
+
anchor_dir = os.path.dirname(os.path.abspath(file_path))
|
|
563
|
+
|
|
294
564
|
for item in markdown_items:
|
|
295
|
-
|
|
296
|
-
|
|
565
|
+
resolved = resolve_existing_path(item, anchor_dir)
|
|
566
|
+
# Keep the listing line readable, show the original relative item
|
|
567
|
+
content += item + "\n"
|
|
568
|
+
|
|
569
|
+
ext = os.path.splitext(resolved)[1].lower()
|
|
570
|
+
|
|
571
|
+
# Image branch
|
|
572
|
+
if ext in (".png", ".jpeg", ".jpg", ".gif", ".bmp"):
|
|
573
|
+
with open(resolved, "rb") as image_file:
|
|
297
574
|
base64_image = base64.b64encode(image_file.read()).decode("utf-8")
|
|
298
|
-
image_data_list.append({"type": "image_url", "image_url": {"url": f"data:image/png;base64,{base64_image}"}})
|
|
299
|
-
content += item + "\n"
|
|
300
|
-
content += f'' + "\n"
|
|
301
|
-
else:
|
|
302
|
-
# Check if the item specifies line ranges
|
|
303
|
-
# TODO item has currently no trailing [] see extraction and handover method in extract and load
|
|
304
|
-
# item = f"[10:29] {item}"
|
|
305
|
-
# print(f"found {item}, check for subsection")
|
|
306
|
-
# TODO re.match can not split the item with [] correctly and extract the line numbers
|
|
307
|
-
# TODO logic of subsections is not supported by the update algorithm of the config prompt givens updater
|
|
308
|
-
# TODO extract in lines of *.md files potential images and add them to the image list
|
|
309
575
|
|
|
576
|
+
mime_type = {
|
|
577
|
+
".png": "image/png",
|
|
578
|
+
".jpg": "image/jpeg",
|
|
579
|
+
".jpeg": "image/jpeg",
|
|
580
|
+
".gif": "image/gif",
|
|
581
|
+
".bmp": "image/bmp",
|
|
582
|
+
}.get(ext, "image/png")
|
|
583
|
+
|
|
584
|
+
image_data_list.append(
|
|
585
|
+
{
|
|
586
|
+
"type": "image_url",
|
|
587
|
+
"image_url": {"url": f"data:{mime_type};base64,{base64_image}"},
|
|
588
|
+
}
|
|
589
|
+
)
|
|
590
|
+
# Also embed inline for the prompt markdown (use png as a neutral default for data URI)
|
|
591
|
+
content += f"\n"
|
|
592
|
+
|
|
593
|
+
else:
|
|
594
|
+
# Check if the item specifies line ranges: e.g. "[10:20,25:30] filePath"
|
|
310
595
|
match = re.match(r".*?\[(\d+:\d+(?:,\s*\d+:\d+)*)\]\s+(.+)", item)
|
|
311
596
|
if match:
|
|
312
597
|
line_ranges, file_name = match.groups()
|
|
313
|
-
|
|
314
|
-
content +=
|
|
598
|
+
resolved_sub = resolve_existing_path(file_name, anchor_dir)
|
|
599
|
+
content += "```\n"
|
|
600
|
+
content += get_partial_file_content(resolved_sub, line_ranges) + "\n"
|
|
315
601
|
content += "```\n\n"
|
|
316
602
|
else:
|
|
317
|
-
content +=
|
|
318
|
-
content += get_file_content(
|
|
603
|
+
content += "```\n"
|
|
604
|
+
content += get_file_content(resolved) + "\n"
|
|
319
605
|
content += "```\n\n"
|
|
606
|
+
|
|
320
607
|
return content, image_data_list
|
|
321
608
|
|
|
322
609
|
|
|
@@ -325,25 +612,25 @@ def get_partial_file_content(file_name, line_ranges):
|
|
|
325
612
|
Reads specific lines from a file based on the line ranges provided.
|
|
326
613
|
|
|
327
614
|
Args:
|
|
328
|
-
file_name (str): The path to the file.
|
|
615
|
+
file_name (str): The path to the file (absolute or relative, already resolved by caller).
|
|
329
616
|
line_ranges (str): A string representing the line ranges to read, e.g., '10:20,25:30'.
|
|
330
617
|
|
|
331
618
|
Returns:
|
|
332
619
|
str: The content of the specified lines.
|
|
333
620
|
"""
|
|
334
|
-
line_ranges = line_ranges.strip(
|
|
621
|
+
line_ranges = line_ranges.strip("[]").split(",")
|
|
335
622
|
lines_to_read = []
|
|
336
623
|
for line_range in line_ranges:
|
|
337
|
-
start, end = map(int, line_range.split(
|
|
624
|
+
start, end = map(int, line_range.split(":"))
|
|
338
625
|
lines_to_read.extend(range(start, end + 1))
|
|
339
626
|
|
|
340
627
|
partial_content = []
|
|
341
|
-
with open(file_name,
|
|
628
|
+
with open(file_name, "r", encoding="utf-8") as file:
|
|
342
629
|
for i, line in enumerate(file, 1):
|
|
343
630
|
if i in lines_to_read:
|
|
344
631
|
partial_content.append(line)
|
|
345
632
|
|
|
346
|
-
return
|
|
633
|
+
return "".join(partial_content)
|
|
347
634
|
|
|
348
635
|
|
|
349
636
|
def collect_file_content_by_extension(prompt_data_path, extensions):
|
|
@@ -353,7 +640,7 @@ def collect_file_content_by_extension(prompt_data_path, extensions):
|
|
|
353
640
|
files = find_files_with_endings(prompt_data_path, [ext])
|
|
354
641
|
for file_name in files:
|
|
355
642
|
file_path = join(prompt_data_path, file_name)
|
|
356
|
-
if ext
|
|
643
|
+
if ext in [".prompt_givens.md", ".prompt_global_givens.md"]:
|
|
357
644
|
givens, image_data = load_givens(file_path)
|
|
358
645
|
combined_content += givens
|
|
359
646
|
image_data_list.extend(image_data)
|
|
@@ -363,27 +650,53 @@ def collect_file_content_by_extension(prompt_data_path, extensions):
|
|
|
363
650
|
|
|
364
651
|
|
|
365
652
|
def prepend_system_prompt(message_list):
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
653
|
+
try:
|
|
654
|
+
langfuse_prompt = LLMSingleton.get_instance().langfuse.get_prompt(
|
|
655
|
+
"ara-cli/system-prompt"
|
|
656
|
+
)
|
|
657
|
+
system_prompt = langfuse_prompt.prompt if langfuse_prompt.prompt else None
|
|
658
|
+
except (LangfuseError, NotFoundError, Exception) as e:
|
|
659
|
+
logging.info(f"Could not fetch Langfuse system prompt: {e}")
|
|
660
|
+
system_prompt = None
|
|
661
|
+
|
|
662
|
+
# Fallback to default prompt if Langfuse prompt is not available
|
|
663
|
+
if not system_prompt:
|
|
664
|
+
logging.info("Using default system prompt.")
|
|
665
|
+
system_prompt = "You are a helpful assistant that can process both text and images."
|
|
666
|
+
|
|
667
|
+
# Prepend the system prompt
|
|
668
|
+
system_prompt_message = {"role": "system", "content": system_prompt}
|
|
669
|
+
|
|
670
|
+
message_list.insert(0, system_prompt_message)
|
|
371
671
|
return message_list
|
|
372
672
|
|
|
373
673
|
|
|
374
674
|
def append_images_to_message(message, image_data_list):
|
|
675
|
+
"""
|
|
676
|
+
Appends image data list to a single message dict (NOT to a list).
|
|
677
|
+
"""
|
|
375
678
|
logger = logging.getLogger(__name__)
|
|
376
679
|
|
|
377
|
-
logger.debug(
|
|
680
|
+
logger.debug(
|
|
681
|
+
f"append_images_to_message called with image_data_list length: {len(image_data_list) if image_data_list else 0}"
|
|
682
|
+
)
|
|
378
683
|
|
|
379
684
|
if not image_data_list:
|
|
380
685
|
logger.debug("No images to append, returning original message")
|
|
381
686
|
return message
|
|
382
687
|
|
|
383
|
-
message_content = message
|
|
688
|
+
message_content = message.get("content")
|
|
384
689
|
logger.debug(f"Original message content: {message_content}")
|
|
385
690
|
|
|
386
|
-
|
|
691
|
+
if isinstance(message_content, str):
|
|
692
|
+
message["content"] = [{"type": "text", "text": message_content}]
|
|
693
|
+
|
|
694
|
+
if isinstance(message["content"], list):
|
|
695
|
+
message["content"].extend(image_data_list)
|
|
696
|
+
else:
|
|
697
|
+
# If somehow content is not list or str, coerce to list
|
|
698
|
+
message["content"] = [{"type": "text", "text": str(message_content)}] + image_data_list
|
|
699
|
+
|
|
387
700
|
logger.debug(f"Updated message content with {len(image_data_list)} images")
|
|
388
701
|
|
|
389
702
|
return message
|
|
@@ -391,74 +704,123 @@ def append_images_to_message(message, image_data_list):
|
|
|
391
704
|
|
|
392
705
|
def create_and_send_custom_prompt(classifier, parameter):
|
|
393
706
|
sub_directory = Classifier.get_sub_directory(classifier)
|
|
394
|
-
prompt_data_path = f"ara/{sub_directory}/{parameter}.data/prompt.data"
|
|
707
|
+
prompt_data_path = _norm(f"ara/{sub_directory}/{parameter}.data/prompt.data")
|
|
395
708
|
prompt_file_path_markdown = join(prompt_data_path, f"{classifier}.prompt.md")
|
|
396
709
|
|
|
397
|
-
extensions = [
|
|
398
|
-
|
|
710
|
+
extensions = [
|
|
711
|
+
".blueprint.md",
|
|
712
|
+
".rules.md",
|
|
713
|
+
".prompt_givens.md",
|
|
714
|
+
".prompt_global_givens.md",
|
|
715
|
+
".intention.md",
|
|
716
|
+
".commands.md",
|
|
717
|
+
]
|
|
718
|
+
combined_content_markdown, image_data_list = collect_file_content_by_extension(
|
|
719
|
+
prompt_data_path, extensions
|
|
720
|
+
)
|
|
399
721
|
|
|
400
|
-
with open(prompt_file_path_markdown,
|
|
722
|
+
with open(prompt_file_path_markdown, "w", encoding="utf-8") as file:
|
|
401
723
|
file.write(combined_content_markdown)
|
|
402
724
|
|
|
403
725
|
prompt = read_string_from_file(prompt_file_path_markdown)
|
|
404
726
|
append_headings(classifier, parameter, "prompt")
|
|
405
727
|
write_prompt_result(classifier, parameter, prompt)
|
|
406
728
|
|
|
407
|
-
message
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
|
|
729
|
+
# Build message and append images correctly (fixed)
|
|
730
|
+
message = {"role": "user", "content": combined_content_markdown}
|
|
731
|
+
message = append_images_to_message(message, image_data_list)
|
|
412
732
|
message_list = [message]
|
|
413
733
|
|
|
414
|
-
message_list = append_images_to_message(message_list, image_data_list)
|
|
415
734
|
append_headings(classifier, parameter, "result")
|
|
416
735
|
|
|
417
|
-
artefact_data_path =
|
|
418
|
-
|
|
736
|
+
artefact_data_path = _norm(
|
|
737
|
+
f"ara/{sub_directory}/{parameter}.data/{classifier}.prompt_log.md"
|
|
738
|
+
)
|
|
739
|
+
with open(artefact_data_path, "a", encoding="utf-8") as file:
|
|
419
740
|
for chunk in send_prompt(message_list):
|
|
420
741
|
chunk_content = chunk.choices[0].delta.content
|
|
421
742
|
if not chunk_content:
|
|
422
743
|
continue
|
|
423
744
|
file.write(chunk_content)
|
|
424
745
|
file.flush()
|
|
425
|
-
# write_prompt_result(classifier, parameter, response)
|
|
426
746
|
|
|
427
747
|
|
|
428
|
-
def generate_config_prompt_template_file(
|
|
429
|
-
|
|
430
|
-
|
|
748
|
+
def generate_config_prompt_template_file(
|
|
749
|
+
prompt_data_path, config_prompt_templates_name
|
|
750
|
+
):
|
|
751
|
+
config_prompt_templates_path = os.path.join(
|
|
752
|
+
prompt_data_path, config_prompt_templates_name
|
|
753
|
+
)
|
|
754
|
+
# Use instance method consistently
|
|
755
|
+
config = ConfigManager().get_config()
|
|
431
756
|
global_prompt_template_path = TemplatePathManager.get_template_base_path()
|
|
432
|
-
dir_list = ["ara/.araconfig/custom-prompt-modules"] + [
|
|
433
|
-
|
|
757
|
+
dir_list = ["ara/.araconfig/custom-prompt-modules"] + [
|
|
758
|
+
f"{os.path.join(global_prompt_template_path,'prompt-modules')}"
|
|
759
|
+
]
|
|
760
|
+
file_list = ["*.blueprint.md", "*.rules.md", "*.intention.md", "*.commands.md"]
|
|
434
761
|
|
|
435
762
|
print(f"used {dir_list} for prompt templates file listing")
|
|
436
763
|
generate_markdown_listing(dir_list, file_list, config_prompt_templates_path)
|
|
437
764
|
|
|
438
765
|
|
|
439
|
-
def generate_config_prompt_givens_file(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
766
|
+
def generate_config_prompt_givens_file(
|
|
767
|
+
prompt_data_path, config_prompt_givens_name, artefact_to_mark=None
|
|
768
|
+
):
|
|
769
|
+
config_prompt_givens_path = os.path.join(
|
|
770
|
+
prompt_data_path, config_prompt_givens_name
|
|
771
|
+
)
|
|
772
|
+
config = ConfigManager().get_config()
|
|
773
|
+
dir_list = (
|
|
774
|
+
["ara"]
|
|
775
|
+
+ [path for d in config.ext_code_dirs for path in d.values()]
|
|
776
|
+
+ [config.doc_dir]
|
|
777
|
+
+ [config.glossary_dir]
|
|
778
|
+
)
|
|
443
779
|
|
|
444
780
|
print(f"used {dir_list} for prompt givens file listing")
|
|
445
|
-
generate_markdown_listing(
|
|
781
|
+
generate_markdown_listing(
|
|
782
|
+
dir_list, config.ara_prompt_given_list_includes, config_prompt_givens_path
|
|
783
|
+
)
|
|
446
784
|
|
|
447
785
|
# If an artefact is specified, mark it with [x]
|
|
448
786
|
if artefact_to_mark:
|
|
449
|
-
print(
|
|
787
|
+
print(
|
|
788
|
+
f"artefact {artefact_to_mark} marked in related config.prompt_givens.md per default"
|
|
789
|
+
)
|
|
450
790
|
|
|
451
791
|
# Read the generated file content
|
|
452
|
-
with open(config_prompt_givens_path,
|
|
792
|
+
with open(config_prompt_givens_path, "r", encoding="utf-8") as file:
|
|
453
793
|
markdown_listing = file.readlines()
|
|
454
794
|
|
|
455
795
|
updated_listing = []
|
|
456
796
|
for line in markdown_listing:
|
|
457
797
|
# Use a regular expression to match the exact string
|
|
458
|
-
if re.search(r
|
|
798
|
+
if re.search(r"\b" + re.escape(artefact_to_mark) + r"\b", line):
|
|
459
799
|
line = line.replace("[]", "[x]")
|
|
460
800
|
updated_listing.append(line)
|
|
461
801
|
|
|
462
802
|
# Write the updated listing back to the file
|
|
463
|
-
with open(config_prompt_givens_path,
|
|
464
|
-
file.write("".join(updated_listing))
|
|
803
|
+
with open(config_prompt_givens_path, "w", encoding="utf-8") as file:
|
|
804
|
+
file.write("".join(updated_listing))
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def generate_config_prompt_global_givens_file(
|
|
808
|
+
prompt_data_path, config_prompt_givens_name, artefact_to_mark=None
|
|
809
|
+
):
|
|
810
|
+
from ara_cli.global_file_lister import generate_global_markdown_listing
|
|
811
|
+
|
|
812
|
+
config_prompt_givens_path = os.path.join(
|
|
813
|
+
prompt_data_path, config_prompt_givens_name
|
|
814
|
+
)
|
|
815
|
+
config = ConfigManager().get_config()
|
|
816
|
+
|
|
817
|
+
if not hasattr(config, "global_dirs") or not config.global_dirs:
|
|
818
|
+
return
|
|
819
|
+
|
|
820
|
+
dir_list = [path for d in config.global_dirs for path in d.values()]
|
|
821
|
+
print(
|
|
822
|
+
f"used {dir_list} for global prompt givens file listing with absolute paths"
|
|
823
|
+
)
|
|
824
|
+
generate_global_markdown_listing(
|
|
825
|
+
dir_list, config.ara_prompt_given_list_includes, config_prompt_givens_path
|
|
826
|
+
)
|