eager-dog-1123
12/18/2025, 11:00 AMtall-airline-16756
12/18/2025, 11:00 AMtall-airline-16756
12/18/2025, 11:00 AMeager-dog-1123
12/18/2025, 11:05 AMlivekit-agents framework, but replace the built-in LLM block with a custom one based on pydantic-ai, while keeping everything working seamlessly.tall-airline-16756
12/18/2025, 11:06 AMllm_node(...) so it calls your pydantic-ai runner.
• Ensure llm_node returns an async iterable that yields ChatChunk (or strings) so the pipeline stays real-time compatible.
• If pydantic-ai can stream tokens, yield chunks incrementally; if not, yield a single ChatChunk with the full response.
Minimal example (adapt to your codebase)
1) pydantic-ai runner (async helper)
# my_pydantic_agent.py
from pydantic_ai import Runner
runner = Runner(...) # your pydantic-ai setup
async def run_pydantic(prompt: str) -> str:
# call your pydantic-ai runner and return full text (or provide a streaming API)
result = await runner.run(prompt)
return result.data # adjust to your runner's response shape
2) Custom Agent overriding llm_node
# custom_agent.py
import asyncio
from typing import AsyncIterable
from livekit.agents.llm import ChatChunk, ChatContext, ModelSettings, FunctionTool
from livekit.agents import Agent
from my_pydantic_agent import run_pydantic
class PydanticAgent(Agent):
def __init__(self, instructions: str = ""):
super().__init__(instructions=instructions)
async def llm_node(
self,
chat_ctx: ChatContext,
tools: list[FunctionTool],
model_settings: ModelSettings,
) -> AsyncIterable[ChatChunk | str]:
# Build a prompt from chat context (system + history + latest user)
prompt = "\n".join(
f"{m.role.upper()}: {m.text_content}" for m in chat_ctx.messages if m.text_content
)
# If pydantic-ai supports streaming, iterate and yield incremental ChatChunks.
# If not, await full response and yield a single ChatChunk.
try:
response = await run_pydantic(prompt)
except Exception as e:
response = f"Error: {str(e)}"
# Simple non-streaming yield (works if pydantic-ai returns full text)
yield ChatChunk(text=response)
3) AgentSession wiring (Cartesia STT + ElevenLabs TTS)
# session_setup.py
from livekit.agents import AgentSession
from livekit.plugins import cartesia, elevenlabs
def create_session():
session = AgentSession(
stt=cartesia.STT(model="ink-whisper"), # Cartesia ink model
tts=elevenlabs.TTS(voice_id="your_voice_id"),
# vad optional: use silero or built-in VAD if desired
)
return session
# usage
session = create_session()
agent = PydanticAgent(instructions="You are a helpful assistant.")
await session.start(agent=agent, room=room)
Streaming notes
• If pydantic-ai exposes incremental outputs/tokens, change run_pydantic to be an async generator and in llm_node iterate and yield ChatChunk(text=partial) for each partial piece to preserve low-latency TTS playback.
• If pydantic-ai only returns full text, yielding a single ChatChunk still works; TTS will synthesize after the full response is ready.
Error handling & function tools
• Wrap calls to pydantic-ai in try/except and yield an error ChatChunk to avoid pipeline crashes.
• If you need function/tool calls, translate pydantic-ai’s function-call outputs into the framework’s `FunctionTool`/`ChatChunk` conventions; embed the tool results back into subsequent prompts as needed.
Next step Would you like:
• a streaming example using an async generator from pydantic-ai (if your runner supports it), or
• a complete ready-to-run example file that plugs into your current livekit-agents project structure?
Which would you prefer?
Sources: Tool definition and use | LiveKit docs | Pipeline nodes and hooks | LiveKit docs | LLM Output Replacement | LiveKit docs | Models | LiveKit Docs | Agents (Node.js) | livekit/livekit_composite | DeepWikieager-dog-1123
12/19/2025, 4:46 AMtall-airline-16756
12/19/2025, 4:46 AMsession.start(agent=agent, room=room). The framework expects the agent (which overrides llm_node) at start time and will wire it into the real-time pipeline.
Quick reminders:
• Ensure your agent's llm_node returns an async iterable of ChatChunk (or strings) so streaming and low-latency TTS work.
• Handle errors inside llm_node (catch exceptions and yield an error chunk) to avoid pipeline crashes.
• If you want incremental TTS playback, make llm_node stream partial outputs when pydantic-ai supports it.
If you want, I can show the exact minimal code snippet that instantiates AgentSession (no LLM) and calls session.start(...) with your PydanticAgent. Which snippet would you prefer: non-streaming or streaming?eager-dog-1123
12/19/2025, 4:48 AMtall-airline-16756
12/19/2025, 4:48 AMeager-dog-1123
12/19/2025, 5:03 AMasync def on_enter(self):
self.session.generate_reply()
why do I need on_enter function in my custom agent? How does it work with my custom agent?
btw every llm invocation in my whole agents goes tghrough llm_node function?eager-dog-1123
12/19/2025, 5:05 AMeager-dog-1123
12/19/2025, 5:05 AMasync def on_enter(self):
self.session.generate_reply()
why do I need on_enter function in my custom agent? How does it work with my custom agent?
btw every llm invocation in my whole agents goes tghrough llm_node function?