Zhen Ye Claude Opus 4.6 (1M context) commited on
Commit
7e09916
Β·
1 Parent(s): f34ecf3

feat: auto-suggest ISR missions from video preview frame

Browse files

On video select, extract a frame client-side and send to GPT-4o-mini
vision to suggest 3-5 feasible mission objectives. Suggestions render
as clickable chips below the mission input β€” click to populate.

- New BAML SuggestMissions function with detailed vision prompt
constraining suggestions to COCO-detectable, analyst-decidable missions
- New POST /suggest-missions endpoint
- Frontend frame extraction with race guard, video/canvas cleanup,
and 512px cap for efficient LLM processing
- Suggestion chips with fade-in animation, hover tooltips, selection state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

app.py CHANGED
@@ -27,6 +27,7 @@ except Exception as e:
27
  print(f"Startup Diagnostics Error: {e}")
28
 
29
  import asyncio
 
30
  import json
31
  import shutil
32
  import tempfile
@@ -399,6 +400,25 @@ async def detect_endpoint(
399
  return response
400
 
401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  @app.post("/detect/async")
403
  async def detect_async_endpoint(
404
  video: UploadFile = File(...),
 
27
  print(f"Startup Diagnostics Error: {e}")
28
 
29
  import asyncio
30
+ import base64
31
  import json
32
  import shutil
33
  import tempfile
 
400
  return response
401
 
402
 
403
+ @app.post("/suggest-missions")
404
+ async def suggest_missions_endpoint(frame: UploadFile = File(...)):
405
+ """Analyze a video frame and suggest 3-5 ISR mission objectives."""
406
+ try:
407
+ from baml_client.sync_client import b as baml
408
+ from baml_py import Image
409
+
410
+ data = await frame.read()
411
+ b64 = base64.b64encode(data).decode("utf-8")
412
+ media_type = frame.content_type or "image/jpeg"
413
+ frame_image = Image.from_base64(media_type, b64)
414
+
415
+ suggestions = await asyncio.to_thread(baml.SuggestMissions, frame=frame_image)
416
+ return [{"mission": s.mission, "reasoning": s.reasoning} for s in suggestions]
417
+ except Exception:
418
+ logging.exception("SuggestMissions failed")
419
+ return []
420
+
421
+
422
  @app.post("/detect/async")
423
  async def detect_async_endpoint(
424
  video: UploadFile = File(...),
baml_client/async_client.py CHANGED
@@ -112,6 +112,21 @@ class BamlAsyncClient:
112
  "mission_text": mission_text,
113
  })
114
  return typing.cast(types.MissionPlan, __result__.cast_to(types, types, stream_types, False, __runtime__))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
 
117
 
@@ -145,6 +160,18 @@ class BamlStreamClient:
145
  lambda x: typing.cast(types.MissionPlan, x.cast_to(types, types, stream_types, False, __runtime__)),
146
  __ctx__,
147
  )
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
 
150
  class BamlHttpRequestClient:
@@ -167,6 +194,13 @@ class BamlHttpRequestClient:
167
  "mission_text": mission_text,
168
  }, mode="request")
169
  return __result__
 
 
 
 
 
 
 
170
 
171
 
172
  class BamlHttpStreamRequestClient:
@@ -189,6 +223,13 @@ class BamlHttpStreamRequestClient:
189
  "mission_text": mission_text,
190
  }, mode="stream")
191
  return __result__
 
 
 
 
 
 
 
192
 
193
 
194
  b = BamlAsyncClient(DoNotUseDirectlyCallManager({}))
 
112
  "mission_text": mission_text,
113
  })
114
  return typing.cast(types.MissionPlan, __result__.cast_to(types, types, stream_types, False, __runtime__))
115
+ async def SuggestMissions(self, frame: baml_py.Image,
116
+ baml_options: BamlCallOptions = {},
117
+ ) -> typing.List["types.MissionSuggestion"]:
118
+ # Check if on_tick is provided
119
+ if 'on_tick' in baml_options:
120
+ # Use streaming internally when on_tick is provided
121
+ __stream__ = self.stream.SuggestMissions(frame=frame,
122
+ baml_options=baml_options)
123
+ return await __stream__.get_final_response()
124
+ else:
125
+ # Original non-streaming code
126
+ __result__ = await self.__options.merge_options(baml_options).call_function_async(function_name="SuggestMissions", args={
127
+ "frame": frame,
128
+ })
129
+ return typing.cast(typing.List["types.MissionSuggestion"], __result__.cast_to(types, types, stream_types, False, __runtime__))
130
 
131
 
132
 
 
160
  lambda x: typing.cast(types.MissionPlan, x.cast_to(types, types, stream_types, False, __runtime__)),
161
  __ctx__,
162
  )
163
+ def SuggestMissions(self, frame: baml_py.Image,
164
+ baml_options: BamlCallOptions = {},
165
+ ) -> baml_py.BamlStream[typing.List["stream_types.MissionSuggestion"], typing.List["types.MissionSuggestion"]]:
166
+ __ctx__, __result__ = self.__options.merge_options(baml_options).create_async_stream(function_name="SuggestMissions", args={
167
+ "frame": frame,
168
+ })
169
+ return baml_py.BamlStream[typing.List["stream_types.MissionSuggestion"], typing.List["types.MissionSuggestion"]](
170
+ __result__,
171
+ lambda x: typing.cast(typing.List["stream_types.MissionSuggestion"], x.cast_to(types, types, stream_types, True, __runtime__)),
172
+ lambda x: typing.cast(typing.List["types.MissionSuggestion"], x.cast_to(types, types, stream_types, False, __runtime__)),
173
+ __ctx__,
174
+ )
175
 
176
 
177
  class BamlHttpRequestClient:
 
194
  "mission_text": mission_text,
195
  }, mode="request")
196
  return __result__
197
+ async def SuggestMissions(self, frame: baml_py.Image,
198
+ baml_options: BamlCallOptions = {},
199
+ ) -> baml_py.baml_py.HTTPRequest:
200
+ __result__ = await self.__options.merge_options(baml_options).create_http_request_async(function_name="SuggestMissions", args={
201
+ "frame": frame,
202
+ }, mode="request")
203
+ return __result__
204
 
205
 
206
  class BamlHttpStreamRequestClient:
 
223
  "mission_text": mission_text,
224
  }, mode="stream")
225
  return __result__
226
+ async def SuggestMissions(self, frame: baml_py.Image,
227
+ baml_options: BamlCallOptions = {},
228
+ ) -> baml_py.baml_py.HTTPRequest:
229
+ __result__ = await self.__options.merge_options(baml_options).create_http_request_async(function_name="SuggestMissions", args={
230
+ "frame": frame,
231
+ }, mode="stream")
232
+ return __result__
233
 
234
 
235
  b = BamlAsyncClient(DoNotUseDirectlyCallManager({}))
baml_client/inlinedbaml.py CHANGED
@@ -14,7 +14,7 @@ _file_map = {
14
 
15
  "clients.baml": "// ISR LLM clients\n\nclient<llm> GPT4oMini {\n provider openai\n retry_policy Retry\n options {\n model \"gpt-4o-mini\"\n api_key env.OPENAI_API_KEY\n temperature 0.1\n }\n}\n\nclient<llm> GPT4o {\n provider openai\n retry_policy Retry\n options {\n model \"gpt-4o\"\n api_key env.OPENAI_API_KEY\n temperature 0.2\n }\n}\n\nretry_policy Retry {\n max_retries 2\n strategy {\n type exponential_backoff\n delay_ms 500\n multiplier 2.0\n max_delay_ms 5000\n }\n}\n",
16
  "generators.baml": "// This helps use auto generate libraries you can use in the language of\n// your choice. You can have multiple generators if you use multiple languages.\n// Just ensure that the output_dir is different for each generator.\ngenerator target {\n // Valid values: \"python/pydantic\", \"typescript\", \"go\", \"rust\", \"ruby/sorbet\", \"rest/openapi\"\n output_type \"python/pydantic\"\n\n // Where the generated code will be saved (relative to baml_src/)\n output_dir \"../\"\n\n // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).\n // The BAML VSCode extension version should also match this version.\n version \"0.220.0\"\n\n // Valid values: \"sync\", \"async\"\n // This controls what `b.FunctionName()` will be (sync or async).\n default_client_mode sync\n}\n",
17
- "isr.baml": "// ISR Mission Planning & Assessment Functions\n\n// ── Mission Planning ─────────────────────────────────────────────\n// Takes a free-form mission objective and produces:\n// 1. Concrete object class queries for the detector (YOLO/DETR/GDINO)\n// 2. A refined mission statement for downstream assessment\n\nclass MissionPlan {\n detector_queries string[] @description(\"ONLY the object classes directly targeted by the mission. Use COCO class names (person, car, truck, bicycle, motorcycle, bus, dog, cat, etc.). Include ONLY classes the mission explicitly asks to find β€” do NOT pad with tangentially related classes. Typically 1-4 items.\")\n refined_mission string @description(\"A clear, one-sentence restatement of the mission objective that a downstream analyst LLM will evaluate each detection against.\")\n reasoning string @description(\"Brief explanation of why these queries were chosen.\")\n}\n\nfunction PlanMission(mission_text: string) -> MissionPlan {\n client GPT4oMini\n prompt #\"\n You are an ISR (Intelligence, Surveillance, Reconnaissance) mission planner.\n\n Your job is to decide what BROAD object categories a visual detector (YOLO / DETR / Grounding DINO) should look for.\n\n IMPORTANT β€” separation of concerns:\n - The DETECTOR can only recognize broad visual categories (person, car, truck, bus, etc.).\n It CANNOT judge intent, context, or mission-specific conditions.\n - A DOWNSTREAM ANALYST LLM will later examine each detection and decide whether it\n satisfies the mission. That is where nuanced judgment happens.\n\n Therefore:\n - Output ONLY the object classes the mission EXPLICITLY targets. Keep the list MINIMAL.\n - NEVER pad with \"context\" classes that happen to appear in the scene but are not the\n mission target. Every extra class wastes GPU cycles on irrelevant detections.\n - NEVER output mission-specific or subjective labels (e.g. \"stranded person\", \"cargo truck\",\n \"suspicious vehicle\"). The detector cannot distinguish these from their parent category.\n - Use standard COCO class names when possible: person, car, truck, bus, motorcycle, bicycle,\n dog, cat, horse, sheep, cow, elephant, bear, zebra, giraffe, bird,\n boat, airplane, backpack, suitcase, handbag, umbrella, etc.\n\n KEY RULE β€” ask \"Is the mission trying to FIND this class of object?\"\n - YES β†’ include it. NO β†’ leave it out, even if it appears in the scene.\n\n Examples:\n - \"identify person stranded on rooftop\" β†’ detector_queries: [\"person\"]\n (mission targets people, NOT rooftops. Downstream LLM judges context.)\n - \"find motorcycles lane-splitting between traffic\" β†’ detector_queries: [\"motorcycle\"]\n (mission targets motorcycles, NOT the surrounding cars/trucks.)\n - \"identify vehicles that can carry heavy cargos\" β†’ detector_queries: [\"truck\", \"bus\", \"car\"]\n (mission targets vehicles broadly β€” multiple vehicle classes needed.)\n - \"find abandoned luggage in airport\" β†’ detector_queries: [\"suitcase\", \"backpack\", \"handbag\"]\n (mission targets luggage β€” multiple luggage classes needed.)\n\n Mission objective: \"{{ mission_text }}\"\n\n {{ ctx.output_format }}\n \"#\n}\n\n\n// ── Detection Assessment ─────────────────────────────────────────\n// Replaces hand-rolled JSON parsing with type-safe BAML output\n\nclass DetectionInfo {\n track_id string\n class_label string\n confidence float @description(\"Detection model confidence 0.0-1.0. Lower values mean the detector is less sure about the class.\")\n center_x float @description(\"Horizontal position in frame, 0.0=left edge, 1.0=right edge\")\n center_y float @description(\"Vertical position in frame, 0.0=top edge, 1.0=bottom edge\")\n speed_kph float\n direction string @description(\"Clock direction (e.g. '3 o'clock' for rightward) or 'stationary'\")\n}\n\nclass DetectionVerdict {\n track_id string\n mission_relevant bool @description(\"Does this broad object CLASS relate to the mission? e.g. 'person' is relevant to a rescue mission, 'dog' is not.\")\n satisfies bool? @description(\"Based on visual context, does THIS SPECIFIC detection actually meet the mission criteria? e.g. a person on a rooftop satisfies 'find stranded people', but a person walking on a street does not. null if the image is too ambiguous to judge.\")\n reason string @description(\"1-2 sentences: what you observe in the image that supports your verdict. Reference specific visual cues (location, posture, surroundings, motion).\")\n features map<string, string> @description(\"2-5 observable properties from the image relevant to the mission. e.g. location: 'rooftop', posture: 'standing', surroundings: 'floodwater', vehicle_type: 'flatbed truck'\")\n}\n\n// ── PlanMission Tests ────────────────────────────────────────────\n\ntest HeavyCargoVehicles {\n functions [PlanMission]\n args {\n mission_text \"identify vehicles that can carry heavy cargos\"\n }\n // Mission targets vehicles broadly β€” multiple vehicle types needed\n @@assert( {{ this.detector_queries|length >= 2 }} )\n @@assert( {{ this.detector_queries|length <= 5 }} )\n @@assert( {{ \"truck\" in this.detector_queries }} )\n @@assert( {{ \"cargo\" not in this.detector_queries|join(\" \") }} )\n // No non-vehicle padding\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"dog\" not in this.detector_queries }} )\n}\n\ntest PersonOnRooftop {\n functions [PlanMission]\n args {\n mission_text \"identify person stranded on rooftop\"\n }\n // Mission targets person only β€” rooftop is context for assessor\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"stranded\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"rooftop\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"dog\" not in this.detector_queries }} )\n}\n\ntest AbandonedLuggage {\n functions [PlanMission]\n args {\n mission_text \"find abandoned luggage in airport terminal\"\n }\n // Mission targets luggage β€” multiple luggage classes needed\n @@assert( {{ this.detector_queries|length >= 2 }} )\n @@assert( {{ this.detector_queries|length <= 4 }} )\n @@assert( {{ \"abandoned\" not in this.detector_queries|join(\" \") }} )\n // No non-luggage padding\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"airplane\" not in this.detector_queries }} )\n}\n\ntest VehiclesBlockingHighway {\n functions [PlanMission]\n args {\n mission_text \"locate vehicles blocking the highway exit ramp\"\n }\n // Mission targets vehicles β€” multiple vehicle types needed\n @@assert( {{ \"car\" in this.detector_queries }} )\n @@assert( {{ \"truck\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 5 }} )\n @@assert( {{ \"blocking\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"highway\" not in this.detector_queries|join(\" \") }} )\n // No non-vehicle padding\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"dog\" not in this.detector_queries }} )\n}\n\ntest PeopleJaywalking {\n functions [PlanMission]\n args {\n mission_text \"identify people jaywalking across the main road\"\n }\n // Mission targets people only β€” road/vehicles are context\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"jaywalking\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"road\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"truck\" not in this.detector_queries }} )\n @@assert( {{ \"bus\" not in this.detector_queries }} )\n @@assert( {{ \"bicycle\" not in this.detector_queries }} )\n}\n\ntest BoatsNearRestrictedDock {\n functions [PlanMission]\n args {\n mission_text \"find boats anchored near the restricted dock area\"\n }\n // Mission targets boats only\n @@assert( {{ \"boat\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"anchored\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"restricted\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"dock\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\ntest DogsOffLeash {\n functions [PlanMission]\n args {\n mission_text \"detect dogs off-leash in the public park\"\n }\n // Mission targets dogs only β€” leash/park are context\n @@assert( {{ \"dog\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"leash\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"off-leash\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"park\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"cat\" not in this.detector_queries }} )\n}\n\ntest DeliveryTrucksInDriveways {\n functions [PlanMission]\n args {\n mission_text \"locate delivery trucks parked in residential driveways\"\n }\n // Mission targets trucks only β€” delivery/residential are context\n @@assert( {{ \"truck\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"delivery\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"residential\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"driveway\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\ntest CyclistsOnSidewalk {\n functions [PlanMission]\n args {\n mission_text \"identify cyclists riding on the pedestrian sidewalk\"\n }\n // Mission targets cyclists = bicycle + person\n @@assert( {{ \"bicycle\" in this.detector_queries }} )\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 3 }} )\n @@assert( {{ \"cyclist\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"sidewalk\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"pedestrian\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"truck\" not in this.detector_queries }} )\n}\n\ntest PersonsLoiteringNearExit {\n functions [PlanMission]\n args {\n mission_text \"find persons loitering near the emergency exit\"\n }\n // Mission targets persons only β€” exit is context\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"loitering\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"emergency\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"door\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n}\n\ntest OverturnedVehicles {\n functions [PlanMission]\n args {\n mission_text \"detect overturned vehicles on the freeway\"\n }\n // Mission targets vehicles β€” multiple vehicle types needed\n @@assert( {{ \"car\" in this.detector_queries }} )\n @@assert( {{ \"truck\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 5 }} )\n @@assert( {{ \"overturned\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"freeway\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"dog\" not in this.detector_queries }} )\n}\n\ntest AircraftOutsideApron {\n functions [PlanMission]\n args {\n mission_text \"locate aircraft parked outside designated apron area\"\n }\n // Mission targets aircraft only\n @@assert( {{ \"airplane\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"designated\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"apron\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n}\n\ntest PersonsWithBackpacksNearFence {\n functions [PlanMission]\n args {\n mission_text \"identify persons carrying large backpacks near the perimeter fence\"\n }\n // Mission targets persons + backpacks\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ \"backpack\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 3 }} )\n @@assert( {{ \"perimeter\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"fence\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"carrying\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n}\n\ntest MotorcyclesLaneSplitting {\n functions [PlanMission]\n args {\n mission_text \"find motorcycles lane-splitting between traffic\"\n }\n // Mission targets motorcycles only β€” surrounding traffic is context\n @@assert( {{ \"motorcycle\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"lane-splitting\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"traffic\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"truck\" not in this.detector_queries }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\ntest CattleOnRoadway {\n functions [PlanMission]\n args {\n mission_text \"detect cattle that have crossed onto the roadway\"\n }\n // Mission targets cattle (COCO: cow) only\n @@assert( {{ \"cow\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"cattle\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"roadway\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"crossed\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\ntest BusesAtUnauthorizedStops {\n functions [PlanMission]\n args {\n mission_text \"locate buses stopped at unauthorized pickup points\"\n }\n // Mission targets buses only\n @@assert( {{ \"bus\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"unauthorized\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"pickup\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"truck\" not in this.detector_queries }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\ntest PeopleSwimmingInRestrictedArea {\n functions [PlanMission]\n args {\n mission_text \"identify people swimming in the restricted waterway\"\n }\n // Mission targets people only β€” waterway is context\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"swimming\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"restricted\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"waterway\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"boat\" not in this.detector_queries }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n}\n\ntest TrucksWithOversizedLoads {\n functions [PlanMission]\n args {\n mission_text \"find trucks carrying visible oversized loads on the bridge\"\n }\n // Mission targets trucks only β€” bridge/load are context\n @@assert( {{ \"truck\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"oversized\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"cargo\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"bridge\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\n\n// ── Detection Assessment ─────────────────────────────────────────\n// Replaces hand-rolled JSON parsing with type-safe BAML output\n\nfunction AssessDetections(mission: string, detections: DetectionInfo[], frame_image: image) -> DetectionVerdict[] {\n client GPT4oMini\n prompt #\"\n {{ _.role(\"system\") }}\n You are an ISR analyst. A broad-net detector found candidate objects. Your job: decide\n which ones actually satisfy the mission using the image and metadata below.\n\n Reading the metadata:\n - **confidence**: detector certainty (0-1). Below 0.4 = likely false positive β€” verify visually before trusting the class label.\n - **position**: (x, y) in frame where (0,0)=top-left, (1,1)=bottom-right. Use this to locate each detection in the image.\n - **speed/direction**: from multi-frame tracking. speed=0 means stationary. Direction uses clock notation (12=up, 3=right, 6=down, 9=left).\n\n For each detection, decide:\n - **mission_relevant**: Does this object CLASS relate to the mission? (person vs dog, not this specific instance.)\n - **satisfies**: Based on what you SEE in the image β€” does this specific detection meet the mission criteria? Consider location, posture, surroundings, object subtype. Set null only when genuinely ambiguous.\n - **reason**: Cite what you observe in the image. No generic statements.\n - **features**: 2-5 observable properties from the image relevant to the mission.\n\n {{ _.role(\"user\") }}\n Mission: \"{{ mission }}\"\n\n Detected objects:\n {% for d in detections %}\n - {{ d.track_id }}: {{ d.class_label }} (conf={{ d.confidence }}) at ({{ d.center_x }}, {{ d.center_y }}), {{ d.speed_kph }}kph {{ d.direction }}\n {% endfor %}\n\n {{ frame_image }}\n\n Assess every detection above.\n\n {{ ctx.output_format }}\n \"#\n}\n\n// ── AssessDetections Tests ───────────────────────────────────────\n// SAR flood scene: person + dog on rooftop, surrounded by floodwater\n\n// Wide net: detector found person + dog. Assessor must judge context from image.\ntest SAR_PersonOnRooftop {\n functions [AssessDetections]\n args {\n mission \"identify person stranded on rooftop needing rescue\"\n detections [\n {\n track_id \"T01\"\n class_label \"person\"\n confidence 0.87\n center_x 0.45\n center_y 0.35\n speed_kph 0.0\n direction \"stationary\"\n },\n {\n track_id \"T02\"\n class_label \"dog\"\n confidence 0.72\n center_x 0.52\n center_y 0.40\n speed_kph 0.0\n direction \"stationary\"\n }\n ]\n frame_image {\n file \"fixtures/sar_rooftop.jpg\"\n media_type \"image/jpeg\"\n }\n }\n @@assert( {{ this|length == 2 }} )\n // Person on rooftop: class is relevant AND visual context satisfies mission\n @@assert( {{ this[0].track_id == \"T01\" }} )\n @@assert( {{ this[0].mission_relevant == true }} )\n @@assert( {{ this[0].satisfies == true }} )\n // Dog: class is not relevant to \"person stranded\" mission\n @@assert( {{ this[1].track_id == \"T02\" }} )\n @@assert( {{ this[1].mission_relevant == false }} )\n}\n\n// Wide net caught person + dog, but mission is about vehicles β€” both should be irrelevant.\ntest NeitherPersonNorDogIsVehicle {\n functions [AssessDetections]\n args {\n mission \"identify vehicles capable of transporting heavy cargo\"\n detections [\n {\n track_id \"T01\"\n class_label \"person\"\n confidence 0.87\n center_x 0.45\n center_y 0.35\n speed_kph 0.0\n direction \"stationary\"\n },\n {\n track_id \"T02\"\n class_label \"dog\"\n confidence 0.72\n center_x 0.52\n center_y 0.40\n speed_kph 0.0\n direction \"stationary\"\n }\n ]\n frame_image {\n file \"fixtures/sar_rooftop.jpg\"\n media_type \"image/jpeg\"\n }\n }\n @@assert( {{ this|length == 2 }} )\n // Neither person nor dog is a vehicle β€” both mission_relevant=false\n @@assert( {{ this[0].mission_relevant == false }} )\n @@assert( {{ this[1].mission_relevant == false }} )\n @@assert( {{ this[0].satisfies != true }} )\n @@assert( {{ this[1].satisfies != true }} )\n}\n",
18
  }
19
 
20
  def get_baml_files():
 
14
 
15
  "clients.baml": "// ISR LLM clients\n\nclient<llm> GPT4oMini {\n provider openai\n retry_policy Retry\n options {\n model \"gpt-4o-mini\"\n api_key env.OPENAI_API_KEY\n temperature 0.1\n }\n}\n\nclient<llm> GPT4o {\n provider openai\n retry_policy Retry\n options {\n model \"gpt-4o\"\n api_key env.OPENAI_API_KEY\n temperature 0.2\n }\n}\n\nretry_policy Retry {\n max_retries 2\n strategy {\n type exponential_backoff\n delay_ms 500\n multiplier 2.0\n max_delay_ms 5000\n }\n}\n",
16
  "generators.baml": "// This helps use auto generate libraries you can use in the language of\n// your choice. You can have multiple generators if you use multiple languages.\n// Just ensure that the output_dir is different for each generator.\ngenerator target {\n // Valid values: \"python/pydantic\", \"typescript\", \"go\", \"rust\", \"ruby/sorbet\", \"rest/openapi\"\n output_type \"python/pydantic\"\n\n // Where the generated code will be saved (relative to baml_src/)\n output_dir \"../\"\n\n // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).\n // The BAML VSCode extension version should also match this version.\n version \"0.220.0\"\n\n // Valid values: \"sync\", \"async\"\n // This controls what `b.FunctionName()` will be (sync or async).\n default_client_mode sync\n}\n",
17
+ "isr.baml": "// ISR Mission Planning & Assessment Functions\n\n// ── Mission Planning ─────────────────────────────────────────────\n// Takes a free-form mission objective and produces:\n// 1. Concrete object class queries for the detector (YOLO/DETR/GDINO)\n// 2. A refined mission statement for downstream assessment\n\nclass MissionPlan {\n detector_queries string[] @description(\"ONLY the object classes directly targeted by the mission. Use COCO class names (person, car, truck, bicycle, motorcycle, bus, dog, cat, etc.). Include ONLY classes the mission explicitly asks to find β€” do NOT pad with tangentially related classes. Typically 1-4 items.\")\n refined_mission string @description(\"A clear, one-sentence restatement of the mission objective that a downstream analyst LLM will evaluate each detection against.\")\n reasoning string @description(\"Brief explanation of why these queries were chosen.\")\n}\n\nfunction PlanMission(mission_text: string) -> MissionPlan {\n client GPT4oMini\n prompt #\"\n You are an ISR (Intelligence, Surveillance, Reconnaissance) mission planner.\n\n Your job is to decide what BROAD object categories a visual detector (YOLO / DETR / Grounding DINO) should look for.\n\n IMPORTANT β€” separation of concerns:\n - The DETECTOR can only recognize broad visual categories (person, car, truck, bus, etc.).\n It CANNOT judge intent, context, or mission-specific conditions.\n - A DOWNSTREAM ANALYST LLM will later examine each detection and decide whether it\n satisfies the mission. That is where nuanced judgment happens.\n\n Therefore:\n - Output ONLY the object classes the mission EXPLICITLY targets. Keep the list MINIMAL.\n - NEVER pad with \"context\" classes that happen to appear in the scene but are not the\n mission target. Every extra class wastes GPU cycles on irrelevant detections.\n - NEVER output mission-specific or subjective labels (e.g. \"stranded person\", \"cargo truck\",\n \"suspicious vehicle\"). The detector cannot distinguish these from their parent category.\n - Use standard COCO class names when possible: person, car, truck, bus, motorcycle, bicycle,\n dog, cat, horse, sheep, cow, elephant, bear, zebra, giraffe, bird,\n boat, airplane, backpack, suitcase, handbag, umbrella, etc.\n\n KEY RULE β€” ask \"Is the mission trying to FIND this class of object?\"\n - YES β†’ include it. NO β†’ leave it out, even if it appears in the scene.\n\n Examples:\n - \"identify person stranded on rooftop\" β†’ detector_queries: [\"person\"]\n (mission targets people, NOT rooftops. Downstream LLM judges context.)\n - \"find motorcycles lane-splitting between traffic\" β†’ detector_queries: [\"motorcycle\"]\n (mission targets motorcycles, NOT the surrounding cars/trucks.)\n - \"identify vehicles that can carry heavy cargos\" β†’ detector_queries: [\"truck\", \"bus\", \"car\"]\n (mission targets vehicles broadly β€” multiple vehicle classes needed.)\n - \"find abandoned luggage in airport\" β†’ detector_queries: [\"suitcase\", \"backpack\", \"handbag\"]\n (mission targets luggage β€” multiple luggage classes needed.)\n\n Mission objective: \"{{ mission_text }}\"\n\n {{ ctx.output_format }}\n \"#\n}\n\n\n// ── Mission Suggestion ──────────────────────────────────────────\n// Analyzes a video frame and suggests actionable ISR missions\n\nclass MissionSuggestion {\n mission string @description(\"A concise, natural-language mission objective. Written as an imperative command, e.g. 'Track pedestrians crossing the intersection'. 8-15 words.\")\n reasoning string @description(\"One sentence: what you see in the image that makes this mission feasible and interesting.\")\n}\n\nfunction SuggestMissions(frame: image) -> MissionSuggestion[] {\n client GPT4oMini\n prompt #\"\n {{ _.role(\"system\") }}\n You are an ISR (Intelligence, Surveillance, Reconnaissance) mission advisor.\n Given a single video frame, suggest 3-5 interesting surveillance missions that\n a detector + analyst pipeline could realistically accomplish.\n\n RULES β€” what makes a good suggestion:\n 1. **Visually grounded**: Every suggestion must reference objects you can ACTUALLY SEE\n in the frame. Do not hallucinate objects that are not there.\n 2. **Detector-feasible**: The mission must target objects a COCO-trained detector can\n find (person, car, truck, bus, motorcycle, bicycle, boat, dog, cat, airplane, etc.).\n Do not suggest missions requiring detection of abstract concepts, emotions, or\n objects not in standard detection models.\n 3. **Analyst-decidable**: The mission criteria must be something a downstream vision LLM\n can judge from a single annotated frame β€” spatial position, object state, proximity,\n relative size, posture, motion direction. Avoid criteria needing temporal history,\n audio, or information not visible in images.\n 4. **Simple and clear**: Each mission should be a single, unambiguous objective.\n Not compound (\"find X and also Y\"). 8-15 words.\n 5. **Interesting variety**: Vary the missions β€” mix counting, tracking, spatial\n monitoring, and anomaly detection. Don't repeat the same pattern.\n\n AVOID these β€” they sound good but fail in practice:\n - Missions about intent or mental state (\"find suspicious people\", \"detect distressed individuals\")\n - Missions requiring world knowledge not in the image (\"unauthorized vehicles\", \"restricted zones\")\n - Missions about very small or occluded objects the detector would miss\n - Missions that are trivially satisfied by everything in the scene (\"detect all objects\")\n\n GOOD examples for different scene types:\n - Street: \"Track vehicles stopped at the intersection\" / \"Monitor pedestrians jaywalking across the road\"\n - Parking lot: \"Identify cars parked in the fire lane\" / \"Count trucks in the loading area\"\n - Aerial: \"Locate boats near the shoreline\" / \"Track vehicles on the highway bridge\"\n - Indoor: \"Monitor people near the emergency exits\" / \"Track shopping carts left in the walkway\"\n\n {{ _.role(\"user\") }}\n Analyze this video frame and suggest 3-5 feasible ISR missions:\n\n {{ frame }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n\n// ── Detection Assessment ─────────────────────────────────────────\n// Replaces hand-rolled JSON parsing with type-safe BAML output\n\nclass DetectionInfo {\n track_id string\n class_label string\n confidence float @description(\"Detection model confidence 0.0-1.0. Lower values mean the detector is less sure about the class.\")\n center_x float @description(\"Horizontal position in frame, 0.0=left edge, 1.0=right edge\")\n center_y float @description(\"Vertical position in frame, 0.0=top edge, 1.0=bottom edge\")\n speed_kph float\n direction string @description(\"Clock direction (e.g. '3 o'clock' for rightward) or 'stationary'\")\n}\n\nclass DetectionVerdict {\n track_id string\n mission_relevant bool @description(\"Does this broad object CLASS relate to the mission? e.g. 'person' is relevant to a rescue mission, 'dog' is not.\")\n satisfies bool? @description(\"Based on visual context, does THIS SPECIFIC detection actually meet the mission criteria? e.g. a person on a rooftop satisfies 'find stranded people', but a person walking on a street does not. null if the image is too ambiguous to judge.\")\n reason string @description(\"1-2 sentences: what you observe in the image that supports your verdict. Reference specific visual cues (location, posture, surroundings, motion).\")\n features map<string, string> @description(\"2-5 observable properties from the image relevant to the mission. e.g. location: 'rooftop', posture: 'standing', surroundings: 'floodwater', vehicle_type: 'flatbed truck'\")\n}\n\n// ── PlanMission Tests ────────────────────────────────────────────\n\ntest HeavyCargoVehicles {\n functions [PlanMission]\n args {\n mission_text \"identify vehicles that can carry heavy cargos\"\n }\n // Mission targets vehicles broadly β€” multiple vehicle types needed\n @@assert( {{ this.detector_queries|length >= 2 }} )\n @@assert( {{ this.detector_queries|length <= 5 }} )\n @@assert( {{ \"truck\" in this.detector_queries }} )\n @@assert( {{ \"cargo\" not in this.detector_queries|join(\" \") }} )\n // No non-vehicle padding\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"dog\" not in this.detector_queries }} )\n}\n\ntest PersonOnRooftop {\n functions [PlanMission]\n args {\n mission_text \"identify person stranded on rooftop\"\n }\n // Mission targets person only β€” rooftop is context for assessor\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"stranded\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"rooftop\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"dog\" not in this.detector_queries }} )\n}\n\ntest AbandonedLuggage {\n functions [PlanMission]\n args {\n mission_text \"find abandoned luggage in airport terminal\"\n }\n // Mission targets luggage β€” multiple luggage classes needed\n @@assert( {{ this.detector_queries|length >= 2 }} )\n @@assert( {{ this.detector_queries|length <= 4 }} )\n @@assert( {{ \"abandoned\" not in this.detector_queries|join(\" \") }} )\n // No non-luggage padding\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"airplane\" not in this.detector_queries }} )\n}\n\ntest VehiclesBlockingHighway {\n functions [PlanMission]\n args {\n mission_text \"locate vehicles blocking the highway exit ramp\"\n }\n // Mission targets vehicles β€” multiple vehicle types needed\n @@assert( {{ \"car\" in this.detector_queries }} )\n @@assert( {{ \"truck\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 5 }} )\n @@assert( {{ \"blocking\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"highway\" not in this.detector_queries|join(\" \") }} )\n // No non-vehicle padding\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"dog\" not in this.detector_queries }} )\n}\n\ntest PeopleJaywalking {\n functions [PlanMission]\n args {\n mission_text \"identify people jaywalking across the main road\"\n }\n // Mission targets people only β€” road/vehicles are context\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"jaywalking\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"road\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"truck\" not in this.detector_queries }} )\n @@assert( {{ \"bus\" not in this.detector_queries }} )\n @@assert( {{ \"bicycle\" not in this.detector_queries }} )\n}\n\ntest BoatsNearRestrictedDock {\n functions [PlanMission]\n args {\n mission_text \"find boats anchored near the restricted dock area\"\n }\n // Mission targets boats only\n @@assert( {{ \"boat\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"anchored\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"restricted\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"dock\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\ntest DogsOffLeash {\n functions [PlanMission]\n args {\n mission_text \"detect dogs off-leash in the public park\"\n }\n // Mission targets dogs only β€” leash/park are context\n @@assert( {{ \"dog\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"leash\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"off-leash\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"park\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"cat\" not in this.detector_queries }} )\n}\n\ntest DeliveryTrucksInDriveways {\n functions [PlanMission]\n args {\n mission_text \"locate delivery trucks parked in residential driveways\"\n }\n // Mission targets trucks only β€” delivery/residential are context\n @@assert( {{ \"truck\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"delivery\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"residential\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"driveway\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\ntest CyclistsOnSidewalk {\n functions [PlanMission]\n args {\n mission_text \"identify cyclists riding on the pedestrian sidewalk\"\n }\n // Mission targets cyclists = bicycle + person\n @@assert( {{ \"bicycle\" in this.detector_queries }} )\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 3 }} )\n @@assert( {{ \"cyclist\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"sidewalk\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"pedestrian\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"truck\" not in this.detector_queries }} )\n}\n\ntest PersonsLoiteringNearExit {\n functions [PlanMission]\n args {\n mission_text \"find persons loitering near the emergency exit\"\n }\n // Mission targets persons only β€” exit is context\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"loitering\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"emergency\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"door\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n}\n\ntest OverturnedVehicles {\n functions [PlanMission]\n args {\n mission_text \"detect overturned vehicles on the freeway\"\n }\n // Mission targets vehicles β€” multiple vehicle types needed\n @@assert( {{ \"car\" in this.detector_queries }} )\n @@assert( {{ \"truck\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 5 }} )\n @@assert( {{ \"overturned\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"freeway\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"dog\" not in this.detector_queries }} )\n}\n\ntest AircraftOutsideApron {\n functions [PlanMission]\n args {\n mission_text \"locate aircraft parked outside designated apron area\"\n }\n // Mission targets aircraft only\n @@assert( {{ \"airplane\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"designated\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"apron\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n}\n\ntest PersonsWithBackpacksNearFence {\n functions [PlanMission]\n args {\n mission_text \"identify persons carrying large backpacks near the perimeter fence\"\n }\n // Mission targets persons + backpacks\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ \"backpack\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 3 }} )\n @@assert( {{ \"perimeter\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"fence\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"carrying\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n}\n\ntest MotorcyclesLaneSplitting {\n functions [PlanMission]\n args {\n mission_text \"find motorcycles lane-splitting between traffic\"\n }\n // Mission targets motorcycles only β€” surrounding traffic is context\n @@assert( {{ \"motorcycle\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"lane-splitting\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"traffic\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"truck\" not in this.detector_queries }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\ntest CattleOnRoadway {\n functions [PlanMission]\n args {\n mission_text \"detect cattle that have crossed onto the roadway\"\n }\n // Mission targets cattle (COCO: cow) only\n @@assert( {{ \"cow\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"cattle\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"roadway\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"crossed\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\ntest BusesAtUnauthorizedStops {\n functions [PlanMission]\n args {\n mission_text \"locate buses stopped at unauthorized pickup points\"\n }\n // Mission targets buses only\n @@assert( {{ \"bus\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"unauthorized\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"pickup\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"truck\" not in this.detector_queries }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\ntest PeopleSwimmingInRestrictedArea {\n functions [PlanMission]\n args {\n mission_text \"identify people swimming in the restricted waterway\"\n }\n // Mission targets people only β€” waterway is context\n @@assert( {{ \"person\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"swimming\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"restricted\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"waterway\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"boat\" not in this.detector_queries }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n}\n\ntest TrucksWithOversizedLoads {\n functions [PlanMission]\n args {\n mission_text \"find trucks carrying visible oversized loads on the bridge\"\n }\n // Mission targets trucks only β€” bridge/load are context\n @@assert( {{ \"truck\" in this.detector_queries }} )\n @@assert( {{ this.detector_queries|length <= 2 }} )\n @@assert( {{ \"oversized\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"cargo\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"bridge\" not in this.detector_queries|join(\" \") }} )\n @@assert( {{ \"car\" not in this.detector_queries }} )\n @@assert( {{ \"person\" not in this.detector_queries }} )\n}\n\n\n// ── Detection Assessment ─────────────────────────────────────────\n// Replaces hand-rolled JSON parsing with type-safe BAML output\n\nfunction AssessDetections(mission: string, detections: DetectionInfo[], frame_image: image) -> DetectionVerdict[] {\n client GPT4oMini\n prompt #\"\n {{ _.role(\"system\") }}\n You are an ISR analyst. A broad-net detector found candidate objects. Your job: decide\n which ones actually satisfy the mission using the image and metadata below.\n\n Reading the metadata:\n - **confidence**: detector certainty (0-1). Below 0.4 = likely false positive β€” verify visually before trusting the class label.\n - **position**: (x, y) in frame where (0,0)=top-left, (1,1)=bottom-right. Use this to locate each detection in the image.\n - **speed/direction**: from multi-frame tracking. speed=0 means stationary. Direction uses clock notation (12=up, 3=right, 6=down, 9=left).\n\n For each detection, decide:\n - **mission_relevant**: Does this object CLASS relate to the mission? (person vs dog, not this specific instance.)\n - **satisfies**: Based on what you SEE in the image β€” does this specific detection meet the mission criteria? Consider location, posture, surroundings, object subtype. Set null only when genuinely ambiguous.\n - **reason**: Cite what you observe in the image. No generic statements.\n - **features**: 2-5 observable properties from the image relevant to the mission.\n\n {{ _.role(\"user\") }}\n Mission: \"{{ mission }}\"\n\n Detected objects:\n {% for d in detections %}\n - {{ d.track_id }}: {{ d.class_label }} (conf={{ d.confidence }}) at ({{ d.center_x }}, {{ d.center_y }}), {{ d.speed_kph }}kph {{ d.direction }}\n {% endfor %}\n\n {{ frame_image }}\n\n Assess every detection above.\n\n {{ ctx.output_format }}\n \"#\n}\n\n// ── AssessDetections Tests ───────────────────────────────────────\n// SAR flood scene: person + dog on rooftop, surrounded by floodwater\n\n// Wide net: detector found person + dog. Assessor must judge context from image.\ntest SAR_PersonOnRooftop {\n functions [AssessDetections]\n args {\n mission \"identify person stranded on rooftop needing rescue\"\n detections [\n {\n track_id \"T01\"\n class_label \"person\"\n confidence 0.87\n center_x 0.45\n center_y 0.35\n speed_kph 0.0\n direction \"stationary\"\n },\n {\n track_id \"T02\"\n class_label \"dog\"\n confidence 0.72\n center_x 0.52\n center_y 0.40\n speed_kph 0.0\n direction \"stationary\"\n }\n ]\n frame_image {\n file \"fixtures/sar_rooftop.jpg\"\n media_type \"image/jpeg\"\n }\n }\n @@assert( {{ this|length == 2 }} )\n // Person on rooftop: class is relevant AND visual context satisfies mission\n @@assert( {{ this[0].track_id == \"T01\" }} )\n @@assert( {{ this[0].mission_relevant == true }} )\n @@assert( {{ this[0].satisfies == true }} )\n // Dog: class is not relevant to \"person stranded\" mission\n @@assert( {{ this[1].track_id == \"T02\" }} )\n @@assert( {{ this[1].mission_relevant == false }} )\n}\n\n// Wide net caught person + dog, but mission is about vehicles β€” both should be irrelevant.\ntest NeitherPersonNorDogIsVehicle {\n functions [AssessDetections]\n args {\n mission \"identify vehicles capable of transporting heavy cargo\"\n detections [\n {\n track_id \"T01\"\n class_label \"person\"\n confidence 0.87\n center_x 0.45\n center_y 0.35\n speed_kph 0.0\n direction \"stationary\"\n },\n {\n track_id \"T02\"\n class_label \"dog\"\n confidence 0.72\n center_x 0.52\n center_y 0.40\n speed_kph 0.0\n direction \"stationary\"\n }\n ]\n frame_image {\n file \"fixtures/sar_rooftop.jpg\"\n media_type \"image/jpeg\"\n }\n }\n @@assert( {{ this|length == 2 }} )\n // Neither person nor dog is a vehicle β€” both mission_relevant=false\n @@assert( {{ this[0].mission_relevant == false }} )\n @@assert( {{ this[1].mission_relevant == false }} )\n @@assert( {{ this[0].satisfies != true }} )\n @@assert( {{ this[1].satisfies != true }} )\n}\n",
18
  }
19
 
20
  def get_baml_files():
baml_client/parser.py CHANGED
@@ -35,6 +35,12 @@ class LlmResponseParser:
35
  __result__ = self.__options.merge_options(baml_options).parse_response(function_name="PlanMission", llm_response=llm_response, mode="request")
36
  return typing.cast(types.MissionPlan, __result__)
37
 
 
 
 
 
 
 
38
 
39
 
40
  class LlmStreamParser:
@@ -55,4 +61,10 @@ class LlmStreamParser:
55
  __result__ = self.__options.merge_options(baml_options).parse_response(function_name="PlanMission", llm_response=llm_response, mode="stream")
56
  return typing.cast(stream_types.MissionPlan, __result__)
57
 
 
 
 
 
 
 
58
 
 
35
  __result__ = self.__options.merge_options(baml_options).parse_response(function_name="PlanMission", llm_response=llm_response, mode="request")
36
  return typing.cast(types.MissionPlan, __result__)
37
 
38
+ def SuggestMissions(
39
+ self, llm_response: str, baml_options: BamlCallOptions = {},
40
+ ) -> typing.List["types.MissionSuggestion"]:
41
+ __result__ = self.__options.merge_options(baml_options).parse_response(function_name="SuggestMissions", llm_response=llm_response, mode="request")
42
+ return typing.cast(typing.List["types.MissionSuggestion"], __result__)
43
+
44
 
45
 
46
  class LlmStreamParser:
 
61
  __result__ = self.__options.merge_options(baml_options).parse_response(function_name="PlanMission", llm_response=llm_response, mode="stream")
62
  return typing.cast(stream_types.MissionPlan, __result__)
63
 
64
+ def SuggestMissions(
65
+ self, llm_response: str, baml_options: BamlCallOptions = {},
66
+ ) -> typing.List["stream_types.MissionSuggestion"]:
67
+ __result__ = self.__options.merge_options(baml_options).parse_response(function_name="SuggestMissions", llm_response=llm_response, mode="stream")
68
+ return typing.cast(typing.List["stream_types.MissionSuggestion"], __result__)
69
+
70
 
baml_client/stream_types.py CHANGED
@@ -23,7 +23,7 @@ class StreamState(BaseModel, typing.Generic[StreamStateValueT]):
23
  value: StreamStateValueT
24
  state: typing_extensions.Literal["Pending", "Incomplete", "Complete"]
25
  # #########################################################################
26
- # Generated classes (3)
27
  # #########################################################################
28
 
29
  class DetectionInfo(BaseModel):
@@ -47,6 +47,10 @@ class MissionPlan(BaseModel):
47
  refined_mission: typing.Optional[str] = Field(default=None, description='A clear, one-sentence restatement of the mission objective that a downstream analyst LLM will evaluate each detection against.')
48
  reasoning: typing.Optional[str] = Field(default=None, description='Brief explanation of why these queries were chosen.')
49
 
 
 
 
 
50
  # #########################################################################
51
  # Generated type aliases (0)
52
  # #########################################################################
 
23
  value: StreamStateValueT
24
  state: typing_extensions.Literal["Pending", "Incomplete", "Complete"]
25
  # #########################################################################
26
+ # Generated classes (4)
27
  # #########################################################################
28
 
29
  class DetectionInfo(BaseModel):
 
47
  refined_mission: typing.Optional[str] = Field(default=None, description='A clear, one-sentence restatement of the mission objective that a downstream analyst LLM will evaluate each detection against.')
48
  reasoning: typing.Optional[str] = Field(default=None, description='Brief explanation of why these queries were chosen.')
49
 
50
+ class MissionSuggestion(BaseModel):
51
+ mission: typing.Optional[str] = Field(default=None, description='A concise, natural-language mission objective. Written as an imperative command, e.g. \'Track pedestrians crossing the intersection\'. 8-15 words.')
52
+ reasoning: typing.Optional[str] = Field(default=None, description='One sentence: what you see in the image that makes this mission feasible and interesting.')
53
+
54
  # #########################################################################
55
  # Generated type aliases (0)
56
  # #########################################################################
baml_client/sync_client.py CHANGED
@@ -122,6 +122,20 @@ class BamlSyncClient:
122
  "mission_text": mission_text,
123
  })
124
  return typing.cast(types.MissionPlan, __result__.cast_to(types, types, stream_types, False, __runtime__))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
 
127
 
@@ -155,6 +169,18 @@ class BamlStreamClient:
155
  lambda x: typing.cast(types.MissionPlan, x.cast_to(types, types, stream_types, False, __runtime__)),
156
  __ctx__,
157
  )
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
 
160
  class BamlHttpRequestClient:
@@ -177,6 +203,13 @@ class BamlHttpRequestClient:
177
  "mission_text": mission_text,
178
  }, mode="request")
179
  return __result__
 
 
 
 
 
 
 
180
 
181
 
182
  class BamlHttpStreamRequestClient:
@@ -199,6 +232,13 @@ class BamlHttpStreamRequestClient:
199
  "mission_text": mission_text,
200
  }, mode="stream")
201
  return __result__
 
 
 
 
 
 
 
202
 
203
 
204
  b = BamlSyncClient(DoNotUseDirectlyCallManager({}))
 
122
  "mission_text": mission_text,
123
  })
124
  return typing.cast(types.MissionPlan, __result__.cast_to(types, types, stream_types, False, __runtime__))
125
+ def SuggestMissions(self, frame: baml_py.Image,
126
+ baml_options: BamlCallOptions = {},
127
+ ) -> typing.List["types.MissionSuggestion"]:
128
+ # Check if on_tick is provided
129
+ if 'on_tick' in baml_options:
130
+ __stream__ = self.stream.SuggestMissions(frame=frame,
131
+ baml_options=baml_options)
132
+ return __stream__.get_final_response()
133
+ else:
134
+ # Original non-streaming code
135
+ __result__ = self.__options.merge_options(baml_options).call_function_sync(function_name="SuggestMissions", args={
136
+ "frame": frame,
137
+ })
138
+ return typing.cast(typing.List["types.MissionSuggestion"], __result__.cast_to(types, types, stream_types, False, __runtime__))
139
 
140
 
141
 
 
169
  lambda x: typing.cast(types.MissionPlan, x.cast_to(types, types, stream_types, False, __runtime__)),
170
  __ctx__,
171
  )
172
+ def SuggestMissions(self, frame: baml_py.Image,
173
+ baml_options: BamlCallOptions = {},
174
+ ) -> baml_py.BamlSyncStream[typing.List["stream_types.MissionSuggestion"], typing.List["types.MissionSuggestion"]]:
175
+ __ctx__, __result__ = self.__options.merge_options(baml_options).create_sync_stream(function_name="SuggestMissions", args={
176
+ "frame": frame,
177
+ })
178
+ return baml_py.BamlSyncStream[typing.List["stream_types.MissionSuggestion"], typing.List["types.MissionSuggestion"]](
179
+ __result__,
180
+ lambda x: typing.cast(typing.List["stream_types.MissionSuggestion"], x.cast_to(types, types, stream_types, True, __runtime__)),
181
+ lambda x: typing.cast(typing.List["types.MissionSuggestion"], x.cast_to(types, types, stream_types, False, __runtime__)),
182
+ __ctx__,
183
+ )
184
 
185
 
186
  class BamlHttpRequestClient:
 
203
  "mission_text": mission_text,
204
  }, mode="request")
205
  return __result__
206
+ def SuggestMissions(self, frame: baml_py.Image,
207
+ baml_options: BamlCallOptions = {},
208
+ ) -> baml_py.baml_py.HTTPRequest:
209
+ __result__ = self.__options.merge_options(baml_options).create_http_request_sync(function_name="SuggestMissions", args={
210
+ "frame": frame,
211
+ }, mode="request")
212
+ return __result__
213
 
214
 
215
  class BamlHttpStreamRequestClient:
 
232
  "mission_text": mission_text,
233
  }, mode="stream")
234
  return __result__
235
+ def SuggestMissions(self, frame: baml_py.Image,
236
+ baml_options: BamlCallOptions = {},
237
+ ) -> baml_py.baml_py.HTTPRequest:
238
+ __result__ = self.__options.merge_options(baml_options).create_http_request_sync(function_name="SuggestMissions", args={
239
+ "frame": frame,
240
+ }, mode="stream")
241
+ return __result__
242
 
243
 
244
  b = BamlSyncClient(DoNotUseDirectlyCallManager({}))
baml_client/type_builder.py CHANGED
@@ -20,7 +20,7 @@ from .globals import DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIM
20
  class TypeBuilder(type_builder.TypeBuilder):
21
  def __init__(self):
22
  super().__init__(classes=set(
23
- ["DetectionInfo","DetectionVerdict","MissionPlan",]
24
  ), enums=set(
25
  []
26
  ), runtime=DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME)
@@ -31,7 +31,7 @@ class TypeBuilder(type_builder.TypeBuilder):
31
 
32
 
33
  # #########################################################################
34
- # Generated classes 3
35
  # #########################################################################
36
 
37
  @property
@@ -46,6 +46,10 @@ class TypeBuilder(type_builder.TypeBuilder):
46
  def MissionPlan(self) -> "MissionPlanViewer":
47
  return MissionPlanViewer(self)
48
 
 
 
 
 
49
 
50
 
51
  # #########################################################################
@@ -54,7 +58,7 @@ class TypeBuilder(type_builder.TypeBuilder):
54
 
55
 
56
  # #########################################################################
57
- # Generated classes 3
58
  # #########################################################################
59
 
60
  class DetectionInfoAst:
@@ -221,3 +225,46 @@ class MissionPlanProperties:
221
 
222
 
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  class TypeBuilder(type_builder.TypeBuilder):
21
  def __init__(self):
22
  super().__init__(classes=set(
23
+ ["DetectionInfo","DetectionVerdict","MissionPlan","MissionSuggestion",]
24
  ), enums=set(
25
  []
26
  ), runtime=DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME)
 
31
 
32
 
33
  # #########################################################################
34
+ # Generated classes 4
35
  # #########################################################################
36
 
37
  @property
 
46
  def MissionPlan(self) -> "MissionPlanViewer":
47
  return MissionPlanViewer(self)
48
 
49
+ @property
50
+ def MissionSuggestion(self) -> "MissionSuggestionViewer":
51
+ return MissionSuggestionViewer(self)
52
+
53
 
54
 
55
  # #########################################################################
 
58
 
59
 
60
  # #########################################################################
61
+ # Generated classes 4
62
  # #########################################################################
63
 
64
  class DetectionInfoAst:
 
225
 
226
 
227
 
228
+
229
+ class MissionSuggestionAst:
230
+ def __init__(self, tb: type_builder.TypeBuilder):
231
+ _tb = tb._tb # type: ignore (we know how to use this private attribute)
232
+ self._bldr = _tb.class_("MissionSuggestion")
233
+ self._properties: typing.Set[str] = set([ "mission", "reasoning", ])
234
+ self._props = MissionSuggestionProperties(self._bldr, self._properties)
235
+
236
+ def type(self) -> baml_py.FieldType:
237
+ return self._bldr.field()
238
+
239
+ @property
240
+ def props(self) -> "MissionSuggestionProperties":
241
+ return self._props
242
+
243
+
244
+ class MissionSuggestionViewer(MissionSuggestionAst):
245
+ def __init__(self, tb: type_builder.TypeBuilder):
246
+ super().__init__(tb)
247
+
248
+
249
+ def list_properties(self) -> typing.List[typing.Tuple[str, type_builder.ClassPropertyViewer]]:
250
+ return [(name, type_builder.ClassPropertyViewer(self._bldr.property(name))) for name in self._properties]
251
+
252
+
253
+
254
+ class MissionSuggestionProperties:
255
+ def __init__(self, bldr: baml_py.ClassBuilder, properties: typing.Set[str]):
256
+ self.__bldr = bldr
257
+ self.__properties = properties # type: ignore (we know how to use this private attribute) # noqa: F821
258
+
259
+
260
+
261
+ @property
262
+ def mission(self) -> type_builder.ClassPropertyViewer:
263
+ return type_builder.ClassPropertyViewer(self.__bldr.property("mission"))
264
+
265
+ @property
266
+ def reasoning(self) -> type_builder.ClassPropertyViewer:
267
+ return type_builder.ClassPropertyViewer(self.__bldr.property("reasoning"))
268
+
269
+
270
+
baml_client/type_map.py CHANGED
@@ -25,5 +25,8 @@ type_map = {
25
  "types.MissionPlan": types.MissionPlan,
26
  "stream_types.MissionPlan": stream_types.MissionPlan,
27
 
 
 
 
28
 
29
  }
 
25
  "types.MissionPlan": types.MissionPlan,
26
  "stream_types.MissionPlan": stream_types.MissionPlan,
27
 
28
+ "types.MissionSuggestion": types.MissionSuggestion,
29
+ "stream_types.MissionSuggestion": stream_types.MissionSuggestion,
30
+
31
 
32
  }
baml_client/types.py CHANGED
@@ -41,7 +41,7 @@ def all_succeeded(checks: typing.Dict[CheckName, Check]) -> bool:
41
  # #########################################################################
42
 
43
  # #########################################################################
44
- # Generated classes (3)
45
  # #########################################################################
46
 
47
  class DetectionInfo(BaseModel):
@@ -65,6 +65,10 @@ class MissionPlan(BaseModel):
65
  refined_mission: str = Field(description='A clear, one-sentence restatement of the mission objective that a downstream analyst LLM will evaluate each detection against.')
66
  reasoning: str = Field(description='Brief explanation of why these queries were chosen.')
67
 
 
 
 
 
68
  # #########################################################################
69
  # Generated type aliases (0)
70
  # #########################################################################
 
41
  # #########################################################################
42
 
43
  # #########################################################################
44
+ # Generated classes (4)
45
  # #########################################################################
46
 
47
  class DetectionInfo(BaseModel):
 
65
  refined_mission: str = Field(description='A clear, one-sentence restatement of the mission objective that a downstream analyst LLM will evaluate each detection against.')
66
  reasoning: str = Field(description='Brief explanation of why these queries were chosen.')
67
 
68
+ class MissionSuggestion(BaseModel):
69
+ mission: str = Field(description='A concise, natural-language mission objective. Written as an imperative command, e.g. \'Track pedestrians crossing the intersection\'. 8-15 words.')
70
+ reasoning: str = Field(description='One sentence: what you see in the image that makes this mission feasible and interesting.')
71
+
72
  # #########################################################################
73
  # Generated type aliases (0)
74
  # #########################################################################
baml_src/isr.baml CHANGED
@@ -54,6 +54,60 @@ function PlanMission(mission_text: string) -> MissionPlan {
54
  }
55
 
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  // ── Detection Assessment ─────────────────────────────────────────
58
  // Replaces hand-rolled JSON parsing with type-safe BAML output
59
 
 
54
  }
55
 
56
 
57
+ // ── Mission Suggestion ──────────────────────────────────────────
58
+ // Analyzes a video frame and suggests actionable ISR missions
59
+
60
+ class MissionSuggestion {
61
+ mission string @description("A concise, natural-language mission objective. Written as an imperative command, e.g. 'Track pedestrians crossing the intersection'. 8-15 words.")
62
+ reasoning string @description("One sentence: what you see in the image that makes this mission feasible and interesting.")
63
+ }
64
+
65
+ function SuggestMissions(frame: image) -> MissionSuggestion[] {
66
+ client GPT4oMini
67
+ prompt #"
68
+ {{ _.role("system") }}
69
+ You are an ISR (Intelligence, Surveillance, Reconnaissance) mission advisor.
70
+ Given a single video frame, suggest 3-5 interesting surveillance missions that
71
+ a detector + analyst pipeline could realistically accomplish.
72
+
73
+ RULES β€” what makes a good suggestion:
74
+ 1. **Visually grounded**: Every suggestion must reference objects you can ACTUALLY SEE
75
+ in the frame. Do not hallucinate objects that are not there.
76
+ 2. **Detector-feasible**: The mission must target objects a COCO-trained detector can
77
+ find (person, car, truck, bus, motorcycle, bicycle, boat, dog, cat, airplane, etc.).
78
+ Do not suggest missions requiring detection of abstract concepts, emotions, or
79
+ objects not in standard detection models.
80
+ 3. **Analyst-decidable**: The mission criteria must be something a downstream vision LLM
81
+ can judge from a single annotated frame β€” spatial position, object state, proximity,
82
+ relative size, posture, motion direction. Avoid criteria needing temporal history,
83
+ audio, or information not visible in images.
84
+ 4. **Simple and clear**: Each mission should be a single, unambiguous objective.
85
+ Not compound ("find X and also Y"). 8-15 words.
86
+ 5. **Interesting variety**: Vary the missions β€” mix counting, tracking, spatial
87
+ monitoring, and anomaly detection. Don't repeat the same pattern.
88
+
89
+ AVOID these β€” they sound good but fail in practice:
90
+ - Missions about intent or mental state ("find suspicious people", "detect distressed individuals")
91
+ - Missions requiring world knowledge not in the image ("unauthorized vehicles", "restricted zones")
92
+ - Missions about very small or occluded objects the detector would miss
93
+ - Missions that are trivially satisfied by everything in the scene ("detect all objects")
94
+
95
+ GOOD examples for different scene types:
96
+ - Street: "Track vehicles stopped at the intersection" / "Monitor pedestrians jaywalking across the road"
97
+ - Parking lot: "Identify cars parked in the fire lane" / "Count trucks in the loading area"
98
+ - Aerial: "Locate boats near the shoreline" / "Track vehicles on the highway bridge"
99
+ - Indoor: "Monitor people near the emergency exits" / "Track shopping carts left in the walkway"
100
+
101
+ {{ _.role("user") }}
102
+ Analyze this video frame and suggest 3-5 feasible ISR missions:
103
+
104
+ {{ frame }}
105
+
106
+ {{ ctx.output_format }}
107
+ "#
108
+ }
109
+
110
+
111
  // ── Detection Assessment ─────────────────────────────────────────
112
  // Replaces hand-rolled JSON parsing with type-safe BAML output
113
 
demo/index.html CHANGED
@@ -124,6 +124,7 @@
124
  <div class="config-group">
125
  <div class="label">MISSION QUERY</div>
126
  <input type="text" id="queriesInput" class="config-input" value="person,car,truck" placeholder="e.g., person,car or Monitor unauthorized personnel...">
 
127
  </div>
128
  <div class="config-row">
129
  <label class="config-checkbox"><input type="checkbox" id="aiToggle" checked> AI Postprocessing</label>
 
124
  <div class="config-group">
125
  <div class="label">MISSION QUERY</div>
126
  <input type="text" id="queriesInput" class="config-input" value="person,car,truck" placeholder="e.g., person,car or Monitor unauthorized personnel...">
127
+ <div id="missionSuggestions" class="suggestion-chips hidden"></div>
128
  </div>
129
  <div class="config-row">
130
  <label class="config-checkbox"><input type="checkbox" id="aiToggle" checked> AI Postprocessing</label>
demo/js/api.js CHANGED
@@ -8,6 +8,16 @@ const API_BASE = window.location.hostname.includes('hf.space')
8
 
9
  // ── Async Data Functions (real backend API) ───────────────────────
10
 
 
 
 
 
 
 
 
 
 
 
11
  async function startDetection(videoFile, config) {
12
  const form = new FormData();
13
  form.append('video', videoFile);
@@ -167,6 +177,7 @@ function resolveUrl(path) {
167
 
168
  Object.assign(window.ISR, {
169
  API_BASE,
 
170
  startDetection,
171
  pollStatus,
172
  fetchTracks,
 
8
 
9
  // ── Async Data Functions (real backend API) ───────────────────────
10
 
11
+ async function suggestMissions(frameBlob) {
12
+ const form = new FormData();
13
+ form.append('frame', frameBlob, 'frame.jpg');
14
+ try {
15
+ const res = await fetch(`${API_BASE}/suggest-missions`, { method: 'POST', body: form });
16
+ if (!res.ok) return [];
17
+ return res.json();
18
+ } catch (err) { console.warn('[ISR] suggestMissions failed:', err); return []; }
19
+ }
20
+
21
  async function startDetection(videoFile, config) {
22
  const form = new FormData();
23
  form.append('video', videoFile);
 
177
 
178
  Object.assign(window.ISR, {
179
  API_BASE,
180
+ suggestMissions,
181
  startDetection,
182
  pollStatus,
183
  fetchTracks,
demo/js/init.js CHANGED
@@ -49,6 +49,9 @@ document.addEventListener('DOMContentLoaded', () => {
49
  // Task 3+4: Start button β€” real API if video file, mock fallback otherwise
50
  document.getElementById('startBtn').addEventListener('click', async () => {
51
  if (STATE.videoFile) {
 
 
 
52
  // Gather config from controls
53
  const activeMode = document.querySelector('#modeToggle .config-toggle-btn.active');
54
  const mode = activeMode ? activeMode.dataset.mode : 'object_detection';
@@ -97,6 +100,9 @@ document.addEventListener('DOMContentLoaded', () => {
97
  document.getElementById('fileName').textContent = file.name;
98
  document.getElementById('fileUploadLabel').classList.add('has-file');
99
  document.getElementById('startBtn').disabled = false;
 
 
 
100
  });
101
 
102
  // Task 4: Cancel button
@@ -251,3 +257,85 @@ document.addEventListener('DOMContentLoaded', () => {
251
 
252
  console.log('[ISR Command Center] UI initialized. State:', STATE.current);
253
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  // Task 3+4: Start button β€” real API if video file, mock fallback otherwise
50
  document.getElementById('startBtn').addEventListener('click', async () => {
51
  if (STATE.videoFile) {
52
+ // Hide suggestion chips on start
53
+ const suggestionsEl = document.getElementById('missionSuggestions');
54
+ if (suggestionsEl) suggestionsEl.classList.add('hidden');
55
  // Gather config from controls
56
  const activeMode = document.querySelector('#modeToggle .config-toggle-btn.active');
57
  const mode = activeMode ? activeMode.dataset.mode : 'object_detection';
 
100
  document.getElementById('fileName').textContent = file.name;
101
  document.getElementById('fileUploadLabel').classList.add('has-file');
102
  document.getElementById('startBtn').disabled = false;
103
+
104
+ // Extract first frame and request mission suggestions
105
+ _extractFrameAndSuggest(file);
106
  });
107
 
108
  // Task 4: Cancel button
 
257
 
258
  console.log('[ISR Command Center] UI initialized. State:', STATE.current);
259
  });
260
+
261
+
262
+ /** Extract first frame from video file and fetch mission suggestions. */
263
+ let _extractionId = 0; // race guard: only the latest extraction wins
264
+
265
+ function _extractFrameAndSuggest(file) {
266
+ const container = document.getElementById('missionSuggestions');
267
+ if (!container) return;
268
+
269
+ const myId = ++_extractionId;
270
+
271
+ container.innerHTML = '<span class="suggestion-loading">Analyzing scene\u2026</span>';
272
+ container.classList.remove('hidden');
273
+
274
+ const url = URL.createObjectURL(file);
275
+ const video = document.createElement('video');
276
+ video.muted = true;
277
+ video.preload = 'auto';
278
+
279
+ function _cleanup() {
280
+ video.onloadeddata = null;
281
+ video.onseeked = null;
282
+ video.onerror = null;
283
+ video.src = '';
284
+ URL.revokeObjectURL(url);
285
+ }
286
+
287
+ video.onloadeddata = () => {
288
+ video.currentTime = Math.min(0.5, video.duration || 0);
289
+ };
290
+
291
+ video.onseeked = () => {
292
+ const w = Math.min(video.videoWidth, 512);
293
+ const h = Math.round(w * (video.videoHeight / video.videoWidth));
294
+ const canvas = document.createElement('canvas');
295
+ canvas.width = w;
296
+ canvas.height = h;
297
+ canvas.getContext('2d').drawImage(video, 0, 0, w, h);
298
+ _cleanup();
299
+
300
+ canvas.toBlob(async (blob) => {
301
+ canvas.width = 0; canvas.height = 0; // free bitmap memory
302
+ if (!blob || myId !== _extractionId) return;
303
+ const suggestions = await suggestMissions(blob);
304
+ if (myId !== _extractionId) return; // stale result
305
+ _renderSuggestionChips(container, suggestions);
306
+ }, 'image/jpeg', 0.5);
307
+ };
308
+
309
+ video.onerror = () => {
310
+ _cleanup();
311
+ if (myId === _extractionId) container.classList.add('hidden');
312
+ };
313
+
314
+ video.src = url;
315
+ }
316
+
317
+ /** Render suggestion chips into the container. */
318
+ function _renderSuggestionChips(container, suggestions) {
319
+ if (!suggestions || suggestions.length === 0) {
320
+ container.classList.add('hidden');
321
+ return;
322
+ }
323
+
324
+ container.innerHTML = '';
325
+ const input = document.getElementById('queriesInput');
326
+
327
+ for (const s of suggestions) {
328
+ const chip = document.createElement('button');
329
+ chip.className = 'suggestion-chip';
330
+ chip.textContent = s.mission;
331
+ chip.title = s.reasoning;
332
+ chip.addEventListener('click', () => {
333
+ input.value = s.mission;
334
+ // Highlight selected chip
335
+ container.querySelectorAll('.suggestion-chip').forEach(c => c.classList.remove('selected'));
336
+ chip.classList.add('selected');
337
+ });
338
+ container.appendChild(chip);
339
+ }
340
+ container.classList.remove('hidden');
341
+ }
demo/styles/components.css CHANGED
@@ -228,6 +228,54 @@
228
  align-items: center;
229
  }
230
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  .config-checkbox-label {
232
  display: flex;
233
  align-items: center;
 
228
  align-items: center;
229
  }
230
 
231
+ /* ── Mission suggestion chips ─────────────────────────── */
232
+ .suggestion-chips {
233
+ display: flex;
234
+ flex-wrap: wrap;
235
+ gap: 5px;
236
+ margin-top: 2px;
237
+ }
238
+ .suggestion-chips.hidden { display: none; }
239
+
240
+ .suggestion-chip {
241
+ background: rgba(59, 130, 246, 0.08);
242
+ border: 1px solid rgba(59, 130, 246, 0.2);
243
+ color: var(--accent-light);
244
+ font-family: inherit;
245
+ font-size: 10px;
246
+ padding: 4px 9px;
247
+ border-radius: 12px;
248
+ cursor: pointer;
249
+ transition: all 0.15s ease;
250
+ line-height: 1.3;
251
+ text-align: left;
252
+ animation: chipFadeIn 0.3s ease both;
253
+ }
254
+ .suggestion-chip:hover {
255
+ background: rgba(59, 130, 246, 0.15);
256
+ border-color: rgba(59, 130, 246, 0.4);
257
+ }
258
+ .suggestion-chip.selected {
259
+ background: rgba(59, 130, 246, 0.2);
260
+ border-color: var(--accent);
261
+ color: #fff;
262
+ }
263
+
264
+ .suggestion-loading {
265
+ font-size: 10px;
266
+ color: var(--text-muted);
267
+ animation: pulse 1.5s ease-in-out infinite;
268
+ }
269
+
270
+ @keyframes chipFadeIn {
271
+ from { opacity: 0; transform: translateY(4px); }
272
+ to { opacity: 1; transform: translateY(0); }
273
+ }
274
+ @keyframes pulse {
275
+ 0%, 100% { opacity: 0.4; }
276
+ 50% { opacity: 1; }
277
+ }
278
+
279
  .config-checkbox-label {
280
  display: flex;
281
  align-items: center;