primeira versão customizada de VAD- Voice Activity Detection
This commit is contained in:
173
src/voice_assistant/agent/deep_agent.py
Normal file
173
src/voice_assistant/agent/deep_agent.py
Normal 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()
|
||||
Reference in New Issue
Block a user