janus-llm 4.3.5__py3-none-any.whl → 4.5.4__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 (48) hide show
  1. janus/__init__.py +1 -1
  2. janus/cli/aggregate.py +2 -2
  3. janus/cli/cli.py +6 -0
  4. janus/cli/constants.py +6 -0
  5. janus/cli/diagram.py +36 -7
  6. janus/cli/document.py +10 -1
  7. janus/cli/llm.py +7 -3
  8. janus/cli/partition.py +10 -1
  9. janus/cli/pipeline.py +126 -0
  10. janus/cli/self_eval.py +10 -3
  11. janus/cli/translate.py +10 -1
  12. janus/converter/__init__.py +2 -0
  13. janus/converter/_tests/test_translate.py +6 -5
  14. janus/converter/chain.py +100 -0
  15. janus/converter/converter.py +467 -90
  16. janus/converter/diagram.py +12 -8
  17. janus/converter/document.py +17 -7
  18. janus/converter/evaluate.py +174 -147
  19. janus/converter/partition.py +6 -11
  20. janus/converter/passthrough.py +29 -0
  21. janus/converter/pool.py +74 -0
  22. janus/converter/requirements.py +7 -40
  23. janus/converter/translate.py +2 -58
  24. janus/language/_tests/test_combine.py +1 -0
  25. janus/language/block.py +115 -5
  26. janus/llm/model_callbacks.py +6 -0
  27. janus/llm/models_info.py +19 -0
  28. janus/metrics/_tests/test_reading.py +48 -4
  29. janus/metrics/_tests/test_rouge_score.py +5 -11
  30. janus/metrics/metric.py +47 -124
  31. janus/metrics/reading.py +48 -28
  32. janus/metrics/rouge_score.py +21 -34
  33. janus/parsers/_tests/test_code_parser.py +1 -1
  34. janus/parsers/code_parser.py +2 -2
  35. janus/parsers/eval_parsers/incose_parser.py +3 -3
  36. janus/parsers/reqs_parser.py +3 -3
  37. janus/prompts/templates/cyclic/human.txt +16 -0
  38. janus/prompts/templates/cyclic/system.txt +1 -0
  39. janus/prompts/templates/eval_prompts/incose/human.txt +1 -1
  40. janus/prompts/templates/extract_variables/human.txt +5 -0
  41. janus/prompts/templates/extract_variables/system.txt +1 -0
  42. {janus_llm-4.3.5.dist-info → janus_llm-4.5.4.dist-info}/METADATA +14 -15
  43. {janus_llm-4.3.5.dist-info → janus_llm-4.5.4.dist-info}/RECORD +46 -40
  44. {janus_llm-4.3.5.dist-info → janus_llm-4.5.4.dist-info}/WHEEL +1 -1
  45. janus/metrics/_tests/test_llm.py +0 -90
  46. janus/metrics/llm_metrics.py +0 -202
  47. {janus_llm-4.3.5.dist-info → janus_llm-4.5.4.dist-info}/LICENSE +0 -0
  48. {janus_llm-4.3.5.dist-info → janus_llm-4.5.4.dist-info}/entry_points.txt +0 -0
janus/__init__.py CHANGED
@@ -5,7 +5,7 @@ from langchain_core._api.deprecation import LangChainDeprecationWarning
5
5
  from janus.converter.translate import Translator
6
6
  from janus.metrics import * # noqa: F403
7
7
 
8
- __version__ = "4.3.5"
8
+ __version__ = "4.5.4"
9
9
 
10
10
  # Ignoring a deprecation warning from langchain_core that I can't seem to hunt down
11
11
  warnings.filterwarnings("ignore", category=LangChainDeprecationWarning)
janus/cli/aggregate.py CHANGED
@@ -33,7 +33,7 @@ def aggregate(
33
33
  output_dir: Annotated[
34
34
  Path,
35
35
  typer.Option(
36
- "--output-dir", "-o", help="The directory to store the translated code in."
36
+ "--output", "-o", help="The directory to store the translated code in."
37
37
  ),
38
38
  ],
39
39
  llm_name: Annotated[
@@ -130,6 +130,6 @@ def aggregate(
130
130
  db_path=db_loc,
131
131
  db_config=collections_config,
132
132
  splitter_type=splitter_type,
133
- prompt_template="basic_aggregation",
133
+ prompt_templates="basic_aggregation",
134
134
  )
135
135
  aggregator.translate(input_dir, output_dir, failure_dir, overwrite, collection)
janus/cli/cli.py CHANGED
@@ -10,6 +10,7 @@ from janus.cli.document import document
10
10
  from janus.cli.embedding import embedding
11
11
  from janus.cli.llm import llm
12
12
  from janus.cli.partition import partition
13
+ from janus.cli.pipeline import pipeline
13
14
  from janus.cli.self_eval import llm_self_eval
14
15
  from janus.cli.translate import translate
15
16
  from janus.metrics.cli import evaluate
@@ -101,6 +102,11 @@ translate = app.command(
101
102
  no_args_is_help=True,
102
103
  )(translate)
103
104
 
105
+ pipeline = app.command(
106
+ help="Run a janus pipeline",
107
+ no_args_is_help=True,
108
+ )(pipeline)
109
+
104
110
  app.add_typer(db, name="db")
105
111
  app.add_typer(llm, name="llm")
106
112
  app.add_typer(evaluate, name="evaluate")
janus/cli/constants.py CHANGED
@@ -33,6 +33,12 @@ REFINER_TYPES = get_subclasses(janus.refiners.refiner.JanusRefiner).union(
33
33
  )
34
34
  REFINERS = {r.__name__: r for r in REFINER_TYPES}
35
35
 
36
+ CONVERTER_TYPES = get_subclasses(janus.converter.converter.Converter).union(
37
+ {janus.converter.converter.Converter}
38
+ )
39
+
40
+ CONVERTERS = {c.__name__: c for c in CONVERTER_TYPES}
41
+
36
42
 
37
43
  def get_collections_config():
38
44
  if collections_config_file.exists():
janus/cli/diagram.py CHANGED
@@ -32,7 +32,7 @@ def diagram(
32
32
  output_dir: Annotated[
33
33
  Path,
34
34
  typer.Option(
35
- "--output-dir", "-o", help="The directory to store the translated code in."
35
+ "--output", "-o", help="The directory to store the translated code in."
36
36
  ),
37
37
  ],
38
38
  llm_name: Annotated[
@@ -112,7 +112,7 @@ def diagram(
112
112
  refinement chain",
113
113
  click_type=click.Choice(list(REFINERS.keys())),
114
114
  ),
115
- ] = ["JanusRefiner"],
115
+ ] = ["CodeFormatRefiner"],
116
116
  retriever_type: Annotated[
117
117
  str,
118
118
  typer.Option(
@@ -122,6 +122,24 @@ def diagram(
122
122
  click_type=click.Choice(["active_usings", "language_docs"]),
123
123
  ),
124
124
  ] = None,
125
+ extract_variables: Annotated[
126
+ bool,
127
+ typer.Option(
128
+ "-ev",
129
+ "--extract-variables",
130
+ help="Present when diagram generator should \
131
+ extract variables before producing diagram",
132
+ ),
133
+ ] = False,
134
+ use_janus_inputs: Annotated[
135
+ bool,
136
+ typer.Option(
137
+ "-j",
138
+ "--use-janus-inputs",
139
+ help="Present when diagram generator should be\
140
+ be using janus files as inputs",
141
+ ),
142
+ ] = False,
125
143
  ):
126
144
  from janus.cli.constants import db_loc, get_collections_config
127
145
  from janus.converter.diagram import DiagramGenerator
@@ -141,6 +159,8 @@ def diagram(
141
159
  retriever_type=retriever_type,
142
160
  diagram_type=diagram_type,
143
161
  add_documentation=add_documentation,
162
+ extract_variables=extract_variables,
163
+ use_janus_inputs=use_janus_inputs,
144
164
  )
145
165
  diagram_generator.translate(input_dir, output_dir, failure_dir, overwrite, collection)
146
166
 
@@ -170,9 +190,18 @@ def render(
170
190
  if not output_file.parent.exists():
171
191
  output_file.parent.mkdir()
172
192
 
173
- text = data["output"].replace("\\n", "\n").strip()
174
- output_file.write_text(text)
193
+ def _render(obj, ind=0):
194
+ for o in obj["outputs"]:
195
+ if isinstance(o, dict):
196
+ ind += _render(o, ind)
197
+ else:
198
+ outfile_new = output_file.with_stem(f"{output_file.stem}_{ind}")
199
+ text = o.replace("\\n", "\n").strip()
200
+ outfile_new.write_text(text)
201
+ jar_path = homedir / ".janus/lib/plantuml.jar"
202
+ subprocess.run(["java", "-jar", jar_path, outfile_new]) # nosec
203
+ outfile_new.unlink()
204
+ ind += 1
205
+ return ind
175
206
 
176
- jar_path = homedir / ".janus/lib/plantuml.jar"
177
- subprocess.run(["java", "-jar", jar_path, output_file]) # nosec
178
- output_file.unlink()
207
+ _render(data)
janus/cli/document.py CHANGED
@@ -32,7 +32,7 @@ def document(
32
32
  output_dir: Annotated[
33
33
  Path,
34
34
  typer.Option(
35
- "--output-dir", "-o", help="The directory to store the translated code in."
35
+ "--output", "-o", help="The directory to store the translated code in."
36
36
  ),
37
37
  ],
38
38
  llm_name: Annotated[
@@ -142,6 +142,14 @@ def document(
142
142
  "If unspecificed, model's default max will be used.",
143
143
  ),
144
144
  ] = None,
145
+ use_janus_inputs: Annotated[
146
+ bool,
147
+ typer.Option(
148
+ "-j",
149
+ "--use-janus-inputs",
150
+ help="Present if converter should use janus files as inputs",
151
+ ),
152
+ ] = False,
145
153
  ):
146
154
  from janus.cli.constants import db_loc, get_collections_config
147
155
  from janus.converter.document import ClozeDocumenter, Documenter, MultiDocumenter
@@ -161,6 +169,7 @@ def document(
161
169
  splitter_type=splitter_type,
162
170
  refiner_types=refiner_types,
163
171
  retriever_type=retriever_type,
172
+ use_janus_inputs=use_janus_inputs,
164
173
  )
165
174
  if doc_mode == "cloze":
166
175
  documenter = ClozeDocumenter(comments_per_request=comments_per_request, **kwargs)
janus/cli/llm.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import click
2
2
  import typer
3
+ from rich import print
3
4
  from typing_extensions import Annotated
4
5
 
5
6
  from janus.llm.models_info import MODEL_TYPE_CONSTRUCTORS
@@ -45,7 +46,10 @@ def llm_add(
45
46
  if model_type == "HuggingFace":
46
47
  url = typer.prompt("Enter the model's URL")
47
48
  max_tokens = typer.prompt(
48
- "Enter the model's maximum tokens", default=4096, type=int
49
+ "Enter the model's token limit", default=65536, type=int
50
+ )
51
+ max_tokens = typer.prompt(
52
+ "Enter the model's max output tokens", default=8192, type=int
49
53
  )
50
54
  in_cost = typer.prompt("Enter the cost per input token", default=0, type=float)
51
55
  out_cost = typer.prompt("Enter the cost per output token", default=0, type=float)
@@ -61,6 +65,7 @@ def llm_add(
61
65
  )
62
66
  cfg = {
63
67
  "model_type": model_type,
68
+ "model_id": "gpt-4o", # This is a placeholder to use the Azure PromptEngine
64
69
  "model_args": params,
65
70
  "token_limit": max_tokens,
66
71
  "model_cost": {"input": in_cost, "output": out_cost},
@@ -172,8 +177,7 @@ def llm_ls(
172
177
  ):
173
178
  import json
174
179
 
175
- from janus.cli.constants import MODEL_CONFIG_DIR
176
- from janus.llm.models_info import MODEL_TYPES
180
+ from janus.llm.models_info import MODEL_CONFIG_DIR, MODEL_TYPES
177
181
 
178
182
  print("\n[green]User-configured models[/green]:")
179
183
  for model_cfg in MODEL_CONFIG_DIR.glob("*.json"):
janus/cli/partition.py CHANGED
@@ -31,7 +31,7 @@ def partition(
31
31
  output_dir: Annotated[
32
32
  Path,
33
33
  typer.Option(
34
- "--output-dir", "-o", help="The directory to store the partitioned code in."
34
+ "--output", "-o", help="The directory to store the partitioned code in."
35
35
  ),
36
36
  ],
37
37
  llm_name: Annotated[
@@ -106,6 +106,14 @@ def partition(
106
106
  help="The limit on the number of tokens per partition.",
107
107
  ),
108
108
  ] = 8192,
109
+ use_janus_inputs: Annotated[
110
+ bool,
111
+ typer.Option(
112
+ "-j",
113
+ "--use-janus-inputs",
114
+ help="Present if converter should use janus inputs",
115
+ ),
116
+ ] = False,
109
117
  ):
110
118
  from janus.converter.partition import Partitioner
111
119
 
@@ -120,6 +128,7 @@ def partition(
120
128
  splitter_type=splitter_type,
121
129
  refiner_types=refiner_types,
122
130
  partition_token_limit=partition_token_limit,
131
+ use_janus_inputs=use_janus_inputs,
123
132
  )
124
133
  partitioner = Partitioner(**kwargs)
125
134
  partitioner.translate(input_dir, output_dir, failure_dir, overwrite)
janus/cli/pipeline.py ADDED
@@ -0,0 +1,126 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ import click
6
+ import typer
7
+ from typing_extensions import Annotated
8
+
9
+ from janus.cli.constants import CONVERTERS
10
+ from janus.converter.chain import ConverterChain
11
+ from janus.converter.pool import ConverterPool
12
+ from janus.utils.enums import LANGUAGES
13
+
14
+
15
+ def instiantiate(x):
16
+ if isinstance(x, dict):
17
+ if "type" in x:
18
+ if "args" not in x:
19
+ x["args"] = []
20
+ x["args"] = [instiantiate(a) for a in x["args"]]
21
+ if "kwargs" not in x:
22
+ x["kwargs"] = {}
23
+ x["kwargs"] = {k: instiantiate(x["kwargs"][k]) for k in x["kwargs"]}
24
+ if x["type"] not in CONVERTERS:
25
+ raise ValueError(f"Error: {x['type']} is not a Converter")
26
+ return CONVERTERS[x["type"]](*x["args"], **x["kwargs"])
27
+ else:
28
+ return {k: instiantiate(x[k]) for k in x}
29
+ elif isinstance(x, list):
30
+ return [instiantiate(a) for a in x]
31
+ else:
32
+ return x
33
+
34
+
35
+ def instiantiate_pipeline(
36
+ pipeline: list[dict],
37
+ language: str = "text",
38
+ model: str = "gpt-4o",
39
+ use_janus_inputs: None | bool = None,
40
+ ):
41
+ if "kwargs" not in pipeline[0]:
42
+ pipeline[0]["kwargs"] = {}
43
+ pipeline[0]["kwargs"].update(source_language=language, model=model)
44
+ if use_janus_inputs is not None:
45
+ pipeline[0]["kwargs"].update(use_janus_inputs=use_janus_inputs)
46
+ converters = [instiantiate(pipeline[0])]
47
+ for p in pipeline[1:]:
48
+ if not isinstance(converters[-1], ConverterPool) and p["type"] != "ConverterPool":
49
+ p["kwargs"].update(
50
+ source_language=converters[-1].target_language, model=model
51
+ )
52
+ converters.append(instiantiate(p))
53
+ return ConverterChain(*converters)
54
+
55
+
56
+ def pipeline(
57
+ pipeline_file: Annotated[
58
+ Path, typer.Option("-p", "--pipeline", help="Name of pipeline file to use")
59
+ ],
60
+ input_dir: Annotated[
61
+ Path,
62
+ typer.Option(
63
+ "--input",
64
+ "-i",
65
+ help="The directory containing the source code to be translated. "
66
+ "The files should all be in one flat directory.",
67
+ ),
68
+ ],
69
+ language: Annotated[
70
+ str,
71
+ typer.Option(
72
+ "--language",
73
+ "-l",
74
+ help="The language of the source code.",
75
+ click_type=click.Choice(sorted(LANGUAGES)),
76
+ ),
77
+ ],
78
+ output_dir: Annotated[
79
+ Path,
80
+ typer.Option(
81
+ "--output", "-o", help="The directory to store the translated code in."
82
+ ),
83
+ ],
84
+ llm_name: Annotated[
85
+ str,
86
+ typer.Option(
87
+ "--llm",
88
+ "-L",
89
+ help="The custom name of the model set with 'janus llm add'.",
90
+ ),
91
+ ],
92
+ failure_dir: Annotated[
93
+ Optional[Path],
94
+ typer.Option(
95
+ "--failure-directory",
96
+ "-f",
97
+ help="The directory to store failure files during documentation",
98
+ ),
99
+ ] = None,
100
+ overwrite: Annotated[
101
+ bool,
102
+ typer.Option(
103
+ "--overwrite/--preserve",
104
+ help="Whether to overwrite existing files in the output directory",
105
+ ),
106
+ ] = False,
107
+ use_janus_inputs: Annotated[
108
+ Optional[bool],
109
+ typer.Option(
110
+ "-j",
111
+ "--use-janus-inputs",
112
+ help="Present if converter chain should use janus input files",
113
+ ),
114
+ ] = None,
115
+ ):
116
+ with open(pipeline_file, "r") as f:
117
+ json_obj = json.load(f)
118
+ pipeline = instiantiate_pipeline(
119
+ json_obj, language=language, model=llm_name, use_janus_inputs=use_janus_inputs
120
+ )
121
+ pipeline.translate(
122
+ input_directory=input_dir,
123
+ output_directory=output_dir,
124
+ failure_directory=failure_dir,
125
+ overwrite=overwrite,
126
+ )
janus/cli/self_eval.py CHANGED
@@ -31,9 +31,7 @@ def llm_self_eval(
31
31
  ],
32
32
  output_dir: Annotated[
33
33
  Path,
34
- typer.Option(
35
- "--output-dir", "-o", help="The directory to store the evaluations in."
36
- ),
34
+ typer.Option("--output", "-o", help="The directory to store the evaluations in."),
37
35
  ],
38
36
  failure_dir: Annotated[
39
37
  Optional[Path],
@@ -125,6 +123,14 @@ def llm_self_eval(
125
123
  "If unspecificed, model's default max will be used.",
126
124
  ),
127
125
  ] = None,
126
+ use_janus_inputs: Annotated[
127
+ bool,
128
+ typer.Option(
129
+ "-j",
130
+ "--use-janus-inputs",
131
+ help="Prsent if translator should use janus files as inputs",
132
+ ),
133
+ ] = False,
128
134
  ):
129
135
  from janus.converter.evaluate import InlineCommentEvaluator, RequirementEvaluator
130
136
 
@@ -139,6 +145,7 @@ def llm_self_eval(
139
145
  max_tokens=max_tokens,
140
146
  splitter_type=splitter_type,
141
147
  refiner_types=refiner_types,
148
+ use_janus_inputs=use_janus_inputs,
142
149
  )
143
150
  # Setting parser type here
144
151
  if evaluation_type == "incose":
janus/cli/translate.py CHANGED
@@ -148,6 +148,14 @@ def translate(
148
148
  "If unspecificed, model's default max will be used.",
149
149
  ),
150
150
  ] = None,
151
+ use_janus_inputs: Annotated[
152
+ bool,
153
+ typer.Option(
154
+ "-j",
155
+ "--use-janus-inputs",
156
+ help="Prsent if translator should use janus files as inputs",
157
+ ),
158
+ ] = False,
151
159
  ):
152
160
  from janus.cli.constants import db_loc, get_collections_config
153
161
  from janus.converter.translate import Translator
@@ -173,11 +181,12 @@ def translate(
173
181
  target_version=target_version,
174
182
  max_prompts=max_prompts,
175
183
  max_tokens=max_tokens,
176
- prompt_template=prompt_template,
184
+ prompt_templates=prompt_template,
177
185
  db_path=db_loc,
178
186
  db_config=collections_config,
179
187
  splitter_type=splitter_type,
180
188
  refiner_types=refiner_types,
181
189
  retriever_type=retriever_type,
190
+ use_janus_inputs=use_janus_inputs,
182
191
  )
183
192
  translator.translate(input_dir, output_dir, failure_dir, overwrite, collection)
@@ -3,5 +3,7 @@ from janus.converter.diagram import DiagramGenerator
3
3
  from janus.converter.document import ClozeDocumenter, Documenter, MultiDocumenter
4
4
  from janus.converter.evaluate import Evaluator
5
5
  from janus.converter.partition import Partitioner
6
+ from janus.converter.passthrough import ConverterPassthrough
7
+ from janus.converter.pool import ConverterPool
6
8
  from janus.converter.requirements import RequirementsDocumenter
7
9
  from janus.converter.translate import Translator
@@ -59,14 +59,14 @@ class TestTranslator(unittest.TestCase):
59
59
  self.req_translator = RequirementsDocumenter(
60
60
  model="gpt-4o-mini",
61
61
  source_language="fortran",
62
- prompt_template="requirements",
62
+ prompt_templates="requirements",
63
63
  )
64
64
 
65
65
  @pytest.mark.translate
66
66
  def test_translate(self):
67
67
  """Test translate method."""
68
68
  # Delete a file if it's already there
69
- python_file = self.test_file.parent / "python" / f"{self.test_file.stem}.py"
69
+ python_file = self.test_file.parent / "python" / f"{self.test_file.stem}.json"
70
70
  python_file.unlink(missing_ok=True)
71
71
  python_file.parent.rmdir() if python_file.parent.is_dir() else None
72
72
  self.translator.translate(self.test_file.parent, self.test_file.parent / "python")
@@ -82,7 +82,7 @@ class TestTranslator(unittest.TestCase):
82
82
  self.assertRaises(
83
83
  ValueError, self.translator.set_source_language, "scribbledy-doop"
84
84
  )
85
- self.translator.set_prompt("pish posh")
85
+ self.translator.set_prompts(["pish posh"])
86
86
  self.assertRaises(ValueError, self.translator._load_parameters)
87
87
 
88
88
 
@@ -120,6 +120,7 @@ class TestDiagramGenerator(unittest.TestCase):
120
120
  children=[],
121
121
  ),
122
122
  language="python",
123
+ converter=self.diagram_generator,
123
124
  )
124
125
  self.diagram_generator._add_translation(block)
125
126
  self.assertTrue(block.translated)
@@ -149,10 +150,10 @@ def test_language_combinations(
149
150
  translator.set_model("gpt-4o")
150
151
  translator.set_source_language(source_language)
151
152
  translator.set_target_language(expected_target_language, expected_target_version)
152
- translator.set_prompt(prompt_template)
153
+ translator.set_prompts(prompt_template)
153
154
  translator._load_parameters()
154
155
  assert translator._target_language == expected_target_language # nosec
155
156
  assert translator._target_version == expected_target_version # nosec
156
157
  assert translator._splitter.language == source_language # nosec
157
158
  assert translator._splitter.model.model_name == "gpt-4o" # nosec
158
- assert translator._prompt_template_name == prompt_template # nosec
159
+ assert translator._prompt_template_names == [prompt_template] # nosec
@@ -0,0 +1,100 @@
1
+ from pathlib import Path
2
+
3
+ from janus.converter.converter import Converter
4
+ from janus.language.block import BlockCollection, CodeBlock, TranslatedCodeBlock
5
+ from janus.utils.logger import create_logger
6
+
7
+ log = create_logger(__name__)
8
+
9
+
10
+ class ConverterChain(Converter):
11
+ """
12
+ Class for representing multiple converters chained together
13
+ """
14
+
15
+ def __init__(self, *args, **kwargs) -> None:
16
+ if len(args) == 0:
17
+ raise ValueError("Error: Converter chain must be passed at least 1 converter")
18
+ for converter in args:
19
+ if not isinstance(converter, Converter):
20
+ raise ValueError(f"Error: unrecognized type: {type(converter)}")
21
+ self._converters = args
22
+ kwargs.update(
23
+ source_language=self._converters[0].source_language,
24
+ target_language=self._converters[-1]._target_language,
25
+ target_version=self._converters[-1]._target_version,
26
+ use_janus_inputs=self._converters[0]._use_janus_inputs,
27
+ input_types=self._converters[0]._input_types,
28
+ input_labels=self._converters[0]._input_labels,
29
+ output_type=self._converters[-1]._output_type,
30
+ output_label=self._converters[-1]._output_label,
31
+ )
32
+ super().__init__(**kwargs)
33
+
34
+ def translate_blocks(
35
+ self, input_blocks: CodeBlock | list[CodeBlock], failure_path: Path | None = None
36
+ ):
37
+ failed = False
38
+ for i, converter in enumerate(self._converters):
39
+ translated_code_blocks = converter.translate_blocks(input_blocks)
40
+ if not translated_code_blocks.translation_completed:
41
+ log.info(
42
+ f"Error: chain failed to translate at step {i}:"
43
+ f"{self._converters[i].__class__.__name__}"
44
+ )
45
+ failed = True
46
+ break
47
+ input_blocks = translated_code_blocks.to_codeblock()
48
+ if not failed and not translated_code_blocks.translation_completed:
49
+ log.info(
50
+ f"Error: chain failed to translate at step {len(self._converters)-1}: "
51
+ f"{self._converters[-1].__class__.__name__}"
52
+ )
53
+ return translated_code_blocks
54
+
55
+ def _combine_metadata(self, metadatas: list[dict]):
56
+ metadata = super()._combine_metadata(metadatas)
57
+ if isinstance(metadata["type"], list):
58
+ metadata["type"] = metadata["type"][-1]
59
+ if isinstance(metadata["label"], list):
60
+ metadata["label"] = metadata["label"][-1]
61
+ metadata["type"] = metadatas[-1]["type"]
62
+ metadata["label"] = metadatas[-1]["label"]
63
+ return metadata
64
+
65
+ def _get_output_obj(
66
+ self,
67
+ block: TranslatedCodeBlock | BlockCollection,
68
+ combine_children: bool = True,
69
+ include_previous_outputs: bool = True,
70
+ ) -> dict[str, int | float | str | dict[str, str] | dict[str, float]]:
71
+ intermediate_outputs = []
72
+ c_index = 0 # current converter index
73
+ start_index = 0 # start index of newly generated intermediate outputs
74
+ for g in block.previous_generations:
75
+ if isinstance(g, dict):
76
+ intermediate_outputs.append(g)
77
+ # Find the first index where we generated code
78
+ start_index += 1
79
+ else:
80
+ intermediate_outputs.append(
81
+ self._converters[c_index]._get_output_obj(
82
+ g, self._converters[c_index]._combine_output, False
83
+ )
84
+ )
85
+ c_index += 1
86
+ intermediate_outputs.append(
87
+ self._converters[-1]._get_output_obj(
88
+ block, self._converters[-1]._combine_output, False
89
+ )
90
+ )
91
+ out = dict(
92
+ input=intermediate_outputs[start_index]["input"],
93
+ metadata=self._combine_metadata(
94
+ [i["metadata"] for i in intermediate_outputs]
95
+ ),
96
+ outputs=intermediate_outputs[-1]["outputs"],
97
+ )
98
+ if include_previous_outputs:
99
+ out["intermediate_outputs"] = intermediate_outputs
100
+ return out