kwabs22 Claude Opus 5.5 commited on
Commit
bbe0bad
·
1 Parent(s): 065c4f9

Harden tool inputs against blank and junk values

Browse files

Seeds are strings cast inside the tools (some MCP clients can't send
numbers); JSON arguments fall back to defaults when blank and name the
field when invalid; ledger tools say to translate first when empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +33 -13
app.py CHANGED
@@ -23,6 +23,26 @@ def _fail(e):
23
  raise gr.Error(str(e))
24
 
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  @spaces.GPU(duration=60)
27
  def generate_mesh(prompt):
28
  """GPU entry point for the generation tier (last in the fallback chain).
@@ -54,12 +74,12 @@ def check_words(sentence: str, primitives_json: str = "{}") -> str:
54
  "primitive_shapes", "completions"}.
55
  """
56
  try:
57
- return json.dumps(_check_words(sentence, json.loads(primitives_json or "{}")))
58
  except Exception as e:
59
  _fail(e)
60
 
61
 
62
- def translate_to_scene(sentence: str, primitives_json: str = "{}", seed: int = 0) -> tuple[str, str, str]:
63
  """Translate an English sentence into a 3D scene.
64
 
65
  Produces the serial ledger (tokens in reading order, the items each word commits,
@@ -76,12 +96,12 @@ def translate_to_scene(sentence: str, primitives_json: str = "{}", seed: int = 0
76
  (ledger JSON string, rendered PNG, GLB scene file).
77
  """
78
  try:
79
- return pipeline.translate(sentence, json.loads(primitives_json or "{}"), int(seed))
80
  except Exception as e:
81
  _fail(e)
82
 
83
 
84
- def refine_scene(ledger_json: str, changes_json: str = "[]", seed: int = 0) -> tuple[str, str, str]:
85
  """Refine a ledger: freeze some decisions and change the others, then re-render.
86
 
87
  Changes are a JSON list of:
@@ -102,9 +122,9 @@ def refine_scene(ledger_json: str, changes_json: str = "[]", seed: int = 0) -> t
102
  (ledger JSON string, rendered PNG, GLB scene file).
103
  """
104
  try:
105
- ledger = json.loads(ledger_json)
106
- pipeline.apply_changes(ledger, json.loads(changes_json or "[]"), int(seed))
107
- return pipeline.outputs(ledger, int(seed))
108
  except Exception as e:
109
  _fail(e)
110
 
@@ -124,7 +144,7 @@ def simplify_item(ledger_json: str, item_id: str, check_recognition: bool = Fals
124
  (report JSON string, gallery of rung pictures, GLB file per rung).
125
  """
126
  try:
127
- ledger = json.loads(ledger_json)
128
  item = next((i for i in ledger["items"] if i["id"] == item_id), None)
129
  if item is None or item["kind"] != "object":
130
  raise ValueError(f"{item_id!r} is not an object item in this ledger")
@@ -152,7 +172,7 @@ def simplify_item(ledger_json: str, item_id: str, check_recognition: bool = Fals
152
  _fail(e)
153
 
154
 
155
- def describe_scene(ledger_json: str, seed: int = 0) -> str:
156
  """Back-translate: render the ledger's scene and have a vision model describe it in English.
157
 
158
  Comparing the description with the original sentence checks the translation.
@@ -165,9 +185,9 @@ def describe_scene(ledger_json: str, seed: int = 0) -> str:
165
  JSON string: {"original", "back_translation", "nouns_recovered": {word: bool}}.
166
  """
167
  try:
168
- ledger = json.loads(ledger_json)
169
  meshes = pipeline.settle_all(ledger)
170
- config = scene.configure(ledger, meshes, int(seed))
171
  png = scene.render_scene(ledger, meshes, config, os.path.join(tempfile.mkdtemp(), "scene.png"))
172
  text = vlm.describe(png)
173
  nouns = [i["word"] for i in ledger["items"] if i["kind"] == "object"]
@@ -206,7 +226,7 @@ def _complete(sentence, choice):
206
 
207
 
208
  def _use_primitive(primitives_json, word, shape):
209
- prims = json.loads(primitives_json or "{}")
210
  if word:
211
  prims[word] = shape
212
  return json.dumps(prims)
@@ -219,7 +239,7 @@ with gr.Blocks(title="VisualLearningIdea") as demo:
219
  "verbs and prepositions are relations between meshes.")
220
  with gr.Row():
221
  sentence = gr.Textbox(label="Sentence", placeholder="the cat sat and the sun shone", scale=4)
222
- seed = gr.Number(label="Configuration seed", value=0, precision=0, scale=1)
223
  status = gr.Markdown()
224
  suggest = gr.Radio(label="Suggestions", choices=[], interactive=True)
225
  with gr.Row():
 
23
  raise gr.Error(str(e))
24
 
25
 
26
+ def _json(text, name, default=None):
27
+ """Parse a JSON argument; blank -> default, junk -> a readable error."""
28
+ if not (text or "").strip():
29
+ if default is None:
30
+ raise ValueError(f"{name} is empty; translate a sentence first")
31
+ return default
32
+ try:
33
+ return json.loads(text)
34
+ except json.JSONDecodeError:
35
+ raise ValueError(f"{name} is not valid JSON")
36
+
37
+
38
+ def _seed(value):
39
+ """Seeds arrive as strings (some MCP clients can't send numbers)."""
40
+ try:
41
+ return int(float(str(value).strip() or 0))
42
+ except ValueError:
43
+ raise ValueError(f"seed must be a whole number, got {value!r}")
44
+
45
+
46
  @spaces.GPU(duration=60)
47
  def generate_mesh(prompt):
48
  """GPU entry point for the generation tier (last in the fallback chain).
 
74
  "primitive_shapes", "completions"}.
75
  """
76
  try:
77
+ return json.dumps(_check_words(sentence, _json(primitives_json, "primitives_json", {})))
78
  except Exception as e:
79
  _fail(e)
80
 
81
 
82
+ def translate_to_scene(sentence: str, primitives_json: str = "{}", seed: str = "0") -> tuple[str, str, str]:
83
  """Translate an English sentence into a 3D scene.
84
 
85
  Produces the serial ledger (tokens in reading order, the items each word commits,
 
96
  (ledger JSON string, rendered PNG, GLB scene file).
97
  """
98
  try:
99
+ return pipeline.translate(sentence, _json(primitives_json, "primitives_json", {}), _seed(seed))
100
  except Exception as e:
101
  _fail(e)
102
 
103
 
104
+ def refine_scene(ledger_json: str, changes_json: str = "[]", seed: str = "0") -> tuple[str, str, str]:
105
  """Refine a ledger: freeze some decisions and change the others, then re-render.
106
 
107
  Changes are a JSON list of:
 
122
  (ledger JSON string, rendered PNG, GLB scene file).
123
  """
124
  try:
125
+ ledger = _json(ledger_json, "ledger_json")
126
+ pipeline.apply_changes(ledger, _json(changes_json, "changes_json", []), _seed(seed))
127
+ return pipeline.outputs(ledger, _seed(seed))
128
  except Exception as e:
129
  _fail(e)
130
 
 
144
  (report JSON string, gallery of rung pictures, GLB file per rung).
145
  """
146
  try:
147
+ ledger = _json(ledger_json, "ledger_json")
148
  item = next((i for i in ledger["items"] if i["id"] == item_id), None)
149
  if item is None or item["kind"] != "object":
150
  raise ValueError(f"{item_id!r} is not an object item in this ledger")
 
172
  _fail(e)
173
 
174
 
175
+ def describe_scene(ledger_json: str, seed: str = "0") -> str:
176
  """Back-translate: render the ledger's scene and have a vision model describe it in English.
177
 
178
  Comparing the description with the original sentence checks the translation.
 
185
  JSON string: {"original", "back_translation", "nouns_recovered": {word: bool}}.
186
  """
187
  try:
188
+ ledger = _json(ledger_json, "ledger_json")
189
  meshes = pipeline.settle_all(ledger)
190
+ config = scene.configure(ledger, meshes, _seed(seed))
191
  png = scene.render_scene(ledger, meshes, config, os.path.join(tempfile.mkdtemp(), "scene.png"))
192
  text = vlm.describe(png)
193
  nouns = [i["word"] for i in ledger["items"] if i["kind"] == "object"]
 
226
 
227
 
228
  def _use_primitive(primitives_json, word, shape):
229
+ prims = _json(primitives_json, "primitives_json", {})
230
  if word:
231
  prims[word] = shape
232
  return json.dumps(prims)
 
239
  "verbs and prepositions are relations between meshes.")
240
  with gr.Row():
241
  sentence = gr.Textbox(label="Sentence", placeholder="the cat sat and the sun shone", scale=4)
242
+ seed = gr.Textbox(label="Configuration seed", value="0", scale=1)
243
  status = gr.Markdown()
244
  suggest = gr.Radio(label="Suggestions", choices=[], interactive=True)
245
  with gr.Row():