Improve review summary (#20216)

* Add debug logging for review summaries report

* Improve debug logging

* Improve review report prompt

* Cleanup

* Add date to report
This commit is contained in:
Nicolas Mowen
2025-09-25 20:05:22 -06:00
committed by GitHub
parent 2f209b2cf4
commit 8b293449f9
4 changed files with 78 additions and 23 deletions

View File

@@ -93,7 +93,7 @@ class ReviewDescriptionProcessor(PostProcessorApi):
if camera_config.review.genai.debug_save_thumbnails: if camera_config.review.genai.debug_save_thumbnails:
id = data["after"]["id"] id = data["after"]["id"]
Path(os.path.join(CLIPS_DIR, f"genai-requests/{id}")).mkdir( Path(os.path.join(CLIPS_DIR, "genai-requests", f"{id}")).mkdir(
parents=True, exist_ok=True parents=True, exist_ok=True
) )
shutil.copy( shutil.copy(
@@ -124,6 +124,9 @@ class ReviewDescriptionProcessor(PostProcessorApi):
if topic == EmbeddingsRequestEnum.summarize_review.value: if topic == EmbeddingsRequestEnum.summarize_review.value:
start_ts = request_data["start_ts"] start_ts = request_data["start_ts"]
end_ts = request_data["end_ts"] end_ts = request_data["end_ts"]
logger.debug(
f"Found GenAI Review Summary request for {start_ts} to {end_ts}"
)
items: list[dict[str, Any]] = [ items: list[dict[str, Any]] = [
r["data"]["metadata"] r["data"]["metadata"]
for r in ( for r in (
@@ -141,7 +144,7 @@ class ReviewDescriptionProcessor(PostProcessorApi):
if len(items) == 0: if len(items) == 0:
logger.debug("No review items with metadata found during time period") logger.debug("No review items with metadata found during time period")
return None return "No activity was found during this time."
important_items = list( important_items = list(
filter( filter(
@@ -154,8 +157,16 @@ class ReviewDescriptionProcessor(PostProcessorApi):
if not important_items: if not important_items:
return "No concerns were found during this time period." return "No concerns were found during this time period."
if self.config.review.genai.debug_save_thumbnails:
Path(
os.path.join(CLIPS_DIR, "genai-requests", f"{start_ts}-{end_ts}")
).mkdir(parents=True, exist_ok=True)
return self.genai_client.generate_review_summary( return self.genai_client.generate_review_summary(
start_ts, end_ts, important_items start_ts,
end_ts,
important_items,
self.config.review.genai.debug_save_thumbnails,
) )
else: else:
return None return None

View File

@@ -19,3 +19,4 @@ class ReviewMetadata(BaseModel):
default=None, default=None,
description="Other concerns highlighted by the user that are observed.", description="Other concerns highlighted by the user that are observed.",
) )
time: str | None = Field(default=None, description="Time of activity.")

View File

@@ -313,6 +313,7 @@ class EmbeddingMaintainer(threading.Thread):
if resp is not None: if resp is not None:
return resp return resp
logger.error(f"No processor handled the topic {topic}")
return None return None
except Exception as e: except Exception as e:
logger.error(f"Unable to handle embeddings request {e}", exc_info=True) logger.error(f"Unable to handle embeddings request {e}", exc_info=True)

View File

@@ -73,7 +73,7 @@ Your task is to provide a clear, security-focused description of the scene that:
Facts come first, but identifying security risks is the primary goal. Facts come first, but identifying security risks is the primary goal.
When forming your description: When forming your description:
- Describe the time, people, and objects exactly as seen. Include any observable environmental changes (e.g., lighting changes triggered by activity). - Describe the people and objects exactly as seen. Include any observable environmental changes (e.g., lighting changes triggered by activity).
- Time of day should **increase suspicion only when paired with unusual or security-relevant behaviors**. Do not raise the threat level for common residential activities (e.g., residents walking pets, retrieving mail, gardening, playing with pets, supervising children) even at unusual hours, unless other suspicious indicators are present. - Time of day should **increase suspicion only when paired with unusual or security-relevant behaviors**. Do not raise the threat level for common residential activities (e.g., residents walking pets, retrieving mail, gardening, playing with pets, supervising children) even at unusual hours, unless other suspicious indicators are present.
- Focus on behaviors that are uncharacteristic of innocent activity: loitering without clear purpose, avoiding cameras, inspecting vehicles/doors, changing behavior when lights activate, scanning surroundings without an apparent benign reason. - Focus on behaviors that are uncharacteristic of innocent activity: loitering without clear purpose, avoiding cameras, inspecting vehicles/doors, changing behavior when lights activate, scanning surroundings without an apparent benign reason.
- **Benign context override**: If scanning or looking around is clearly part of an innocent activity (such as playing with a dog, gardening, supervising children, or watching for a pet), do not treat it as suspicious. - **Benign context override**: If scanning or looking around is clearly part of an innocent activity (such as playing with a dog, gardening, supervising children, or watching for a pet), do not treat it as suspicious.
@@ -99,7 +99,7 @@ Sequence details:
**IMPORTANT:** **IMPORTANT:**
- Values must be plain strings, floats, or integers — no nested objects, no extra commentary. - Values must be plain strings, floats, or integers — no nested objects, no extra commentary.
{get_language_prompt()} {get_language_prompt()}
""" """
logger.debug( logger.debug(
f"Sending {len(thumbnails)} images to create review description on {review_data['camera']}" f"Sending {len(thumbnails)} images to create review description on {review_data['camera']}"
) )
@@ -135,6 +135,7 @@ Sequence details:
if review_data["recognized_objects"]: if review_data["recognized_objects"]:
metadata.potential_threat_level = 0 metadata.potential_threat_level = 0
metadata.time = review_data["start"]
return metadata return metadata
except Exception as e: except Exception as e:
# rarely LLMs can fail to follow directions on output format # rarely LLMs can fail to follow directions on output format
@@ -146,34 +147,75 @@ Sequence details:
return None return None
def generate_review_summary( def generate_review_summary(
self, start_ts: float, end_ts: float, segments: list[dict[str, Any]] self,
start_ts: float,
end_ts: float,
segments: list[dict[str, Any]],
debug_save: bool,
) -> str | None: ) -> str | None:
"""Generate a summary of review item descriptions over a period of time.""" """Generate a summary of review item descriptions over a period of time."""
time_range = f"{datetime.datetime.fromtimestamp(start_ts).strftime('%I:%M %p')} to {datetime.datetime.fromtimestamp(end_ts).strftime('%I:%M %p')}" time_range = f"{datetime.datetime.fromtimestamp(start_ts).strftime('%B %d, %Y at %I:%M %p')} to {datetime.datetime.fromtimestamp(end_ts).strftime('%B %d, %Y at %I:%M %p')}"
timeline_summary_prompt = f""" timeline_summary_prompt = f"""
You are a security officer. Time range: {time_range}. You are a security officer.
Time range: {time_range}.
Input: JSON list with "scene", "confidence", "potential_threat_level" (1-2), "other_concerns". Input: JSON list with "scene", "confidence", "potential_threat_level" (1-2), "other_concerns".
Write a report:
Security Summary - {time_range} Task: Write a concise, human-presentable security report in markdown format.
[One-sentence overview of activity]
[Chronological bullet list of events with timestamps if in scene]
[Final threat assessment]
Rules: Rules for the report:
- List events in order.
- Highlight potential_threat_level ≥ 1 with exact times. - Title & overview
- Note any of the additional concerns which are present. - Start with:
- Note unusual activity even if not threats. # Security Summary - {time_range}
- If no threats: "Final assessment: Only normal activity observed during this period." - Write a 1-2 sentence situational overview capturing the general pattern of the period.
- No commentary, questions, or recommendations.
- Output only the report. - Event details
""" - Present events in chronological order as a bullet list.
- **If multiple events occur within the same minute or overlapping time range, COMBINE them into a single bullet.**
- Summarize the distinct activities as sub-points under the shared timestamp.
- If no timestamp is given, preserve order but label as “Time not specified.”
- Use bold timestamps for clarity.
- Group bullets under subheadings when multiple events fall into the same category (e.g., Vehicle Activity, Porch Activity, Unusual Behavior).
- Threat levels
- Always show (threat level: X) for each event.
- If multiple events at the same time share the same threat level, only state it once.
- Final assessment
- End with a Final Assessment section.
- If all events are threat level 1 with no escalation:
Final assessment: Only normal residential activity observed during this period.
- If threat level 2+ events are present, clearly summarize them as Potential concerns requiring review.
- Conciseness
- Do not repeat benign clothing/appearance details unless they distinguish individuals.
- Summarize similar routine events instead of restating full scene descriptions.
"""
for item in segments: for item in segments:
timeline_summary_prompt += f"\n{item}" timeline_summary_prompt += f"\n{item}"
return self._send(timeline_summary_prompt, []) if debug_save:
with open(
os.path.join(
CLIPS_DIR, "genai-requests", f"{start_ts}-{end_ts}", "prompt.txt"
),
"w",
) as f:
f.write(timeline_summary_prompt)
response = self._send(timeline_summary_prompt, [])
if debug_save and response:
with open(
os.path.join(
CLIPS_DIR, "genai-requests", f"{start_ts}-{end_ts}", "response.txt"
),
"w",
) as f:
f.write(response)
return response
def generate_object_description( def generate_object_description(
self, self,