Spaces:
Sleeping
Sleeping
Commit
·
d25df55
1
Parent(s):
b6650bb
update the redflag resopnse
Browse files- .gitignore +1 -0
- backend/api/services/agent_orchestrator.py +55 -7
- data/admin_rules.db +0 -0
- data/analytics.db +0 -0
- frontend/components/analytics-panel.tsx +12 -4
.gitignore
CHANGED
|
@@ -1,3 +1,4 @@
|
|
| 1 |
venv/
|
| 2 |
.env
|
| 3 |
.pytest_cache
|
|
|
|
|
|
| 1 |
venv/
|
| 2 |
.env
|
| 3 |
.pytest_cache
|
| 4 |
+
/_pycache_/
|
backend/api/services/agent_orchestrator.py
CHANGED
|
@@ -89,25 +89,73 @@ class AgentOrchestrator:
|
|
| 89 |
tool_input={"violations": [m.__dict__ for m in matches]},
|
| 90 |
reason="redflag_triggered"
|
| 91 |
)
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
total_latency_ms = int((time.time() - start_time) * 1000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
self.analytics.log_agent_query(
|
| 99 |
tenant_id=req.tenant_id,
|
| 100 |
message_preview=req.message[:200],
|
| 101 |
intent="admin",
|
| 102 |
-
tools_used=["admin"],
|
| 103 |
-
total_tokens=
|
| 104 |
total_latency_ms=total_latency_ms,
|
| 105 |
success=False,
|
| 106 |
user_id=req.user_id
|
| 107 |
)
|
| 108 |
|
| 109 |
return AgentResponse(
|
| 110 |
-
text=
|
| 111 |
decision=decision,
|
| 112 |
tool_traces=[{"redflags": [m.__dict__ for m in matches]}],
|
| 113 |
reasoning_trace=reasoning_trace
|
|
|
|
| 89 |
tool_input={"violations": [m.__dict__ for m in matches]},
|
| 90 |
reason="redflag_triggered"
|
| 91 |
)
|
| 92 |
+
|
| 93 |
+
# Build detailed prompt for LLM to generate natural red flag response
|
| 94 |
+
violations_details = []
|
| 95 |
+
for i, m in enumerate(matches, 1):
|
| 96 |
+
rule_name = m.description or m.pattern or "Policy violation"
|
| 97 |
+
detail = f"{i}. **{rule_name}** (Severity: {m.severity})"
|
| 98 |
+
if m.matched_text:
|
| 99 |
+
detail += f"\n - Detected phrase: \"{m.matched_text}\""
|
| 100 |
+
violations_details.append(detail)
|
| 101 |
+
|
| 102 |
+
llm_prompt = f"""A user made the following request: "{req.message}"
|
| 103 |
+
|
| 104 |
+
However, this request violates company policies. The following policy violations were detected:
|
| 105 |
+
|
| 106 |
+
{chr(10).join(violations_details)}
|
| 107 |
+
|
| 108 |
+
Your task: Write a clear, professional, and empathetic response to inform the user that:
|
| 109 |
+
1. Their request cannot be processed due to policy violations
|
| 110 |
+
2. Which specific policy was violated (mention it naturally)
|
| 111 |
+
3. The incident has been logged for security review
|
| 112 |
+
4. They should contact an administrator if they need assistance or believe this is an error
|
| 113 |
+
|
| 114 |
+
Write a natural, conversational response (2-4 sentences) that feels helpful rather than robotic. Be professional but understanding.
|
| 115 |
+
|
| 116 |
+
Response:"""
|
| 117 |
+
|
| 118 |
+
# Generate LLM response for red flag
|
| 119 |
+
try:
|
| 120 |
+
llm_response = await self.llm.simple_call(llm_prompt, temperature=min(req.temperature + 0.2, 0.7)) # Slightly higher temp for more natural response
|
| 121 |
+
llm_response = llm_response.strip()
|
| 122 |
+
# Add warning emoji if not present
|
| 123 |
+
if not llm_response.startswith("⚠️") and not llm_response.startswith("🚨"):
|
| 124 |
+
llm_response = f"⚠️ {llm_response}"
|
| 125 |
+
except Exception as e:
|
| 126 |
+
# Fallback to a simple message if LLM fails
|
| 127 |
+
summary = "; ".join(
|
| 128 |
+
f"{m.description or m.pattern}"
|
| 129 |
+
for m in matches
|
| 130 |
+
)
|
| 131 |
+
llm_response = f"⚠️ I'm unable to process your request because it violates our company policy: {summary}. This incident has been logged. Please contact your administrator if you need assistance."
|
| 132 |
|
| 133 |
total_latency_ms = int((time.time() - start_time) * 1000)
|
| 134 |
+
|
| 135 |
+
# Log LLM usage for red flag response
|
| 136 |
+
estimated_tokens = len(llm_response) // 4 + len(llm_prompt) // 4
|
| 137 |
+
self.analytics.log_tool_usage(
|
| 138 |
+
tenant_id=req.tenant_id,
|
| 139 |
+
tool_name="llm",
|
| 140 |
+
latency_ms=total_latency_ms,
|
| 141 |
+
tokens_used=estimated_tokens,
|
| 142 |
+
success=True,
|
| 143 |
+
user_id=req.user_id
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
self.analytics.log_agent_query(
|
| 147 |
tenant_id=req.tenant_id,
|
| 148 |
message_preview=req.message[:200],
|
| 149 |
intent="admin",
|
| 150 |
+
tools_used=["admin", "llm"],
|
| 151 |
+
total_tokens=estimated_tokens,
|
| 152 |
total_latency_ms=total_latency_ms,
|
| 153 |
success=False,
|
| 154 |
user_id=req.user_id
|
| 155 |
)
|
| 156 |
|
| 157 |
return AgentResponse(
|
| 158 |
+
text=llm_response,
|
| 159 |
decision=decision,
|
| 160 |
tool_traces=[{"redflags": [m.__dict__ for m in matches]}],
|
| 161 |
reasoning_trace=reasoning_trace
|
data/admin_rules.db
CHANGED
|
Binary files a/data/admin_rules.db and b/data/admin_rules.db differ
|
|
|
data/analytics.db
CHANGED
|
Binary files a/data/analytics.db and b/data/analytics.db differ
|
|
|
frontend/components/analytics-panel.tsx
CHANGED
|
@@ -3,10 +3,18 @@
|
|
| 3 |
import { useState } from "react";
|
| 4 |
import { useTenant } from "@/contexts/TenantContext";
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
type AnalyticsOverview = {
|
| 7 |
overview: {
|
| 8 |
total_queries: number;
|
| 9 |
-
tool_usage: Record<string,
|
| 10 |
redflag_count: number;
|
| 11 |
active_users: number;
|
| 12 |
};
|
|
@@ -89,7 +97,7 @@ export function AnalyticsPanel() {
|
|
| 89 |
<p className="mt-2 text-3xl font-semibold">
|
| 90 |
{data
|
| 91 |
? Object.entries(data.tool_usage)
|
| 92 |
-
.sort((a, b) => b[1] - a[1])[0]?.[0] ?? "—"
|
| 93 |
: "—"}
|
| 94 |
</p>
|
| 95 |
</div>
|
|
@@ -101,7 +109,7 @@ export function AnalyticsPanel() {
|
|
| 101 |
</p>
|
| 102 |
<div className="mt-4 grid gap-3 sm:grid-cols-3">
|
| 103 |
{data
|
| 104 |
-
? Object.entries(data.tool_usage).map(([tool,
|
| 105 |
<div
|
| 106 |
key={tool}
|
| 107 |
className="rounded-xl border border-white/10 bg-white/5 px-4 py-3"
|
|
@@ -109,7 +117,7 @@ export function AnalyticsPanel() {
|
|
| 109 |
<p className="text-sm uppercase tracking-widest text-slate-400">
|
| 110 |
{tool}
|
| 111 |
</p>
|
| 112 |
-
<p className="text-2xl font-semibold text-white">{count}</p>
|
| 113 |
</div>
|
| 114 |
))
|
| 115 |
: Array.from({ length: 3 }).map((_, idx) => (
|
|
|
|
| 3 |
import { useState } from "react";
|
| 4 |
import { useTenant } from "@/contexts/TenantContext";
|
| 5 |
|
| 6 |
+
type ToolUsageStats = {
|
| 7 |
+
count: number;
|
| 8 |
+
avg_latency_ms: number;
|
| 9 |
+
total_tokens: number;
|
| 10 |
+
success_count: number;
|
| 11 |
+
error_count: number;
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
type AnalyticsOverview = {
|
| 15 |
overview: {
|
| 16 |
total_queries: number;
|
| 17 |
+
tool_usage: Record<string, ToolUsageStats>;
|
| 18 |
redflag_count: number;
|
| 19 |
active_users: number;
|
| 20 |
};
|
|
|
|
| 97 |
<p className="mt-2 text-3xl font-semibold">
|
| 98 |
{data
|
| 99 |
? Object.entries(data.tool_usage)
|
| 100 |
+
.sort((a, b) => b[1].count - a[1].count)[0]?.[0] ?? "—"
|
| 101 |
: "—"}
|
| 102 |
</p>
|
| 103 |
</div>
|
|
|
|
| 109 |
</p>
|
| 110 |
<div className="mt-4 grid gap-3 sm:grid-cols-3">
|
| 111 |
{data
|
| 112 |
+
? Object.entries(data.tool_usage).map(([tool, stats]) => (
|
| 113 |
<div
|
| 114 |
key={tool}
|
| 115 |
className="rounded-xl border border-white/10 bg-white/5 px-4 py-3"
|
|
|
|
| 117 |
<p className="text-sm uppercase tracking-widest text-slate-400">
|
| 118 |
{tool}
|
| 119 |
</p>
|
| 120 |
+
<p className="text-2xl font-semibold text-white">{stats.count}</p>
|
| 121 |
</div>
|
| 122 |
))
|
| 123 |
: Array.from({ length: 3 }).map((_, idx) => (
|