janus-llm 4.4.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 (35) hide show
  1. janus/__init__.py +1 -1
  2. janus/cli/pipeline.py +6 -3
  3. janus/cli/self_eval.py +9 -0
  4. janus/converter/__init__.py +2 -0
  5. janus/converter/_tests/test_translate.py +1 -0
  6. janus/converter/chain.py +53 -133
  7. janus/converter/converter.py +199 -77
  8. janus/converter/diagram.py +5 -3
  9. janus/converter/document.py +10 -4
  10. janus/converter/evaluate.py +148 -113
  11. janus/converter/partition.py +4 -1
  12. janus/converter/passthrough.py +29 -0
  13. janus/converter/pool.py +74 -0
  14. janus/converter/requirements.py +4 -1
  15. janus/language/_tests/test_combine.py +1 -0
  16. janus/language/block.py +84 -3
  17. janus/llm/model_callbacks.py +6 -0
  18. janus/llm/models_info.py +19 -0
  19. janus/metrics/_tests/test_reading.py +48 -4
  20. janus/metrics/_tests/test_rouge_score.py +5 -11
  21. janus/metrics/reading.py +48 -28
  22. janus/metrics/rouge_score.py +21 -34
  23. janus/parsers/_tests/test_code_parser.py +1 -1
  24. janus/parsers/code_parser.py +2 -2
  25. janus/parsers/eval_parsers/incose_parser.py +3 -3
  26. janus/prompts/templates/cyclic/human.txt +16 -0
  27. janus/prompts/templates/cyclic/system.txt +1 -0
  28. janus/prompts/templates/eval_prompts/incose/human.txt +1 -1
  29. janus/prompts/templates/extract_variables/human.txt +5 -0
  30. janus/prompts/templates/extract_variables/system.txt +1 -0
  31. {janus_llm-4.4.5.dist-info → janus_llm-4.5.4.dist-info}/METADATA +3 -4
  32. {janus_llm-4.4.5.dist-info → janus_llm-4.5.4.dist-info}/RECORD +35 -29
  33. {janus_llm-4.4.5.dist-info → janus_llm-4.5.4.dist-info}/WHEEL +1 -1
  34. {janus_llm-4.4.5.dist-info → janus_llm-4.5.4.dist-info}/LICENSE +0 -0
  35. {janus_llm-4.4.5.dist-info → janus_llm-4.5.4.dist-info}/entry_points.txt +0 -0
@@ -1,12 +1,11 @@
1
1
  import json
2
2
  import re
3
- from copy import deepcopy
4
3
  from pathlib import Path
5
- from typing import Any
6
4
 
7
5
  from langchain_core.runnables import Runnable, RunnableLambda, RunnableParallel
8
6
 
9
7
  from janus.converter.converter import Converter
8
+ from janus.language.block import CodeBlock, TranslatedCodeBlock
10
9
  from janus.language.combine import JsonCombiner
11
10
  from janus.parsers.eval_parsers.incose_parser import IncoseParser
12
11
  from janus.parsers.eval_parsers.inline_comment_parser import InlineCommentParser
@@ -51,7 +50,13 @@ class RequirementEvaluator(Evaluator):
51
50
 
52
51
  """
53
52
 
54
- def __init__(self, eval_items_per_request: int | None = None, **kwargs) -> None:
53
+ def __init__(
54
+ self,
55
+ eval_items_per_request: int | None = None,
56
+ input_types: str | set[str] = set(["requirements"]),
57
+ output_type: str = "requirements_eval",
58
+ **kwargs,
59
+ ) -> None:
55
60
  """Initialize the Evaluator class
56
61
 
57
62
  Arguments:
@@ -60,6 +65,7 @@ class RequirementEvaluator(Evaluator):
60
65
  model_arguments: Additional arguments to pass to the LLM constructor.
61
66
  max_prompts: The maximum number of prompts to try before giving up.
62
67
  """
68
+ kwargs.update(input_types=input_types, output_type=output_type)
63
69
  super().__init__(**kwargs)
64
70
  self.eval_items_per_request = eval_items_per_request
65
71
  self._parser = IncoseParser()
@@ -78,55 +84,67 @@ class RequirementEvaluator(Evaluator):
78
84
  context=self._retriever,
79
85
  )
80
86
 
81
- def translate_janus_obj(self, obj: Any, name: str, failure_path: Path | None = None):
82
- results = []
83
- for o in obj["outputs"]:
84
- if isinstance(o, dict):
85
- results += self.translate_janus_obj(o, name, failure_path)
86
- elif isinstance(o, str):
87
- temp_obj = deepcopy(obj)
88
- requirements = json.loads(o)
89
- if not requirements:
90
- log.debug(f"[{name}] Skipping empty output")
91
- continue
92
- if (
93
- not self.eval_items_per_request
94
- or len(requirements) < self.eval_items_per_request
95
- ):
96
- obj_str = json.dumps(
97
- dict(
98
- requirements=requirements,
99
- code=obj["input"],
100
- )
87
+ def translate_block(self, input_block: CodeBlock, failure_path: Path | None = None):
88
+ if len(input_block.previous_generations) == 0:
89
+ raise ValueError(
90
+ "Error: Evaluating requirements without previous generations"
91
+ )
92
+ if isinstance(input_block.previous_generations[-1], dict):
93
+ input_str = input_block.previous_generations[-1]["input"]
94
+ else:
95
+ input_str = input_block.previous_generations[-1].original.text
96
+ requirements = json.loads(input_block.text)
97
+ # The requirements are often a list of lists
98
+ if isinstance(requirements[0], list):
99
+ requirements = requirements[0]
100
+ if not requirements:
101
+ log.debug(f"[{input_block.name}] Skipping empty output")
102
+ return []
103
+ if (
104
+ not self.eval_items_per_request
105
+ or len(requirements) < self.eval_items_per_request
106
+ ):
107
+ obj_str = json.dumps(
108
+ dict(
109
+ requirements=requirements,
110
+ code=input_str,
111
+ )
112
+ )
113
+ temp_block = self._split_text(obj_str, input_block.name)
114
+ translated_block = super().translate_block(temp_block, failure_path)
115
+ translated_block.original = input_block
116
+ translated_block.previous_generations = input_block.previous_generations
117
+ return translated_block
118
+ else:
119
+ translated_blocks = []
120
+ translated_str: str
121
+ translate_obj = {}
122
+ for i in range(0, len(requirements), self.eval_items_per_request):
123
+ working_requirements = requirements[i : i + self.eval_items_per_request]
124
+ obj_str = json.dumps(
125
+ dict(
126
+ requirements=working_requirements,
127
+ code=input_str,
101
128
  )
102
- temp_obj["outputs"] = [obj_str]
103
- temp_block = self._janus_object_to_codeblock(temp_obj, name)
104
- translated_block = self.translate_block(temp_block, failure_path)
105
- translated_block.previous_generations[-1] = obj
106
- translated_block.original = self._janus_object_to_codeblock(obj, name)
107
- results.append(translated_block)
108
- else:
109
- for i in range(0, len(requirements), self.eval_items_per_request):
110
- working_requirements = requirements[
111
- i : i + self.eval_items_per_request
112
- ]
113
- obj_str = json.dumps(
114
- dict(
115
- requirements=working_requirements,
116
- code=obj["input"],
117
- )
118
- )
119
- temp_obj["outputs"] = [obj_str]
120
- temp_block = self._janus_object_to_codeblock(temp_obj, name)
121
- translated_block = self.translate_block(temp_block, failure_path)
122
- translated_block.previous_generations[-1] = obj
123
- translated_block.original = self._janus_object_to_codeblock(
124
- obj, name
125
- )
126
- results.append(translated_block)
127
- else:
128
- raise ValueError(f"Error: unable to find janus object: {type(o)}")
129
- return results
129
+ )
130
+ temp_block = self._split_text(obj_str, input_block.name)
131
+ translated_block = super().translate_block(temp_block, failure_path)
132
+ translated_blocks.append(translated_block)
133
+ translate_obj.update(json.loads(translated_block.text))
134
+ translated_str = json.dumps(translate_obj)
135
+
136
+ translated_block = TranslatedCodeBlock(
137
+ input_block,
138
+ self._target_language,
139
+ self,
140
+ self._output_type,
141
+ self._output_label,
142
+ )
143
+ translated_block.text = translated_str
144
+ translated_block.children = translated_blocks
145
+ translated_block.tokens = self._llm.get_num_tokens(translated_str)
146
+ translated_block.translated = True
147
+ return translated_block
130
148
 
131
149
 
132
150
  class InlineCommentEvaluator(Evaluator):
@@ -136,7 +154,13 @@ class InlineCommentEvaluator(Evaluator):
136
154
  with an associated prompt.
137
155
  """
138
156
 
139
- def __init__(self, eval_items_per_request: int | None = None, **kwargs) -> None:
157
+ def __init__(
158
+ self,
159
+ eval_items_per_request: int | None = None,
160
+ input_types: str | set[str] = set(["cloze_comments"]),
161
+ output_type: str = "cloze_comments_eval",
162
+ **kwargs,
163
+ ) -> None:
140
164
  """Initialize the Evaluator class
141
165
 
142
166
  Arguments:
@@ -145,6 +169,7 @@ class InlineCommentEvaluator(Evaluator):
145
169
  model_arguments: Additional arguments to pass to the LLM constructor.
146
170
  max_prompts: The maximum number of prompts to try before giving up.
147
171
  """
172
+ kwargs.update(input_types=input_types, output_type=output_type)
148
173
  super().__init__(**kwargs)
149
174
  self._combiner = JsonCombiner()
150
175
  self._parser = InlineCommentParser()
@@ -176,65 +201,75 @@ class InlineCommentEvaluator(Evaluator):
176
201
  processed_str = re.sub(r"\s*<JANUS_PARTITION>\s*\n", "\n", input_str)
177
202
  return processed_str.strip("\n"), missing_comments
178
203
 
179
- def translate_janus_obj(self, obj: Any, name: str, failure_path: Path | None = None):
204
+ def translate_block(self, input_block: CodeBlock, failure_path: Path | None = None):
180
205
  comment_pattern = r"<(?:INLINE|BLOCK)_COMMENT \w{8}>.*$"
181
- results = []
182
- input_str = obj["input"]
183
- for o in obj["outputs"]:
184
- if isinstance(o, dict):
185
- results += self.translate_janus_obj(o, name, failure_path)
186
- elif isinstance(o, str):
187
- temp_obj = deepcopy(obj)
188
- generated_comments = json.loads(o)
189
- processed_input, missing_comments = self._process_comments(
190
- input_str, generated_comments
191
- )
192
- if missing_comments:
193
- log.info(f"[{name}] Warning: missing {missing_comments} comments")
194
- comments = list(
195
- re.finditer(comment_pattern, processed_input, flags=re.MULTILINE)
196
- )
197
- if not comments:
198
- log.info(f"[{name}] Skipping commentless block")
199
- continue
200
- if (
201
- self.eval_items_per_request is None
202
- or len(comments) < self.eval_items_per_request
203
- ):
204
- temp_obj["outputs"] = [processed_input]
205
- temp_block = self._janus_object_to_codeblock(temp_obj, name)
206
- translated_block = self.translate_block(temp_block, failure_path)
207
- translated_block.previous_generations[-1] = obj
208
- translated_block.original = self._janus_object_to_codeblock(obj, name)
209
- results.append(translated_block)
210
- continue
211
- comment_group_indices = list(
212
- range(0, len(comments), self.eval_items_per_request)
213
- )
214
- log.debug(
215
- f"[{name}] Block contains more than {self.eval_items_per_request}"
216
- f" comments, splitting {len(comments)} comments into"
217
- f" {len(comment_group_indices)} groups"
218
- )
219
- for comment_ind in comment_group_indices:
220
- working_comments = comments[
221
- comment_ind : comment_ind + self.eval_items_per_request
222
- ]
223
- start_idx = working_comments[0].start()
224
- end_idx = working_comments[-1].end()
225
- prefix = processed_input[:start_idx]
226
- keeper = processed_input[start_idx:end_idx]
227
- suffix = processed_input[end_idx:]
228
-
229
- # Strip all comment placeholders outside of the section of interest
230
- prefix = re.sub(comment_pattern, "", prefix, flags=re.MULTILINE)
231
- suffix = re.sub(comment_pattern, "", suffix, flags=re.MULTILINE)
232
- temp_obj["outputs"] = [prefix + keeper + suffix]
233
- temp_block = self._janus_object_to_codeblock(temp_obj, name)
234
- translated_block = self.translate_block(temp_block, failure_path)
235
- translated_block.previous_generations[-1] = obj
236
- translated_block.original = self._janus_object_to_codeblock(obj, name)
237
- results.append(translated_block)
238
- else:
239
- raise ValueError(f"Error: unrecognized janus object type: {type(o)}")
240
- return results
206
+ if len(input_block.previous_generations) == 0:
207
+ raise ValueError(
208
+ "Error: cannot evaluate block, no previous generations found"
209
+ )
210
+ if isinstance(input_block.previous_generations[-1], dict):
211
+ input_str = input_block.previous_generations[-1]["input"]
212
+ else:
213
+ input_str = input_block.previous_generations[-1].original.text
214
+ generated_comments = json.loads(input_block.text)
215
+ processed_input, missing_comments = self._process_comments(
216
+ input_str, generated_comments
217
+ )
218
+ if missing_comments:
219
+ log.info(f"[{input_block.name}] Warning: missing {missing_comments} comments")
220
+ comments = list(re.finditer(comment_pattern, processed_input, flags=re.MULTILINE))
221
+ if not comments:
222
+ log.info(f"[{input_block.name}] Skipping commentless block")
223
+ return []
224
+ if (
225
+ self.eval_items_per_request is None
226
+ or len(comments) < self.eval_items_per_request
227
+ ):
228
+ temp_block = self._split_text(processed_input, input_block.name)
229
+ translated_block = super().translate_block(temp_block, failure_path)
230
+ translated_block.original = input_block
231
+ translated_block.previous_generations = input_block.previous_generations
232
+ return translated_block
233
+ else:
234
+ comment_group_indices = list(
235
+ range(0, len(comments), self.eval_items_per_request)
236
+ )
237
+ log.debug(
238
+ f"[{input_block.name}]"
239
+ f" Block contains more than {self.eval_items_per_request}"
240
+ f" comments, splitting {len(comments)} comments into"
241
+ f" {len(comment_group_indices)} groups"
242
+ )
243
+ translated_blocks = []
244
+ translated_str: str
245
+ translate_obj = {}
246
+ for comment_ind in comment_group_indices:
247
+ working_comments = comments[
248
+ comment_ind : comment_ind + self.eval_items_per_request
249
+ ]
250
+ start_idx = working_comments[0].start()
251
+ end_idx = working_comments[-1].end()
252
+ prefix = processed_input[:start_idx]
253
+ keeper = processed_input[start_idx:end_idx]
254
+ suffix = processed_input[end_idx:]
255
+
256
+ # Strip all comment placeholders outside of the section of interest
257
+ prefix = re.sub(comment_pattern, "", prefix, flags=re.MULTILINE)
258
+ suffix = re.sub(comment_pattern, "", suffix, flags=re.MULTILINE)
259
+ temp_block = self._split_text(prefix + keeper + suffix, input_block.name)
260
+ translated_block = super().translate_block(temp_block, failure_path)
261
+ translated_blocks.append(translated_block)
262
+ translate_obj.update(json.loads(translated_block.text))
263
+ translated_str = json.dumps(translate_obj)
264
+ translated_block = TranslatedCodeBlock(
265
+ input_block,
266
+ self._target_language,
267
+ self,
268
+ self._output_type,
269
+ self._output_label,
270
+ )
271
+ translated_block.children = translated_blocks
272
+ translated_block.text = translated_str
273
+ translated_block.tokens = self._llm.get_num_tokens(translated_str)
274
+ translated_block.translated = True
275
+ return translated_block
@@ -6,7 +6,10 @@ log = create_logger(__name__)
6
6
 
7
7
 
8
8
  class Partitioner(Converter):
9
- def __init__(self, partition_token_limit: int, **kwargs):
9
+ def __init__(
10
+ self, partition_token_limit: int, output_type: str = "partition", **kwargs
11
+ ):
12
+ kwargs.update(output_type=output_type)
10
13
  super().__init__(**kwargs)
11
14
  self.set_prompts("partition")
12
15
  self._load_model()
@@ -0,0 +1,29 @@
1
+ from pathlib import Path
2
+
3
+ from janus.converter.converter import Converter
4
+ from janus.language.block import CodeBlock, TranslatedCodeBlock
5
+
6
+
7
+ class ConverterPassthrough(Converter):
8
+ def __init__(self, **kwargs) -> None:
9
+ super().__init__(**kwargs)
10
+
11
+ def translate_block(
12
+ self, input_block: CodeBlock, failure_path: Path | None = None
13
+ ) -> TranslatedCodeBlock:
14
+ self._output_label = input_block.block_label
15
+ self._output_type = input_block.block_type
16
+ res = super().translate_block(input_block, failure_path)
17
+ if isinstance(input_block.previous_generations[-1], dict):
18
+ res.original = self._split_text(
19
+ input_block.previous_generations[-1]["input"], res.name
20
+ )
21
+ else:
22
+ res.original = input_block.previous_generations[-1].original
23
+ res.previous_generations = input_block.previous_generations[:-1]
24
+ return res
25
+
26
+ def _add_translation(self, block: TranslatedCodeBlock) -> None:
27
+ block.text = block.original.text
28
+ block.tokens = block.original.tokens
29
+ block.translated = True
@@ -0,0 +1,74 @@
1
+ from pathlib import Path
2
+
3
+ from janus.converter.converter import Converter
4
+ from janus.language.block import BlockCollection, CodeBlock, TranslatedCodeBlock
5
+
6
+
7
+ class ConverterPool(Converter):
8
+ def __init__(self, *args, **kwargs):
9
+ if len(args) == 0:
10
+ raise ValueError("Error: Converter chain must be passed at least 1 converter")
11
+ for converter in args:
12
+ if not isinstance(converter, Converter):
13
+ raise ValueError(f"Error: unrecognized type: {type(converter)}")
14
+ self._converters = args
15
+ if "source_language" in kwargs:
16
+ for c in self._converters:
17
+ c.set_source_language(kwargs["source_language"])
18
+ if "model" in kwargs:
19
+ for c in self._converters:
20
+ c.set_model(kwargs["model"])
21
+ super().__init__(**kwargs)
22
+
23
+ def translate_blocks(
24
+ self, input_blocks: CodeBlock | BlockCollection, failure_path: Path | None = None
25
+ ):
26
+ output_blocks = []
27
+ for c in self._converters:
28
+ collection = c.translate_blocks(input_blocks)
29
+ for b in collection.blocks:
30
+ c._combiner.combine(b)
31
+ output_blocks += collection.blocks
32
+ return BlockCollection(output_blocks, input_blocks.previous_generations)
33
+
34
+ def _get_output_obj(
35
+ self,
36
+ block: TranslatedCodeBlock | BlockCollection | dict,
37
+ combine_children: bool = True,
38
+ include_previous_outputs: bool = True,
39
+ ) -> dict[str, int | float | str | dict[str, str] | dict[str, float]]:
40
+ outputs = []
41
+ for b in block.blocks:
42
+ for c in self._converters:
43
+ if c == b.converter:
44
+ outputs.append(c._get_output_obj(b, c._combine_output, False))
45
+ break
46
+
47
+ def _get_input(block):
48
+ if isinstance(block, BlockCollection):
49
+ return self._combine_inputs([_get_input(b) for b in block.blocks])
50
+ return block.original.text or ""
51
+
52
+ out = dict(
53
+ input=_get_input(block),
54
+ metadata=dict(
55
+ cost=block.total_cost,
56
+ processing_time=block.total_processing_time,
57
+ num_requests=block.total_num_requests,
58
+ input_tokens=block.total_request_input_tokens,
59
+ output_tokens=block.total_request_output_tokens,
60
+ converter_name=self.__class__.__name__,
61
+ type=block.block_type,
62
+ label=block.block_label,
63
+ ),
64
+ outputs=outputs,
65
+ )
66
+ if include_previous_outputs and len(block.previous_generations) > 0:
67
+ intermediate_outputs = [
68
+ self._get_output_obj(g, combine_children, False)
69
+ for g in block.previous_generations
70
+ if isinstance(g, dict)
71
+ ]
72
+ if len(intermediate_outputs) > 0:
73
+ out["intermediate_outputs"] = intermediate_outputs
74
+ return out
@@ -12,7 +12,10 @@ class RequirementsDocumenter(Documenter):
12
12
  A class that translates code from one programming language to its requirements.
13
13
  """
14
14
 
15
- def __init__(self, combine_output: bool = False, **kwargs):
15
+ def __init__(
16
+ self, combine_output: bool = False, output_type: str = "requirements", **kwargs
17
+ ):
18
+ kwargs.update(output_type=output_type)
16
19
  super().__init__(combine_output=combine_output, **kwargs)
17
20
  self.set_prompts("requirements")
18
21
  self._combiner = ChunkCombiner()
@@ -36,6 +36,7 @@ class TestCombiner(unittest.TestCase):
36
36
  self.translated_block = TranslatedCodeBlock(
37
37
  self.block,
38
38
  language="python",
39
+ converter=None,
39
40
  )
40
41
 
41
42
  def test_combine(self):
janus/language/block.py CHANGED
@@ -1,9 +1,12 @@
1
1
  from functools import total_ordering
2
- from typing import ForwardRef, Hashable, Optional, Tuple
2
+ from typing import TYPE_CHECKING, ForwardRef, Hashable, Optional, Tuple
3
3
 
4
4
  from janus.language.node import NodeType
5
5
  from janus.utils.logger import create_logger
6
6
 
7
+ if TYPE_CHECKING:
8
+ from janus.converter.converter import Converter
9
+
7
10
  log = create_logger(__name__)
8
11
 
9
12
 
@@ -47,6 +50,8 @@ class CodeBlock:
47
50
  affixes: Tuple[str, str] = ("", ""),
48
51
  context_tags: dict[str, str] = {},
49
52
  previous_generations: list["TranslatedCodeBlock"] = [],
53
+ block_type: str | None = None,
54
+ block_label: str | None = None,
50
55
  ) -> None:
51
56
  self.id: Hashable = id
52
57
  self.name: Optional[str] = name
@@ -67,6 +72,8 @@ class CodeBlock:
67
72
  self.omit_prefix = True
68
73
  self.omit_suffix = False
69
74
  self.previous_generations = previous_generations
75
+ self.block_type = block_type
76
+ self.block_label = block_label
70
77
 
71
78
  if self.children:
72
79
  self.children[0].omit_prefix = False
@@ -186,12 +193,23 @@ class TranslatedCodeBlock(CodeBlock):
186
193
  translated: Whether this block has been successfully translated
187
194
  """
188
195
 
189
- def __init__(self, original: CodeBlock, language: str) -> None:
196
+ def __init__(
197
+ self,
198
+ original: CodeBlock,
199
+ language: str,
200
+ converter: ForwardRef("Converter"),
201
+ block_type: str | None = None,
202
+ block_label: str | None = None,
203
+ ) -> None:
190
204
  """Create an "empty" `TranslatedCodeBlock` from the given original
191
205
 
192
206
  Arguments:
193
207
  original: The original code block
194
208
  language: The language to translate to
209
+ converter: the converter used to translate
210
+ block_type: type of the block
211
+ block_label: label for block
212
+ (for mapping outputs to inputs through ConverterChain)
195
213
 
196
214
  Returns:
197
215
  A `TranslatedCodeBlock` with the same attributes as the original, except
@@ -209,12 +227,17 @@ class TranslatedCodeBlock(CodeBlock):
209
227
  end_byte=None,
210
228
  tokens=0,
211
229
  children=[
212
- TranslatedCodeBlock(child, language) for child in original.children
230
+ TranslatedCodeBlock(child, language, block_type, block_label)
231
+ for child in original.children
213
232
  ],
214
233
  affixes=original.affixes,
215
234
  previous_generations=original.previous_generations,
235
+ block_type=block_type,
236
+ block_label=block_label,
216
237
  )
238
+
217
239
  self.original = original
240
+ self.converter = converter
218
241
 
219
242
  self.complete = original.complete
220
243
  self.translated = False
@@ -279,6 +302,11 @@ class TranslatedCodeBlock(CodeBlock):
279
302
  children_sum = sum(c.total_num_requests for c in self.children)
280
303
  return children_sum + self.num_requests
281
304
 
305
+ @property
306
+ def total_processing_time(self) -> float:
307
+ children_sum = sum(c.total_processing_time for c in self.children)
308
+ return children_sum + self.processing_time
309
+
282
310
  @property
283
311
  def translation_completed(self) -> bool:
284
312
  """Whether or not the code block was successfully translated
@@ -317,6 +345,8 @@ class TranslatedCodeBlock(CodeBlock):
317
345
  children=[child.to_codeblock() for child in self.children],
318
346
  affixes=self.affixes,
319
347
  previous_generations=self.previous_generations + [self],
348
+ block_type=self.block_type,
349
+ block_label=self.block_label,
320
350
  )
321
351
 
322
352
  def __iadd__(self, other):
@@ -326,3 +356,54 @@ class TranslatedCodeBlock(CodeBlock):
326
356
  self.request_input_tokens += other.request_input_tokens
327
357
  self.request_output_tokens += other.request_output_tokens
328
358
  return self
359
+
360
+
361
+ class BlockCollection:
362
+ def __init__(
363
+ self,
364
+ blocks: list[CodeBlock],
365
+ previous_generations: list[ForwardRef("BlockCollection")] = [],
366
+ ):
367
+ self.blocks = blocks
368
+ self.previous_generations = previous_generations
369
+
370
+ def to_codeblock(self) -> ForwardRef("BlockCollection"):
371
+ return BlockCollection(
372
+ [b.to_codeblock() for b in self.blocks], self.previous_generations + [self]
373
+ )
374
+
375
+ @property
376
+ def total_cost(self):
377
+ return sum(b.total_cost for b in self.blocks)
378
+
379
+ @property
380
+ def total_processing_time(self):
381
+ return sum(b.total_processing_time for b in self.blocks)
382
+
383
+ @property
384
+ def total_request_input_tokens(self):
385
+ return sum(b.total_request_input_tokens for b in self.blocks)
386
+
387
+ @property
388
+ def total_request_output_tokens(self):
389
+ return sum(b.total_request_output_tokens for b in self.blocks)
390
+
391
+ @property
392
+ def total_num_requests(self):
393
+ return sum(b.total_num_requests for b in self.blocks)
394
+
395
+ @property
396
+ def block_type(self):
397
+ return None
398
+
399
+ @property
400
+ def block_label(self):
401
+ return None
402
+
403
+ @property
404
+ def translation_completed(self):
405
+ return all(b.translation_completed for b in self.blocks)
406
+
407
+ @property
408
+ def complete(self):
409
+ return all(b.complete for b in self.blocks)
@@ -44,12 +44,18 @@ COST_PER_1K_TOKENS: dict[str, dict[str, float]] = {
44
44
  "anthropic.claude-instant-v1": {"input": 0.0008, "output": 0.0024},
45
45
  "anthropic.claude-3-haiku-20240307-v1:0": {"input": 0.00025, "output": 0.00125},
46
46
  "anthropic.claude-3-sonnet-20240229-v1:0": {"input": 0.003, "output": 0.015},
47
+ "anthropic.claude-3-5-sonnet-20240620-v1:0": {"input": 0.003, "output": 0.015},
48
+ "anthropic.claude-3-5-sonnet-20241022-v2:0": {"input": 0.003, "output": 0.015},
47
49
  "meta.llama2-13b-chat-v1": {"input": 0.00075, "output": 0.001},
48
50
  "meta.llama2-70b-chat-v1": {"input": 0.00195, "output": 0.00256},
49
51
  "meta.llama2-13b-v1": {"input": 0.0, "output": 0.0},
50
52
  "meta.llama2-70b-v1": {"input": 0.00265, "output": 0.0035},
51
53
  "meta.llama3-8b-instruct-v1:0": {"input": 0.0003, "output": 0.0006},
52
54
  "meta.llama3-70b-instruct-v1:0": {"input": 0.00265, "output": 0.0035},
55
+ "meta.llama3-3-70b-instruct-v1:0": {"input": 0.00072, "output": 0.00072},
56
+ "amazon.nova-lite-v1:0": {"input": 0.00006, "output": 0.00024},
57
+ "amazon.nova-micro-v1:0": {"input": 0.000035, "output": 0.00014},
58
+ "amazon.nova-pro-v1:0": {"input": 0.0008, "output": 0.0032},
53
59
  "amazon.titan-text-lite-v1": {"input": 0.00015, "output": 0.0002},
54
60
  "amazon.titan-text-express-v1": {"input": 0.0002, "output": 0.0006},
55
61
  "ai21.j2-mid-v1": {"input": 0.0125, "output": 0.0125},