ara-cli 0.1.10.5__py3-none-any.whl → 0.1.14.0__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.
Files changed (151) hide show
  1. ara_cli/__init__.py +51 -6
  2. ara_cli/__main__.py +87 -75
  3. ara_cli/ara_command_action.py +189 -101
  4. ara_cli/ara_config.py +187 -128
  5. ara_cli/ara_subcommands/common.py +2 -2
  6. ara_cli/ara_subcommands/config.py +221 -0
  7. ara_cli/ara_subcommands/convert.py +107 -0
  8. ara_cli/ara_subcommands/fetch.py +41 -0
  9. ara_cli/ara_subcommands/fetch_agents.py +22 -0
  10. ara_cli/ara_subcommands/fetch_scripts.py +19 -0
  11. ara_cli/ara_subcommands/fetch_templates.py +15 -10
  12. ara_cli/ara_subcommands/list.py +97 -23
  13. ara_cli/ara_subcommands/prompt.py +266 -106
  14. ara_cli/artefact_autofix.py +117 -64
  15. ara_cli/artefact_converter.py +355 -0
  16. ara_cli/artefact_creator.py +41 -17
  17. ara_cli/artefact_lister.py +3 -3
  18. ara_cli/artefact_models/artefact_model.py +1 -1
  19. ara_cli/artefact_models/artefact_templates.py +0 -9
  20. ara_cli/artefact_models/feature_artefact_model.py +8 -8
  21. ara_cli/artefact_reader.py +62 -43
  22. ara_cli/artefact_scan.py +39 -17
  23. ara_cli/chat.py +300 -71
  24. ara_cli/chat_agent/__init__.py +0 -0
  25. ara_cli/chat_agent/agent_process_manager.py +155 -0
  26. ara_cli/chat_script_runner/__init__.py +0 -0
  27. ara_cli/chat_script_runner/script_completer.py +23 -0
  28. ara_cli/chat_script_runner/script_finder.py +41 -0
  29. ara_cli/chat_script_runner/script_lister.py +36 -0
  30. ara_cli/chat_script_runner/script_runner.py +36 -0
  31. ara_cli/chat_web_search/__init__.py +0 -0
  32. ara_cli/chat_web_search/web_search.py +263 -0
  33. ara_cli/children_contribution_updater.py +737 -0
  34. ara_cli/classifier.py +34 -0
  35. ara_cli/commands/agent_run_command.py +98 -0
  36. ara_cli/commands/fetch_agents_command.py +106 -0
  37. ara_cli/commands/fetch_scripts_command.py +43 -0
  38. ara_cli/commands/fetch_templates_command.py +39 -0
  39. ara_cli/commands/fetch_templates_commands.py +39 -0
  40. ara_cli/commands/list_agents_command.py +39 -0
  41. ara_cli/commands/load_command.py +4 -3
  42. ara_cli/commands/load_image_command.py +1 -1
  43. ara_cli/commands/read_command.py +23 -27
  44. ara_cli/completers.py +95 -35
  45. ara_cli/constants.py +2 -0
  46. ara_cli/directory_navigator.py +37 -4
  47. ara_cli/error_handler.py +26 -11
  48. ara_cli/file_loaders/document_reader.py +0 -178
  49. ara_cli/file_loaders/factories/__init__.py +0 -0
  50. ara_cli/file_loaders/factories/document_reader_factory.py +32 -0
  51. ara_cli/file_loaders/factories/file_loader_factory.py +27 -0
  52. ara_cli/file_loaders/file_loader.py +1 -30
  53. ara_cli/file_loaders/loaders/__init__.py +0 -0
  54. ara_cli/file_loaders/{document_file_loader.py → loaders/document_file_loader.py} +1 -1
  55. ara_cli/file_loaders/loaders/text_file_loader.py +47 -0
  56. ara_cli/file_loaders/readers/__init__.py +0 -0
  57. ara_cli/file_loaders/readers/docx_reader.py +49 -0
  58. ara_cli/file_loaders/readers/excel_reader.py +27 -0
  59. ara_cli/file_loaders/{markdown_reader.py → readers/markdown_reader.py} +1 -1
  60. ara_cli/file_loaders/readers/odt_reader.py +59 -0
  61. ara_cli/file_loaders/readers/pdf_reader.py +54 -0
  62. ara_cli/file_loaders/readers/pptx_reader.py +104 -0
  63. ara_cli/file_loaders/tools/__init__.py +0 -0
  64. ara_cli/llm_utils.py +58 -0
  65. ara_cli/output_suppressor.py +53 -0
  66. ara_cli/prompt_chat.py +20 -4
  67. ara_cli/prompt_extractor.py +47 -32
  68. ara_cli/prompt_handler.py +123 -17
  69. ara_cli/tag_extractor.py +8 -7
  70. ara_cli/template_loader.py +2 -1
  71. ara_cli/template_manager.py +52 -21
  72. ara_cli/templates/global-scripts/hello_global.py +1 -0
  73. ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
  74. ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
  75. ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
  76. ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
  77. ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
  78. ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
  79. ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
  80. ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
  81. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  82. ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
  83. ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
  84. ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
  85. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  86. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  87. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  88. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  89. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  90. ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
  91. ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
  92. ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
  93. ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
  94. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  95. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  96. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  97. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  98. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  99. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  100. ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
  101. ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
  102. ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
  103. ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
  104. ara_cli/version.py +1 -1
  105. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +49 -11
  106. ara_cli-0.1.14.0.dist-info/RECORD +253 -0
  107. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
  108. tests/test_ara_command_action.py +31 -19
  109. tests/test_ara_config.py +177 -90
  110. tests/test_artefact_autofix.py +170 -97
  111. tests/test_artefact_autofix_integration.py +495 -0
  112. tests/test_artefact_converter.py +312 -0
  113. tests/test_artefact_extraction.py +564 -0
  114. tests/test_artefact_lister.py +11 -8
  115. tests/test_chat.py +166 -130
  116. tests/test_chat_givens_images.py +603 -0
  117. tests/test_chat_script_runner.py +454 -0
  118. tests/test_children_contribution_updater.py +98 -0
  119. tests/test_document_loader_office.py +267 -0
  120. tests/test_llm_utils.py +164 -0
  121. tests/test_prompt_chat.py +343 -0
  122. tests/test_prompt_extractor.py +683 -0
  123. tests/test_prompt_handler.py +416 -214
  124. tests/test_setup_default_chat_prompt_mode.py +198 -0
  125. tests/test_tag_extractor.py +95 -49
  126. tests/test_web_search.py +467 -0
  127. ara_cli/file_loaders/document_readers.py +0 -233
  128. ara_cli/file_loaders/file_loaders.py +0 -123
  129. ara_cli/file_loaders/text_file_loader.py +0 -187
  130. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  131. ara_cli/templates/prompt-modules/blueprints/pytest_unittest_prompt.blueprint.md +0 -32
  132. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  133. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  134. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  135. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  136. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  137. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  138. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  139. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  140. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  141. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  142. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  143. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  144. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  145. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  146. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  147. ara_cli-0.1.10.5.dist-info/RECORD +0 -194
  148. /ara_cli/file_loaders/{binary_file_loader.py → loaders/binary_file_loader.py} +0 -0
  149. /ara_cli/file_loaders/{image_processor.py → tools/image_processor.py} +0 -0
  150. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
  151. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,47 @@
1
+ import os
2
+ import re
3
+ import base64
4
+ import tempfile
5
+ from typing import Optional, Tuple
6
+ import requests
7
+ from charset_normalizer import from_path
8
+ from ara_cli.file_loaders.file_loader import FileLoader
9
+ from ara_cli.file_loaders.readers.markdown_reader import MarkdownReader
10
+
11
+
12
+ class TextFileLoader(FileLoader):
13
+ """Loads text files"""
14
+
15
+ def load(
16
+ self,
17
+ file_path: str,
18
+ prefix: str = "",
19
+ suffix: str = "",
20
+ block_delimiter: str = "",
21
+ extract_images: bool = False,
22
+ **kwargs,
23
+ ) -> bool:
24
+ """Load text file with optional markdown image extraction"""
25
+
26
+ is_md_file = file_path.lower().endswith(".md")
27
+
28
+ if is_md_file and extract_images:
29
+ reader = MarkdownReader(file_path)
30
+ file_content = reader.read(extract_images=True).replace("\r\n", "\n")
31
+ else:
32
+ # Use charset-normalizer to detect encoding
33
+ encoded_content = from_path(file_path).best()
34
+ if not encoded_content:
35
+ print(f"Failed to detect encoding for {file_path}")
36
+ return False
37
+ file_content = str(encoded_content).replace("\r\n", "\n")
38
+
39
+ if block_delimiter:
40
+ file_content = f"{block_delimiter}\n{file_content}\n{block_delimiter}"
41
+
42
+ write_content = f"{prefix}{file_content}{suffix}\n"
43
+
44
+ with open(self.chat.chat_name, "a", encoding="utf-8") as chat_file:
45
+ chat_file.write(write_content)
46
+
47
+ return True
File without changes
@@ -0,0 +1,49 @@
1
+ from ara_cli.file_loaders.document_reader import DocumentReader
2
+
3
+ class DocxReader(DocumentReader):
4
+ """Reader for DOCX files"""
5
+
6
+ def read(self, extract_images: bool = False) -> str:
7
+ import docx
8
+
9
+ doc = docx.Document(self.file_path)
10
+ text_content = '\n'.join(para.text for para in doc.paragraphs)
11
+
12
+ if not extract_images:
13
+ return text_content
14
+
15
+ from PIL import Image
16
+ import io
17
+
18
+ # Create data directory for images
19
+ images_dir = self.create_image_data_dir("docx")
20
+
21
+ # Extract and process images
22
+ image_descriptions = []
23
+ image_counter = 1
24
+
25
+ for rel in doc.part.rels.values():
26
+ if "image" in rel.reltype:
27
+ image_data = rel.target_part.blob
28
+
29
+ # Determine image format
30
+ image = Image.open(io.BytesIO(image_data))
31
+ image_format = image.format.lower()
32
+
33
+ # Save and describe image
34
+ relative_path, description = self.save_and_describe_image(
35
+ image_data, image_format, images_dir, image_counter
36
+ )
37
+
38
+ # Add formatted description to list
39
+ image_description = f"\nImage: {relative_path}\n[{description}]\n"
40
+ image_descriptions.append(image_description)
41
+
42
+ image_counter += 1
43
+
44
+ # Combine text content with image descriptions
45
+ if image_descriptions:
46
+ text_content += "\n\n### Extracted Images\n" + \
47
+ "\n".join(image_descriptions)
48
+
49
+ return text_content
@@ -0,0 +1,27 @@
1
+ from ara_cli.file_loaders.document_reader import DocumentReader
2
+
3
+ class ExcelReader(DocumentReader):
4
+ """Reader for Excel files"""
5
+
6
+ def read(self, extract_images: bool = False) -> str:
7
+ import pandas as pd
8
+
9
+ try:
10
+ # Read all sheets
11
+ sheets_dict = pd.read_excel(self.file_path, sheet_name=None)
12
+ markdown_output = []
13
+
14
+ for sheet_name, df in sheets_dict.items():
15
+ markdown_output.append(f"### Sheet: {sheet_name}")
16
+ if df.empty:
17
+ markdown_output.append("_Empty Sheet_")
18
+ else:
19
+ # Convert to markdown, managing NaN values
20
+ markdown_table = df.fillna("").to_markdown(index=False)
21
+ markdown_output.append(markdown_table)
22
+ markdown_output.append("") # Add empty line between sheets
23
+
24
+ return "\n".join(markdown_output)
25
+
26
+ except Exception as e:
27
+ return f"Error reading Excel file: {str(e)}"
@@ -2,7 +2,7 @@ import os
2
2
  import re
3
3
  from typing import Optional
4
4
  from charset_normalizer import from_path
5
- from ara_cli.file_loaders.image_processor import ImageProcessor
5
+ from ara_cli.file_loaders.tools.image_processor import ImageProcessor
6
6
 
7
7
 
8
8
  class MarkdownReader:
@@ -0,0 +1,59 @@
1
+ from ara_cli.file_loaders.document_reader import DocumentReader
2
+
3
+ class OdtReader(DocumentReader):
4
+ """Reader for ODT files"""
5
+
6
+ def read(self, extract_images: bool = False) -> str:
7
+ import pymupdf4llm
8
+
9
+ if not extract_images:
10
+ return pymupdf4llm.to_markdown(self.file_path, write_images=False)
11
+
12
+ import zipfile
13
+ from PIL import Image
14
+ import io
15
+
16
+ # Create data directory for images
17
+ images_dir = self.create_image_data_dir("odt")
18
+
19
+ # Get text content
20
+ text_content = pymupdf4llm.to_markdown(
21
+ self.file_path, write_images=False)
22
+
23
+ # Extract and process images from ODT
24
+ image_descriptions = []
25
+ image_counter = 1
26
+
27
+ try:
28
+ with zipfile.ZipFile(self.file_path, 'r') as odt_zip:
29
+ # List all files in the Pictures directory
30
+ picture_files = [
31
+ f for f in odt_zip.namelist() if f.startswith('Pictures/')]
32
+
33
+ for picture_file in picture_files:
34
+ # Extract image data
35
+ image_data = odt_zip.read(picture_file)
36
+
37
+ # Determine image format
38
+ image = Image.open(io.BytesIO(image_data))
39
+ image_format = image.format.lower()
40
+
41
+ # Save and describe image
42
+ relative_path, description = self.save_and_describe_image(
43
+ image_data, image_format, images_dir, image_counter
44
+ )
45
+
46
+ # Add formatted description to list
47
+ image_description = f"\nImage: {relative_path}\n[{description}]\n"
48
+ image_descriptions.append(image_description)
49
+
50
+ image_counter += 1
51
+ except Exception as e:
52
+ print(f"Warning: Could not extract images from ODT: {e}")
53
+
54
+ # Combine text content with image descriptions
55
+ if image_descriptions:
56
+ text_content += "\n\n### Extracted Images\n" + \
57
+ "\n".join(image_descriptions)
58
+
59
+ return text_content
@@ -0,0 +1,54 @@
1
+ from ara_cli.file_loaders.document_reader import DocumentReader
2
+
3
+ class PdfReader(DocumentReader):
4
+ """Reader for PDF files"""
5
+
6
+ def read(self, extract_images: bool = False) -> str:
7
+ import pymupdf4llm
8
+
9
+ if not extract_images:
10
+ return pymupdf4llm.to_markdown(self.file_path, write_images=False)
11
+
12
+ import fitz # PyMuPDF
13
+
14
+ # Create images directory
15
+ images_dir = self.create_image_data_dir("pdf")
16
+
17
+ # Extract text without images first
18
+ text_content = pymupdf4llm.to_markdown(
19
+ self.file_path, write_images=False)
20
+
21
+ # Extract and process images
22
+ doc = fitz.open(self.file_path)
23
+ image_descriptions = []
24
+ image_counter = 1
25
+
26
+ for page_num, page in enumerate(doc):
27
+ image_list = page.get_images()
28
+
29
+ for img_index, img in enumerate(image_list):
30
+ # Extract image
31
+ xref = img[0]
32
+ base_image = doc.extract_image(xref)
33
+ image_bytes = base_image["image"]
34
+ image_ext = base_image["ext"]
35
+
36
+ # Save and describe image
37
+ relative_path, description = self.save_and_describe_image(
38
+ image_bytes, image_ext, images_dir, image_counter
39
+ )
40
+
41
+ # Add formatted description to list
42
+ image_description = f"\nImage: {relative_path}\n[{description}]\n"
43
+ image_descriptions.append(image_description)
44
+
45
+ image_counter += 1
46
+
47
+ doc.close()
48
+
49
+ # Combine text content with image descriptions
50
+ if image_descriptions:
51
+ text_content += "\n\n### Extracted Images\n" + \
52
+ "\n".join(image_descriptions)
53
+
54
+ return text_content
@@ -0,0 +1,104 @@
1
+ from ara_cli.file_loaders.document_reader import DocumentReader
2
+
3
+
4
+ class PptxReader(DocumentReader):
5
+ """Reader for PowerPoint files"""
6
+
7
+ @staticmethod
8
+ def _getActionImage(shape, MSO_SHAPE_TYPE):
9
+ try:
10
+ if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
11
+ return shape.image.blob, shape.image.ext
12
+ elif shape.is_placeholder and hasattr(shape, "image"):
13
+ return shape.image.blob, shape.image.ext
14
+ except Exception:
15
+ pass
16
+ return None, None
17
+
18
+ @staticmethod
19
+ def _get_shape_text(shape, slide):
20
+ if not shape.has_text_frame:
21
+ return []
22
+
23
+ lines = []
24
+ is_title = False
25
+ try:
26
+ if shape == slide.shapes.title:
27
+ is_title = True
28
+ except AttributeError:
29
+ pass
30
+
31
+ text_frame = shape.text_frame
32
+ if is_title:
33
+ lines.append(f"### {text_frame.text}")
34
+ else:
35
+ for paragraph in text_frame.paragraphs:
36
+ text = paragraph.text.strip()
37
+ if text:
38
+ lines.append(f"- {text}")
39
+ return lines
40
+
41
+ def read(self, extract_images: bool = False) -> str:
42
+ from pptx import Presentation
43
+ from pptx.enum.shapes import MSO_SHAPE_TYPE
44
+ import io
45
+
46
+ try:
47
+ prs = Presentation(self.file_path)
48
+ md_lines = []
49
+
50
+ # Prepare image extraction if requested
51
+ images_dir = None
52
+ image_counter = 1
53
+ image_descriptions = []
54
+
55
+ if extract_images:
56
+ images_dir = self.create_image_data_dir("pptx")
57
+
58
+ def process_shape(shape):
59
+ # Recursive function to handle groups and extract images
60
+ if shape.shape_type == MSO_SHAPE_TYPE.GROUP:
61
+ for sub_shape in shape.shapes:
62
+ process_shape(sub_shape)
63
+ return
64
+
65
+ # Text extraction
66
+ md_lines.extend(self._get_shape_text(shape, slide))
67
+
68
+ # Image extraction
69
+ if extract_images:
70
+ blob, ext = self._getActionImage(shape, MSO_SHAPE_TYPE)
71
+ if blob and ext:
72
+ try:
73
+ nonlocal image_counter
74
+ relative_path, description = self.save_and_describe_image(
75
+ blob, ext, images_dir, image_counter
76
+ )
77
+ image_desc_text = (
78
+ f"\nImage: {relative_path}\n[{description}]\n"
79
+ )
80
+ md_lines.append(image_desc_text)
81
+ image_descriptions.append(image_desc_text)
82
+ image_counter += 1
83
+ except Exception as img_err:
84
+ print(
85
+ f"Warning: Failed to extract image from slide {index+1}: {img_err}"
86
+ )
87
+
88
+ for index, slide in enumerate(prs.slides):
89
+ md_lines.append(f"## Slide {index + 1}")
90
+
91
+ # Collect shapes and sort by top position
92
+ shapes = sorted(
93
+ [s for s in slide.shapes], key=lambda x: (x.top or 0, x.left or 0)
94
+ )
95
+
96
+ for shape in shapes:
97
+ process_shape(shape)
98
+
99
+ md_lines.append("\n---\n")
100
+
101
+ return "\n".join(md_lines)
102
+
103
+ except Exception as e:
104
+ return f"Error reading PowerPoint file: {str(e)}"
File without changes
ara_cli/llm_utils.py ADDED
@@ -0,0 +1,58 @@
1
+ from ara_cli.ara_config import ConfigManager
2
+ from pydantic_ai import Agent
3
+
4
+ FALLBACK_MODEL = "anthropic:claude-4-sonnet-20250514"
5
+
6
+
7
+ def get_configured_conversion_llm_model() -> str:
8
+ """
9
+ Retrieves the configured conversion LLM model string, adapted for pydantic_ai.
10
+ Falls back to a default model if configuration is missing or invalid.
11
+ """
12
+ model_name = FALLBACK_MODEL
13
+ try:
14
+ config = ConfigManager.get_config()
15
+ conversion_llm_key = config.conversion_llm
16
+
17
+ if conversion_llm_key and conversion_llm_key in config.llm_config:
18
+ llm_config_item = config.llm_config[conversion_llm_key]
19
+ raw_model_name = llm_config_item.model
20
+
21
+ # Adapt LiteLLM model string to PydanticAI format
22
+ # LiteLLM: provider/model-name (e.g. openai/gpt-4o)
23
+ # PydanticAI: provider:model-name (e.g. openai:gpt-4o)
24
+ if "/" in raw_model_name and ":" not in raw_model_name:
25
+ parts = raw_model_name.split("/", 1)
26
+ if len(parts) == 2:
27
+ model_name = f"{parts[0]}:{parts[1]}"
28
+ else:
29
+ model_name = raw_model_name
30
+ else:
31
+ model_name = raw_model_name
32
+ else:
33
+ print(
34
+ f"Warning: Conversion LLM configuration issue. Using fallback model: {FALLBACK_MODEL}"
35
+ )
36
+ except Exception as e:
37
+ print(
38
+ f"Warning: Error resolving LLM config ({e}). Using fallback model: {FALLBACK_MODEL}"
39
+ )
40
+ model_name = FALLBACK_MODEL
41
+
42
+ return model_name
43
+
44
+
45
+ def create_pydantic_ai_agent(
46
+ output_type, model_name: str = None, instrument: bool = True
47
+ ) -> Agent:
48
+ """
49
+ Creates a pydantic_ai Agent with the specified or configured model.
50
+ """
51
+ if not model_name:
52
+ model_name = get_configured_conversion_llm_model()
53
+
54
+ return Agent(
55
+ model=model_name,
56
+ output_type=output_type,
57
+ instrument=instrument,
58
+ )
@@ -15,3 +15,56 @@ def suppress_stdout(suppress=False):
15
15
  sys.stdout = old_stdout
16
16
  else:
17
17
  yield
18
+
19
+
20
+ @contextmanager
21
+ def suppress_stderr():
22
+ """Suppress stderr output - useful for hiding library debug/error messages."""
23
+ with open(os.devnull, "w", encoding="utf-8") as devnull:
24
+ old_stderr = sys.stderr
25
+ sys.stderr = devnull
26
+ try:
27
+ yield
28
+ finally:
29
+ sys.stderr = old_stderr
30
+
31
+
32
+ class FilteredStdout:
33
+ """A stdout wrapper that filters out specific unwanted messages."""
34
+
35
+ FILTERED_PATTERNS = [
36
+ "Provider List: https://docs.litellm.ai/docs/providers",
37
+ ]
38
+
39
+ def __init__(self, original_stdout):
40
+ self.original_stdout = original_stdout
41
+
42
+ def write(self, text):
43
+ # Check if text contains any filtered patterns
44
+ for pattern in self.FILTERED_PATTERNS:
45
+ if pattern in text:
46
+ return # Suppress this output
47
+ self.original_stdout.write(text)
48
+
49
+ def flush(self):
50
+ self.original_stdout.flush()
51
+
52
+ def __getattr__(self, name):
53
+ return getattr(self.original_stdout, name)
54
+
55
+
56
+ @contextmanager
57
+ def filter_unwanted_output():
58
+ """Filter out unwanted stdout messages and suppress stderr."""
59
+ old_stdout = sys.stdout
60
+ old_stderr = sys.stderr
61
+
62
+ sys.stdout = FilteredStdout(old_stdout)
63
+
64
+ with open(os.devnull, "w", encoding="utf-8") as devnull:
65
+ sys.stderr = devnull
66
+ try:
67
+ yield
68
+ finally:
69
+ sys.stdout = old_stdout
70
+ sys.stderr = old_stderr
ara_cli/prompt_chat.py CHANGED
@@ -6,9 +6,18 @@ from ara_cli.update_config_prompt import update_artefact_config_prompt_files
6
6
  from ara_cli.output_suppressor import suppress_stdout
7
7
 
8
8
 
9
- def initialize_prompt_chat_mode(classifier, param, chat_name, reset=None, output_mode=False, append_strings=[], restricted=False):
9
+ def initialize_prompt_chat_mode(
10
+ classifier,
11
+ param,
12
+ chat_name,
13
+ reset=None,
14
+ output_mode=False,
15
+ append_strings=[],
16
+ restricted=False,
17
+ ):
10
18
  sub_directory = Classifier.get_sub_directory(classifier)
11
- artefact_data_path = os.path.join("ara", sub_directory, f"{param}.data") # f"ara/{sub_directory}/{parameter}.data"
19
+ # f"ara/{sub_directory}/{parameter}.data"
20
+ artefact_data_path = os.path.join("ara", sub_directory, f"{param}.data")
12
21
 
13
22
  if chat_name is None:
14
23
  chat_name = classifier
@@ -17,11 +26,18 @@ def initialize_prompt_chat_mode(classifier, param, chat_name, reset=None, output
17
26
  update_artefact_config_prompt_files(classifier, param, automatic_update=True)
18
27
 
19
28
  classifier_chat_file = os.path.join(artefact_data_path, f"{chat_name}")
20
- start_chat_session(classifier_chat_file, reset, output_mode, append_strings, restricted)
29
+ start_chat_session(
30
+ classifier_chat_file, reset, output_mode, append_strings, restricted
31
+ )
32
+
21
33
 
22
34
  def start_chat_session(chat_file, reset, output_mode, append_strings, restricted):
23
35
  with suppress_stdout(suppress=output_mode):
24
- chat = Chat(chat_file, reset=reset) if not restricted else Chat(chat_file, reset=reset, enable_commands=whitelisted_commands)
36
+ chat = (
37
+ Chat(chat_file, reset=reset)
38
+ if not restricted
39
+ else Chat(chat_file, reset=reset, enable_commands=whitelisted_commands)
40
+ )
25
41
  if append_strings:
26
42
  chat.append_strings(append_strings)
27
43
  if output_mode: