github-actions[bot] commited on
Commit
3cf33dd
Β·
1 Parent(s): 3edeefe

deploy: switch to chatterbox requirements @ d36fc80

Browse files
Files changed (1) hide show
  1. tools_api/scene_writer.py +86 -15
tools_api/scene_writer.py CHANGED
@@ -1,18 +1,25 @@
1
  """
2
- Hook β†’ DramaBox scene drafting via AWS Bedrock (Amazon Nova).
3
 
4
  The frontend's "Adapt to my niche" step POSTs to /api/tools/scene; this turns a
5
  viral-hook structure plus the creator's niche into a directed DramaBox scene.
 
 
 
6
  """
7
  from __future__ import annotations
8
 
9
  import os
10
 
11
- from steps.lang._shared import bedrock_converse
 
 
 
 
 
 
12
 
13
- # Amazon Nova Micro β€” the quickest/cheapest Nova text model. Override via env;
14
- # if Bedrock asks for an inference profile, set "us.amazon.nova-micro-v1:0".
15
- SCENE_MODEL = os.getenv("SCENE_BEDROCK_MODEL", "amazon.nova-micro-v1:0")
16
 
17
  _SYSTEM_PROMPT = """You are a scriptwriter for DramaBox, a text-to-speech engine that performs short, emotionally directed monologues.
18
 
@@ -31,33 +38,97 @@ CONTENT:
31
  - Output ONLY the scene text. No preamble, no markdown, no surrounding quotes."""
32
 
33
 
34
- def write_scene(
35
- *,
36
  hook_pattern: str,
37
  hook_title: str,
38
  register: str,
39
  register_label: str,
40
  niche: str,
41
  ) -> str:
42
- """Draft a directed DramaBox scene with Bedrock Nova. Raises on empty output."""
43
- user_prompt = (
44
  f'Viral hook: "{hook_title}"\n'
45
  f"Hook structure: {hook_pattern}\n"
46
  f"Emotional register: {register_label} ({register})\n"
47
  f"Creator's niche / topic: {niche}\n\n"
48
  "Write the DramaBox scene now."
49
  )
 
 
 
 
 
 
 
 
 
 
 
 
50
  raw = bedrock_converse(
51
  _SYSTEM_PROMPT,
52
  user_prompt,
53
- temperature=0.8,
54
  step="dramabox_scene",
55
  model_id=SCENE_MODEL,
56
  )
57
- scene = (raw or "").strip()
58
- # Strip a markdown fence if the model wrapped its output.
59
- if scene.startswith("```"):
60
- scene = scene.split("\n", 1)[-1].rsplit("```", 1)[0].strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  if not scene:
62
- raise RuntimeError("Scene model returned an empty response.")
63
  return scene
 
1
  """
2
+ Hook β†’ DramaBox scene drafting via an LLM.
3
 
4
  The frontend's "Adapt to my niche" step POSTs to /api/tools/scene; this turns a
5
  viral-hook structure plus the creator's niche into a directed DramaBox scene.
6
+
7
+ Provider chain: AWS Bedrock (primary) β†’ Pollinations (fallback). If both fail,
8
+ the endpoint errors and the frontend falls back to its offline template engine.
9
  """
10
  from __future__ import annotations
11
 
12
  import os
13
 
14
+ from steps.lang._shared import MODEL, bedrock_converse, build_client, log_llm_call
15
+
16
+ # Bedrock model for scene drafting. Defaults to qwen.qwen3-next-80b-a3b β€” the
17
+ # same model the backend's translation step uses, so it's known-good on this
18
+ # account (works with Converse, no inference-profile requirement). Override via
19
+ # env SCENE_BEDROCK_MODEL.
20
+ SCENE_MODEL = os.getenv("SCENE_BEDROCK_MODEL", "qwen.qwen3-next-80b-a3b")
21
 
22
+ _TEMPERATURE = 0.8
 
 
23
 
24
  _SYSTEM_PROMPT = """You are a scriptwriter for DramaBox, a text-to-speech engine that performs short, emotionally directed monologues.
25
 
 
38
  - Output ONLY the scene text. No preamble, no markdown, no surrounding quotes."""
39
 
40
 
41
+ def _build_user_prompt(
 
42
  hook_pattern: str,
43
  hook_title: str,
44
  register: str,
45
  register_label: str,
46
  niche: str,
47
  ) -> str:
48
+ return (
 
49
  f'Viral hook: "{hook_title}"\n'
50
  f"Hook structure: {hook_pattern}\n"
51
  f"Emotional register: {register_label} ({register})\n"
52
  f"Creator's niche / topic: {niche}\n\n"
53
  "Write the DramaBox scene now."
54
  )
55
+
56
+
57
+ def _clean(scene: str) -> str:
58
+ """Strip whitespace and a markdown fence if the model wrapped its output."""
59
+ s = (scene or "").strip()
60
+ if s.startswith("```"):
61
+ s = s.split("\n", 1)[-1].rsplit("```", 1)[0].strip()
62
+ return s
63
+
64
+
65
+ def _bedrock_scene(user_prompt: str) -> str:
66
+ """Primary provider β€” AWS Bedrock."""
67
  raw = bedrock_converse(
68
  _SYSTEM_PROMPT,
69
  user_prompt,
70
+ temperature=_TEMPERATURE,
71
  step="dramabox_scene",
72
  model_id=SCENE_MODEL,
73
  )
74
+ return _clean(raw)
75
+
76
+
77
+ def _pollinations_scene(user_prompt: str) -> str:
78
+ """Fallback provider β€” Pollinations (OpenAI-compatible, see _shared.py)."""
79
+ client = build_client()
80
+ response = client.chat.completions.create(
81
+ model=MODEL,
82
+ messages=[
83
+ {"role": "system", "content": _SYSTEM_PROMPT},
84
+ {"role": "user", "content": user_prompt},
85
+ ],
86
+ temperature=_TEMPERATURE,
87
+ )
88
+ raw = (response.choices[0].message.content or "").strip()
89
+ log_llm_call(
90
+ step="dramabox_scene",
91
+ provider="pollinations",
92
+ model=MODEL,
93
+ system_prompt=_SYSTEM_PROMPT,
94
+ user_prompt=user_prompt,
95
+ response=raw,
96
+ temperature=_TEMPERATURE,
97
+ )
98
+ return _clean(raw)
99
+
100
+
101
+ def write_scene(
102
+ *,
103
+ hook_pattern: str,
104
+ hook_title: str,
105
+ register: str,
106
+ register_label: str,
107
+ niche: str,
108
+ ) -> str:
109
+ """Draft a directed DramaBox scene β€” Bedrock primary, Pollinations fallback.
110
+
111
+ Raises if both providers fail; the caller surfaces a 500 and the frontend
112
+ falls back to its offline template engine.
113
+ """
114
+ user_prompt = _build_user_prompt(
115
+ hook_pattern, hook_title, register, register_label, niche
116
+ )
117
+
118
+ # Primary β€” Bedrock.
119
+ try:
120
+ scene = _bedrock_scene(user_prompt)
121
+ if scene:
122
+ return scene
123
+ print("[scene] Bedrock returned an empty scene β€” trying Pollinations.")
124
+ except Exception as e: # noqa: BLE001
125
+ print(f"[scene] Bedrock failed ({e}) β€” trying Pollinations.")
126
+
127
+ # Fallback β€” Pollinations.
128
+ try:
129
+ scene = _pollinations_scene(user_prompt)
130
+ except Exception as e: # noqa: BLE001
131
+ raise RuntimeError(f"Bedrock and Pollinations both failed: {e}")
132
  if not scene:
133
+ raise RuntimeError("Bedrock and Pollinations both returned empty scenes.")
134
  return scene