This commit is contained in:
1 parent
120ab192f1
commit
63e15b6023
5 files changed
+83
-10
No files matched your search
+2
-1
@@ -631,7 +631,8 @@ async def handle_job(ctx: JobContext) -> None:
|
|||||||
voice_enabled = mode == "voice"
|
voice_enabled = mode == "voice"
|
||||||
logger.info("Switching conversation mode to %s", mode)
|
logger.info("Switching conversation mode to %s", mode)
|
||||||
if not voice_enabled:
|
if not voice_enabled:
|
||||||
asyncio.create_task(session.interrupt())
|
# interrupt() returns an already-scheduled Future, not a coroutine.
|
||||||
|
session.interrupt()
|
||||||
session.input.set_audio_enabled(voice_enabled)
|
session.input.set_audio_enabled(voice_enabled)
|
||||||
session.output.set_audio_enabled(voice_enabled)
|
session.output.set_audio_enabled(voice_enabled)
|
||||||
return
|
return
|
||||||
|
|||||||
+26
-6
@@ -102,11 +102,11 @@ class TaskWorker:
|
|||||||
if not text:
|
if not text:
|
||||||
reg.fail(self.task_id, "LLM returned empty response")
|
reg.fail(self.task_id, "LLM returned empty response")
|
||||||
return
|
return
|
||||||
if self._is_deferred_answer(text):
|
if self._is_deferred_answer(text) or self._is_underfilled_news_answer(text):
|
||||||
reg.add_step(
|
reg.add_step(
|
||||||
self.task_id,
|
self.task_id,
|
||||||
"thinking",
|
"thinking",
|
||||||
"Draft deferred the work; continuing automatically.",
|
"Draft was incomplete; continuing automatically.",
|
||||||
)
|
)
|
||||||
if iteration == max_iterations - 1:
|
if iteration == max_iterations - 1:
|
||||||
reg.fail(self.task_id, "Could not produce a complete answer")
|
reg.fail(self.task_id, "Could not produce a complete answer")
|
||||||
@@ -117,8 +117,9 @@ class TaskWorker:
|
|||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": (
|
"content": (
|
||||||
"Do not ask me a follow-up or offer to continue later. "
|
"The previous draft did not fully answer the original request. "
|
||||||
"Continue the task now, run any more targeted tool calls "
|
"Do not ask a follow-up or offer to continue later. Continue "
|
||||||
|
"the task now, run any more targeted tool calls "
|
||||||
"you need, make reasonable assumptions, and only finish "
|
"you need, make reasonable assumptions, and only finish "
|
||||||
"when you have directly answered the original request."
|
"when you have directly answered the original request."
|
||||||
),
|
),
|
||||||
@@ -153,6 +154,23 @@ class TaskWorker:
|
|||||||
)
|
)
|
||||||
return "?" in text or any(phrase in lowered for phrase in deferral_phrases)
|
return "?" in text or any(phrase in lowered for phrase in deferral_phrases)
|
||||||
|
|
||||||
|
def _is_underfilled_news_answer(self, text: str) -> bool:
|
||||||
|
"""Reject generic news summaries that do not name actual events."""
|
||||||
|
description = self.description.lower()
|
||||||
|
if "news" not in description and "headline" not in description:
|
||||||
|
return False
|
||||||
|
lowered = text.lower()
|
||||||
|
generic_phrases = (
|
||||||
|
"reports also cover global developments",
|
||||||
|
"other sources indicate",
|
||||||
|
"various international perspectives",
|
||||||
|
"ongoing coverage of world events",
|
||||||
|
"recommend checking major",
|
||||||
|
"unable to retrieve",
|
||||||
|
"could not retrieve",
|
||||||
|
)
|
||||||
|
return any(phrase in lowered for phrase in generic_phrases)
|
||||||
|
|
||||||
def _system_prompt(self) -> str:
|
def _system_prompt(self) -> str:
|
||||||
today = datetime.now(timezone.utc).strftime("%B %d, %Y")
|
today = datetime.now(timezone.utc).strftime("%B %d, %Y")
|
||||||
return (
|
return (
|
||||||
@@ -162,8 +180,10 @@ class TaskWorker:
|
|||||||
"to finish later; make reasonable assumptions and continue autonomously. If a web "
|
"to finish later; make reasonable assumptions and continue autonomously. If a web "
|
||||||
"search is broad or inconclusive, immediately run a more targeted query and scrape "
|
"search is broad or inconclusive, immediately run a more targeted query and scrape "
|
||||||
"the most promising result pages, including today's date and specific reputable "
|
"the most promising result pages, including today's date and specific reputable "
|
||||||
"sources when freshness matters. For headline "
|
"sources when freshness matters. For plural news or headline requests, provide three "
|
||||||
"requests, return concrete named events rather than links to news homepages, and "
|
"distinct concrete events unless the user requests a different count. For three "
|
||||||
|
"headlines, write exactly three sentences with one event per sentence and no extras. Never pad the "
|
||||||
|
"answer with generic phrases about broad coverage or international perspectives, and "
|
||||||
"respect any requested item count exactly. Only "
|
"respect any requested item count exactly. Only "
|
||||||
"produce a final answer once it fulfills the original request. The final answer must "
|
"produce a final answer once it fulfills the original request. The final answer must "
|
||||||
"be 1-3 concise sentences suitable for speaking aloud, with plain text only and no "
|
"be 1-3 concise sentences suitable for speaking aloud, with plain text only and no "
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ def test_deferred_task_answers_are_rejected():
|
|||||||
"Reuters reports that the agreement was signed today, while the BBC says voting begins Monday."
|
"Reuters reports that the agreement was signed today, while the BBC says voting begins Monday."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
news_worker = TaskWorker("test", "Find the latest international news headlines")
|
||||||
|
assert news_worker._is_underfilled_news_answer(
|
||||||
|
"One flood was reported. Other sources indicate ongoing coverage of world events."
|
||||||
|
)
|
||||||
|
assert not news_worker._is_underfilled_news_answer(
|
||||||
|
"Nepal began flood recovery, Venezuela signed an oil agreement, and Iran resumed talks."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_task_event_directories_are_isolated(tmp_path):
|
def test_task_event_directories_are_isolated(tmp_path):
|
||||||
first = TaskRegistry(tmp_path / "room-one")
|
first = TaskRegistry(tmp_path / "room-one")
|
||||||
|
|||||||
+11
-3
@@ -166,9 +166,16 @@ function updateModeUI() {
|
|||||||
|
|
||||||
async function setConversationMode(mode) {
|
async function setConversationMode(mode) {
|
||||||
if (mode !== "voice" && mode !== "text") return;
|
if (mode !== "voice" && mode !== "text") return;
|
||||||
|
const previousMode = conversationMode;
|
||||||
conversationMode = mode;
|
conversationMode = mode;
|
||||||
localStorage.setItem(MODE_KEY, mode);
|
localStorage.setItem(MODE_KEY, mode);
|
||||||
updateModeUI();
|
updateModeUI();
|
||||||
|
if (mode === "voice" && previousMode === "text") {
|
||||||
|
chatInput.blur();
|
||||||
|
window.setTimeout(() => {
|
||||||
|
document.querySelector(".workspace").scrollIntoView({ block: "start", behavior: "smooth" });
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (!room || !room.localParticipant) return;
|
if (!room || !room.localParticipant) return;
|
||||||
sendModeMessage(mode);
|
sendModeMessage(mode);
|
||||||
@@ -314,9 +321,10 @@ function inferState() {
|
|||||||
userSpeaking = micLevel > 0.035;
|
userSpeaking = micLevel > 0.035;
|
||||||
if (agentAudioLevel > 0.02) return "speaking";
|
if (agentAudioLevel > 0.02) return "speaking";
|
||||||
if (userSpeaking) return "listening";
|
if (userSpeaking) return "listening";
|
||||||
// Not speaking, agent silent: stay in thinking until audio arrives,
|
// Once agent audio ends, return to listening. The previous implementation
|
||||||
// otherwise settle back to listening.
|
// converted speaking → thinking forever, leaving the UI apparently frozen.
|
||||||
if (uiState === "thinking" || uiState === "speaking") return "thinking";
|
if (uiState === "speaking") return "listening";
|
||||||
|
if (uiState === "thinking") return "thinking";
|
||||||
return "listening";
|
return "listening";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -511,6 +511,7 @@ button {
|
|||||||
.controls {
|
.controls {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -656,6 +657,7 @@ button {
|
|||||||
|
|
||||||
.connection-strip {
|
.connection-strip {
|
||||||
width: min(340px, 100%);
|
width: min(340px, 100%);
|
||||||
|
flex-shrink: 0;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,6 +687,7 @@ button {
|
|||||||
|
|
||||||
.privacy-note {
|
.privacy-note {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
margin-top: 1.15rem;
|
margin-top: 1.15rem;
|
||||||
@@ -1223,6 +1226,39 @@ body[data-mode="text"] .mic-meter {
|
|||||||
.task-error { margin-top: 0.5rem; color: #ffc2ba; background: rgba(255, 128, 111, 0.1); }
|
.task-error { margin-top: 0.5rem; color: #ffc2ba; background: rgba(255, 128, 111, 0.1); }
|
||||||
|
|
||||||
/* ── Responsive ─────────────────────────────────────────────────────────── */
|
/* ── Responsive ─────────────────────────────────────────────────────────── */
|
||||||
|
@media (max-height: 760px) and (min-width: 881px) {
|
||||||
|
.workspace {
|
||||||
|
min-height: 540px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voice-console {
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-intro h2 {
|
||||||
|
font-size: 1.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-intro p {
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb-stage {
|
||||||
|
min-height: 220px;
|
||||||
|
margin: -1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb {
|
||||||
|
transform: scale(0.78);
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-strip,
|
||||||
|
.privacy-note {
|
||||||
|
margin-top: 0.65rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 880px) {
|
@media (max-width: 880px) {
|
||||||
body {
|
body {
|
||||||
padding: calc(0.85rem + env(safe-area-inset-top))
|
padding: calc(0.85rem + env(safe-area-inset-top))
|
||||||
|
|||||||
Reference in new issue
Block a user