Commit ·
2f1910b
1
Parent(s): 7fdbbb5
fix: harden OpenBMB backend runtime
Browse files- app.py +198 -27
- puppet_theater/__init__.py +4 -0
- puppet_theater/backends.py +166 -24
- puppet_theater/director.py +2 -1
- puppet_theater/models.py +2 -0
- puppet_theater/session.py +9 -1
app.py
CHANGED
|
@@ -8,10 +8,12 @@ from puppet_theater import (
|
|
| 8 |
DEFAULT_OPENBMB_MODEL_ID,
|
| 9 |
TheaterSession,
|
| 10 |
create_show_from_premise,
|
|
|
|
| 11 |
request_finale,
|
| 12 |
run_one_beat,
|
| 13 |
summon_actor,
|
| 14 |
throw_prop,
|
|
|
|
| 15 |
)
|
| 16 |
|
| 17 |
|
|
@@ -29,9 +31,16 @@ EMPTY_STAGE = """
|
|
| 29 |
EMPTY_TRANSCRIPT = "No show yet. The transcript will appear here."
|
| 30 |
EMPTY_DIRECTOR_LOG = "No director notes yet."
|
| 31 |
EMPTY_TRACE = "No trace events yet."
|
| 32 |
-
EMPTY_BACKEND =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
BACKEND_CHOICES = ["deterministic", "openbmb"]
|
| 34 |
OPENBMB_MODEL_ID = os.getenv("OPENBMB_MODEL_ID", DEFAULT_OPENBMB_MODEL_ID)
|
|
|
|
|
|
|
| 35 |
PLAYBACK_DELAY_SECONDS = 0.75
|
| 36 |
PROP_EMOJI = {
|
| 37 |
"rubber duck": "🐤",
|
|
@@ -920,24 +929,63 @@ def normalize_backend_name(backend_name: str | None) -> str:
|
|
| 920 |
return backend_name if backend_name in BACKEND_CHOICES else "deterministic"
|
| 921 |
|
| 922 |
|
| 923 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 924 |
if session is None:
|
| 925 |
return None
|
| 926 |
session.backend_name = normalize_backend_name(backend_name)
|
| 927 |
session.backend_model_id = OPENBMB_MODEL_ID if session.backend_name == "openbmb" else None
|
|
|
|
|
|
|
| 928 |
return session
|
| 929 |
|
| 930 |
|
| 931 |
-
def render_backend_settings(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 932 |
selected_backend = normalize_backend_name(backend_name)
|
| 933 |
active_backend = session.backend_name if session is not None else selected_backend
|
| 934 |
model_id = session.backend_model_id if session is not None else None
|
| 935 |
if active_backend == "openbmb":
|
| 936 |
model_id = model_id or OPENBMB_MODEL_ID
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 937 |
return (
|
| 938 |
f"Active backend: {active_backend}\n"
|
| 939 |
"Available backends: deterministic, openbmb\n"
|
| 940 |
f"OpenBMB model id: {model_id or 'not selected'}\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 941 |
"Fallback behavior: invalid model output falls back to deterministic actor lines"
|
| 942 |
)
|
| 943 |
|
|
@@ -952,9 +1000,17 @@ def render_outputs(session: TheaterSession | None):
|
|
| 952 |
)
|
| 953 |
|
| 954 |
|
| 955 |
-
def create_show(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 956 |
premise = premise.strip()
|
| 957 |
selected_backend = normalize_backend_name(backend_name)
|
|
|
|
|
|
|
| 958 |
if not premise:
|
| 959 |
return (
|
| 960 |
None,
|
|
@@ -962,13 +1018,15 @@ def create_show(premise: str, session: TheaterSession | None, backend_name: str)
|
|
| 962 |
"No premise yet. Add a premise to raise the curtain.",
|
| 963 |
EMPTY_DIRECTOR_LOG,
|
| 964 |
EMPTY_TRACE,
|
| 965 |
-
render_backend_settings(None, selected_backend),
|
| 966 |
)
|
| 967 |
|
| 968 |
session = create_show_from_premise(
|
| 969 |
premise,
|
| 970 |
backend_name=selected_backend,
|
| 971 |
backend_model_id=OPENBMB_MODEL_ID if selected_backend == "openbmb" else None,
|
|
|
|
|
|
|
| 972 |
)
|
| 973 |
return session, *render_outputs(session)
|
| 974 |
|
|
@@ -984,11 +1042,19 @@ def reset_show():
|
|
| 984 |
EMPTY_DIRECTOR_LOG,
|
| 985 |
EMPTY_TRACE,
|
| 986 |
"deterministic",
|
|
|
|
|
|
|
|
|
|
| 987 |
EMPTY_BACKEND,
|
| 988 |
)
|
| 989 |
|
| 990 |
|
| 991 |
-
def advance_one_beat(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 992 |
if session is None:
|
| 993 |
return (
|
| 994 |
None,
|
|
@@ -996,15 +1062,21 @@ def advance_one_beat(session: TheaterSession | None, backend_name: str):
|
|
| 996 |
"Create a show before running a beat.",
|
| 997 |
EMPTY_DIRECTOR_LOG,
|
| 998 |
EMPTY_TRACE,
|
| 999 |
-
render_backend_settings(None, backend_name),
|
| 1000 |
)
|
| 1001 |
|
| 1002 |
-
session = apply_backend_selection(session, backend_name)
|
| 1003 |
session = run_one_beat(session)
|
| 1004 |
return session, *render_outputs(session)
|
| 1005 |
|
| 1006 |
|
| 1007 |
-
def advance_full_act(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1008 |
if session is None:
|
| 1009 |
yield (
|
| 1010 |
None,
|
|
@@ -1012,24 +1084,46 @@ def advance_full_act(session: TheaterSession | None, backend_name: str):
|
|
| 1012 |
"Create a show before running the full act.",
|
| 1013 |
EMPTY_DIRECTOR_LOG,
|
| 1014 |
EMPTY_TRACE,
|
| 1015 |
-
render_backend_settings(None, backend_name),
|
| 1016 |
)
|
| 1017 |
return
|
| 1018 |
|
| 1019 |
-
session = apply_backend_selection(session, backend_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1020 |
if session.beat_index >= session.max_beats:
|
|
|
|
|
|
|
| 1021 |
session = run_one_beat(session)
|
|
|
|
|
|
|
| 1022 |
yield session, *render_outputs(session)
|
| 1023 |
return
|
| 1024 |
|
| 1025 |
while session.beat_index < session.max_beats:
|
|
|
|
|
|
|
| 1026 |
session = run_one_beat(session)
|
|
|
|
|
|
|
| 1027 |
yield session, *render_outputs(session)
|
| 1028 |
if session.beat_index < session.max_beats:
|
| 1029 |
sleep(PLAYBACK_DELAY_SECONDS)
|
| 1030 |
|
| 1031 |
|
| 1032 |
-
def throw_audience_prop(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1033 |
if session is None:
|
| 1034 |
return (
|
| 1035 |
None,
|
|
@@ -1037,15 +1131,21 @@ def throw_audience_prop(session: TheaterSession | None, prop_name: str, backend_
|
|
| 1037 |
"Create a show before throwing a prop.",
|
| 1038 |
EMPTY_DIRECTOR_LOG,
|
| 1039 |
EMPTY_TRACE,
|
| 1040 |
-
render_backend_settings(None, backend_name),
|
| 1041 |
)
|
| 1042 |
|
| 1043 |
-
session = apply_backend_selection(session, backend_name)
|
| 1044 |
session = throw_prop(session, prop_name)
|
| 1045 |
return session, *render_outputs(session)
|
| 1046 |
|
| 1047 |
|
| 1048 |
-
def summon_audience_actor(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1049 |
if session is None:
|
| 1050 |
return (
|
| 1051 |
None,
|
|
@@ -1053,15 +1153,20 @@ def summon_audience_actor(session: TheaterSession | None, actor_name: str, backe
|
|
| 1053 |
"Create a show before summoning an actor.",
|
| 1054 |
EMPTY_DIRECTOR_LOG,
|
| 1055 |
EMPTY_TRACE,
|
| 1056 |
-
render_backend_settings(None, backend_name),
|
| 1057 |
)
|
| 1058 |
|
| 1059 |
-
session = apply_backend_selection(session, backend_name)
|
| 1060 |
session = summon_actor(session, actor_name)
|
| 1061 |
return session, *render_outputs(session)
|
| 1062 |
|
| 1063 |
|
| 1064 |
-
def request_audience_finale(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1065 |
if session is None:
|
| 1066 |
return (
|
| 1067 |
None,
|
|
@@ -1069,14 +1174,43 @@ def request_audience_finale(session: TheaterSession | None, backend_name: str):
|
|
| 1069 |
"Create a show before requesting a finale.",
|
| 1070 |
EMPTY_DIRECTOR_LOG,
|
| 1071 |
EMPTY_TRACE,
|
| 1072 |
-
render_backend_settings(None, backend_name),
|
| 1073 |
)
|
| 1074 |
|
| 1075 |
-
session = apply_backend_selection(session, backend_name)
|
| 1076 |
session = request_finale(session)
|
| 1077 |
return session, *render_outputs(session)
|
| 1078 |
|
| 1079 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1080 |
with gr.Blocks(title="AI Puppet Theater") as app:
|
| 1081 |
session_state = gr.State(None)
|
| 1082 |
|
|
@@ -1163,43 +1297,77 @@ with gr.Blocks(title="AI Puppet Theater") as app:
|
|
| 1163 |
label="Actor Line Backend",
|
| 1164 |
interactive=True,
|
| 1165 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1166 |
backend_output = gr.Textbox(
|
| 1167 |
value=EMPTY_BACKEND,
|
| 1168 |
label="Model Settings",
|
| 1169 |
-
lines=
|
| 1170 |
interactive=False,
|
| 1171 |
)
|
| 1172 |
|
| 1173 |
create_button.click(
|
| 1174 |
create_show,
|
| 1175 |
-
inputs=[premise_input, session_state, backend_select],
|
| 1176 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1177 |
)
|
| 1178 |
run_one_button.click(
|
| 1179 |
advance_one_beat,
|
| 1180 |
-
inputs=[session_state, backend_select],
|
| 1181 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1182 |
)
|
| 1183 |
run_full_button.click(
|
| 1184 |
advance_full_act,
|
| 1185 |
-
inputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1186 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1187 |
)
|
| 1188 |
throw_prop_button.click(
|
| 1189 |
throw_audience_prop,
|
| 1190 |
-
inputs=[session_state, prop_input, backend_select],
|
| 1191 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1192 |
)
|
| 1193 |
summon_actor_button.click(
|
| 1194 |
summon_audience_actor,
|
| 1195 |
-
inputs=[session_state, actor_input, backend_select],
|
| 1196 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1197 |
)
|
| 1198 |
request_finale_button.click(
|
| 1199 |
request_audience_finale,
|
| 1200 |
-
inputs=[session_state, backend_select],
|
| 1201 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1202 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1203 |
reset_button.click(
|
| 1204 |
reset_show,
|
| 1205 |
outputs=[
|
|
@@ -1212,6 +1380,9 @@ with gr.Blocks(title="AI Puppet Theater") as app:
|
|
| 1212 |
director_output,
|
| 1213 |
trace_output,
|
| 1214 |
backend_select,
|
|
|
|
|
|
|
|
|
|
| 1215 |
backend_output,
|
| 1216 |
],
|
| 1217 |
)
|
|
|
|
| 8 |
DEFAULT_OPENBMB_MODEL_ID,
|
| 9 |
TheaterSession,
|
| 10 |
create_show_from_premise,
|
| 11 |
+
get_backend_status,
|
| 12 |
request_finale,
|
| 13 |
run_one_beat,
|
| 14 |
summon_actor,
|
| 15 |
throw_prop,
|
| 16 |
+
warm_up_openbmb,
|
| 17 |
)
|
| 18 |
|
| 19 |
|
|
|
|
| 31 |
EMPTY_TRANSCRIPT = "No show yet. The transcript will appear here."
|
| 32 |
EMPTY_DIRECTOR_LOG = "No director notes yet."
|
| 33 |
EMPTY_TRACE = "No trace events yet."
|
| 34 |
+
EMPTY_BACKEND = (
|
| 35 |
+
"Active backend: deterministic\n"
|
| 36 |
+
"OpenBMB model id: openbmb/MiniCPM5-1B\n"
|
| 37 |
+
"Model status: unloaded\n"
|
| 38 |
+
"Fallback: deterministic safety path enabled"
|
| 39 |
+
)
|
| 40 |
BACKEND_CHOICES = ["deterministic", "openbmb"]
|
| 41 |
OPENBMB_MODEL_ID = os.getenv("OPENBMB_MODEL_ID", DEFAULT_OPENBMB_MODEL_ID)
|
| 42 |
+
DEFAULT_MAX_NEW_TOKENS = 80
|
| 43 |
+
DEFAULT_TEMPERATURE = 0.8
|
| 44 |
PLAYBACK_DELAY_SECONDS = 0.75
|
| 45 |
PROP_EMOJI = {
|
| 46 |
"rubber duck": "🐤",
|
|
|
|
| 929 |
return backend_name if backend_name in BACKEND_CHOICES else "deterministic"
|
| 930 |
|
| 931 |
|
| 932 |
+
def normalize_max_new_tokens(max_new_tokens: int | float | None) -> int:
|
| 933 |
+
if max_new_tokens is None:
|
| 934 |
+
return DEFAULT_MAX_NEW_TOKENS
|
| 935 |
+
return max(16, min(160, int(max_new_tokens)))
|
| 936 |
+
|
| 937 |
+
|
| 938 |
+
def normalize_temperature(temperature: int | float | None) -> float:
|
| 939 |
+
if temperature is None:
|
| 940 |
+
return DEFAULT_TEMPERATURE
|
| 941 |
+
return max(0.0, min(1.5, float(temperature)))
|
| 942 |
+
|
| 943 |
+
|
| 944 |
+
def apply_backend_selection(
|
| 945 |
+
session: TheaterSession | None,
|
| 946 |
+
backend_name: str | None,
|
| 947 |
+
max_new_tokens: int | float | None = None,
|
| 948 |
+
temperature: int | float | None = None,
|
| 949 |
+
) -> TheaterSession | None:
|
| 950 |
if session is None:
|
| 951 |
return None
|
| 952 |
session.backend_name = normalize_backend_name(backend_name)
|
| 953 |
session.backend_model_id = OPENBMB_MODEL_ID if session.backend_name == "openbmb" else None
|
| 954 |
+
session.backend_max_new_tokens = normalize_max_new_tokens(max_new_tokens)
|
| 955 |
+
session.backend_temperature = normalize_temperature(temperature)
|
| 956 |
return session
|
| 957 |
|
| 958 |
|
| 959 |
+
def render_backend_settings(
|
| 960 |
+
session: TheaterSession | None,
|
| 961 |
+
backend_name: str | None = None,
|
| 962 |
+
max_new_tokens: int | float | None = None,
|
| 963 |
+
temperature: int | float | None = None,
|
| 964 |
+
) -> str:
|
| 965 |
selected_backend = normalize_backend_name(backend_name)
|
| 966 |
active_backend = session.backend_name if session is not None else selected_backend
|
| 967 |
model_id = session.backend_model_id if session is not None else None
|
| 968 |
if active_backend == "openbmb":
|
| 969 |
model_id = model_id or OPENBMB_MODEL_ID
|
| 970 |
+
status = get_backend_status(active_backend)
|
| 971 |
+
openbmb_status = get_backend_status("openbmb")
|
| 972 |
+
configured_max_new_tokens = (
|
| 973 |
+
session.backend_max_new_tokens if session is not None else normalize_max_new_tokens(max_new_tokens)
|
| 974 |
+
)
|
| 975 |
+
configured_temperature = (
|
| 976 |
+
session.backend_temperature if session is not None else normalize_temperature(temperature)
|
| 977 |
+
)
|
| 978 |
+
latency = f"{status.latest_latency_ms}ms" if status.latest_latency_ms is not None else "none yet"
|
| 979 |
+
fallback_reason = status.latest_fallback_reason or "none"
|
| 980 |
return (
|
| 981 |
f"Active backend: {active_backend}\n"
|
| 982 |
"Available backends: deterministic, openbmb\n"
|
| 983 |
f"OpenBMB model id: {model_id or 'not selected'}\n"
|
| 984 |
+
f"Model status: {status.load_status}\n"
|
| 985 |
+
f"OpenBMB status: {openbmb_status.load_status}\n"
|
| 986 |
+
f"Latest latency: {latency}\n"
|
| 987 |
+
f"Latest fallback reason: {fallback_reason}\n"
|
| 988 |
+
f"Generation: max_new_tokens={configured_max_new_tokens}, temperature={configured_temperature:.2f}\n"
|
| 989 |
"Fallback behavior: invalid model output falls back to deterministic actor lines"
|
| 990 |
)
|
| 991 |
|
|
|
|
| 1000 |
)
|
| 1001 |
|
| 1002 |
|
| 1003 |
+
def create_show(
|
| 1004 |
+
premise: str,
|
| 1005 |
+
session: TheaterSession | None,
|
| 1006 |
+
backend_name: str,
|
| 1007 |
+
max_new_tokens: int | float,
|
| 1008 |
+
temperature: int | float,
|
| 1009 |
+
):
|
| 1010 |
premise = premise.strip()
|
| 1011 |
selected_backend = normalize_backend_name(backend_name)
|
| 1012 |
+
selected_max_new_tokens = normalize_max_new_tokens(max_new_tokens)
|
| 1013 |
+
selected_temperature = normalize_temperature(temperature)
|
| 1014 |
if not premise:
|
| 1015 |
return (
|
| 1016 |
None,
|
|
|
|
| 1018 |
"No premise yet. Add a premise to raise the curtain.",
|
| 1019 |
EMPTY_DIRECTOR_LOG,
|
| 1020 |
EMPTY_TRACE,
|
| 1021 |
+
render_backend_settings(None, selected_backend, selected_max_new_tokens, selected_temperature),
|
| 1022 |
)
|
| 1023 |
|
| 1024 |
session = create_show_from_premise(
|
| 1025 |
premise,
|
| 1026 |
backend_name=selected_backend,
|
| 1027 |
backend_model_id=OPENBMB_MODEL_ID if selected_backend == "openbmb" else None,
|
| 1028 |
+
backend_max_new_tokens=selected_max_new_tokens,
|
| 1029 |
+
backend_temperature=selected_temperature,
|
| 1030 |
)
|
| 1031 |
return session, *render_outputs(session)
|
| 1032 |
|
|
|
|
| 1042 |
EMPTY_DIRECTOR_LOG,
|
| 1043 |
EMPTY_TRACE,
|
| 1044 |
"deterministic",
|
| 1045 |
+
DEFAULT_MAX_NEW_TOKENS,
|
| 1046 |
+
DEFAULT_TEMPERATURE,
|
| 1047 |
+
True,
|
| 1048 |
EMPTY_BACKEND,
|
| 1049 |
)
|
| 1050 |
|
| 1051 |
|
| 1052 |
+
def advance_one_beat(
|
| 1053 |
+
session: TheaterSession | None,
|
| 1054 |
+
backend_name: str,
|
| 1055 |
+
max_new_tokens: int | float,
|
| 1056 |
+
temperature: int | float,
|
| 1057 |
+
):
|
| 1058 |
if session is None:
|
| 1059 |
return (
|
| 1060 |
None,
|
|
|
|
| 1062 |
"Create a show before running a beat.",
|
| 1063 |
EMPTY_DIRECTOR_LOG,
|
| 1064 |
EMPTY_TRACE,
|
| 1065 |
+
render_backend_settings(None, backend_name, max_new_tokens, temperature),
|
| 1066 |
)
|
| 1067 |
|
| 1068 |
+
session = apply_backend_selection(session, backend_name, max_new_tokens, temperature)
|
| 1069 |
session = run_one_beat(session)
|
| 1070 |
return session, *render_outputs(session)
|
| 1071 |
|
| 1072 |
|
| 1073 |
+
def advance_full_act(
|
| 1074 |
+
session: TheaterSession | None,
|
| 1075 |
+
backend_name: str,
|
| 1076 |
+
max_new_tokens: int | float,
|
| 1077 |
+
temperature: int | float,
|
| 1078 |
+
use_deterministic_full_act: bool,
|
| 1079 |
+
):
|
| 1080 |
if session is None:
|
| 1081 |
yield (
|
| 1082 |
None,
|
|
|
|
| 1084 |
"Create a show before running the full act.",
|
| 1085 |
EMPTY_DIRECTOR_LOG,
|
| 1086 |
EMPTY_TRACE,
|
| 1087 |
+
render_backend_settings(None, backend_name, max_new_tokens, temperature),
|
| 1088 |
)
|
| 1089 |
return
|
| 1090 |
|
| 1091 |
+
session = apply_backend_selection(session, backend_name, max_new_tokens, temperature)
|
| 1092 |
+
selected_backend = session.backend_name
|
| 1093 |
+
deterministic_full_act = selected_backend == "openbmb" and use_deterministic_full_act
|
| 1094 |
+
if deterministic_full_act:
|
| 1095 |
+
session.director_log.append(
|
| 1096 |
+
"OpenBMB is selected, so Run Full Act will use deterministic actor lines for this playback."
|
| 1097 |
+
)
|
| 1098 |
+
session.trace_events.append("full_act_openbmb_deterministic_playback")
|
| 1099 |
+
|
| 1100 |
if session.beat_index >= session.max_beats:
|
| 1101 |
+
if deterministic_full_act:
|
| 1102 |
+
session.backend_name = "deterministic"
|
| 1103 |
session = run_one_beat(session)
|
| 1104 |
+
if deterministic_full_act:
|
| 1105 |
+
session.backend_name = selected_backend
|
| 1106 |
yield session, *render_outputs(session)
|
| 1107 |
return
|
| 1108 |
|
| 1109 |
while session.beat_index < session.max_beats:
|
| 1110 |
+
if deterministic_full_act:
|
| 1111 |
+
session.backend_name = "deterministic"
|
| 1112 |
session = run_one_beat(session)
|
| 1113 |
+
if deterministic_full_act:
|
| 1114 |
+
session.backend_name = selected_backend
|
| 1115 |
yield session, *render_outputs(session)
|
| 1116 |
if session.beat_index < session.max_beats:
|
| 1117 |
sleep(PLAYBACK_DELAY_SECONDS)
|
| 1118 |
|
| 1119 |
|
| 1120 |
+
def throw_audience_prop(
|
| 1121 |
+
session: TheaterSession | None,
|
| 1122 |
+
prop_name: str,
|
| 1123 |
+
backend_name: str,
|
| 1124 |
+
max_new_tokens: int | float,
|
| 1125 |
+
temperature: int | float,
|
| 1126 |
+
):
|
| 1127 |
if session is None:
|
| 1128 |
return (
|
| 1129 |
None,
|
|
|
|
| 1131 |
"Create a show before throwing a prop.",
|
| 1132 |
EMPTY_DIRECTOR_LOG,
|
| 1133 |
EMPTY_TRACE,
|
| 1134 |
+
render_backend_settings(None, backend_name, max_new_tokens, temperature),
|
| 1135 |
)
|
| 1136 |
|
| 1137 |
+
session = apply_backend_selection(session, backend_name, max_new_tokens, temperature)
|
| 1138 |
session = throw_prop(session, prop_name)
|
| 1139 |
return session, *render_outputs(session)
|
| 1140 |
|
| 1141 |
|
| 1142 |
+
def summon_audience_actor(
|
| 1143 |
+
session: TheaterSession | None,
|
| 1144 |
+
actor_name: str,
|
| 1145 |
+
backend_name: str,
|
| 1146 |
+
max_new_tokens: int | float,
|
| 1147 |
+
temperature: int | float,
|
| 1148 |
+
):
|
| 1149 |
if session is None:
|
| 1150 |
return (
|
| 1151 |
None,
|
|
|
|
| 1153 |
"Create a show before summoning an actor.",
|
| 1154 |
EMPTY_DIRECTOR_LOG,
|
| 1155 |
EMPTY_TRACE,
|
| 1156 |
+
render_backend_settings(None, backend_name, max_new_tokens, temperature),
|
| 1157 |
)
|
| 1158 |
|
| 1159 |
+
session = apply_backend_selection(session, backend_name, max_new_tokens, temperature)
|
| 1160 |
session = summon_actor(session, actor_name)
|
| 1161 |
return session, *render_outputs(session)
|
| 1162 |
|
| 1163 |
|
| 1164 |
+
def request_audience_finale(
|
| 1165 |
+
session: TheaterSession | None,
|
| 1166 |
+
backend_name: str,
|
| 1167 |
+
max_new_tokens: int | float,
|
| 1168 |
+
temperature: int | float,
|
| 1169 |
+
):
|
| 1170 |
if session is None:
|
| 1171 |
return (
|
| 1172 |
None,
|
|
|
|
| 1174 |
"Create a show before requesting a finale.",
|
| 1175 |
EMPTY_DIRECTOR_LOG,
|
| 1176 |
EMPTY_TRACE,
|
| 1177 |
+
render_backend_settings(None, backend_name, max_new_tokens, temperature),
|
| 1178 |
)
|
| 1179 |
|
| 1180 |
+
session = apply_backend_selection(session, backend_name, max_new_tokens, temperature)
|
| 1181 |
session = request_finale(session)
|
| 1182 |
return session, *render_outputs(session)
|
| 1183 |
|
| 1184 |
|
| 1185 |
+
def warm_up_backend(
|
| 1186 |
+
session: TheaterSession | None,
|
| 1187 |
+
max_new_tokens: int | float,
|
| 1188 |
+
temperature: int | float,
|
| 1189 |
+
):
|
| 1190 |
+
selected_max_new_tokens = normalize_max_new_tokens(max_new_tokens)
|
| 1191 |
+
selected_temperature = normalize_temperature(temperature)
|
| 1192 |
+
status = warm_up_openbmb(
|
| 1193 |
+
max_new_tokens=selected_max_new_tokens,
|
| 1194 |
+
temperature=selected_temperature,
|
| 1195 |
+
)
|
| 1196 |
+
if session is not None:
|
| 1197 |
+
session.backend_max_new_tokens = selected_max_new_tokens
|
| 1198 |
+
session.backend_temperature = selected_temperature
|
| 1199 |
+
if status.load_status == "loaded":
|
| 1200 |
+
session.director_log.append(f"OpenBMB warm-up loaded {status.model_id}.")
|
| 1201 |
+
session.trace_events.append(f"openbmb_warmup_loaded:{status.model_id}:latency_ms={status.latest_latency_ms}")
|
| 1202 |
+
else:
|
| 1203 |
+
reason = status.latest_fallback_reason or "unknown error"
|
| 1204 |
+
session.director_log.append(f"OpenBMB warm-up failed: {reason}.")
|
| 1205 |
+
session.trace_events.append(f"openbmb_warmup_failed:{status.model_id}:{reason}")
|
| 1206 |
+
return (
|
| 1207 |
+
session,
|
| 1208 |
+
render_director_log(session),
|
| 1209 |
+
render_trace(session),
|
| 1210 |
+
render_backend_settings(session, "openbmb", selected_max_new_tokens, selected_temperature),
|
| 1211 |
+
)
|
| 1212 |
+
|
| 1213 |
+
|
| 1214 |
with gr.Blocks(title="AI Puppet Theater") as app:
|
| 1215 |
session_state = gr.State(None)
|
| 1216 |
|
|
|
|
| 1297 |
label="Actor Line Backend",
|
| 1298 |
interactive=True,
|
| 1299 |
)
|
| 1300 |
+
with gr.Row():
|
| 1301 |
+
max_new_tokens_input = gr.Slider(
|
| 1302 |
+
minimum=16,
|
| 1303 |
+
maximum=160,
|
| 1304 |
+
value=DEFAULT_MAX_NEW_TOKENS,
|
| 1305 |
+
step=8,
|
| 1306 |
+
label="Max New Tokens",
|
| 1307 |
+
interactive=True,
|
| 1308 |
+
)
|
| 1309 |
+
temperature_input = gr.Slider(
|
| 1310 |
+
minimum=0.0,
|
| 1311 |
+
maximum=1.5,
|
| 1312 |
+
value=DEFAULT_TEMPERATURE,
|
| 1313 |
+
step=0.1,
|
| 1314 |
+
label="Temperature",
|
| 1315 |
+
interactive=True,
|
| 1316 |
+
)
|
| 1317 |
+
deterministic_full_act_input = gr.Checkbox(
|
| 1318 |
+
value=True,
|
| 1319 |
+
label="Use deterministic actor lines for OpenBMB full-act playback",
|
| 1320 |
+
interactive=True,
|
| 1321 |
+
)
|
| 1322 |
+
warm_up_button = gr.Button("Warm up OpenBMB", elem_classes=["secondary-action"])
|
| 1323 |
backend_output = gr.Textbox(
|
| 1324 |
value=EMPTY_BACKEND,
|
| 1325 |
label="Model Settings",
|
| 1326 |
+
lines=8,
|
| 1327 |
interactive=False,
|
| 1328 |
)
|
| 1329 |
|
| 1330 |
create_button.click(
|
| 1331 |
create_show,
|
| 1332 |
+
inputs=[premise_input, session_state, backend_select, max_new_tokens_input, temperature_input],
|
| 1333 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1334 |
)
|
| 1335 |
run_one_button.click(
|
| 1336 |
advance_one_beat,
|
| 1337 |
+
inputs=[session_state, backend_select, max_new_tokens_input, temperature_input],
|
| 1338 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1339 |
)
|
| 1340 |
run_full_button.click(
|
| 1341 |
advance_full_act,
|
| 1342 |
+
inputs=[
|
| 1343 |
+
session_state,
|
| 1344 |
+
backend_select,
|
| 1345 |
+
max_new_tokens_input,
|
| 1346 |
+
temperature_input,
|
| 1347 |
+
deterministic_full_act_input,
|
| 1348 |
+
],
|
| 1349 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1350 |
)
|
| 1351 |
throw_prop_button.click(
|
| 1352 |
throw_audience_prop,
|
| 1353 |
+
inputs=[session_state, prop_input, backend_select, max_new_tokens_input, temperature_input],
|
| 1354 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1355 |
)
|
| 1356 |
summon_actor_button.click(
|
| 1357 |
summon_audience_actor,
|
| 1358 |
+
inputs=[session_state, actor_input, backend_select, max_new_tokens_input, temperature_input],
|
| 1359 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1360 |
)
|
| 1361 |
request_finale_button.click(
|
| 1362 |
request_audience_finale,
|
| 1363 |
+
inputs=[session_state, backend_select, max_new_tokens_input, temperature_input],
|
| 1364 |
outputs=[session_state, stage_output, transcript_output, director_output, trace_output, backend_output],
|
| 1365 |
)
|
| 1366 |
+
warm_up_button.click(
|
| 1367 |
+
warm_up_backend,
|
| 1368 |
+
inputs=[session_state, max_new_tokens_input, temperature_input],
|
| 1369 |
+
outputs=[session_state, director_output, trace_output, backend_output],
|
| 1370 |
+
)
|
| 1371 |
reset_button.click(
|
| 1372 |
reset_show,
|
| 1373 |
outputs=[
|
|
|
|
| 1380 |
director_output,
|
| 1381 |
trace_output,
|
| 1382 |
backend_select,
|
| 1383 |
+
max_new_tokens_input,
|
| 1384 |
+
temperature_input,
|
| 1385 |
+
deterministic_full_act_input,
|
| 1386 |
backend_output,
|
| 1387 |
],
|
| 1388 |
)
|
puppet_theater/__init__.py
CHANGED
|
@@ -5,7 +5,9 @@ from puppet_theater.backends import (
|
|
| 5 |
ModelBackend,
|
| 6 |
OpenBMBTransformersBackend,
|
| 7 |
generate_actor_response,
|
|
|
|
| 8 |
parse_actor_output,
|
|
|
|
| 9 |
)
|
| 10 |
from puppet_theater.director import BEAT_ARC, run_full_act, run_one_beat
|
| 11 |
from puppet_theater.models import Actor, ActorResponse, Beat, TheaterSession
|
|
@@ -23,10 +25,12 @@ __all__ = [
|
|
| 23 |
"TheaterSession",
|
| 24 |
"create_show_from_premise",
|
| 25 |
"generate_actor_response",
|
|
|
|
| 26 |
"parse_actor_output",
|
| 27 |
"request_finale",
|
| 28 |
"run_full_act",
|
| 29 |
"run_one_beat",
|
| 30 |
"summon_actor",
|
| 31 |
"throw_prop",
|
|
|
|
| 32 |
]
|
|
|
|
| 5 |
ModelBackend,
|
| 6 |
OpenBMBTransformersBackend,
|
| 7 |
generate_actor_response,
|
| 8 |
+
get_backend_status,
|
| 9 |
parse_actor_output,
|
| 10 |
+
warm_up_openbmb,
|
| 11 |
)
|
| 12 |
from puppet_theater.director import BEAT_ARC, run_full_act, run_one_beat
|
| 13 |
from puppet_theater.models import Actor, ActorResponse, Beat, TheaterSession
|
|
|
|
| 25 |
"TheaterSession",
|
| 26 |
"create_show_from_premise",
|
| 27 |
"generate_actor_response",
|
| 28 |
+
"get_backend_status",
|
| 29 |
"parse_actor_output",
|
| 30 |
"request_finale",
|
| 31 |
"run_full_act",
|
| 32 |
"run_one_beat",
|
| 33 |
"summon_actor",
|
| 34 |
"throw_prop",
|
| 35 |
+
"warm_up_openbmb",
|
| 36 |
]
|
puppet_theater/backends.py
CHANGED
|
@@ -13,7 +13,8 @@ from puppet_theater.prompts import ACTOR_LINE_PROMPT
|
|
| 13 |
|
| 14 |
MAX_ACTOR_LINE_CHARS = 220
|
| 15 |
DEFAULT_OPENBMB_MODEL_ID = "openbmb/MiniCPM5-1B"
|
| 16 |
-
OPENBMB_MAX_NEW_TOKENS =
|
|
|
|
| 17 |
|
| 18 |
|
| 19 |
@dataclass(frozen=True)
|
|
@@ -23,10 +24,24 @@ class BackendGeneration:
|
|
| 23 |
model_id: str | None
|
| 24 |
fallback_used: bool
|
| 25 |
validation_status: str
|
|
|
|
| 26 |
latency_ms: int | None = None
|
| 27 |
error: str | None = None
|
| 28 |
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
class ModelBackend(ABC):
|
| 31 |
name: str = "base"
|
| 32 |
model_id: str | None = None
|
|
@@ -55,6 +70,7 @@ class ModelBackend(ABC):
|
|
| 55 |
|
| 56 |
class DeterministicBackend(ModelBackend):
|
| 57 |
name = "deterministic"
|
|
|
|
| 58 |
|
| 59 |
def generate_actor_response(
|
| 60 |
self,
|
|
@@ -69,13 +85,30 @@ class DeterministicBackend(ModelBackend):
|
|
| 69 |
class OpenBMBTransformersBackend(ModelBackend):
|
| 70 |
name = "openbmb"
|
| 71 |
|
| 72 |
-
def __init__(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
self.model_id = model_id or os.getenv("OPENBMB_MODEL_ID", DEFAULT_OPENBMB_MODEL_ID)
|
| 74 |
self.max_new_tokens = max_new_tokens
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
self._tokenizer = None
|
| 76 |
self._model = None
|
| 77 |
self._torch = None
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
def generate_actor_response(
|
| 80 |
self,
|
| 81 |
session: TheaterSession,
|
|
@@ -105,22 +138,35 @@ class OpenBMBTransformersBackend(ModelBackend):
|
|
| 105 |
|
| 106 |
def _load(self) -> None:
|
| 107 |
if self._tokenizer is not None and self._model is not None:
|
|
|
|
| 108 |
return
|
| 109 |
|
|
|
|
| 110 |
try:
|
| 111 |
import torch
|
| 112 |
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 113 |
except ImportError as exc:
|
|
|
|
|
|
|
| 114 |
raise RuntimeError("OpenBMB backend dependencies are not installed") from exc
|
| 115 |
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
self.
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
def _generate_text(self, prompt: str) -> str:
|
| 126 |
self._load()
|
|
@@ -149,14 +195,17 @@ class OpenBMBTransformersBackend(ModelBackend):
|
|
| 149 |
inputs = inputs.to(model.device)
|
| 150 |
eos_token_id = tokenizer.eos_token_id
|
| 151 |
pad_token_id = tokenizer.pad_token_id or eos_token_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
with self._torch.inference_mode():
|
| 153 |
-
outputs = model.generate(
|
| 154 |
-
**inputs,
|
| 155 |
-
max_new_tokens=self.max_new_tokens,
|
| 156 |
-
do_sample=False,
|
| 157 |
-
pad_token_id=pad_token_id,
|
| 158 |
-
eos_token_id=eos_token_id,
|
| 159 |
-
)
|
| 160 |
new_tokens = outputs[0][inputs["input_ids"].shape[-1] :]
|
| 161 |
return tokenizer.decode(new_tokens, skip_special_tokens=True).strip()
|
| 162 |
|
|
@@ -183,7 +232,11 @@ def generate_actor_response(
|
|
| 183 |
prop: str | None,
|
| 184 |
backend: ModelBackend | None = None,
|
| 185 |
) -> BackendGeneration:
|
| 186 |
-
active_backend = backend or get_backend(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
start_time = time.perf_counter()
|
| 188 |
raw_output: ActorResponse | dict[str, Any] | str | None = None
|
| 189 |
try:
|
|
@@ -203,14 +256,17 @@ def generate_actor_response(
|
|
| 203 |
|
| 204 |
response, validation_status = parse_actor_output(raw_output)
|
| 205 |
if response is not None:
|
| 206 |
-
|
| 207 |
response=response,
|
| 208 |
backend_name=active_backend.name,
|
| 209 |
model_id=active_backend.model_id,
|
| 210 |
fallback_used=False,
|
| 211 |
validation_status=validation_status,
|
|
|
|
| 212 |
latency_ms=_elapsed_ms(start_time),
|
| 213 |
)
|
|
|
|
|
|
|
| 214 |
|
| 215 |
try:
|
| 216 |
repair_output = active_backend.repair_actor_response(
|
|
@@ -235,14 +291,17 @@ def generate_actor_response(
|
|
| 235 |
if repair_output is not None:
|
| 236 |
response, repair_status = parse_actor_output(repair_output)
|
| 237 |
if response is not None:
|
| 238 |
-
|
| 239 |
response=response,
|
| 240 |
backend_name=active_backend.name,
|
| 241 |
model_id=active_backend.model_id,
|
| 242 |
fallback_used=False,
|
| 243 |
validation_status=f"repair_{repair_status}",
|
|
|
|
| 244 |
latency_ms=_elapsed_ms(start_time),
|
| 245 |
)
|
|
|
|
|
|
|
| 246 |
validation_status = f"{validation_status};repair_{repair_status}"
|
| 247 |
|
| 248 |
return _fallback_generation(
|
|
@@ -256,17 +315,64 @@ def generate_actor_response(
|
|
| 256 |
)
|
| 257 |
|
| 258 |
|
| 259 |
-
def get_backend(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
normalized_name = (backend_name or "deterministic").strip().lower()
|
| 261 |
if normalized_name == "openbmb":
|
| 262 |
model_id = os.getenv("OPENBMB_MODEL_ID", DEFAULT_OPENBMB_MODEL_ID)
|
| 263 |
cache_key = f"openbmb:{model_id}"
|
| 264 |
if cache_key not in _BACKEND_CACHE:
|
| 265 |
-
_BACKEND_CACHE[cache_key] = OpenBMBTransformersBackend(
|
| 266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
return _BACKEND_CACHE["deterministic"]
|
| 268 |
|
| 269 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
def build_actor_line_prompt(
|
| 271 |
session: TheaterSession,
|
| 272 |
beat_type: str,
|
|
@@ -304,15 +410,18 @@ def _fallback_generation(
|
|
| 304 |
error: str | None = None,
|
| 305 |
) -> BackendGeneration:
|
| 306 |
fallback_response = deterministic_actor_response(session, beat_type, speaker, prop)
|
| 307 |
-
|
| 308 |
response=fallback_response,
|
| 309 |
backend_name=backend.name,
|
| 310 |
model_id=backend.model_id,
|
| 311 |
fallback_used=True,
|
| 312 |
validation_status=validation_status,
|
|
|
|
| 313 |
latency_ms=latency_ms,
|
| 314 |
error=error,
|
| 315 |
)
|
|
|
|
|
|
|
| 316 |
|
| 317 |
|
| 318 |
def parse_actor_output(raw_output: ActorResponse | dict[str, Any] | str) -> tuple[ActorResponse | None, str]:
|
|
@@ -364,6 +473,39 @@ def _summarize_error(exc: Exception) -> str:
|
|
| 364 |
return message[:180]
|
| 365 |
|
| 366 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
def _line_for_beat(
|
| 368 |
session: TheaterSession,
|
| 369 |
beat_type: str,
|
|
|
|
| 13 |
|
| 14 |
MAX_ACTOR_LINE_CHARS = 220
|
| 15 |
DEFAULT_OPENBMB_MODEL_ID = "openbmb/MiniCPM5-1B"
|
| 16 |
+
OPENBMB_MAX_NEW_TOKENS = 80
|
| 17 |
+
OPENBMB_TEMPERATURE = 0.8
|
| 18 |
|
| 19 |
|
| 20 |
@dataclass(frozen=True)
|
|
|
|
| 24 |
model_id: str | None
|
| 25 |
fallback_used: bool
|
| 26 |
validation_status: str
|
| 27 |
+
load_status: str
|
| 28 |
latency_ms: int | None = None
|
| 29 |
error: str | None = None
|
| 30 |
|
| 31 |
|
| 32 |
+
@dataclass(frozen=True)
|
| 33 |
+
class BackendRuntimeStatus:
|
| 34 |
+
backend_name: str
|
| 35 |
+
model_id: str | None
|
| 36 |
+
load_status: str
|
| 37 |
+
latest_latency_ms: int | None = None
|
| 38 |
+
latest_validation_status: str | None = None
|
| 39 |
+
latest_fallback_used: bool | None = None
|
| 40 |
+
latest_fallback_reason: str | None = None
|
| 41 |
+
max_new_tokens: int | None = None
|
| 42 |
+
temperature: float | None = None
|
| 43 |
+
|
| 44 |
+
|
| 45 |
class ModelBackend(ABC):
|
| 46 |
name: str = "base"
|
| 47 |
model_id: str | None = None
|
|
|
|
| 70 |
|
| 71 |
class DeterministicBackend(ModelBackend):
|
| 72 |
name = "deterministic"
|
| 73 |
+
load_status = "loaded"
|
| 74 |
|
| 75 |
def generate_actor_response(
|
| 76 |
self,
|
|
|
|
| 85 |
class OpenBMBTransformersBackend(ModelBackend):
|
| 86 |
name = "openbmb"
|
| 87 |
|
| 88 |
+
def __init__(
|
| 89 |
+
self,
|
| 90 |
+
model_id: str | None = None,
|
| 91 |
+
max_new_tokens: int = OPENBMB_MAX_NEW_TOKENS,
|
| 92 |
+
temperature: float = OPENBMB_TEMPERATURE,
|
| 93 |
+
) -> None:
|
| 94 |
self.model_id = model_id or os.getenv("OPENBMB_MODEL_ID", DEFAULT_OPENBMB_MODEL_ID)
|
| 95 |
self.max_new_tokens = max_new_tokens
|
| 96 |
+
self.temperature = temperature
|
| 97 |
+
self.load_status = "unloaded"
|
| 98 |
+
self.latest_latency_ms: int | None = None
|
| 99 |
+
self.latest_validation_status: str | None = None
|
| 100 |
+
self.latest_fallback_used: bool | None = None
|
| 101 |
+
self.latest_fallback_reason: str | None = None
|
| 102 |
self._tokenizer = None
|
| 103 |
self._model = None
|
| 104 |
self._torch = None
|
| 105 |
|
| 106 |
+
def configure(self, max_new_tokens: int | None = None, temperature: float | None = None) -> None:
|
| 107 |
+
if max_new_tokens is not None:
|
| 108 |
+
self.max_new_tokens = _clamp_int(max_new_tokens, 16, 160)
|
| 109 |
+
if temperature is not None:
|
| 110 |
+
self.temperature = _clamp_float(temperature, 0.0, 1.5)
|
| 111 |
+
|
| 112 |
def generate_actor_response(
|
| 113 |
self,
|
| 114 |
session: TheaterSession,
|
|
|
|
| 138 |
|
| 139 |
def _load(self) -> None:
|
| 140 |
if self._tokenizer is not None and self._model is not None:
|
| 141 |
+
self.load_status = "loaded"
|
| 142 |
return
|
| 143 |
|
| 144 |
+
self.load_status = "loading"
|
| 145 |
try:
|
| 146 |
import torch
|
| 147 |
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 148 |
except ImportError as exc:
|
| 149 |
+
self.load_status = "error"
|
| 150 |
+
self.latest_fallback_reason = "OpenBMB backend dependencies are not installed"
|
| 151 |
raise RuntimeError("OpenBMB backend dependencies are not installed") from exc
|
| 152 |
|
| 153 |
+
try:
|
| 154 |
+
self._torch = torch
|
| 155 |
+
self._tokenizer = AutoTokenizer.from_pretrained(self.model_id)
|
| 156 |
+
self._model = AutoModelForCausalLM.from_pretrained(
|
| 157 |
+
self.model_id,
|
| 158 |
+
torch_dtype="auto",
|
| 159 |
+
device_map="auto",
|
| 160 |
+
)
|
| 161 |
+
self._model.eval()
|
| 162 |
+
except Exception as exc:
|
| 163 |
+
self._tokenizer = None
|
| 164 |
+
self._model = None
|
| 165 |
+
self.load_status = "error"
|
| 166 |
+
self.latest_fallback_reason = _summarize_error(exc)
|
| 167 |
+
raise
|
| 168 |
+
|
| 169 |
+
self.load_status = "loaded"
|
| 170 |
|
| 171 |
def _generate_text(self, prompt: str) -> str:
|
| 172 |
self._load()
|
|
|
|
| 195 |
inputs = inputs.to(model.device)
|
| 196 |
eos_token_id = tokenizer.eos_token_id
|
| 197 |
pad_token_id = tokenizer.pad_token_id or eos_token_id
|
| 198 |
+
do_sample = self.temperature > 0
|
| 199 |
+
generation_kwargs = {
|
| 200 |
+
"max_new_tokens": self.max_new_tokens,
|
| 201 |
+
"do_sample": do_sample,
|
| 202 |
+
"pad_token_id": pad_token_id,
|
| 203 |
+
"eos_token_id": eos_token_id,
|
| 204 |
+
}
|
| 205 |
+
if do_sample:
|
| 206 |
+
generation_kwargs["temperature"] = self.temperature
|
| 207 |
with self._torch.inference_mode():
|
| 208 |
+
outputs = model.generate(**inputs, **generation_kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
new_tokens = outputs[0][inputs["input_ids"].shape[-1] :]
|
| 210 |
return tokenizer.decode(new_tokens, skip_special_tokens=True).strip()
|
| 211 |
|
|
|
|
| 232 |
prop: str | None,
|
| 233 |
backend: ModelBackend | None = None,
|
| 234 |
) -> BackendGeneration:
|
| 235 |
+
active_backend = backend or get_backend(
|
| 236 |
+
session.backend_name,
|
| 237 |
+
max_new_tokens=session.backend_max_new_tokens,
|
| 238 |
+
temperature=session.backend_temperature,
|
| 239 |
+
)
|
| 240 |
start_time = time.perf_counter()
|
| 241 |
raw_output: ActorResponse | dict[str, Any] | str | None = None
|
| 242 |
try:
|
|
|
|
| 256 |
|
| 257 |
response, validation_status = parse_actor_output(raw_output)
|
| 258 |
if response is not None:
|
| 259 |
+
generation = BackendGeneration(
|
| 260 |
response=response,
|
| 261 |
backend_name=active_backend.name,
|
| 262 |
model_id=active_backend.model_id,
|
| 263 |
fallback_used=False,
|
| 264 |
validation_status=validation_status,
|
| 265 |
+
load_status=getattr(active_backend, "load_status", "loaded"),
|
| 266 |
latency_ms=_elapsed_ms(start_time),
|
| 267 |
)
|
| 268 |
+
_record_generation_status(active_backend, generation)
|
| 269 |
+
return generation
|
| 270 |
|
| 271 |
try:
|
| 272 |
repair_output = active_backend.repair_actor_response(
|
|
|
|
| 291 |
if repair_output is not None:
|
| 292 |
response, repair_status = parse_actor_output(repair_output)
|
| 293 |
if response is not None:
|
| 294 |
+
generation = BackendGeneration(
|
| 295 |
response=response,
|
| 296 |
backend_name=active_backend.name,
|
| 297 |
model_id=active_backend.model_id,
|
| 298 |
fallback_used=False,
|
| 299 |
validation_status=f"repair_{repair_status}",
|
| 300 |
+
load_status=getattr(active_backend, "load_status", "loaded"),
|
| 301 |
latency_ms=_elapsed_ms(start_time),
|
| 302 |
)
|
| 303 |
+
_record_generation_status(active_backend, generation)
|
| 304 |
+
return generation
|
| 305 |
validation_status = f"{validation_status};repair_{repair_status}"
|
| 306 |
|
| 307 |
return _fallback_generation(
|
|
|
|
| 315 |
)
|
| 316 |
|
| 317 |
|
| 318 |
+
def get_backend(
|
| 319 |
+
backend_name: str | None,
|
| 320 |
+
max_new_tokens: int | None = None,
|
| 321 |
+
temperature: float | None = None,
|
| 322 |
+
) -> ModelBackend:
|
| 323 |
normalized_name = (backend_name or "deterministic").strip().lower()
|
| 324 |
if normalized_name == "openbmb":
|
| 325 |
model_id = os.getenv("OPENBMB_MODEL_ID", DEFAULT_OPENBMB_MODEL_ID)
|
| 326 |
cache_key = f"openbmb:{model_id}"
|
| 327 |
if cache_key not in _BACKEND_CACHE:
|
| 328 |
+
_BACKEND_CACHE[cache_key] = OpenBMBTransformersBackend(
|
| 329 |
+
model_id=model_id,
|
| 330 |
+
max_new_tokens=max_new_tokens or OPENBMB_MAX_NEW_TOKENS,
|
| 331 |
+
temperature=temperature if temperature is not None else OPENBMB_TEMPERATURE,
|
| 332 |
+
)
|
| 333 |
+
backend = _BACKEND_CACHE[cache_key]
|
| 334 |
+
if isinstance(backend, OpenBMBTransformersBackend):
|
| 335 |
+
backend.configure(max_new_tokens=max_new_tokens, temperature=temperature)
|
| 336 |
+
return backend
|
| 337 |
return _BACKEND_CACHE["deterministic"]
|
| 338 |
|
| 339 |
|
| 340 |
+
def warm_up_openbmb(
|
| 341 |
+
max_new_tokens: int = OPENBMB_MAX_NEW_TOKENS,
|
| 342 |
+
temperature: float = OPENBMB_TEMPERATURE,
|
| 343 |
+
) -> BackendRuntimeStatus:
|
| 344 |
+
backend = get_backend("openbmb", max_new_tokens=max_new_tokens, temperature=temperature)
|
| 345 |
+
if not isinstance(backend, OpenBMBTransformersBackend):
|
| 346 |
+
return get_backend_status("deterministic")
|
| 347 |
+
|
| 348 |
+
start_time = time.perf_counter()
|
| 349 |
+
try:
|
| 350 |
+
backend._load()
|
| 351 |
+
backend.latest_latency_ms = _elapsed_ms(start_time)
|
| 352 |
+
backend.latest_fallback_reason = None
|
| 353 |
+
except Exception as exc:
|
| 354 |
+
backend.latest_latency_ms = _elapsed_ms(start_time)
|
| 355 |
+
backend.latest_fallback_reason = _summarize_error(exc)
|
| 356 |
+
return get_backend_status("openbmb")
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
def get_backend_status(backend_name: str | None = None) -> BackendRuntimeStatus:
|
| 360 |
+
normalized_name = (backend_name or "deterministic").strip().lower()
|
| 361 |
+
if normalized_name == "openbmb":
|
| 362 |
+
model_id = os.getenv("OPENBMB_MODEL_ID", DEFAULT_OPENBMB_MODEL_ID)
|
| 363 |
+
backend = _BACKEND_CACHE.get(f"openbmb:{model_id}")
|
| 364 |
+
if backend is None:
|
| 365 |
+
return BackendRuntimeStatus(
|
| 366 |
+
backend_name="openbmb",
|
| 367 |
+
model_id=model_id,
|
| 368 |
+
load_status="unloaded",
|
| 369 |
+
max_new_tokens=OPENBMB_MAX_NEW_TOKENS,
|
| 370 |
+
temperature=OPENBMB_TEMPERATURE,
|
| 371 |
+
)
|
| 372 |
+
return _runtime_status_from_backend(backend)
|
| 373 |
+
return _runtime_status_from_backend(_BACKEND_CACHE["deterministic"])
|
| 374 |
+
|
| 375 |
+
|
| 376 |
def build_actor_line_prompt(
|
| 377 |
session: TheaterSession,
|
| 378 |
beat_type: str,
|
|
|
|
| 410 |
error: str | None = None,
|
| 411 |
) -> BackendGeneration:
|
| 412 |
fallback_response = deterministic_actor_response(session, beat_type, speaker, prop)
|
| 413 |
+
generation = BackendGeneration(
|
| 414 |
response=fallback_response,
|
| 415 |
backend_name=backend.name,
|
| 416 |
model_id=backend.model_id,
|
| 417 |
fallback_used=True,
|
| 418 |
validation_status=validation_status,
|
| 419 |
+
load_status=getattr(backend, "load_status", "loaded"),
|
| 420 |
latency_ms=latency_ms,
|
| 421 |
error=error,
|
| 422 |
)
|
| 423 |
+
_record_generation_status(backend, generation)
|
| 424 |
+
return generation
|
| 425 |
|
| 426 |
|
| 427 |
def parse_actor_output(raw_output: ActorResponse | dict[str, Any] | str) -> tuple[ActorResponse | None, str]:
|
|
|
|
| 473 |
return message[:180]
|
| 474 |
|
| 475 |
|
| 476 |
+
def _clamp_int(value: int, minimum: int, maximum: int) -> int:
|
| 477 |
+
return max(minimum, min(maximum, int(value)))
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
def _clamp_float(value: float, minimum: float, maximum: float) -> float:
|
| 481 |
+
return max(minimum, min(maximum, float(value)))
|
| 482 |
+
|
| 483 |
+
|
| 484 |
+
def _record_generation_status(backend: ModelBackend, generation: BackendGeneration) -> None:
|
| 485 |
+
if hasattr(backend, "latest_latency_ms"):
|
| 486 |
+
backend.latest_latency_ms = generation.latency_ms
|
| 487 |
+
if hasattr(backend, "latest_validation_status"):
|
| 488 |
+
backend.latest_validation_status = generation.validation_status
|
| 489 |
+
if hasattr(backend, "latest_fallback_used"):
|
| 490 |
+
backend.latest_fallback_used = generation.fallback_used
|
| 491 |
+
if hasattr(backend, "latest_fallback_reason"):
|
| 492 |
+
backend.latest_fallback_reason = generation.error
|
| 493 |
+
|
| 494 |
+
|
| 495 |
+
def _runtime_status_from_backend(backend: ModelBackend) -> BackendRuntimeStatus:
|
| 496 |
+
return BackendRuntimeStatus(
|
| 497 |
+
backend_name=backend.name,
|
| 498 |
+
model_id=backend.model_id,
|
| 499 |
+
load_status=getattr(backend, "load_status", "loaded"),
|
| 500 |
+
latest_latency_ms=getattr(backend, "latest_latency_ms", None),
|
| 501 |
+
latest_validation_status=getattr(backend, "latest_validation_status", None),
|
| 502 |
+
latest_fallback_used=getattr(backend, "latest_fallback_used", None),
|
| 503 |
+
latest_fallback_reason=getattr(backend, "latest_fallback_reason", None),
|
| 504 |
+
max_new_tokens=getattr(backend, "max_new_tokens", None),
|
| 505 |
+
temperature=getattr(backend, "temperature", None),
|
| 506 |
+
)
|
| 507 |
+
|
| 508 |
+
|
| 509 |
def _line_for_beat(
|
| 510 |
session: TheaterSession,
|
| 511 |
beat_type: str,
|
puppet_theater/director.py
CHANGED
|
@@ -50,7 +50,7 @@ def run_one_beat(session: TheaterSession | None) -> TheaterSession | None:
|
|
| 50 |
"Backend "
|
| 51 |
f"{backend_generation.backend_name} returned actor output "
|
| 52 |
f"({backend_generation.validation_status}, fallback={backend_generation.fallback_used}"
|
| 53 |
-
f", latency={backend_generation.latency_ms}ms)."
|
| 54 |
)
|
| 55 |
if backend_generation.model_id:
|
| 56 |
session.director_log.append(f"Model id: {backend_generation.model_id}.")
|
|
@@ -61,6 +61,7 @@ def run_one_beat(session: TheaterSession | None) -> TheaterSession | None:
|
|
| 61 |
"backend_result:"
|
| 62 |
f"{backend_generation.backend_name}:"
|
| 63 |
f"model={backend_generation.model_id or 'none'}:"
|
|
|
|
| 64 |
f"fallback={backend_generation.fallback_used}:"
|
| 65 |
f"validation={backend_generation.validation_status}:"
|
| 66 |
f"latency_ms={backend_generation.latency_ms}"
|
|
|
|
| 50 |
"Backend "
|
| 51 |
f"{backend_generation.backend_name} returned actor output "
|
| 52 |
f"({backend_generation.validation_status}, fallback={backend_generation.fallback_used}"
|
| 53 |
+
f", load_status={backend_generation.load_status}, latency={backend_generation.latency_ms}ms)."
|
| 54 |
)
|
| 55 |
if backend_generation.model_id:
|
| 56 |
session.director_log.append(f"Model id: {backend_generation.model_id}.")
|
|
|
|
| 61 |
"backend_result:"
|
| 62 |
f"{backend_generation.backend_name}:"
|
| 63 |
f"model={backend_generation.model_id or 'none'}:"
|
| 64 |
+
f"load_status={backend_generation.load_status}:"
|
| 65 |
f"fallback={backend_generation.fallback_used}:"
|
| 66 |
f"validation={backend_generation.validation_status}:"
|
| 67 |
f"latency_ms={backend_generation.latency_ms}"
|
puppet_theater/models.py
CHANGED
|
@@ -65,3 +65,5 @@ class TheaterSession:
|
|
| 65 |
finale_requested: bool = False
|
| 66 |
backend_name: str = "deterministic"
|
| 67 |
backend_model_id: str | None = None
|
|
|
|
|
|
|
|
|
| 65 |
finale_requested: bool = False
|
| 66 |
backend_name: str = "deterministic"
|
| 67 |
backend_model_id: str | None = None
|
| 68 |
+
backend_max_new_tokens: int = 80
|
| 69 |
+
backend_temperature: float = 0.8
|
puppet_theater/session.py
CHANGED
|
@@ -27,7 +27,13 @@ def _setting_from_premise(premise: str) -> str:
|
|
| 27 |
return "a pocket-sized improv stage with painted flats and a wobbly spotlight"
|
| 28 |
|
| 29 |
|
| 30 |
-
def create_show_from_premise(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
cleaned_premise = _clean_premise(premise)
|
| 32 |
show_title = _title_from_premise(cleaned_premise)
|
| 33 |
setting = _setting_from_premise(cleaned_premise)
|
|
@@ -90,4 +96,6 @@ def create_show_from_premise(premise: str, backend_name: str = "deterministic",
|
|
| 90 |
finale_requested=False,
|
| 91 |
backend_name=active_backend,
|
| 92 |
backend_model_id=backend_model_id,
|
|
|
|
|
|
|
| 93 |
)
|
|
|
|
| 27 |
return "a pocket-sized improv stage with painted flats and a wobbly spotlight"
|
| 28 |
|
| 29 |
|
| 30 |
+
def create_show_from_premise(
|
| 31 |
+
premise: str,
|
| 32 |
+
backend_name: str = "deterministic",
|
| 33 |
+
backend_model_id: str | None = None,
|
| 34 |
+
backend_max_new_tokens: int = 80,
|
| 35 |
+
backend_temperature: float = 0.8,
|
| 36 |
+
) -> TheaterSession:
|
| 37 |
cleaned_premise = _clean_premise(premise)
|
| 38 |
show_title = _title_from_premise(cleaned_premise)
|
| 39 |
setting = _setting_from_premise(cleaned_premise)
|
|
|
|
| 96 |
finale_requested=False,
|
| 97 |
backend_name=active_backend,
|
| 98 |
backend_model_id=backend_model_id,
|
| 99 |
+
backend_max_new_tokens=backend_max_new_tokens,
|
| 100 |
+
backend_temperature=backend_temperature,
|
| 101 |
)
|