File size: 5,447 Bytes
506c307
 
fe74762
506c307
 
 
 
fe74762
506c307
 
 
 
 
 
 
 
fe74762
 
506c307
 
fe74762
506c307
fe74762
506c307
fe74762
 
 
 
 
506c307
fe74762
 
506c307
fe74762
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506c307
 
 
 
 
 
 
 
 
 
 
 
fe74762
506c307
fe74762
506c307
fe74762
 
 
 
 
 
 
506c307
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fe74762
 
506c307
 
 
 
 
 
 
 
fe74762
 
 
506c307
 
 
 
 
 
 
 
 
 
 
fe74762
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""
agent_workflow.py
Agentic AI workflow using Groq (LLaMA-3).
Falls back to rule-based mock if the key is missing or the call fails.
"""

import json
import os


def run_groq_agent(
    detection_summary: dict,
    video_meta: dict,
    ai_summary: str,
) -> dict:
    """
    Run a Groq-powered analysis and return a JSON report.
    Falls back to mock if Groq is unavailable.
    """
    try:
        from groq import Groq

        client = Groq(api_key=os.getenv("GROQ_API_KEY"))

        payload = {
            "detection_summary": detection_summary,
            "video_meta": video_meta,
            "ai_summary": ai_summary,
        }

        prompt = f"""
You are a professional video-analysis assistant.

Analyze the following structured data and return ONLY valid JSON.

Required JSON schema:
{{
  "insights": ["...", "...", "..."],
  "risk_flags": ["..."],
  "recommended_actions": ["...", "...", "..."]
}}

Rules:
- Return ONLY JSON.
- "insights" should contain exactly 3 concise observations.
- "risk_flags" should be an empty list [] if there are no meaningful risks.
- "recommended_actions" should contain 2 or 3 useful next steps.
- Be factual and do not invent details beyond the provided data.

DATA:
{json.dumps(payload, indent=2)}
""".strip()

        response = client.chat.completions.create(
            model="llama3-8b-8192",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.2,
            max_tokens=400,
        )

        content = response.choices[0].message.content.strip()
        clean = content.removeprefix("```json").removeprefix("```").removesuffix("```").strip()
        result = json.loads(clean)

        if not isinstance(result, dict):
            raise ValueError("LLM output is not a dictionary.")

        result.setdefault("insights", [])
        result.setdefault("risk_flags", [])
        result.setdefault("recommended_actions", [])

        if not isinstance(result["insights"], list):
            result["insights"] = []
        if not isinstance(result["risk_flags"], list):
            result["risk_flags"] = []
        if not isinstance(result["recommended_actions"], list):
            result["recommended_actions"] = []

        if len(result["insights"]) < 3:
            result["insights"] += [
                "Detected objects were analyzed across extracted frames.",
                "The summary is based on object frequency and video metadata.",
                "Manual review of annotated frames can provide additional context.",
            ]
            result["insights"] = result["insights"][:3]

        if len(result["recommended_actions"]) < 2:
            result["recommended_actions"] += [
                "Review the annotated frames to validate detections.",
                "Export the report for further analysis or archiving.",
            ]
            result["recommended_actions"] = result["recommended_actions"][:3]

        return result

    except Exception as e:
        return run_mock_agent(detection_summary, video_meta, ai_summary, error=str(e))


def run_mock_agent(
    detection_summary: dict,
    video_meta: dict,
    ai_summary: str,
    error: str = "",
) -> dict:
    """
    Rule-based fallback report.
    """
    total = sum(detection_summary.values())
    categories = len(detection_summary)
    top_class = max(detection_summary, key=detection_summary.get) if detection_summary else "none"
    top_count = detection_summary.get(top_class, 0)
    fps = max(video_meta.get("fps", 25), 1)
    duration = round(video_meta.get("total_frames", 0) / fps, 1)
    w = video_meta.get("width", "N/A")
    h = video_meta.get("height", "N/A")

    insights = [
        f"Dominant object: '{top_class}' detected {top_count} times across analyzed frames.",
        f"Total: {total} detections across {categories} categories in a {duration}s video.",
        f"Video resolution {w}x{h} @ {fps:.0f} fps — suitable for detailed object analysis.",
    ]

    risk_flags = []
    if detection_summary.get("person", 0) > 20:
        risk_flags.append("High person density — possible crowd or public-safety scenario.")
    if detection_summary.get("knife", 0) > 0 or detection_summary.get("gun", 0) > 0:
        risk_flags.append("Potentially dangerous object detected — manual review recommended.")
    if total == 0:
        risk_flags.append("Zero detections — video may be blank, very dark, or corrupted.")
    if categories == 1 and total > 15:
        risk_flags.append(
            f"Unusually high concentration of a single class ('{top_class}') — verify context."
        )
    if error:
        risk_flags.append(f"Groq agent unavailable, fallback mock analysis used: {error[:120]}")

    recommended_actions = [
        f"Review annotated frames focusing on '{top_class}' detections for context validation.",
        "Lower the confidence threshold if important objects appear to be missed.",
        "Export the JSON report for downstream analytics or alerting integration.",
    ]

    return {
        "insights": insights,
        "risk_flags": risk_flags,
        "recommended_actions": recommended_actions,
    }


def run_agent(
    detection_summary: dict,
    video_meta: dict,
    ai_summary: str,
    mode: str = "mock",
) -> dict:
    if mode == "groq":
        return run_groq_agent(detection_summary, video_meta, ai_summary)
    return run_mock_agent(detection_summary, video_meta, ai_summary)