fix: disable preemptive_generation for tool call compatibility; add get_time tool
- preemptive_generation was incompatible with MCP tool calls: it starts generating before the turn finalizes, breaking the tool execution loop (agent said 'let me check' then hung forever) - Add get_time(location?) to weather_mcp.py for date/time queries - Update system prompt with Weather & Time section
This commit is contained in:
@@ -41,6 +41,7 @@ RUN curl -sSL "https://github.com/livekit/livekit/releases/download/${LIVEKIT_VE
|
|||||||
COPY --from=build /app/agent/.venv /opt/voice-agent/.venv
|
COPY --from=build /app/agent/.venv /opt/voice-agent/.venv
|
||||||
COPY agent/agent.py /opt/voice-agent/agent.py
|
COPY agent/agent.py /opt/voice-agent/agent.py
|
||||||
COPY agent/web_mcp.py /opt/voice-agent/web_mcp.py
|
COPY agent/web_mcp.py /opt/voice-agent/web_mcp.py
|
||||||
|
COPY agent/weather_mcp.py /opt/voice-agent/weather_mcp.py
|
||||||
|
|
||||||
# Copy web frontend + token endpoint
|
# Copy web frontend + token endpoint
|
||||||
COPY web/index.html /var/www/voice/
|
COPY web/index.html /var/www/voice/
|
||||||
|
|||||||
+6
-3
@@ -70,10 +70,12 @@ SYSTEM_PROMPT = textwrap.dedent("""\
|
|||||||
source naturally ("according to..."). If a search comes up empty, say so
|
source naturally ("according to..."). If a search comes up empty, say so
|
||||||
briefly and move on.
|
briefly and move on.
|
||||||
|
|
||||||
# Weather
|
# Weather & Time
|
||||||
You can check the weather for any location. Use get_weather when the user
|
You can check the weather for any location. Use get_weather when the user
|
||||||
asks about current conditions, temperature, or forecasts. It returns a
|
asks about current conditions, temperature, or forecasts. It returns a
|
||||||
short summary — relay it naturally in your own words.
|
short summary — relay it naturally in your own words.
|
||||||
|
Use get_time when the user asks what time or day it is. If they mention
|
||||||
|
a city, pass it as the location argument.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
@@ -269,8 +271,9 @@ async def handle_job(ctx: JobContext) -> None:
|
|||||||
# VAD-based turn detection: agent waits for user to stop speaking.
|
# VAD-based turn detection: agent waits for user to stop speaking.
|
||||||
# ("adaptive" mode requires the LiveKit Cloud barge-in service.)
|
# ("adaptive" mode requires the LiveKit Cloud barge-in service.)
|
||||||
interruption={"mode": "vad"},
|
interruption={"mode": "vad"},
|
||||||
# Start generating the LLM response before the user fully stops
|
# Preemptive generation is incompatible with tool calls — it starts
|
||||||
preemptive_generation={"enabled": True},
|
# generating before the turn finalizes, breaking the MCP execution loop.
|
||||||
|
preemptive_generation={"enabled": False},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ The tool returns a short, conversational summary suitable for speech
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
@@ -84,5 +85,43 @@ async def get_weather(location: str) -> str:
|
|||||||
return summary
|
return summary
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def get_time(location: str = "") -> str:
|
||||||
|
"""Get the current date and time.
|
||||||
|
|
||||||
|
If a location is provided, returns the local time there (approximated
|
||||||
|
via wttr.in's timezone data). Otherwise returns UTC time.
|
||||||
|
Use this when the user asks what time or day it is.
|
||||||
|
"""
|
||||||
|
if not location:
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
return f"It's {now.strftime('%I:%M %p')} on {now.strftime('%A, %B %d')}, UTC."
|
||||||
|
|
||||||
|
try:
|
||||||
|
async def _fetch():
|
||||||
|
async with httpx.AsyncClient(timeout=15) as client:
|
||||||
|
resp = await client.get(
|
||||||
|
f"{WTTR_BASE}/{location}?format=j1",
|
||||||
|
headers={"User-Agent": "curl/8.0"},
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
data = asyncio.get_event_loop().run_until_complete(_fetch())
|
||||||
|
current = data.get("current_condition", [{}])[0] or {}
|
||||||
|
tz_offset = current.get("localObsDateTime", "")
|
||||||
|
area = data.get("nearest_area", [{}])[0] or {}
|
||||||
|
city = (area.get("areaName", [{}])[0].get("value") or location).strip()
|
||||||
|
|
||||||
|
if tz_offset:
|
||||||
|
return f"It's {tz_offset.split(' ')[1][:5]} on {tz_offset.split(' ')[0]} in {city}."
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
pass
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
return f"It's {now.strftime('%I:%M %p')} on {now.strftime('%A, %B %d')}, UTC."
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
mcp.run(transport="stdio")
|
mcp.run(transport="stdio")
|
||||||
|
|||||||
Reference in New Issue
Block a user