primeira versão customizada de VAD- Voice Activity Detection

This commit is contained in:
2026-10-30 19:15:01 +00:00
parent 2449b34137
commit aba05cb826
14 changed files with 3457 additions and 129 deletions

View File

@@ -0,0 +1,173 @@
"""
Deep Agents implementation of the AgentVoice Agent port.
Responsibilities:
- OCI Generative AI inference through LangChain.
- Contextual memory per session.
- Text streaming compatible with VoicePipeline.
- Asynchronous resource cleanup.
Does not manage:
- WebSocket connections.
- STT or TTS.
- Semantic turn lifecycle.
"""
from __future__ import annotations
from collections.abc import AsyncIterator
from contextlib import aclosing
from deepagents import create_deep_agent
from langchain_core.messages import HumanMessage
from langchain_oci import ChatOCIGenAI
from langgraph.checkpoint.memory import MemorySaver
from voice_assistant.agent.adapter import AgentAdapter
DEFAULT_SYSTEM_PROMPT = """
Você é o AgentVoice, um assistente pessoal por voz. Que responde pelo nome de Roberta.
Responda em português brasileiro, de forma natural,
objetiva e adequada para reprodução por voz.
Mantenha o contexto das mensagens anteriores.
Interprete correções e complementos do usuário
considerando o histórico da conversa.
Não invente informações atuais ou resultados
de ferramentas que não foram executadas.
"""
class DeepAgentsAdapter(AgentAdapter):
def __init__(
self,
region: str,
compartment_id: str,
model_id: str,
temperature: float,
max_completion_tokens: int,
timeout_seconds: float,
system_prompt: str = DEFAULT_SYSTEM_PROMPT,
) -> None:
if not region:
raise ValueError("OCI region is required.")
if not compartment_id:
raise ValueError("OCI compartment_id is required.")
if not model_id:
raise ValueError("OCI model_id is required.")
if timeout_seconds <= 0:
raise ValueError("timeout_seconds must be positive.")
self.model = ChatOCIGenAI(
model_id=model_id,
compartment_id=compartment_id,
service_endpoint=(
f"https://inference.generativeai."
f"{region}.oci.oraclecloud.com"
),
auth_type="INSTANCE_PRINCIPAL",
is_stream=True,
model_kwargs={
"temperature": temperature,
"max_tokens": max_completion_tokens,
},
)
self.checkpointer = MemorySaver()
self.agent = create_deep_agent(
model=self.model,
tools=[],
system_prompt=system_prompt,
checkpointer=self.checkpointer,
)
self._closed = False
def _config(self, session_id: str) -> dict:
if not session_id:
raise ValueError("session_id is required.")
return {
"configurable": {
"thread_id": session_id,
}
}
async def process(
self,
session_id: str,
turn_id: int,
text: str,
) -> str:
parts: list[str] = []
async with aclosing(
self.stream(
session_id=session_id,
turn_id=turn_id,
text=text,
)
) as chunks:
async for chunk in chunks:
parts.append(chunk)
response = "".join(parts).strip()
if not response:
raise RuntimeError(
"Deep Agent returned an empty response."
)
return response
async def stream(
self,
session_id: str,
turn_id: int,
text: str,
) -> AsyncIterator[str]:
if self._closed:
raise RuntimeError("Deep Agent is closed.")
if not text.strip():
raise ValueError("Agent input text cannot be empty.")
config = self._config(session_id)
async for message, metadata in self.agent.astream(
{
"messages": [
HumanMessage(content=text)
]
},
config=config,
stream_mode="messages",
):
# Ignore tools and subagent outputs.
if metadata.get("langgraph_node") != "model":
continue
# Ignore tool-call fragments.
if getattr(message, "tool_call_chunks", None):
continue
content = message.content
if isinstance(content, str) and content:
yield content
async def aclose(self) -> None:
if self._closed:
return
self._closed = True
await self.model.aclose()