> ## Documentation Index
> Fetch the complete documentation index at: https://docs.poly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversation

> The Conversation object, its attributes, and methods.

**This page requires Python familiarity.** It is a reference for developers writing functions inside Agent Studio.

The Conversation object (conv) provides access to conversation data and tools for managing the agent's behavior. It handles state management, flow transitions, SMS interactions, environment details, and voice selection.

## Attributes

<AccordionGroup>
  <Accordion title="id">
    **Description**: Unique identifier of the conversation.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    log.info(f"Conversation ID: {conv.id}")
    # Example output: "conv_abc123xyz789"
    ```
  </Accordion>

  <Accordion title="account_id">
    **Description**: PolyAI account ID that owns this project. This is the same account ID visible in your Studio URL: `https://studio.poly.ai/account/{account_id}/...`

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    log.info(f"Account: {conv.account_id}")
    # Example output: "acc_abc123"
    ```
  </Accordion>

  <Accordion title="project_id">
    **Description**: Project ID of the current agent. This is the same project ID visible in your Studio URL: `https://studio.poly.ai/account/{account_id}/project/{project_id}/...`

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    log.info(f"Project ID: {conv.project_id}")
    # Example output: "proj_abc123"

    if conv.project_id == "proj_123":
      print("Running in the main deployment")
    ```
  </Accordion>

  <Accordion title="env">
    **Description**: Current environment.

    **Values**: "sandbox", "pre-release", "live"

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if conv.env == "live":
      log.info("Production traffic")
    ```
  </Accordion>

  <Accordion title="channel_type">
    **Description**: Type of channel the conversation is taking place on.

    **Possible values**:

    * `"sip.polyai"` – Voice calls (telephony/SIP)
    * `"webchat.polyai"` – Webchat widget
    * `"chat.polyai"` – Agent Studio in-browser chat
    * `"sms.twilio"` – SMS text messages

    <Note>
      Use `.startswith()` for broader matching (e.g., `conv.channel_type.startswith("sms")`) when you don't need to distinguish between SMS sub-types.
    </Note>

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if conv.channel_type == "sip.polyai":
      log.info("Running a voice call")
    elif conv.channel_type.startswith("sms"):
      log.info("Running an SMS conversation")
    elif conv.channel_type == "webchat.polyai":
      log.info("Running a webchat conversation")
    ```
  </Accordion>

  <Accordion title="attachments">
    **Description**: List of `Attachment` objects queued to be included with the next agent message (`list[Attachment]`). Append to it with [`conv.add_attachments(...)`](#add_attachments). Attachments are only supported on **webchat** channels.

    **Attachment object structure**:

    * `content_url` (str) – URL to the main content of the attachment
    * `content_type` (str) – The type of the attachment (`"image"`, `"weblink"`, or `"unspecified"`)
    * `title` (str, optional) – Title of the attachment
    * `preview_image_url` (str, optional) – URL to a preview image for the attachment
    * `call_to_action` (str, optional) – Text for the call-to-action button or link

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    for file in conv.attachments:
      print(f"Type: {file.content_type}")
      print(f"URL: {file.content_url}")
      if file.title:
        print(f"Title: {file.title}")
      if file.preview_image_url:
        print(f"Preview: {file.preview_image_url}")
    ```
  </Accordion>

  <Accordion title="sip_headers">
    **Description**: Dictionary of SIP headers (dict\[str, str]) provided by the carrier.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    source_ip = conv.sip_headers.get("X-Src-IP")
    ```
  </Accordion>

  <Accordion title="integration_attributes">
    **Description**: Metadata passed from an external integration (dict\[str, Any]). These attributes are defined per project and per integration during setup.

    **Supported integrations**:

    * [DNIs Pooling](https://docs.poly.ai/integrations/voice/dnis-pool) – Provides `shared_id` for correlating conversation outcomes

    **Best practice**: Validate and extract required attributes in the `start_function` to handle missing data appropriately.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # In start_function
    if shared_id := conv.integration_attributes.get("shared_id"):
      conv.state.shared_id = shared_id
      log.info(f"Tracking with shared_id: {shared_id}")
    else:
      # Handle missing integration data
      log.warning("No shared_id provided by integration")
    ```
  </Accordion>

  <Accordion title="caller_number">
    **Description**: The caller's identifier.

    **For inbound calls**: The phone number of the person calling in, in [E.164](http://twilio.com/docs/glossary/what-e164) format (e.g., `+14155551234` for USA numbers).

    **For outbound calls**: The phone number being called by the agent.

    **For chat channels**: This may be an email address or integration-provided user ID.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    identifier = conv.caller_number
    log.info(f"Caller identity: {identifier}")
    # Inbound example: "+14155551234"
    # Chat example: "user@example.com"
    ```
  </Accordion>

  <Accordion title="callee_number">
    **Description**: Number dialled by the caller.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if conv.callee_number.endswith("1001"):
      conv.state.branch = "Priority"
    ```
  </Accordion>

  <Accordion title="state">
    **Description**: Dictionary-like store that persists values across turns. Validated entities are also written here under their entity name, coerced to their native Python type (`int` for numeric/quantity entities, `float` for decimal numerics and currency, `str` for everything else).

    **Access patterns**: `conv.state` supports both bracket and attribute access — pick whichever reads more clearly. Reads and writes behave identically across the two styles:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # Writes are equivalent
    conv.state["is_verified"] = True
    conv.state.is_verified = True

    # Reads differ for missing keys
    conv.state["is_verified"]        # raises KeyError if missing
    conv.state.is_verified           # returns None if missing
    conv.state.get("is_verified")    # returns None if missing
    ```

    <Warning>
      Missing-key behavior differs by access style:

      * `conv.state["missing_key"]` raises `KeyError`.
      * `conv.state.missing_key` returns `None` — it does **not** raise `AttributeError`.
      * `conv.state.get("missing_key")` returns `None` (or your default).

      This means `if conv.state.is_verified == False:` is **not** the same as "missing or False" — when the key is missing the value is `None`, and `None == False` is `False`, so the branch is skipped. Prefer `if not conv.state.get("is_verified"):` to treat missing and falsy the same way.
    </Warning>

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # Manual state
    conv.state["attempts"] = conv.state.get("attempts", 0) + 1

    # Auto-synced entity, available as a native int
    if conv.state.party_size > 15:
      flow.goto_step("Group Booking")

    # Guarding a function on a verification flag
    if not conv.state.get("is_verified"):
      conv.state.original_topic = "check_account_balance"
      conv.goto_flow("Identify & Verify User")
      return
    ```
  </Accordion>

  <Accordion title="current_flow">
    **Description**: Name of the flow currently executing, or None.
  </Accordion>

  <Accordion title="current_step">
    **Description**: Step name currently executing in the active flow.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    log.info(f"Current step: {conv.current_step}")
    ```
  </Accordion>

  <Accordion title="sms_queue">
    **Description**: List of `OutgoingSMS` / `OutgoingSMSTemplate` objects queued for dispatch at turn end.
  </Accordion>

  <Accordion title="variant_name">
    **Description**: Name of the active variant, or None.
  </Accordion>

  <Accordion title="variants">
    **Description**: Dictionary of all variant definitions (dict\[str, Variant]). Each key is the variant name and each value is a Variant object whose attributes match the columns defined in **Build > Variant management**.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # Iterate variants to find one matching a custom attribute
    for variant_name, variant in conv.variants.items():
      log.info(f"{variant_name}: {variant.phone_number}")

    # Build a lookup from a variant attribute to variant name
    callee_map = {
        variant.callee: name
        for name, variant in conv.variants.items()
    }
    matched = callee_map.get(conv.callee_number)
    if matched:
        conv.set_variant(matched)
    ```
  </Accordion>

  <Accordion title="variant">
    **Description**: Variant object for the active variant, or `None` if no variant has been set. Attribute values are returned as **strings** (the storage type used by Variant management) — parse them yourself if you need structured data. Reading an attribute that doesn't exist on the active variant returns `None`, not an `AttributeError`.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if conv.variant:
      print(conv.variant.description)

      # Attribute values are strings — parse JSON yourself if needed
      import json
      hours = json.loads(conv.variant.opening_hours_json or "{}")
    ```
  </Accordion>

  <Accordion title="sms_templates">
    **Description**: Dictionary of SMS templates (dict\[str, SMSTemplate]).

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    template_body = conv.sms_templates["booking_confirmation"].content
    ```
  </Accordion>

  <Accordion title="voice_change">
    **Description**: Pending TTSVoice change requested this turn, or None.
  </Accordion>

  <Accordion title="language">
    **Description**: Language code configured for the project, which may include a locale suffix (e.g. "en-US", "en-GB", "es-ES").
  </Accordion>

  <Accordion title="history">
    **Description**: Chronological list of UserInput and AgentResponse events so far.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    for event in conv.history:
      print(event.role, event.text)

    # Example output:
    # user "I need to book a table"
    # agent "I'd be happy to help you book a table"
    # user "For 4 people at 7pm"
    ```
  </Accordion>

  <Accordion title="handoffs">
    **Description**: Dictionary of configured hand-off destinations (dict\[str, HandoffConfig]).

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if "support" in conv.handoffs:
      print("Support line is available")
    ```
  </Accordion>

  <Accordion title="transcript_alternatives">
    **Description**: List of transcription alternatives (list\[str]) for the last user utterance, including the primary transcription.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    for alt in conv.transcript_alternatives:
      print(f"Alternative: {alt}")

    # Example output:
    # Alternative: "book a table for two"
    # Alternative: "book a table for too"
    # Alternative: "book a table for to"
    ```
  </Accordion>

  <Accordion title="real_time_config">
    **Description**: Returns a dictionary of real-time configuration values defined in Configuration Builder.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    config = conv.real_time_config
    if config.get("after_hours_enabled"):
      conv.say("Our offices are currently closed.")
    ```
  </Accordion>

  <Accordion title="memory">
    **Description**: Dictionary of memory fields previously stored for the caller, retrieved from Agent Memory.

    **Customer identification**: Memory is retrieved using `caller_number` for voice calls or the integration-provided user identifier for chat channels. This lets the agent recognize returning customers and recall previous interactions.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    cheese = conv.memory.get("cheese_type")
    if cheese:
      conv.say(f"You're a fan of {cheese}, right?")

    # Check if this is a returning customer
    if conv.memory.get("last_order_date"):
      conv.say("Welcome back!")
    ```
  </Accordion>

  <Accordion title="entities">
    **Description**: Dictionary of entity validation results collected from the conversation (dict\[str, EntityValidationResult]). Each result's `.value` is the raw string captured from the caller.

    For a typed value (`int`, `float`, or `str`) read the entity from [`conv.state`](#state) instead -- validated entities are auto-synced there under their entity name.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if "email" in conv.entities:
      validated_email = conv.entities.email.value  # str

    # Numeric entity as a native int
    party_size = conv.state.party_size
    ```
  </Accordion>

  <Accordion title="functions">
    **Description**: Executor for calling other functions defined in the project. Access functions using dot notation.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    result = conv.functions.lookup_order()
    ```
  </Accordion>

  <Accordion title="api">
    **Description**: Executor for calling configured API integrations. Access APIs using `conv.api.{integration_name}.{operation_name}()`.
    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    response = conv.api.salesforce.get_contact(user_id="123")
    if response.status_code == 200:
      contact_data = response.json()
    ```
  </Accordion>

  <Accordion title="integrations">
    **Description**: Executor for calling pre-built third-party integrations (e.g., OpenTable, Tripleseat) configured from **Configure > Integrations**. Access using `conv.integrations.{integration_name}.{method}()`.

    <Note>
      `conv.integrations` is for integrations PolyAI has built and maintains (proxied through Paragon). For custom HTTP APIs you define yourself in the **APIs** tab, use [`conv.api`](#api).
    </Note>

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # OpenTable
    response = conv.integrations.opentable.get_reservation(confirmation_number="ABC123")

    # Tripleseat
    response = conv.integrations.tripleseat.create_lead(
      public_key=conv.utils.get_secret("tripleseat_public_key"),
      first_name="Jane",
      last_name="Smith",
      phone_number=conv.caller_number,
      location_id="12345",
    )
    if response.status_code == 200:
      lead_data = response.json()
    ```

    See [Integrations](/integrations/introduction) for the list of available integrations and setup instructions.
  </Accordion>

  <Accordion title="webchat">
    **Description**: Webchat-specific interface for functionality that only applies on `webchat.polyai` channels. Access webchat-only methods via `conv.webchat.{method}()`.

    **Available methods**:

    * `set_chat_call_actions(actions)` – attach click-to-call buttons to the next agent message.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    from polyai.webchat import ChatCallAction

    if conv.channel_type == "webchat.polyai":
      conv.webchat.set_chat_call_actions([
        ChatCallAction(contact_number="+15551234567", title="Call support")
      ])
    ```

    Calling webchat methods on non-webchat channels has no effect on the conversation, but you should still gate them on `conv.channel_type` for clarity.
  </Accordion>

  <Accordion title="generic_external_events">
    **Description**: List of external events initiated by `generate_external_event`. Each event contains `ext_event_id`, `send_to_llm`, `created_at`, `data`, and `content_type`.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    for event in conv.generic_external_events:
      if event.data:
        log.info(f"Received webhook data: {event.data}")
    ```
  </Accordion>

  <Accordion title="translations">
    **Description**: Proxy for accessing localized translations. Access translation keys as attributes to get the translated text for the current language.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    greeting = conv.translations.welcome_message
    conv.say(greeting)
    ```
  </Accordion>

  <Accordion title="response_suggestions">
    **Description**: List of quick-reply suggestions for the next agent message. Only supported on webchat channels.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    for suggestion in conv.response_suggestions:
      print(suggestion)
    ```
  </Accordion>

  <Accordion title="agentic_dial">
    **Description**: Agentic dial data for the conversation, used for advanced dialing scenarios.
  </Accordion>
</AccordionGroup>

## Methods

<AccordionGroup>
  <Accordion title="say">
    **Description**: Override the next utterance.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.say("I've made that change for you.")
    ```
  </Accordion>

  <Accordion title="randomize_voice">
    **Description**: Randomly choose a voice based on weighted probabilities.

    **Parameters**:

    * voice\_weightings (list\[VoiceWeighting]) – list of VoiceWeighting objects, each containing a voice and weight.

    **Available voices**: Import from `polyai.voice` module (e.g., `ElevenLabsVoice`, `CartesiaVoice`, `PlayHTVoice`, `RimeVoice`). See [Voice classes](/tools/classes/voice) for the full list of available voices and their IDs.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    from polyai.voice import VoiceWeighting, ElevenLabsVoice

    conv.randomize_voice([
      VoiceWeighting(voice=ElevenLabsVoice(provider_voice_id="voice1"), weight=0.7), 
      VoiceWeighting(voice=ElevenLabsVoice(provider_voice_id="voice2"), weight=0.3)
    ])
    ```
  </Accordion>

  <Accordion title="goto_flow">
    **Description**: Transition to another flow at turn end.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.goto_flow("verification")
    ```
  </Accordion>

  <Accordion title="exit_flow">
    **Description**: Exit the current flow.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.exit_flow()
    ```
  </Accordion>

  <Accordion title="set_variant">
    **Description**: Manually set the active variant.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.set_variant("evening")
    ```
  </Accordion>

  <Accordion title="add_attachments">
    **Description**: Attach one or more visual tiles (images or web links) to the conversation. Only supported on webchat channels.

    **Parameters**: attachments (list\[Attachment]) – list of Attachment objects.

    **Attachment fields**:

    * `content_url` (str) – URL to the main content of the attachment
    * `content_type` (str) – The type of the attachment (`"image"`, `"weblink"`, or `"unspecified"`)
    * `title` (str, optional) – Title of the attachment
    * `preview_image_url` (str, optional) – URL to a preview image for the attachment
    * `call_to_action` (str, optional) – Text for the call-to-action button or link

    **Attachment types**:

    * `"weblink"` – Displays the title, preview image, and call-to-action text. Clicking navigates the user to `content_url`.
    * `"image"` – Displays the title (if present), but no call-to-action. `preview_image_url` should be a lower resolution image, and `content_url` should be the full resolution image. Clicking shows the higher resolution version.
    * `"unspecified"` – No specific rendering behaviour is applied.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    from polyai.attachments import Attachment

    conv.add_attachments([
      Attachment(
        content_url="https://example.com/menu",
        content_type="weblink",
        title="View our menu",
        preview_image_url="https://example.com/menu-preview.jpg",
        call_to_action="Open menu"
      ),
      Attachment(
        content_url="https://example.com/logo.png",
        content_type="image"
      )
    ])
    ```
  </Accordion>

  <Accordion title="discard_recording">
    **Description**: Prevents saving the current call recording, e.g. when sensitive data is detected.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if conv.state.get("contains_pii"):
      conv.discard_recording()
    ```
  </Accordion>

  <Accordion title="generate_external_event">
    **Description**: Generates a unique external event ID linked to the current conversation. Use this ID to receive webhook data from an external provider through the `/v1/external-events/webhook` endpoint. The webhook payload is then accessible through `conv.generic_external_events`.

    **Parameters**:

    * send\_to\_llm (bool, keyword-only, optional) – if `True`, the webhook payload is also sent to the LLM as a system prompt. Default `False`.

    **Returns**: str – the generated external event ID (expires after 1 hour).

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    event_id = conv.generate_external_event(send_to_llm=True)
    # Pass event_id to an external provider, which can POST data back via the webhook
    ```
  </Accordion>

  <Accordion title="log_api_response">
    **Description**: Logs an external API response for visibility in Conversation Review → Diagnosis and the analytics pipeline.

    **Where logs appear**:

    * **Conversation Review**: View API responses in the Diagnosis tab for debugging
    * **Analytics pipeline**: API response data is available for reporting and analysis

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    response = requests.get("https://api.example.com/user")
    conv.log_api_response(response)
    # Response will appear in Conversation Review → Diagnosis
    ```
  </Accordion>

  <Accordion title="send_whatsapp">
    **Description**: Sends a WhatsApp message through a Twilio subaccount. Both the sender phone number and the message template must be pre-approved by Meta. Once approved, Twilio provides a `content_id` that must be referenced when sending.

    <Note>WhatsApp messaging requires a configured Twilio integration and is not a native PolyAI channel. Contact your PolyAI representative for setup.</Note>

    **Parameters**:

    * `to_number` (str) – recipient phone number.
    * `from_number` (str) – sender phone number (must be WhatsApp-enabled in Twilio).
    * `content_id` (str) – alphanumeric ID of the approved WhatsApp message template.
    * `content` (str, optional) – text content. Default `""`.
    * `retry_count` (int, optional) – number of retries to attempt on failure.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.send_whatsapp(
      to_number=conv.caller_number,
      from_number="+441234567890",
      content_id="HX5678efgh",
    )
    ```

    For richer templating with variables, use [`send_content_template`](#send_content_template) with `whatsapp=True`.
  </Accordion>

  <Accordion title="send_content_template">
    **Description**: Sends a WhatsApp or SMS template message through Twilio's Content API. Requires the content template to be pre-approved in your Twilio account.

    <Note>WhatsApp messaging requires a configured Twilio integration and is not a native PolyAI channel. Contact your PolyAI representative for setup.</Note>

    **Parameters**:

    * messaging\_service\_id (str) – Twilio messaging service ID.
    * to\_number (str) – recipient phone number.
    * content\_id (str) – alphanumeric ID of the approved message template.
    * content (str, optional) – text content. Default `""`.
    * whatsapp (bool, optional) – set to `True` to send over WhatsApp. Default `False`.
    * content\_variables (dict, optional) – variables to pass to the message template.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.send_content_template(
      messaging_service_id="MG1234abcd",
      to_number="+441234567890",
      content_id="HX5678efgh",
      whatsapp=True,
      content_variables={"1": "12345", "2": "shipped"}
    )
    ```
  </Accordion>

  <Accordion title="send_sms">
    **Description**: Queue a plain-text SMS.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.send_sms(to_number=conv.caller_number, from_number="+441234567890", content="Thanks for calling – here's your link: https://…")
    ```
  </Accordion>

  <Accordion title="send_sms_template">
    **Description**: Queue a pre-configured SMS template.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.send_sms_template(to_number=conv.caller_number, template="booking_confirmation")
    ```
  </Accordion>

  <Accordion title="write_metric">
    **Description**: Write a custom metric to the analytics pipeline. This is the only way custom metrics are recorded for a conversation – the agent does not write them automatically at runtime, so you must call `write_metric` explicitly from a function whenever you want a metric value captured.

    **Parameters**:

    * name (str) – metric key, as defined in your project's metrics configuration.
    * value (str, int, float, bool, or None) – the metric value. Must match the type defined in the metric spec.
    * write\_once (bool, optional) – if `True`, the metric can only be written once per conversation. Subsequent calls with the same name are ignored. Default `False`.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.write_metric("agent_handoff", 1)
    conv.write_metric("call_outcome", "resolved", write_once=True)
    ```
  </Accordion>

  <Accordion title="call_handoff">
    **Description**: Transfer the call to a configured handoff destination.

    **Parameters**:

    * destination (str) – handoff target key, as defined in your handoff configuration.
    * reason (str, optional) – escalation reason. Defaults to the destination name.
    * utterance (str, optional) – message for the agent to say before transferring.
    * sip\_headers (dict\[str, str], optional) – SIP headers to pass through. Merged with any headers configured in the handoff config, with passed headers taking precedence.
    * route (str, optional) – phone number or route to override the configured route.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.call_handoff(
      destination="BillingQueue",
      reason="policy_violation",
      utterance="Let me transfer you to a specialist who can help."
    )
    ```

    **Where it shows up**: In flows using builtin-handoff or using functions; visible in Conversation Review and API.
  </Accordion>

  <Accordion title="utils">
    **Description**: Provides helper functions for extracting data, validating entities, and accessing secrets.

    **Available utilities**:

    * `get_secret(name)` – Retrieve a stored [secret](/secrets/introduction) by name
    * `extract_address()` – Extract postal addresses from user input
    * `extract_city()` – Extract city references from user input
    * `prompt_llm()` – Perform a standalone LLM request
    * `validate_entity()` – Validate a value against an entity config (email, phone, date, etc.)

    **Note**: Some utilities require activation. If a method raises a `NotImplementedError`, contact your PolyAI representative to enable it for your account.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    api_key = conv.utils.get_secret("stripe_api_key")
    address = conv.utils.extract_address(country="US")
    ```

    See [Conversation utilities](./conv-utils) for the full list of available helpers and detailed documentation.
  </Accordion>

  <Accordion title="set_voice">
    **Description**: Change the TTS voice for the current conversation moving forward.

    **Parameters**: voice (TTSVoice) – the voice configuration to use.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    from polyai.voice import ElevenLabsVoice
    conv.set_voice(ElevenLabsVoice(provider_voice_id="abc123"))
    ```
  </Accordion>

  <Accordion title="set_language">
    **Description**: Change the language for the current conversation moving forward.

    **Parameters**: language (str) – ISO 639 language code (e.g., "en-US", "es-ES", "fr-FR").

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.set_language("es-US")
    ```
  </Accordion>

  <Accordion title="set_asr_biasing">
    **Description**: Dynamically configure ASR keywords and custom biases for improved speech recognition. Biasing persists across turns until cleared.
    **Parameters**:

    * keywords (list\[str], optional) – list of keywords to bias ASR recognition toward.
    * custom\_biases (dict\[str, float], optional) – dictionary mapping phrases to bias weights.
      **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.set_asr_biasing(
      keywords=["reservation", "booking", "cancel"],
      custom_biases={"reservation": 3.0, "cancellation": 2.5}
    )
    ```
  </Accordion>

  <Accordion title="clear_asr_biasing">
    **Description**: Clear any previously set ASR biasing for future turns.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.clear_asr_biasing()
    ```
  </Accordion>

  <Accordion title="disable_kb_topics">
    **Description**: Exclude one or more [Managed Topics](/managed-topics/introduction) from RAG retrieval for the rest of the conversation. Useful when an upstream IVR or runtime context means certain topics should not be matched. The list persists across turns until cleared or replaced. See [Disable KB topics](/tools/classes/disable-kb-topics) for full details.

    **Parameters**:

    * topics (list\[str]) – Managed Topic names to disable. Replaces any previously disabled list.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if conv.state.get("ivr_intent") == "billing_handled":
      conv.disable_kb_topics(["refund_policy", "billing_dispute"])
    ```
  </Accordion>

  <Accordion title="clear_disabled_kb_topics">
    **Description**: Re-enable any [Managed Topics](/managed-topics/introduction) that were previously disabled with `conv.disable_kb_topics()`.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.clear_disabled_kb_topics()
    ```
  </Accordion>

  <Accordion title="goto_csat_flow">
    **Description**: Trigger a transition to the CSAT (Customer Satisfaction) survey flow for voice calls.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if conv.state.get("call_completed"):
      conv.goto_csat_flow()
    ```
  </Accordion>

  <Accordion title="set_csat_eligibility">
    **Description**: Override whether the conversation is eligible for a [CSAT survey](/analytics/csat/introduction). When `eligible=False`, the conversation is excluded from CSAT regardless of call type, percentage rollout, or weekly caps. When `eligible=True` or this method isn't called, normal CSAT logic applies.

    **Parameters**:

    * `eligible` (bool) – whether the conversation is eligible for CSAT.
    * `reason` (str, optional) – reason for the decision (logged for debugging).

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if conv.state.get("contains_pii"):
      conv.set_csat_eligibility(False, reason="PII detected")
    ```
  </Accordion>

  <Accordion title="set_csat_phone_number">
    **Description**: Override the phone number used for CSAT SMS surveys. Useful when the real caller number is delivered via a SIP header (e.g., behind an IVR) rather than as the caller ID.

    **Parameters**:

    * `phone_number` (str) – phone number to send the CSAT SMS to, in [E.164](http://twilio.com/docs/glossary/what-e164) format.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    real_caller = conv.sip_headers.get("X-Real-Caller")
    if real_caller:
      conv.set_csat_phone_number(real_caller)
    ```
  </Accordion>

  <Accordion title="set_csat_survey_entered">
    **Description**: Mark that the caller entered the CSAT survey flow. This is used internally by the platform's voice CSAT flow for analytics and is not typically called from custom functions.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.set_csat_survey_entered()
    ```
  </Accordion>

  <Accordion title="send_email">
    **Description**: Send an email from the function.

    This function sends outbound emails during a conversation (e.g., confirmations or notifications). **Email is not a supported inbound channel for PolyAI agents.**

    **SMTP configuration**: Emails are sent through a managed delivery service. For custom email delivery requirements, contact your PolyAI representative.

    **Delivery considerations**:

    * Emails are sent asynchronously after the turn completes
    * Delivery failures are logged but do not interrupt the conversation
    * For high-volume sending, consider rate limits and reputation management

    **Parameters**:

    * to (str) – recipient email address.
    * body (str) – raw body of the email.
    * subject (str) – subject line.

    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.send_email(
      to="customer@example.com",
      body="Thank you for your order!",
      subject="Order Confirmation"
    )
    ```
  </Accordion>

  <Accordion title="set_response_suggestions">
    **Description**: Set quick-reply suggestions that appear as clickable options for the user. Only supported on webchat channels.
    **Parameters**: suggestions (list\[str]) – list of suggested responses.
    **Example**:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    conv.set_response_suggestions(["Yes, confirm", "No, cancel", "More options"])
    ```
  </Accordion>
</AccordionGroup>
