Skip to main content
Sales QA doesn’t scale. Managers can review a handful of calls a week; the rest go unanalyzed. Coaching conversations are based on anecdote rather than evidence, and deal health signals buried in call recordings never make it into the CRM. This playbook uses Velma to analyze every recorded sales call and return three things: a structured set of behavior detections that flag coaching opportunities, topic-level sentiment scores that reveal how the prospect feels about specific subjects, and a narrative summary ready to drop into your CRM.

What this configuration produces

Behavior signals
BehaviorWhat it tells you
Action Plan CreatedA concrete next step was agreed — a strong closing signal
Rapport BuildingThe rep is establishing personal connection with the prospect
Pre-established Professional RelationshipPrior relationship was referenced — useful context for deal history
Off-topic DiscussionConversation wandered from the sales agenda — a coaching flag
Unaddressed QuestionProspect asked something the rep didn’t answer — a common trust eroder
Talk-track Deviation (custom)Rep departed from the agreed messaging for this product or stage
Competitor Mention (custom)A named competitor came up — a deal intelligence signal
Topic sentiment Per-speaker sentiment scores for every topic extracted from the call. Understand how the prospect feels about pricing, implementation timeline, competing options, or any other subject — without listening to the recording. Summary A narrative summary of the call suitable for a CRM activity note, generated automatically at end of stream.

Configuration

{
  "conversation_types": [
    {
      "conversation_type_uuid": "11111111-1111-4111-8111-111111111034",
      "name": "Sales Discovery Call",
      "short_description": "A structured conversation between a salesperson and a prospective customer.",
      "detailed_description": "A professional sales conversation where the rep is qualifying the prospect, understanding their needs, and advancing the deal. The prospect may have varying levels of familiarity with the product. The rep is expected to follow a defined talk track, ask discovery questions, and secure a clear next step by the end of the call."
    }
  ],
  "participant_roles": [
    {
      "participant_role_uuid": "22222222-2222-4222-8222-222222222013",
      "name": "Sales Rep",
      "short_description": "The company's representative conducting the sales call.",
      "detailed_description": "A trained sales professional responsible for qualifying the prospect, presenting the product, handling objections, and securing a commitment to next steps. Expected to follow the team's talk track and discovery framework."
    },
    {
      "participant_role_uuid": "22222222-2222-4222-8222-222222222017",
      "name": "Prospect",
      "short_description": "The potential customer evaluating the product.",
      "detailed_description": "A business stakeholder considering whether to purchase. May be a decision-maker or an evaluator. Their engagement level, objections, and sentiment toward specific topics are key signals for deal health."
    }
  ],
  "behaviors": [
    {
      "behavior_uuid": "33333333-3333-4333-8333-033333333004",
      "name": "Action Plan Created",
      "short_description": "A concrete next step was verbally agreed upon.",
      "detailed_description": "This behavior is present if the speech features an explicit verbal agreement from the prospect to a specific next step at a defined time — a follow-up call, a demo, a trial, or a proposal review. The agreement must include a timeframe or scheduled date. Do not flag vague continuations such as 'let's stay in touch' or 'I'll think about it'.",
      "applies_to_participant_role_uuids": ["22222222-2222-4222-8222-222222222017"]
    },
    {
      "behavior_uuid": "33333333-3333-4333-8333-033333333029",
      "name": "Rapport Building",
      "short_description": "Rep establishes personal connection with the prospect.",
      "detailed_description": "This behavior is present if the rep's speech includes references to shared experiences, personal interests, or non-business topics introduced to build a relationship with the prospect. The reference must be initiated by the rep and must receive an engaged response from the prospect. Do not flag brief social pleasantries at the start or end of the call.",
      "applies_to_participant_role_uuids": ["22222222-2222-4222-8222-222222222013"]
    },
    {
      "behavior_uuid": "33333333-3333-4333-8333-033333333007",
      "name": "Off-topic Discussion",
      "short_description": "Conversation drifts away from the sales agenda.",
      "detailed_description": "This behavior is present if the conversation spends more than two consecutive exchanges on a subject unrelated to the prospect's business needs, the product being sold, or the sales process. Do not flag rapport-building exchanges at the opening or close of the call. Do not flag discussions of implementation details, pricing, or contract terms even if they diverge from the primary topic.",
      "applies_to_participant_role_uuids": ["22222222-2222-4222-8222-222222222013"]
    },
    {
      "behavior_uuid": "33333333-3333-4333-8333-033333333033",
      "name": "Unaddressed Question",
      "short_description": "Prospect asks a question the rep does not answer.",
      "detailed_description": "This behavior is present if the prospect asks a direct question — indicated by rising intonation or an interrogative sentence structure — and the rep's response does not address the substance of the question. A deflection, a pivot to a different topic, or a generic affirmation without substantive answer all qualify. Do not flag if the rep explicitly acknowledges the question and commits to a follow-up.",
      "applies_to_participant_role_uuids": ["22222222-2222-4222-8222-222222222013"]
    },
    {
      "behavior_uuid": "<generate-a-uuid>",
      "name": "Talk-track Deviation",
      "short_description": "Rep departs from the agreed messaging for this product or deal stage.",
      "detailed_description": "This behavior is present if the rep makes claims about the product's capabilities, pricing, or implementation timeline that contradict the approved messaging for this stage of the sales process. Qualifying signals include: stating a price point outside the approved range without explicit manager approval language; claiming a feature is available that is listed as roadmap-only in current materials; committing to an implementation timeline shorter than the standard quoted range. Do not flag when the rep is responding to a direct prospect question with qualifying language ('typically,' 'our standard,' 'subject to scoping').",
      "applies_to_participant_role_uuids": ["22222222-2222-4222-8222-222222222013"]
    },
    {
      "behavior_uuid": "<generate-a-uuid>",
      "name": "Competitor Mention",
      "short_description": "A named competitor product or company is referenced during the call.",
      "detailed_description": "This behavior is present if the speech contains a direct mention of a named competitor company, product, or alternative solution that the prospect could choose instead of ours. The mention may come from either participant. Do not flag generic category references ('other vendors,' 'alternatives on the market') where no specific company or product name is used."
    }
  ],
  "stt": {
    "speaker_diarization": true
  },
  "produce_topics": true,
  "produce_topic_sentiments": true,
  "produce_summary": true
}

Code example

This example processes a recorded call file, collects all analysis outputs, and writes a structured coaching report.
Python
import os, json, asyncio, websockets
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class CallAnalysis:
    call_id: str
    behaviors: list = field(default_factory=list)
    topics: list = field(default_factory=list)
    sentiments: list = field(default_factory=list)
    summary: Optional[str] = None
    duration_ms: Optional[int] = None

config = { ... }  # paste your BatchConfig here

async def analyze_call(audio_path: str, call_id: str) -> CallAnalysis:
    url = f"wss://modulate-developer-apis.com/api/velma-2-streaming?api_key={os.environ['MODULATE_API_KEY']}"
    analysis = CallAnalysis(call_id=call_id)

    async with websockets.connect(url) as ws:
        await ws.send(json.dumps(config))

        async def send_audio():
            with open(audio_path, "rb") as f:
                while chunk := f.read(8192):
                    await ws.send(chunk)
            await ws.send("")

        send_task = asyncio.create_task(send_audio())

        try:
            async for message in ws:
                event = json.loads(message)

                if event["type"] == "behavior_detection" and event["detection"]["detected"]:
                    analysis.behaviors.append(event["detection"])

                elif event["type"] == "topics":
                    analysis.topics = event["topics"]

                elif event["type"] == "topic_sentiment":
                    analysis.sentiments.append(event["topic_sentiment"])

                elif event["type"] == "summary":
                    analysis.summary = event["text"]

                elif event["type"] == "done":
                    analysis.duration_ms = event["duration_ms"]
                    break

        finally:
            if not send_task.done():
                send_task.cancel()

    return analysis

def build_coaching_report(analysis: CallAnalysis) -> dict:
    # Separate coaching flags from positive signals
    coaching_flags = [
        b for b in analysis.behaviors
        if b["behavior_name"] in {"Off-topic Discussion", "Unaddressed Question", "Talk-track Deviation"}
    ]
    positive_signals = [
        b for b in analysis.behaviors
        if b["behavior_name"] in {"Action Plan Created", "Rapport Building"}
    ]
    deal_intel = [
        b for b in analysis.behaviors
        if b["behavior_name"] == "Competitor Mention"
    ]

    # Find prospect sentiment on key topics
    prospect_sentiments = {
        s["topic"]: s["sentiment_score"]
        for s in analysis.sentiments
        if s["speaker_label"] != "Sales Rep"
    }

    return {
        "call_id": analysis.call_id,
        "duration_minutes": round(analysis.duration_ms / 60000, 1) if analysis.duration_ms else None,
        "crm_summary": analysis.summary,
        "coaching_flags": [b["behavior_name"] for b in coaching_flags],
        "positive_signals": [b["behavior_name"] for b in positive_signals],
        "deal_intel": [b["behavior_name"] for b in deal_intel],
        "prospect_sentiment_by_topic": prospect_sentiments,
        "action_plan_confirmed": any(
            b["behavior_name"] == "Action Plan Created" for b in analysis.behaviors
        ),
    }

async def main():
    analysis = await analyze_call("call_recording.mp3", "call-20260602-00381")
    report = build_coaching_report(analysis)
    print(json.dumps(report, indent=2))

asyncio.run(main())
{
  "call_id": "call-20260602-00381",
  "duration_minutes": 28.4,
  "crm_summary": "Discovery call with the VP of Operations. Prospect expressed strong interest in the compliance reporting features but raised concerns about implementation timeline and internal IT resource requirements. Rep committed to a follow-up with a solutions engineer next Thursday. Competitor Acme Platform was mentioned by the prospect as their current incumbent.",
  "coaching_flags": ["Unaddressed Question", "Off-topic Discussion"],
  "positive_signals": ["Rapport Building", "Action Plan Created"],
  "deal_intel": ["Competitor Mention"],
  "prospect_sentiment_by_topic": {
    "compliance reporting": 0.81,
    "implementation timeline": -0.64,
    "IT resource requirements": -0.47,
    "pricing": -0.12
  },
  "action_plan_confirmed": true
}

Reading the output

Topic sentiment is the highest-signal output for deal health in this use case. A prospect who is positive about the product but negative about pricing and implementation is a different deal than one who is negative across the board. Use the per-topic scores to prioritize objection handling in the next call and to build accurate pipeline forecasts. Unaddressed questions from the prospect are one of the most reliable early indicators that a deal will stall. Surface these in coaching reviews as the first thing to address — they represent trust gaps the prospect left the call with. Action Plan Created serves as a lightweight close-call indicator. Deals where this behavior is absent have no confirmed next step and should be followed up immediately. Competitor Mention gives your competitive intelligence team an automatic feed of which competitors are coming up in live deals — without requiring reps to log it manually.

Turning your talk track into behaviors

The custom “Talk-track Deviation” behavior above is a template. To make it useful, you need to encode the specific claims, price ranges, and feature commitments that are off-limits for your team at each deal stage. A practical approach: take your existing sales playbook and for each stage, answer two questions — “What would the rep say that would be acceptable?” and “What would they say that would be a problem?” The second question becomes your detailed_description criteria. The first becomes your negation criteria. Create separate behaviors for each stage if the boundaries differ — early-stage calls have different boundaries than negotiation calls. Use applies_to_conversation_type_uuids to scope them if you configure separate conversation types per stage.
  • Custom behaviors — encoding your talk track and discovery framework
  • Best practices — writing detection criteria that hold up across diverse rep styles
  • Capabilities — topic sentiment and summary event schema