LangChain Integration
Building Agents
Create emotion-aware AI agents with LangChain
Building Emotion-Aware Agents
This guide shows how to build AI agents that understand and respond to emotional context.
Basic Agent Setup
import { ProsodyEmotionTool, ProsodyPredictionTool } from '@prosody/langchain';
import { ChatOpenAI } from '@langchain/openai';
import { AgentExecutor, createToolCallingAgent } from 'langchain/agents';
import { ChatPromptTemplate } from '@langchain/core/prompts';
// Create tools
const emotionTool = new ProsodyEmotionTool({
apiKey: process.env.PROSODY_API_KEY,
vertical: 'contact_center',
});
const predictionTool = new ProsodyPredictionTool({
apiKey: process.env.PROSODY_API_KEY,
});
// Create LLM
const llm = new ChatOpenAI({
model: 'gpt-4',
temperature: 0.7,
});
// Create prompt
const prompt = ChatPromptTemplate.fromMessages([
['system', `You are an empathetic customer service agent.
When handling customer interactions:
1. Always analyze the customer's emotional state first
2. Acknowledge their feelings before addressing the issue
3. Adapt your tone based on their emotional state
4. If escalation risk is high, prioritize de-escalation
Tone guidelines by emotion:
- Frustrated/Angry: Be calm, apologetic, and solution-focused
- Confused: Be patient and provide clear explanations
- Satisfied: Be friendly and confirm resolution
- Anxious: Be reassuring and provide clear next steps`],
['human', '{input}'],
['placeholder', '{agent_scratchpad}'],
]);
// Create agent
const agent = createToolCallingAgent({
llm,
tools: [emotionTool, predictionTool],
prompt,
});
const executor = new AgentExecutor({
agent,
tools: [emotionTool, predictionTool],
verbose: true,
});Agent Patterns
Reactive Agent
Responds to emotional state in real-time:
async function handleCustomerInteraction(audioUrl: string) {
const response = await executor.invoke({
input: `A customer is on the line. Analyze their emotional state
from this audio: ${audioUrl}
Then provide an appropriate response to help them.`,
});
return response.output;
}Proactive Agent
Uses predictions to prevent issues:
async function monitorConversation(sessionId: string) {
const response = await executor.invoke({
input: `Check the predictions for session ${sessionId}.
If escalation risk is above 50%, suggest intervention strategies.
If churn risk is high, identify retention opportunities.`,
});
return response.output;
}Multi-Modal Agent
Handles both voice and text:
const multiModalAgent = createToolCallingAgent({
llm,
tools: [emotionTool, predictionTool, textSentimentTool],
prompt: ChatPromptTemplate.fromMessages([
['system', `You are a multi-modal customer service agent.
You can analyze:
- Voice audio for prosodic emotional signals
- Text messages for written sentiment
Always combine insights from both modalities when available.`],
['human', '{input}'],
['placeholder', '{agent_scratchpad}'],
]),
});Conversation Memory
Track emotional state across turns:
import { BufferMemory } from 'langchain/memory';
import { ConversationChain } from 'langchain/chains';
// Create memory with emotion tracking
const memory = new BufferMemory({
memoryKey: 'chat_history',
returnMessages: true,
});
// Custom memory for emotion history
class EmotionAwareMemory extends BufferMemory {
emotionHistory: Array<{
turn: number;
emotion: string;
valence: number;
}> = [];
async saveContext(input: any, output: any) {
await super.saveContext(input, output);
if (output.emotion) {
this.emotionHistory.push({
turn: this.emotionHistory.length + 1,
emotion: output.emotion,
valence: output.valence,
});
}
}
getEmotionTrend(): string {
if (this.emotionHistory.length < 2) return 'insufficient_data';
const recent = this.emotionHistory.slice(-3);
const avgValence = recent.reduce((a, b) => a + b.valence, 0) / recent.length;
const firstValence = recent[0].valence;
if (avgValence > firstValence + 0.2) return 'improving';
if (avgValence < firstValence - 0.2) return 'declining';
return 'stable';
}
}Agent with Session Management
import { ProsodySessionTool, ProsodyEmotionTool } from '@prosody/langchain';
class ConversationAgent {
private sessionTool: ProsodySessionTool;
private emotionTool: ProsodyEmotionTool;
private executor: AgentExecutor;
private sessionId?: string;
constructor(apiKey: string) {
this.sessionTool = new ProsodySessionTool({ apiKey, vertical: 'contact_center' });
this.emotionTool = new ProsodyEmotionTool({ apiKey, vertical: 'contact_center' });
// Setup agent executor
this.executor = new AgentExecutor({
agent: createToolCallingAgent({
llm: new ChatOpenAI({ model: 'gpt-4' }),
tools: [this.emotionTool, this.sessionTool],
prompt: this.getPrompt(),
}),
tools: [this.emotionTool, this.sessionTool],
});
}
async startConversation(metadata?: Record<string, unknown>) {
const session = await this.sessionTool.invoke({
action: 'create',
metadata,
});
this.sessionId = session.sessionId;
return session;
}
async processUtterance(audio: Buffer, speakerId: string) {
if (!this.sessionId) {
throw new Error('No active session');
}
// Add utterance to session
const emotionResult = await this.sessionTool.invoke({
action: 'add_utterance',
sessionId: this.sessionId,
audio: audio.toString('base64'),
speakerId,
});
// Get agent response
const agentResponse = await this.executor.invoke({
input: `The ${speakerId} just spoke. Their emotion is ${emotionResult.emotion}
with ${emotionResult.metrics?.escalationRisk} escalation risk.
Provide an appropriate response.`,
});
return {
emotion: emotionResult,
response: agentResponse.output,
};
}
async endConversation() {
if (!this.sessionId) return null;
const summary = await this.sessionTool.invoke({
action: 'end',
sessionId: this.sessionId,
});
this.sessionId = undefined;
return summary;
}
private getPrompt() {
return ChatPromptTemplate.fromMessages([
['system', `You are managing a customer service conversation.
Track the emotional trajectory and adapt your responses.
If you detect escalation risk, prioritize de-escalation.`],
['human', '{input}'],
['placeholder', '{agent_scratchpad}'],
]);
}
}
// Usage
const agent = new ConversationAgent(process.env.PROSODY_API_KEY!);
await agent.startConversation({ callId: 'abc123' });
const result1 = await agent.processUtterance(customerAudio1, 'customer');
console.log('Customer emotion:', result1.emotion);
console.log('Agent response:', result1.response);
const result2 = await agent.processUtterance(customerAudio2, 'customer');
// Continue conversation...
const summary = await agent.endConversation();
console.log('Conversation summary:', summary);from prosody_langchain import ProsodySessionTool, ProsodyEmotionTool
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
class ConversationAgent:
def __init__(self, api_key: str):
self.session_tool = ProsodySessionTool(
api_key=api_key,
vertical="contact_center"
)
self.emotion_tool = ProsodyEmotionTool(
api_key=api_key,
vertical="contact_center"
)
self.session_id = None
llm = ChatOpenAI(model="gpt-4")
prompt = ChatPromptTemplate.from_messages([
("system", "You are managing a customer service conversation..."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(
llm=llm,
tools=[self.emotion_tool, self.session_tool],
prompt=prompt
)
self.executor = AgentExecutor(
agent=agent,
tools=[self.emotion_tool, self.session_tool]
)
async def start_conversation(self, metadata=None):
session = await self.session_tool.ainvoke({
"action": "create",
"metadata": metadata
})
self.session_id = session["session_id"]
return session
async def process_utterance(self, audio: bytes, speaker_id: str):
if not self.session_id:
raise ValueError("No active session")
import base64
emotion_result = await self.session_tool.ainvoke({
"action": "add_utterance",
"session_id": self.session_id,
"audio": base64.b64encode(audio).decode(),
"speaker_id": speaker_id
})
agent_response = await self.executor.ainvoke({
"input": f"Customer emotion: {emotion_result['emotion']}. Respond appropriately."
})
return {
"emotion": emotion_result,
"response": agent_response["output"]
}
# Usage
agent = ConversationAgent(os.environ["PROSODY_API_KEY"])
await agent.start_conversation({"call_id": "abc123"})
result = await agent.process_utterance(audio_bytes, "customer")Real-Time Coaching Agent
An agent that provides real-time coaching to human agents:
class CoachingAgent {
private executor: AgentExecutor;
async provideCoaching(
customerEmotion: EmotionResult,
agentResponse: string,
conversationHistory: string[]
): Promise<CoachingAdvice> {
const response = await this.executor.invoke({
input: `Analyze this customer service interaction:
Customer Emotion: ${customerEmotion.emotion} (${customerEmotion.confidence} confidence)
Escalation Risk: ${customerEmotion.metrics?.escalationRisk}
Valence: ${customerEmotion.valence}
Agent's Response: "${agentResponse}"
Recent History:
${conversationHistory.slice(-5).join('\n')}
Provide coaching feedback:
1. Was the agent's response appropriate for the customer's emotional state?
2. What could they do better?
3. Specific phrases to use or avoid?`,
});
return this.parseCoachingResponse(response.output);
}
}For production deployments, consider using streaming responses for lower latency coaching suggestions.