forked from ChelseaKR/outcome-receipts
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodel_draft.py
More file actions
84 lines (67 loc) · 3.23 KB
/
Copy pathmodel_draft.py
File metadata and controls
84 lines (67 loc) · 3.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""Policy-gated optional Claude-on-Bedrock narrative drafting seam."""
from __future__ import annotations
import importlib
from collections.abc import Sequence
from typing import Protocol, cast
from outcome_receipts.draft import draft_template
from outcome_receipts.models import DraftingSpec, Figure
class DraftingPolicyError(ValueError):
"""Cloud drafting was configured but not explicitly authorized for this run."""
class ConverseClient(Protocol):
def converse(self, **kwargs: object) -> dict[str, object]:
raise NotImplementedError
class NarrativeDrafter(Protocol):
def draft(self, template: str, figures: Sequence[Figure]) -> str:
raise NotImplementedError
class BedrockDrafter:
"""Send only the filled template and receipted display allowlist to Bedrock."""
def __init__(
self, model_id: str, *, max_tokens: int = 1200, client: ConverseClient | None = None
) -> None:
self.model_id = model_id
self.max_tokens = max_tokens
self._injected_client = client
def _client(self) -> ConverseClient:
if self._injected_client is not None:
return self._injected_client
try:
boto3 = importlib.import_module("boto3")
except ModuleNotFoundError as exc:
raise RuntimeError(
"Bedrock drafting requires the optional 'bedrock' dependency"
) from exc
return cast(ConverseClient, boto3.client("bedrock-runtime"))
def draft(self, template: str, figures: Sequence[Figure]) -> str:
baseline = draft_template(template, figures)
displays = ", ".join(figure.display for figure in figures)
prompt = (
"Rewrite the supplied funder-report narrative for clarity. Do not add, remove, "
"spell out, round, or alter any numeric claim. Use only numeric displays in the "
f"allowlist.\nALLOWLIST: {displays}\nNARRATIVE:\n{baseline}"
)
response = self._client().converse(
modelId=self.model_id,
messages=[{"role": "user", "content": [{"text": prompt}]}],
inferenceConfig={"maxTokens": self.max_tokens, "temperature": 0},
)
try:
output = cast(dict[str, object], response["output"])
message = cast(dict[str, object], output["message"])
content = cast(list[dict[str, object]], message["content"])
text = content[0]["text"]
except (KeyError, IndexError, TypeError) as exc:
raise RuntimeError("Bedrock returned no narrative text") from exc
if not isinstance(text, str) or not text.strip():
raise RuntimeError("Bedrock returned no narrative text")
return text.strip()
def build_narrative_drafter(
policy: DraftingSpec, *, allow_cloud: bool, client: ConverseClient | None = None
) -> NarrativeDrafter | None:
"""Build the opted-in provider, or return the deterministic default seam."""
if not policy.enabled or policy.provider == "deterministic":
return None
if not allow_cloud:
raise DraftingPolicyError(
"Bedrock drafting is enabled in config but this run lacks --allow-cloud-drafting"
)
return BedrockDrafter(policy.model_id, max_tokens=policy.max_tokens, client=client)