mirror of
https://github.com/arkorty/B.Tech-Project-III.git
synced 2026-04-19 12:41:48 +00:00
189 lines
7.2 KiB
Python
189 lines
7.2 KiB
Python
"""
|
|
Meet Cross-Reference Agent
|
|
Finds connections between meeting signals and existing Telegram group signals.
|
|
Surfaces: confirmations (meeting agrees with chat), contradictions (meeting contradicts chat),
|
|
and blind spots (meeting discusses something chat groups don't know about).
|
|
"""
|
|
import logging
|
|
from backend.providers import call_llm
|
|
from backend.db.chroma import query_signals, get_all_signals
|
|
from backend.config import MEET_CROSS_REF_GROUPS, MEET_DEFAULT_GROUP_ID
|
|
|
|
logger = logging.getLogger("thirdeye.agents.meet_cross_ref")
|
|
|
|
CROSS_REF_SYSTEM_PROMPT = """You are an expert at finding connections between meeting discussions and team chat history.
|
|
|
|
You will receive:
|
|
1. MEETING SIGNALS — decisions, action items, blockers, risks from a recent Google Meet
|
|
2. CHAT SIGNALS — existing signals from team Telegram groups
|
|
|
|
Find meaningful connections across three categories:
|
|
|
|
CONFIRMATIONS: Meeting agrees with or reinforces something from chat history
|
|
CONTRADICTIONS: Meeting decision conflicts with what was said/decided in chat
|
|
BLIND SPOTS: Important things from the meeting that the chat teams don't seem to know about
|
|
|
|
Return ONLY a valid JSON object:
|
|
{
|
|
"confirmations": [
|
|
{"meeting_signal": "...", "chat_signal": "...", "group": "...", "significance": "high|medium|low"}
|
|
],
|
|
"contradictions": [
|
|
{"meeting_signal": "...", "chat_signal": "...", "group": "...", "impact": "...", "significance": "high|medium|low"}
|
|
],
|
|
"blind_spots": [
|
|
{"meeting_signal": "...", "teams_unaware": ["group1", "group2"], "recommendation": "..."}
|
|
]
|
|
}
|
|
|
|
Rules:
|
|
- Only include HIGH confidence matches — do not stretch for weak connections
|
|
- Keep each signal description concise (1 sentence max)
|
|
- significance "high" = this matters for team alignment; "medium" = worth noting; "low" = minor
|
|
- If a category has nothing meaningful, use an empty array []
|
|
- Return JSON only"""
|
|
|
|
|
|
async def find_cross_references(
|
|
meeting_id: str,
|
|
group_id: str = None,
|
|
cross_ref_group_ids: list[str] = None,
|
|
) -> dict:
|
|
"""
|
|
Compare meeting signals against chat group signals.
|
|
|
|
Args:
|
|
meeting_id: The meeting to analyze
|
|
group_id: ChromaDB group where meet signals are stored (defaults to MEET_DEFAULT_GROUP_ID)
|
|
cross_ref_group_ids: Groups to compare against (defaults to MEET_CROSS_REF_GROUPS from config)
|
|
|
|
Returns:
|
|
Dict with confirmations, contradictions, blind_spots lists
|
|
"""
|
|
group_id = group_id or MEET_DEFAULT_GROUP_ID
|
|
cross_ref_group_ids = cross_ref_group_ids or MEET_CROSS_REF_GROUPS
|
|
|
|
if not cross_ref_group_ids:
|
|
return {
|
|
"confirmations": [],
|
|
"contradictions": [],
|
|
"blind_spots": [],
|
|
"error": "No cross-reference groups configured. Set MEET_CROSS_REF_GROUPS in .env",
|
|
}
|
|
|
|
# 1. Get meeting signals (decisions, actions, blockers, risks — NOT raw chunks)
|
|
meet_signals = query_signals(group_id, meeting_id, n_results=30)
|
|
structured_meet = [
|
|
s for s in meet_signals
|
|
if s.get("metadata", {}).get("type") in ("meet_decision", "meet_action_item", "meet_blocker", "meet_risk", "meet_open_q")
|
|
]
|
|
|
|
if not structured_meet:
|
|
return {
|
|
"confirmations": [],
|
|
"contradictions": [],
|
|
"blind_spots": [],
|
|
"error": f"No structured signals found for meeting {meeting_id}. Has it been processed yet?",
|
|
}
|
|
|
|
# 2. Get signals from each cross-reference group
|
|
chat_context_parts = []
|
|
for gid in cross_ref_group_ids:
|
|
try:
|
|
all_sig = get_all_signals(gid)
|
|
if all_sig:
|
|
formatted = "\n".join([
|
|
f" [{s.get('metadata', {}).get('type', '?')}] {s.get('document', '')[:120]}"
|
|
for s in all_sig[:20] # Cap at 20 per group to stay within token limits
|
|
])
|
|
chat_context_parts.append(f"Group '{gid}':\n{formatted}")
|
|
except Exception as e:
|
|
logger.warning(f"Could not load signals for group {gid}: {e}")
|
|
|
|
if not chat_context_parts:
|
|
return {
|
|
"confirmations": [],
|
|
"contradictions": [],
|
|
"blind_spots": [],
|
|
"error": "Could not load any signals from cross-reference groups.",
|
|
}
|
|
|
|
# 3. Format inputs for LLM
|
|
meet_text = "\n".join([
|
|
f" [{s.get('metadata', {}).get('type', '?')}] {s.get('document', '')[:150]}" for s in structured_meet
|
|
])
|
|
chat_text = "\n\n".join(chat_context_parts)
|
|
|
|
prompt = f"""MEETING SIGNALS (from meeting: {meeting_id}):
|
|
{meet_text}
|
|
|
|
CHAT SIGNALS (from monitored Telegram groups):
|
|
{chat_text}"""
|
|
|
|
try:
|
|
import json
|
|
result = await call_llm(
|
|
task_type="reasoning",
|
|
messages=[
|
|
{"role": "system", "content": CROSS_REF_SYSTEM_PROMPT},
|
|
{"role": "user", "content": prompt},
|
|
],
|
|
temperature=0.2,
|
|
max_tokens=1500,
|
|
response_format={"type": "json_object"},
|
|
)
|
|
raw = result["content"].strip()
|
|
if raw.startswith("```"):
|
|
raw = raw.split("```")[1]
|
|
if raw.startswith("json"):
|
|
raw = raw[4:]
|
|
return json.loads(raw)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Cross-reference LLM call failed: {e}")
|
|
return {
|
|
"confirmations": [],
|
|
"contradictions": [],
|
|
"blind_spots": [],
|
|
"error": str(e),
|
|
}
|
|
|
|
|
|
def format_cross_ref_for_telegram(analysis: dict, meeting_id: str) -> str:
|
|
"""Format cross-reference results as a Telegram message."""
|
|
parts = [f"🔗 *Meet ↔ Chat Cross-Reference*\nMeeting: `{meeting_id}`\n"]
|
|
|
|
if analysis.get("error"):
|
|
return f"⚠️ Cross-reference failed: {analysis['error']}"
|
|
|
|
confirmations = analysis.get("confirmations", [])
|
|
contradictions = analysis.get("contradictions", [])
|
|
blind_spots = analysis.get("blind_spots", [])
|
|
|
|
if not confirmations and not contradictions and not blind_spots:
|
|
return f"🔗 *Meet ↔ Chat Cross-Reference*\nMeeting `{meeting_id}`: No significant connections found between this meeting and your chat groups."
|
|
|
|
if confirmations:
|
|
parts.append(f"✅ *Confirmations* ({len(confirmations)})")
|
|
for c in confirmations[:3]: # Cap at 3 for readability
|
|
sig = "🔴" if c.get("significance") == "high" else "🟡"
|
|
parts.append(f"{sig} Meeting: _{c['meeting_signal'][:100]}_")
|
|
parts.append(f" Matches [{c.get('group', '?')}]: _{c['chat_signal'][:100]}_\n")
|
|
|
|
if contradictions:
|
|
parts.append(f"⚡ *Contradictions* ({len(contradictions)}) — ACTION NEEDED")
|
|
for c in contradictions[:3]:
|
|
parts.append(f"🔴 Meeting decided: _{c['meeting_signal'][:100]}_")
|
|
parts.append(f" BUT [{c.get('group', '?')}] says: _{c['chat_signal'][:100]}_")
|
|
if c.get("impact"):
|
|
parts.append(f" Impact: {c['impact'][:100]}\n")
|
|
|
|
if blind_spots:
|
|
parts.append(f"🔦 *Blind Spots* ({len(blind_spots)}) — Teams may not know")
|
|
for b in blind_spots[:3]:
|
|
parts.append(f"🟠 {b['meeting_signal'][:120]}")
|
|
if b.get("recommendation"):
|
|
parts.append(f" → {b['recommendation'][:100]}\n")
|
|
|
|
return "\n".join(parts)
|