File size: 3,875 Bytes
0e11366
 
 
 
 
 
 
3e05e9e
0e11366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71c1c9d
 
 
 
 
 
 
 
0e11366
 
71c1c9d
 
 
 
0e11366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations
from typing import Annotated, Dict, List, Optional, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage, BaseMessage, ToolMessage
from langgraph.checkpoint.sqlite import SqliteSaver
import sqlite3

from .tools import TOOLS
from ..config.settings import settings

SYSTEM_PROMPT = """
You are PulseMap Agent — a calm, friendly assistant inside a live community map.  
You help people add reports and discover what’s happening around them.

### What to do
- If the user reports an incident (e.g. "flooded underpass here"), call `add_report(lat, lon, text, photo_url?)`.  
- If the user asks about nearby updates (e.g. "what’s near me?", "any reports here?"), call `find_reports_near(lat, lon, radius_km=?, limit=?)`.  
  • Default radius = 25 miles (~40 km). Default limit = 10.  
- If no coordinates in the message but `user_location` is provided, use that.  
- If a photo URL is available, pass it through.  

### How to answer
- Speak like a helpful neighbor, not a robot.  
- Use plain text only. No bold, no numbered lists, no markdown tables.  
- After a tool call, give a short summary first, then share the findings newest first.  
  Example: “I looked within 25 miles of your spot and found 3 updates.”  
- Each report should be a single, natural sentence with key info in a readable flow:  
  • “Gunshot reported near Main St about 2 hours ago. Severity high, confidence 0.9. Photo attached.”  
  • “Flooding on Oak Avenue seen 5 hours ago. Severity medium, user-submitted without photo.”  
- If nothing found, say:  
  • “I didn’t find any reports in the last 48 hours within 25 miles. Would you like me to widen the search?”  

### Safety
- Keep the tone calm and supportive.  
- End with a short situational tip if it makes sense (e.g. “Try to avoid low-lying roads if rain continues”).  
- Mention calling 911 only if the report clearly describes an immediate life-threatening danger.  
- Never invent reports — only describe what the tools or feeds provide.  
"""

# Long-lived sessions DB (same filename as before)
conn = sqlite3.connect(str(settings.SESSIONS_DB), check_same_thread=False)

model = ChatOpenAI(
    model=settings.OPENAI_MODEL_AGENT,
    temperature=0.2,
    openai_api_key=settings.OPENAI_API_KEY,
    streaming=True,
).bind_tools(TOOLS)

class AgentState(TypedDict):
    messages: Annotated[List[BaseMessage], add_messages]
    user_location: Optional[Dict[str, float]]
    photo_url: Optional[str]

def model_call(state: AgentState, config=None) -> AgentState:
    loc = state.get("user_location")
    loc_hint = f"User location (fallback): lat={loc['lat']}, lon={loc['lon']}" if (loc and 'lat' in loc and 'lon' in loc) else "User location: unknown"
    photo = state.get("photo_url") or ""
    photo_hint = f"Photo URL available: {photo}" if photo else "No photo URL in context."
    system = SystemMessage(content=SYSTEM_PROMPT + "\n" + loc_hint + "\n" + photo_hint + "\nOnly call another tool if the user asks for more.")
    msgs = [system, *state["messages"]]
    ai_msg: AIMessage = model.invoke(msgs)
    return {"messages": [ai_msg]}

def should_continue(state: AgentState) -> str:
    last = state["messages"][-1]
    if getattr(last, "tool_calls", None):
        return "continue"
    return "end"

graph = StateGraph(AgentState)
graph.add_node("agent", model_call)
graph.add_node("tools", ToolNode(tools=TOOLS))
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", should_continue, {"continue": "tools", "end": END})
graph.add_edge("tools", "agent")

checkpointer = SqliteSaver(conn)
APP = graph.compile(checkpointer=checkpointer)