<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: anusha</title>
    <description>The latest articles on DEV Community by anusha (@botoclock).</description>
    <link>https://dev.to/botoclock</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4004284%2Fc7c15289-9b22-479c-b291-b944235c5687.jpg</url>
      <title>DEV Community: anusha</title>
      <link>https://dev.to/botoclock</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/botoclock"/>
    <language>en</language>
    <item>
      <title>I Ran LangGraph Inside a Telnyx Edge Actor with No API Key</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Thu, 13 Aug 2026 18:35:52 +0000</pubDate>
      <link>https://dev.to/botoclock/i-ran-langgraph-inside-a-telnyx-edge-actor-with-no-api-key-2odn</link>
      <guid>https://dev.to/botoclock/i-ran-langgraph-inside-a-telnyx-edge-actor-with-no-api-key-2odn</guid>
      <description>&lt;p&gt;I wanted to see if LangGraph could run inside a real edge compute actor — not a notebook, not a local REPL, but actual edge infrastructure with durable state, retry semantics, and a 30-second inbound budget.&lt;/p&gt;

&lt;p&gt;And I wanted to do it without managing an API key inside the function.&lt;/p&gt;

&lt;p&gt;The Telnyx code example is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/langgraph-agent-on-edge" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/langgraph-agent-on-edge&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result is an SMS support agent that runs a 3-node LangGraph graph (intent → action → response) inside a Telnyx Edge Compute actor, with LLM inference through a pre-authenticated binding. No API key in code. No API key in the bundle. No API key in the logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Frameworks on Edge
&lt;/h2&gt;

&lt;p&gt;Most agent framework examples assume you have a long-running process with environment variables and a stable network. You import the framework, pass it your API key, and it makes HTTP calls to your LLM provider.&lt;/p&gt;

&lt;p&gt;Edge functions flip that. You have a short-lived runtime, a 30-second inbound budget, and — on Telnyx — a pre-authenticated API binding that eliminates the need for keys entirely.&lt;/p&gt;

&lt;p&gt;The challenge is that frameworks like LangGraph call the LLM through their own HTTP client. The stock &lt;code&gt;ChatOpenAI&lt;/code&gt; from LangChain takes an &lt;code&gt;apiKey&lt;/code&gt; and a &lt;code&gt;baseURL&lt;/code&gt;. If you use it inside a Telnyx Edge function, you are managing a key that the binding was designed to make unnecessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Adapter
&lt;/h2&gt;

&lt;p&gt;I wrote a small adapter called &lt;code&gt;TelnyxBoundChatModel&lt;/code&gt;. It extends LangChain's &lt;code&gt;SimpleChatModel&lt;/code&gt; and calls the Telnyx binding instead of making its own HTTP calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TelnyxBoundChatModel&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;SimpleChatModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BaseMessage&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mapped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;roleForMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;contentToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TELNYX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCompletion&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mapped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is about 70 lines of code. It maps LangChain messages to the format the binding expects, calls &lt;code&gt;createCompletion&lt;/code&gt;, and returns the content. No key. No &lt;code&gt;baseURL&lt;/code&gt;. No secret to rotate.&lt;/p&gt;

&lt;p&gt;LangGraph does not know or care that the model is the binding. It just sees a &lt;code&gt;SimpleChatModel&lt;/code&gt; that returns strings. The graph runs the same way it would with &lt;code&gt;ChatOpenAI&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Graph
&lt;/h2&gt;

&lt;p&gt;Three nodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;intent   → LLM classifies the message as "order" or "smalltalk"
action   → plain TypeScript looks up the order
response → LLM composes a reply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the intent is &lt;code&gt;order&lt;/code&gt;, the graph runs the action node before the response node. If the intent is &lt;code&gt;smalltalk&lt;/code&gt;, it skips action and goes straight to response.&lt;/p&gt;

&lt;p&gt;The whole thing is about 80 lines of graph code. It is not a ReAct agent with tool calling. It is an explicit, typed graph — the kind of thing you would build if you wanted to control the flow rather than let the model decide.&lt;/p&gt;

&lt;h2&gt;
  
  
  The State Problem
&lt;/h2&gt;

&lt;p&gt;Here is where it gets interesting.&lt;/p&gt;

&lt;p&gt;LangGraph has its own state — the channels that flow between nodes. The Agent SDK has durable state — &lt;code&gt;setState&lt;/code&gt; and &lt;code&gt;getState&lt;/code&gt; that survive restarts. And the Agent SDK has message history — &lt;code&gt;this.messages&lt;/code&gt;, which is the conversation log.&lt;/p&gt;

&lt;p&gt;These are three different things. The sample teaches the distinction deliberately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Graph state&lt;/strong&gt; (&lt;code&gt;intentLabel&lt;/code&gt;, &lt;code&gt;actionResult&lt;/code&gt;, &lt;code&gt;replyText&lt;/code&gt;) is ephemeral. It lives and dies inside one &lt;code&gt;process()&lt;/code&gt; run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable state&lt;/strong&gt; (&lt;code&gt;turn&lt;/code&gt;, &lt;code&gt;queuedTurn&lt;/code&gt;, &lt;code&gt;lastSentTurn&lt;/code&gt;) survives restarts. It is for turn tracking and idempotency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message history&lt;/strong&gt; (&lt;code&gt;this.messages&lt;/code&gt;) is the memory. It is the conversation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you conflate them, you end up with bugs. For example, if you put the graph's &lt;code&gt;intentLabel&lt;/code&gt; into durable state, it persists across turns and the next message gets the wrong intent. If you put the conversation into graph state, it resets on every &lt;code&gt;process()&lt;/code&gt; run and the agent has no memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Turn State Machine
&lt;/h2&gt;

&lt;p&gt;Edge actors deliver messages at-least-once. A crash after a successful SMS send can retry the entire &lt;code&gt;process()&lt;/code&gt; method. Without protection, that means duplicate replies.&lt;/p&gt;

&lt;p&gt;The sample uses a per-turn state machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receive() → bump turn, set queuedTurn, queue("process")
process() → if queuedTurn &amp;lt;= lastSentTurn: return (stale)
             → run graph
             → stage pendingOutbound
             → send SMS
             → commit lastSentTurn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If two messages arrive before the first &lt;code&gt;process()&lt;/code&gt; runs, the second bumps &lt;code&gt;queuedTurn&lt;/code&gt;. The first &lt;code&gt;process()&lt;/code&gt; handles the latest turn. The stale second &lt;code&gt;process()&lt;/code&gt; sees &lt;code&gt;queuedTurn &amp;lt;= lastSentTurn&lt;/code&gt; and returns immediately. One reply, not two.&lt;/p&gt;

&lt;p&gt;The guard is on &lt;code&gt;turn&lt;/code&gt;, not reply text. So identical replies across different turns are never suppressed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-Second Budget
&lt;/h2&gt;

&lt;p&gt;The inbound method runs under a 30-second wall-clock budget. That is fine for acking a webhook, but not for an LLM round-trip plus tool calls.&lt;/p&gt;

&lt;p&gt;So the inbound method does zero model I/O. It adds the user message to history, bumps the turn counter, and queues a background task. The webhook acks immediately.&lt;/p&gt;

&lt;p&gt;The queued &lt;code&gt;process()&lt;/code&gt; task runs in the actor's alarm handler, which has a budget on the order of minutes. That is where the graph runs, the LLM is called, and the SMS is sent. If it throws, the scheduler retries with backoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;LangGraph runs fine inside an edge actor. You just need to give it a chat model that calls the binding instead of an HTTP endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The binding is the whole point. Once you have the adapter, the rest of the code has no keys, no secrets, and no authentication logic. The platform handles it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;State layers matter. Graph state, durable state, and message history are three different things. The sample makes that explicit because it is the most common mistake.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At-least-once delivery is real. If you do not guard outbound side effects, you will send duplicate SMS replies under retry. The turn state machine is the answer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The 30-second budget is real. If you call the LLM inside the inbound method, you will time out. Defer to a queued task.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Run It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/team-telnyx/telnyx-code-examples.git
&lt;span class="nb"&gt;cd &lt;/span&gt;telnyx-code-examples/langgraph-agent-on-edge
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fetch the public key and store it as a secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TELNYX_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://api.telnyx.com/v2/public_key | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.data.public'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
telnyx-edge secrets add TELNYX_PUBLIC_KEY &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PUBLIC_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run typecheck
npm run types
npm run ship
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send an SMS with "where is my order ORD-10042?" and get a reply. Visit the function URL for a demo UI that shows the conversation, the turn state counters, and the process log.&lt;/p&gt;

&lt;p&gt;The code example is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/langgraph-agent-on-edge" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/langgraph-agent-on-edge&lt;/a&gt;&lt;/p&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
    <item>
      <title>I Built an Agent That Can Decide When to Text You or Call You</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Thu, 13 Aug 2026 00:48:27 +0000</pubDate>
      <link>https://dev.to/botoclock/i-built-an-agent-that-can-decide-when-to-text-you-or-call-you-33fe</link>
      <guid>https://dev.to/botoclock/i-built-an-agent-that-can-decide-when-to-text-you-or-call-you-33fe</guid>
      <description>&lt;p&gt;I wanted a simple way to show what LLM tool calling looks like when the tools are real communication actions.&lt;/p&gt;

&lt;p&gt;Not a weather lookup. Not a calculator.&lt;/p&gt;

&lt;p&gt;Something you can feel immediately from your phone.&lt;/p&gt;

&lt;p&gt;The Telnyx code example is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/agent-with-tool-calling" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/agent-with-tool-calling&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result is an agent that can read a normal message like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Text +13125550001 I am running five minutes late
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and decide to call the SMS tool.&lt;/p&gt;

&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call me at +13125550001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and decide to call the voice tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;Most demos make the user choose the action first.&lt;/p&gt;

&lt;p&gt;Click this button to send a text. Click that button to place a call. Pick a workflow. Fill in a form.&lt;/p&gt;

&lt;p&gt;This sample flips that around.&lt;/p&gt;

&lt;p&gt;The user writes what they want. The model reads the request, chooses one of the allowed tools, extracts the phone number and message body, and the application executes the action through Telnyx.&lt;/p&gt;

&lt;p&gt;The approved tools are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;send_sms&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;make_call&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;check_status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model does not get to run arbitrary code. It gets a small, explicit set of tools with JSON schemas.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Loop
&lt;/h2&gt;

&lt;p&gt;The important part is that tool calling is a loop, not a single model response.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user message
  -&amp;gt; model receives tool definitions
  -&amp;gt; model returns a tool call
  -&amp;gt; app executes the selected tool
  -&amp;gt; app appends the tool result with the same toolCallId
  -&amp;gt; model sees the tool result
  -&amp;gt; model writes the final response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last model pass matters. The tool result is structured data. The final assistant response is the human-readable summary.&lt;/p&gt;

&lt;p&gt;So if the model chooses &lt;code&gt;send_sms&lt;/code&gt;, the app sends the SMS first. Then the model gets the result and can say what happened in a clean sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Like This Example
&lt;/h2&gt;

&lt;p&gt;It makes tool calling concrete.&lt;/p&gt;

&lt;p&gt;You can inspect the code and see the exact line where Telnyx Inference is called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TELNYX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCompletion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can inspect the SMS dispatch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TELNYX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can inspect the voice dispatch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TELNYX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dial&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you can inspect the ledger that records each tool call and its &lt;code&gt;toolCallId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That makes the demo much easier to trust. If something happens, you can trace it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What The Agent Remembers
&lt;/h2&gt;

&lt;p&gt;The sample runs on Telnyx Edge Compute with the Agent SDK.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ToolAgent&lt;/code&gt; keeps conversation history and records tool activity. That means a user can ask a follow-up like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Did the SMS send?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model can choose &lt;code&gt;check_status&lt;/code&gt;, and the app can look up the latest &lt;code&gt;send_sms&lt;/code&gt; event in the local ledger.&lt;/p&gt;

&lt;p&gt;That is a useful pattern for real workflows. Users ask follow-up questions. They do not always phrase the second message with all the context from the first one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Phone Is The Interface
&lt;/h2&gt;

&lt;p&gt;The fun part of this sample is that the phone becomes the proof.&lt;/p&gt;

&lt;p&gt;When the agent sends an SMS, the recipient sees the message.&lt;/p&gt;

&lt;p&gt;When the agent places a call, the phone rings.&lt;/p&gt;

&lt;p&gt;That makes it different from a lot of LLM demos. You are not just watching a terminal print JSON. You are watching a model choose an action that reaches a real person through a real communications channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running It
&lt;/h2&gt;

&lt;p&gt;Start with the code sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/team-telnyx/telnyx-code-examples.git
&lt;span class="nb"&gt;cd &lt;/span&gt;telnyx-code-examples/agent-with-tool-calling
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run typecheck
npm run types
npm run ship
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure your Telnyx number, Messaging Profile webhook, Call Control application, and Edge function settings.&lt;/p&gt;

&lt;p&gt;Try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Text +13125550001 meet me at the front desk
Call me at +13125550001
Did the SMS send?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Pattern To Reuse
&lt;/h2&gt;

&lt;p&gt;This is the shape I would reuse for a lot of communication workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;let the LLM classify intent&lt;/li&gt;
&lt;li&gt;keep the tool surface small&lt;/li&gt;
&lt;li&gt;validate phone numbers before dispatch&lt;/li&gt;
&lt;li&gt;execute tools exactly once&lt;/li&gt;
&lt;li&gt;store a ledger&lt;/li&gt;
&lt;li&gt;pass the tool result back to the model&lt;/li&gt;
&lt;li&gt;return a short final answer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gives you the flexibility of natural language with the control of normal application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code example: &lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/agent-with-tool-calling" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/agent-with-tool-calling&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
    <item>
      <title>Building an SMS Sentiment Escalation Agent on Telnyx Edge Compute</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Thu, 13 Aug 2026 00:12:07 +0000</pubDate>
      <link>https://dev.to/botoclock/building-an-sms-sentiment-escalation-agent-on-telnyx-edge-compute-400i</link>
      <guid>https://dev.to/botoclock/building-an-sms-sentiment-escalation-agent-on-telnyx-edge-compute-400i</guid>
      <description>&lt;p&gt;I wanted this demo to show a realistic support workflow: a customer texts in, an agent reads the tone of the message, and a human gets pulled in when the conversation is clearly going badly.&lt;/p&gt;

&lt;p&gt;The sample puts the full messaging flow on Telnyx Edge Compute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The request hits a deployed Telnyx Edge function.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SentimentAgent&lt;/code&gt; handles the message with the Agent SDK.&lt;/li&gt;
&lt;li&gt;Telnyx AI Inference classifies the sentiment.&lt;/li&gt;
&lt;li&gt;Actor-local SQL stores the message, score, reply, and escalation state.&lt;/li&gt;
&lt;li&gt;Negative messages send an SMS alert to the ops number.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Put This on the Edge?
&lt;/h2&gt;

&lt;p&gt;Sentiment analysis is often treated like analytics: run a job later, summarize the inbox, and tell the team what happened.&lt;/p&gt;

&lt;p&gt;That is useful, but it misses the moment where automation can actually help. If a customer says "this is broken and nobody is helping me, I want a refund," you want the system to know immediately. The reply should be empathetic, the log should be updated, and a person should be alerted before the thread gets worse.&lt;/p&gt;

&lt;p&gt;Edge Compute is a good fit because the logic runs where the event arrives. The webhook does not need to bounce through a separate backend before the first decision is made.&lt;/p&gt;

&lt;p&gt;The sample flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inbound SMS
  -&amp;gt; Telnyx Edge Compute
  -&amp;gt; Agent SDK actor
  -&amp;gt; Telnyx AI Inference
  -&amp;gt; SQL sentiment log
  -&amp;gt; auto-reply
  -&amp;gt; human escalation when negative
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Demo Experience
&lt;/h2&gt;

&lt;p&gt;After deployment, you open the &lt;code&gt;telnyxcompute.com&lt;/code&gt; URL and use the browser view to test the flow.&lt;/p&gt;

&lt;p&gt;Send a positive message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I love this app, just paid for a year
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send a neutral one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What are your hours?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then send a negative message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this is broken and nobody is helping me, I want a refund
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The log updates with the sentiment label and score. Negative sentiment gets marked for escalation, and the UI shows the empathetic response the agent generated.&lt;/p&gt;

&lt;p&gt;The escalation path sends SMS alerts through Telnyx Messaging so a human can jump in quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Like About This Pattern
&lt;/h2&gt;

&lt;p&gt;This sample is small, but the architecture scales to more serious workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer support escalation&lt;/li&gt;
&lt;li&gt;Refund and cancellation saves&lt;/li&gt;
&lt;li&gt;Abuse or safety triage&lt;/li&gt;
&lt;li&gt;After-hours monitoring&lt;/li&gt;
&lt;li&gt;Routing high-value accounts to a human faster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also swap the sentiment prompt for another classifier. The same shape works for urgency detection, topic routing, language detection, or structured intake.&lt;/p&gt;

&lt;p&gt;The key idea is that the AI decision is not sitting off to the side. It is inside the communication path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The sample lives in the Telnyx code examples repo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/sentiment-analysis-agent" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/sentiment-analysis-agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run the type checks, deploy with &lt;code&gt;telnyx-edge ship&lt;/code&gt;, open the deployed URL, and reset the live log before recording if you want a clean demo.&lt;/p&gt;

&lt;p&gt;Point your Telnyx Messaging webhook at &lt;code&gt;/webhooks/messaging&lt;/code&gt;, send an inbound SMS, and watch the agent classify, log, reply, and escalate from the deployed Edge function.&lt;/p&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
    <item>
      <title>I Built an SMS Quiz Agent That Remembers Each Turn</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Thu, 13 Aug 2026 00:10:06 +0000</pubDate>
      <link>https://dev.to/botoclock/i-built-an-sms-quiz-agent-that-remembers-each-turn-2jk</link>
      <guid>https://dev.to/botoclock/i-built-an-sms-quiz-agent-that-remembers-each-turn-2jk</guid>
      <description>&lt;p&gt;I wanted a simple way to show how Telnyx Edge Compute can support a conversation that lasts longer than one request.&lt;/p&gt;

&lt;p&gt;An SMS quiz is a good example because the state is obvious.&lt;/p&gt;

&lt;p&gt;If I text &lt;code&gt;start&lt;/code&gt;, answer question one, then answer question two, the system has to remember where I am. It needs to know my score, the current question, the correct answer, and whether the next question should be easier or harder.&lt;/p&gt;

&lt;p&gt;The Telnyx code example is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/multi-turn-sms-quiz-agent" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/multi-turn-sms-quiz-agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The end result is a quiz you can answer over SMS. Text &lt;code&gt;start&lt;/code&gt;, reply with &lt;code&gt;A&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, or &lt;code&gt;C&lt;/code&gt;, and the quiz keeps going until the final score.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;The quiz teaches Edge Compute concepts through short multiple-choice questions.&lt;/p&gt;

&lt;p&gt;Instead of asking questions in abstract terms, the prompt pushes the model to use concrete examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;storage units&lt;/li&gt;
&lt;li&gt;lockers&lt;/li&gt;
&lt;li&gt;mail and delivery&lt;/li&gt;
&lt;li&gt;keys&lt;/li&gt;
&lt;li&gt;filing systems&lt;/li&gt;
&lt;li&gt;rooms and spaces&lt;/li&gt;
&lt;li&gt;saving and retrieving items&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes concepts like durable state easier to picture.&lt;/p&gt;

&lt;p&gt;For example, a StatefulActor can be explained like a storage unit for one sender. You leave the score and current question there, come back on the next SMS, and the data is still there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What The Sample Does
&lt;/h2&gt;

&lt;p&gt;The flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text start
receive Q1
answer A, B, or C
receive feedback
receive Q2
repeat until Q5
receive final score
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind that simple SMS flow, the sample uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Telnyx Edge Compute&lt;/li&gt;
&lt;li&gt;the Agent SDK&lt;/li&gt;
&lt;li&gt;one StatefulActor per sender&lt;/li&gt;
&lt;li&gt;Telnyx Messaging&lt;/li&gt;
&lt;li&gt;Telnyx Inference&lt;/li&gt;
&lt;li&gt;actor-local SQL for event history and webhook idempotency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code is Node.js and TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why The Actor Matters
&lt;/h2&gt;

&lt;p&gt;SMS messages arrive as separate webhook events.&lt;/p&gt;

&lt;p&gt;Without durable state, each message looks isolated. The system sees &lt;code&gt;B&lt;/code&gt;, but it does not automatically know which question that answer belongs to.&lt;/p&gt;

&lt;p&gt;The actor solves that.&lt;/p&gt;

&lt;p&gt;The sample routes each sender phone number to a stable actor name. That actor stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;phase&lt;/li&gt;
&lt;li&gt;score&lt;/li&gt;
&lt;li&gt;difficulty&lt;/li&gt;
&lt;li&gt;turn number&lt;/li&gt;
&lt;li&gt;current question&lt;/li&gt;
&lt;li&gt;current answer&lt;/li&gt;
&lt;li&gt;sender and recipient phone numbers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when the user sends &lt;code&gt;B&lt;/code&gt;, the actor can retrieve the current question and grade the answer in context.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Difficulty Adapts
&lt;/h2&gt;

&lt;p&gt;The quiz starts on easy.&lt;/p&gt;

&lt;p&gt;If the user answers correctly, the code moves difficulty up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;easy -&amp;gt; medium -&amp;gt; hard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user answers incorrectly, the code moves difficulty down or keeps it easy.&lt;/p&gt;

&lt;p&gt;The model helps generate and grade questions, but the adaptation rule is controlled by code. That keeps the quiz predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Question Style
&lt;/h2&gt;

&lt;p&gt;The question prompt asks for one JSON object with the question, answer, and hint.&lt;/p&gt;

&lt;p&gt;The question itself has three choices on separate lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Q2/5 (easy)
What is durable state most like?
A) A saved item in a locker you can open later
B) A message that disappears as soon as it is sent
C) A random address that changes every time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The incorrect answers are supposed to be plausible. That matters because the quiz should test understanding, not just make the right answer obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running It
&lt;/h2&gt;

&lt;p&gt;Start with the code sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/team-telnyx/telnyx-code-examples.git
&lt;span class="nb"&gt;cd &lt;/span&gt;telnyx-code-examples/multi-turn-sms-quiz-agent
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run typecheck
npm run types
npm run ship
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set your Telnyx Messaging Profile webhook to the deployed function, then text &lt;code&gt;start&lt;/code&gt; to your Telnyx number.&lt;/p&gt;

&lt;p&gt;You need a Telnyx API key and an SMS-capable Telnyx number. The sample uses the Telnyx Edge binding in the function code, so API credentials are not hard-coded into the message handling logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Like This Example
&lt;/h2&gt;

&lt;p&gt;It is small, but it shows the core pieces of a stateful SMS agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;route each user to their own durable state&lt;/li&gt;
&lt;li&gt;acknowledge inbound webhooks quickly&lt;/li&gt;
&lt;li&gt;do slower AI work in the actor queue&lt;/li&gt;
&lt;li&gt;save enough state to continue on the next message&lt;/li&gt;
&lt;li&gt;send the next SMS from the same workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the pattern I would reuse for many SMS experiences where the user needs to answer one step at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code example: &lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/multi-turn-sms-quiz-agent" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/multi-turn-sms-quiz-agent&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
    <item>
      <title>I Built a Voice Agent That Switches Languages Mid-Call on Telnyx</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Wed, 05 Aug 2026 23:02:02 +0000</pubDate>
      <link>https://dev.to/botoclock/i-built-a-voice-agent-that-switches-languages-mid-call-on-telnyx-1g51</link>
      <guid>https://dev.to/botoclock/i-built-a-voice-agent-that-switches-languages-mid-call-on-telnyx-1g51</guid>
      <description>&lt;p&gt;Translation apps interpret between two people speaking different languages. Code-switching is different: one caller switches languages mid-conversation and the agent follows them. No interpreter, no restart, no separate vendors — one Telnyx AI Assistant that detects the spoken language on every turn and replies in kind.&lt;/p&gt;

&lt;p&gt;The Telnyx code example is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-multilingual-code-switching-agent-python" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-multilingual-code-switching-agent-python&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is a Python Flask app with a simple browser page showing a phone number. No database, no Cloud Storage. Call the number, speak in English, switch to Spanish mid-conversation, and the agent follows you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flow
&lt;/h2&gt;

&lt;p&gt;A customer calls a support line. They start in English, switch to Spanish for a sensitive question, then switch back. The agent detects the language from their speech on every turn and replies in that language. No language picker menu, no "press 1 for English." The caller just speaks.&lt;/p&gt;

&lt;p&gt;One phone number, one AI Assistant, five languages — English, Spanish, Portuguese, Hindi, and Mandarin.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Code-Switching Works
&lt;/h2&gt;

&lt;p&gt;The entire behavior lives in the assistant's instructions. There is no application-layer language detection, no routing, no manual STT/LLM/TTS pipeline. The LLM follows plain English instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;listen carefully to the caller. detect the language they are speaking on every turn.
reply in the same language the caller is using right now.
if the caller switches language mid-conversation or mid-sentence, switch with them.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The STT (Deepgram nova-3 with &lt;code&gt;language: "auto"&lt;/code&gt;) transcribes in whatever language the caller speaks. The LLM follows the instructions. The TTS (&lt;code&gt;voice ultra katie&lt;/code&gt;) renders the reply in that language. One platform, one API key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Different From Translation
&lt;/h2&gt;

&lt;p&gt;The existing &lt;code&gt;ai-real-time-translation-bridge-python&lt;/code&gt; example connects two callers who speak different languages and translates between them. That is interpretation — two people, two languages, one bridge.&lt;/p&gt;

&lt;p&gt;This example is code-switching — one caller, multiple languages, one agent. The agent is not translating. It is the agent. It replies in whatever language you speak, switches when you switch, and never says "I did not understand" or asks you to pick a language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Technical Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;deepgram/nova-3&lt;/code&gt; with &lt;code&gt;language: "auto"&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Per the AI Assistants docs: "To enable a multilingual agent, set the transcription model to &lt;code&gt;deepgram/nova-3&lt;/code&gt;." The &lt;code&gt;language: "auto"&lt;/code&gt; setting lets nova-3 auto-detect the spoken language on every turn. Note: &lt;code&gt;language: "multi"&lt;/code&gt; is not valid and returns a 400 error. Use &lt;code&gt;"auto"&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;voice ultra katie&lt;/code&gt; in the instructions
&lt;/h3&gt;

&lt;p&gt;The existing phone assistant examples use &lt;code&gt;voice ultra katie&lt;/code&gt; in the instructions. Ultra supports 36+ languages, so one voice handles all five demo languages without switching voices per language.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;moonshotai/Kimi-K2.6&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A native Telnyx model — no external API key required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flask App Is Minimal
&lt;/h2&gt;

&lt;p&gt;Since the Voice AI Assistant handles the entire conversation, the Flask app only needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create/reuse the assistant via &lt;code&gt;provision_assistant.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Serve a page showing the inbound phone number&lt;/li&gt;
&lt;li&gt;Log webhook events for observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no call-control logic, no application-layer STT, no manual TTS. The conversation runs entirely on Telnyx.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/team-telnyx/telnyx-code-examples.git
&lt;span class="nb"&gt;cd &lt;/span&gt;telnyx-code-examples/ai-multilingual-code-switching-agent-python
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
python provision_assistant.py
python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the browser, call the number, speak in any language. Switch mid-conversation and the agent follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Examples
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-real-time-translation-bridge-python" rel="noopener noreferrer"&gt;&lt;code&gt;ai-real-time-translation-bridge-python&lt;/code&gt;&lt;/a&gt; — two-caller interpreter&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-language-learning-phone-tutor-python" rel="noopener noreferrer"&gt;&lt;code&gt;ai-language-learning-phone-tutor-python&lt;/code&gt;&lt;/a&gt; — phone-based language tutor&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/language-learning-flashcards-python" rel="noopener noreferrer"&gt;&lt;code&gt;language-learning-flashcards-python&lt;/code&gt;&lt;/a&gt; — browser-based pronunciation scoring&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
    <item>
      <title>I Built a Language Tutor That Listens and Scores Your Pronunciation</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Wed, 05 Aug 2026 22:11:18 +0000</pubDate>
      <link>https://dev.to/botoclock/i-built-a-language-tutor-that-listens-and-scores-your-pronunciation-23f5</link>
      <guid>https://dev.to/botoclock/i-built-a-language-tutor-that-listens-and-scores-your-pronunciation-23f5</guid>
      <description>&lt;p&gt;Language learning apps work fine on a screen, but the hardest part is the thing screens avoid: actually speaking out loud and getting immediate feedback on whether you said it right.&lt;/p&gt;

&lt;p&gt;Most voice-based language tools either play a recording or transcribe your speech. They don't do both in an interactive loop. I wanted to build something that does — something that plays a phrase, listens to you repeat it, and tells you whether you got it right.&lt;/p&gt;

&lt;p&gt;The Telnyx code example is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/language-learning-flashcards-python" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/language-learning-flashcards-python&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is a Python Flask app with a browser UI. No phone number, no webhook tunnel, no Cloud Storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Loop
&lt;/h2&gt;

&lt;p&gt;The app uses all three Telnyx AI primitives in one interactive round-trip:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;TTS speaks&lt;/strong&gt; — an Ultra voice (Camila for Spanish, Valerie for French) plays a flashcard phrase. Audio autoplays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You repeat&lt;/strong&gt; — click Record, speak the phrase, click Stop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;STT transcribes&lt;/strong&gt; — Whisper transcribes your speech.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inference scores&lt;/strong&gt; — Kimi-K2.6 compares your transcription against the target phrase and returns a score: correct, close, or wrong, plus a one-sentence tip.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One platform, one API key, no external services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Different From a Translator
&lt;/h2&gt;

&lt;p&gt;The existing &lt;code&gt;ai-content-translator-python&lt;/code&gt; example does STT → translate → TTS. It's one-directional: you speak, the app translates. This example is interactive: the app speaks to you, you speak back, the app evaluates you. The flow is completely different even though it uses the same three Telnyx primitives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Voices
&lt;/h2&gt;

&lt;p&gt;The app uses native Ultra voices per language, not one English voice with &lt;code&gt;language_boost&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Voice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spanish&lt;/td&gt;
&lt;td&gt;Camila (es, Female)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;French&lt;/td&gt;
&lt;td&gt;Valerie (fr-FR, Female)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To add more languages, enumerate voices via &lt;code&gt;GET /v2/text-to-speech/voices&lt;/code&gt; and add them to &lt;code&gt;LANGUAGE_VOICE_MAP&lt;/code&gt; in &lt;code&gt;app.py&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Scoring Works
&lt;/h2&gt;

&lt;p&gt;The Inference call uses a system prompt that tells Kimi to compare the target phrase against the user's spoken text and return JSON with a score and a tip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"correct"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"feedback"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Great job, your pronunciation matched the target phrase perfectly!"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kimi-K2.6 is a reasoning model — it takes ~10 seconds to think before scoring, but the result is more nuanced than a simple string comparison. It accounts for minor accent differences (correct), noticeable errors like wrong or missing words (close), and completely wrong speech (wrong).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decks
&lt;/h2&gt;

&lt;p&gt;The app ships with 4 pre-built flashcard decks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Deck&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Cards&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spanish — Greetings&lt;/td&gt;
&lt;td&gt;Spanish&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spanish — Numbers&lt;/td&gt;
&lt;td&gt;Spanish&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spanish — Common phrases&lt;/td&gt;
&lt;td&gt;Spanish&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;French — Greetings&lt;/td&gt;
&lt;td&gt;French&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Add more by editing &lt;code&gt;FLASHCARD_DECKS&lt;/code&gt; in &lt;code&gt;app.py&lt;/code&gt;. Each card has a &lt;code&gt;phrase&lt;/code&gt; (what TTS speaks and what you repeat) and a &lt;code&gt;translation&lt;/code&gt; (shown as a hint).&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/team-telnyx/telnyx-code-examples.git
&lt;span class="nb"&gt;cd &lt;/span&gt;telnyx-code-examples/language-learning-flashcards-python
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env    &lt;span class="c"&gt;# fill in TELNYX_API_KEY&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python app.py           &lt;span class="c"&gt;# starts on http://127.0.0.1:5050&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the browser, pick a deck, listen, repeat, get scored.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Thing to Watch
&lt;/h2&gt;

&lt;p&gt;Kimi-K2.6 is a reasoning model. It spends tokens thinking before it produces the JSON score. If &lt;code&gt;max_tokens&lt;/code&gt; is too low (I tried 200 at first), it runs out of tokens during reasoning and returns empty content. The fix is &lt;code&gt;max_tokens: 1000&lt;/code&gt; — enough for reasoning + the short JSON response. This is a Kimi-specific behavior; a non-reasoning model like Llama-3.3-70B would work with 200 tokens, but Kimi is the recommended Telnyx model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Examples
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-language-learning-phone-tutor-python" rel="noopener noreferrer"&gt;&lt;code&gt;ai-language-learning-phone-tutor-python&lt;/code&gt;&lt;/a&gt; — phone-based language tutor&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-content-translator-python" rel="noopener noreferrer"&gt;&lt;code&gt;ai-content-translator-python&lt;/code&gt;&lt;/a&gt; — STT + translate + TTS pipeline&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/multi-character-narrator-python" rel="noopener noreferrer"&gt;&lt;code&gt;multi-character-narrator-python&lt;/code&gt;&lt;/a&gt; — multi-voice TTS with emotions&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
    <item>
      <title>I Wanted to Hear Every Telnyx Voice in One Scene, So I Built a Multi-Character Narrator</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Wed, 05 Aug 2026 00:22:44 +0000</pubDate>
      <link>https://dev.to/botoclock/i-wanted-to-hear-every-telnyx-voice-in-one-scene-so-i-built-a-multi-character-narrator-2ki8</link>
      <guid>https://dev.to/botoclock/i-wanted-to-hear-every-telnyx-voice-in-one-scene-so-i-built-a-multi-character-narrator-2ki8</guid>
      <description>&lt;p&gt;Telnyx ships over 700 Ultra voices across 36 languages with sub-100ms time-to-first-byte. The voices are not the problem. Hearing them is.&lt;/p&gt;

&lt;p&gt;The docs list three. The Voices API returns 4,000+ across every provider. Voice pickers play a fixed sample sentence per voice. None of that tells you how a voice handles emotion, pacing, or character inside a real scene.&lt;/p&gt;

&lt;p&gt;So I built a small app that lets you do exactly that. You write a short scene with a few characters, assign each character a different Telnyx Ultra voice and an SSML emotion, and render the whole thing into one MP3. Every voice speaks in character, in context, in one continuous audio file.&lt;/p&gt;

&lt;p&gt;The Telnyx code example is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/multi-character-narrator-python" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/multi-character-narrator-python&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Use Case
&lt;/h2&gt;

&lt;p&gt;Voice pickers exist. They play a fixed sample sentence per voice. What they do not do is let you hear a voice inside a real scene — a tense argument, a calm narrator, a panicked character, a reassuring guide — because a single sample sentence does not tell you how a voice handles emotion, pacing, or character.&lt;/p&gt;

&lt;p&gt;This example solves that. You write a short scene with a few characters. Each character gets a different Telnyx Ultra voice. Each character gets an SSML emotion. You hit render. The app fans out parallel TTS calls, stitches the per-line audio in script order, and plays you one continuous MP3 with every voice speaking in character.&lt;/p&gt;

&lt;p&gt;The default scene is the Ides of March from Julius Caesar. Five characters, ten lines, five distinct voices, five different emotions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cassius — determined, plotting the assassination&lt;/li&gt;
&lt;li&gt;Caesar — surprised, realizing the betrayal&lt;/li&gt;
&lt;li&gt;Brutus — apologetic, justifying the act&lt;/li&gt;
&lt;li&gt;Mark Antony — angry, mourning the fallen leader&lt;/li&gt;
&lt;li&gt;Narrator — calm, setting the scene&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One render, one MP3, every voice in context. That is the demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Eight Curated Ultra Voices
&lt;/h2&gt;

&lt;p&gt;The app ships with eight pre-built Telnyx Ultra voices curated for the most common use cases. Each one is a real Telnyx voice with a UUID voice ID that works on the REST endpoint.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Voice&lt;/th&gt;
&lt;th&gt;Gender&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Best Use Case&lt;/th&gt;
&lt;th&gt;Sound Profile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Asher&lt;/td&gt;
&lt;td&gt;Male&lt;/td&gt;
&lt;td&gt;en&lt;/td&gt;
&lt;td&gt;Voice Assistants &amp;amp; Media&lt;/td&gt;
&lt;td&gt;Smooth, dynamic, podcaster-style tone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Callie&lt;/td&gt;
&lt;td&gt;Female&lt;/td&gt;
&lt;td&gt;en&lt;/td&gt;
&lt;td&gt;Coaching &amp;amp; Onboarding&lt;/td&gt;
&lt;td&gt;High energy, encouraging, friendly tone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clara&lt;/td&gt;
&lt;td&gt;Female&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;General Purpose IVR/AI&lt;/td&gt;
&lt;td&gt;Clear, standard US accent, versatile pacing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Howard&lt;/td&gt;
&lt;td&gt;Male&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;Conversational Agents&lt;/td&gt;
&lt;td&gt;Deep, reassuring, highly trustworthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allie&lt;/td&gt;
&lt;td&gt;Female&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;Casual &amp;amp; Interactive AI&lt;/td&gt;
&lt;td&gt;Conversational flow, natural pauses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jasper&lt;/td&gt;
&lt;td&gt;Male&lt;/td&gt;
&lt;td&gt;en-GB&lt;/td&gt;
&lt;td&gt;Finance &amp;amp; Healthcare&lt;/td&gt;
&lt;td&gt;Calm, authoritative, precise delivery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skyler&lt;/td&gt;
&lt;td&gt;Neutral&lt;/td&gt;
&lt;td&gt;en&lt;/td&gt;
&lt;td&gt;Modern Brand Voice&lt;/td&gt;
&lt;td&gt;Casual, tech-forward, friendly vibe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arvin&lt;/td&gt;
&lt;td&gt;Male&lt;/td&gt;
&lt;td&gt;en&lt;/td&gt;
&lt;td&gt;Navigation &amp;amp; Directives&lt;/td&gt;
&lt;td&gt;Steady, clear cadence for detailed guidance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pick any of the eight for any character. Click Preview to hear the voice with the selected emotion before rendering the whole scene.&lt;/p&gt;

&lt;h2&gt;
  
  
  Twenty Ultra SSML Emotions
&lt;/h2&gt;

&lt;p&gt;Ultra supports inline SSML emotion tags placed before the text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;emotion&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"excited"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;Great news — your order shipped early!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app exposes all twenty Ultra SSML emotions as a per-character dropdown. Primary emotions: angry, excited, content, sad, scared. Additional: happy, enthusiastic, curious, calm, grateful, affectionate, sarcastic, surprised, confident, hesitant, apologetic, determined, frustrated, disappointed.&lt;/p&gt;

&lt;p&gt;Each character in the default Julius Caesar scene is auto-assigned an emotion that fits the role. Cassius is determined. Caesar is surprised. Brutus is apologetic. Mark Antony is angry. The Narrator is calm. Same voice, different emotion, different delivery — all from one inline SSML tag per line.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Was Made
&lt;/h2&gt;

&lt;p&gt;The app is a single Flask file with an inline browser UI. No phone number, no webhook, no Cloud Storage, no database. One env var: &lt;code&gt;TELNYX_API_KEY&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /narrate  (script with speaker labels)
  -&amp;gt; parse script into ordered lines
  -&amp;gt; map speaker -&amp;gt; voice (8 curated Ultra voices, overridable)
  -&amp;gt; map speaker -&amp;gt; emotion (20 Ultra SSML emotions, overridable)
  -&amp;gt; parallel fan-out: one REST TTS call per line
     POST /v2/text-to-speech/speech
       text_type=ssml, output_type=binary_output
       &amp;lt;emotion value="..." /&amp;gt; wrapping when emotion set
  -&amp;gt; stitch per-line MP3 bytes in script order
  -&amp;gt; store in memory (1h TTL)
  -&amp;gt; return project_id + per_line_ttfb_ms + audio_url
  -&amp;gt; GET /audio/&amp;lt;project_id&amp;gt;.mp3 streams the stitched MP3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why REST, not WebSocket
&lt;/h3&gt;

&lt;p&gt;Ultra is REST-only on the public WebSocket. A 403 on &lt;code&gt;wss://api.telnyx.com/v2/text-to-speech/speech&lt;/code&gt; is intentional. The app uses &lt;code&gt;POST /v2/text-to-speech/speech&lt;/code&gt; with &lt;code&gt;output_type: binary_output&lt;/code&gt; so it can measure true time-to-first-byte per line. Base64 mode would hide the real latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why UUIDs, not display names
&lt;/h3&gt;

&lt;p&gt;Ultra voice IDs are UUIDs in the &lt;code&gt;Telnyx.Ultra.&amp;lt;uuid&amp;gt;&lt;/code&gt; format, not short display names like &lt;code&gt;Telnyx.Ultra.Clara&lt;/code&gt;. Short names return 400 on the REST endpoint. The Voices API at &lt;code&gt;GET /v2/text-to-speech/voices&lt;/code&gt; returns all available voices with their UUIDs — over 700 Ultra voices alone, filterable by &lt;code&gt;provider == "telnyx"&lt;/code&gt; and &lt;code&gt;id | startswith("Telnyx.Ultra.")&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The app ships with eight curated UUIDs so the demo works out of the box, but the dropdown is easy to extend with any voice from the Voices API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel fan-out with per-line error isolation
&lt;/h3&gt;

&lt;p&gt;The app uses &lt;code&gt;ThreadPoolExecutor&lt;/code&gt; to render every line in parallel. If one line fails (e.g. an invalid voice override), the response includes an &lt;code&gt;errors&lt;/code&gt; array and the stitched audio contains only the successful lines in script order. A failed line does not lose the whole render.&lt;/p&gt;

&lt;h3&gt;
  
  
  The browser UI
&lt;/h3&gt;

&lt;p&gt;The UI auto-detects speakers as you type. Each speaker gets a voice dropdown, an emotion dropdown, and a Preview button that renders a short sample line in the selected voice with the selected emotion. The render button fans out the parallel TTS calls, stitches the result, and autoplays the MP3.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/team-telnyx/telnyx-code-examples.git
&lt;span class="nb"&gt;cd &lt;/span&gt;telnyx-code-examples/multi-character-narrator-python
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env    &lt;span class="c"&gt;# fill in TELNYX_API_KEY&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python app.py           &lt;span class="c"&gt;# starts on http://127.0.0.1:5050&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the browser UI. The default Julius Caesar script is pre-loaded. Pick voices, pick emotions, preview, render, play.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Goes Next
&lt;/h2&gt;

&lt;p&gt;The app is a starting point. Add more voices from the 700+ Ultra voices available via the Voices API. Add more languages — Ultra covers 36, and the same script-render-stitch pipeline works for any of them via &lt;code&gt;language_boost&lt;/code&gt;. Add Cloud Storage for persistent, shareable audio URLs. Add more sample scripts — audiobook chapters, podcast intros, e-learning role-plays, game cinematics — any multi-speaker content where you want to hear voices in context.&lt;/p&gt;

&lt;p&gt;The point is the same: hear Telnyx voices in a real scene, not a sample sentence. Everything else follows from that.&lt;/p&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
    <item>
      <title>I Built a Website Voice Assistant That Can Actually Control the Page</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Fri, 31 Jul 2026 17:07:24 +0000</pubDate>
      <link>https://dev.to/botoclock/i-built-a-website-voice-assistant-that-can-actually-control-the-page-2ena</link>
      <guid>https://dev.to/botoclock/i-built-a-website-voice-assistant-that-can-actually-control-the-page-2ena</guid>
      <description>&lt;p&gt;A lot of website AI demos stop at answering questions.&lt;/p&gt;

&lt;p&gt;The assistant can explain where something is, but it does not actually take you there. It can tell you to open settings, but it does not open settings. It can ask you to fill out a form, but it does not touch the form.&lt;/p&gt;

&lt;p&gt;That is the difference I wanted to show with Telnyx AI Assistant client-side tools.&lt;/p&gt;

&lt;p&gt;The Telnyx code example is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-assistant-client-side-tools-nextjs" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-assistant-client-side-tools-nextjs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The process is clone the sample, configure the same tool names and JSON schemas on your Telnyx AI Assistant, set the public assistant ID in the browser app, and then test the UI actions from the website call button.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;Client-side tools let a Telnyx AI Assistant invoke predefined JavaScript functions in the browser during a voice or chat conversation.&lt;/p&gt;

&lt;p&gt;That sounds small, but it changes the demo.&lt;/p&gt;

&lt;p&gt;Instead of saying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go to the AI Assistants page and click Create Assistant.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the assistant can call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;navigate_to_section
open_create_assistant_modal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser updates immediately because those functions are normal React state updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;The sample is a fictional SaaS dashboard called PolarForge AI. It is built with Next.js, React, TypeScript, and Tailwind CSS.&lt;/p&gt;

&lt;p&gt;It has a sidebar, several dashboard sections, light and dark themes, an AI Assistants page, a Create Assistant modal, and an activity panel that logs every tool call.&lt;/p&gt;

&lt;p&gt;The assistant can use five tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;set_theme&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;navigate_to_section&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;open_create_assistant_modal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_form_state&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update_assistant_form&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting one is &lt;code&gt;get_form_state&lt;/code&gt;, because it has to read current React state. It is not returning a hard-coded object. If the modal is open and the form says the assistant name is "Enterprise Concierge", the tool returns that visible state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Client-Side Instead of Webhook?
&lt;/h2&gt;

&lt;p&gt;Webhook tools are still useful. If the assistant needs to create an account, charge a card, update a CRM record, or call something with privileged credentials, that belongs on the backend.&lt;/p&gt;

&lt;p&gt;But a lot of website actions are local:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;switch to dark mode&lt;/li&gt;
&lt;li&gt;open a modal&lt;/li&gt;
&lt;li&gt;navigate inside a single-page app&lt;/li&gt;
&lt;li&gt;fill a visible form&lt;/li&gt;
&lt;li&gt;read page state&lt;/li&gt;
&lt;li&gt;call APIs the browser is already authenticated to call&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where client-side tools are a better fit. The assistant does not need a backend webhook just to change React state.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Integration
&lt;/h2&gt;

&lt;p&gt;The app uses &lt;code&gt;@telnyx/ai-agent-lib&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TelnyxAIAgentProvider&lt;/span&gt; &lt;span class="na"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;agentId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToolRegistrar&lt;/span&gt; &lt;span class="na"&gt;executeTool&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;executeTool&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TelnyxWidget&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;TelnyxAIAgentProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside React, the tools are registered with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerClientTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;navigate_to_section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;executeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;navigate_to_section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The matching tool definitions still need to exist on the Telnyx AI Assistant. The names and JSON schemas need to match what the browser registers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Demo Moment
&lt;/h2&gt;

&lt;p&gt;The clean version of the demo is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: switch to dark mode
Assistant: calls set_theme
Page: changes to dark mode

User: take me to AI Assistants
Assistant: calls navigate_to_section
Page: switches section without reload

User: create an assistant named Enterprise Concierge
Assistant: opens the modal and updates the name field
Page: shows the form update
Activity panel: logs every tool call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the proof: the assistant is not just talking about the app. It is controlling the app through approved browser functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Boundary
&lt;/h2&gt;

&lt;p&gt;The browser does not get a Telnyx API key.&lt;/p&gt;

&lt;p&gt;The sample uses public &lt;code&gt;NEXT_PUBLIC_*&lt;/code&gt; configuration for the AI Agent Lib connection. If you need to provision an assistant or call the Telnyx REST API, do that from a secure server-side environment or through the Telnyx Portal.&lt;/p&gt;

&lt;p&gt;The sample also validates every tool call. Unknown sections fail. Unknown form fields fail. Invalid voices or languages fail. Updating the form while the modal is closed fails.&lt;/p&gt;

&lt;p&gt;That is the shape I would keep in a production app: small explicit tools, strict validation, and no privileged secrets in client-side code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code example: &lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-assistant-client-side-tools-nextjs" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-assistant-client-side-tools-nextjs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Client-side tools docs: &lt;a href="https://developers.telnyx.com/docs/inference/ai-assistants/client-side-tools" rel="noopener noreferrer"&gt;https://developers.telnyx.com/docs/inference/ai-assistants/client-side-tools&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI Agent Lib: &lt;a href="https://www.npmjs.com/package/@telnyx/ai-agent-lib" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@telnyx/ai-agent-lib&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Telnyx release note: &lt;a href="https://telnyx.com/release-notes/client-side-tools-ai-assistants" rel="noopener noreferrer"&gt;https://telnyx.com/release-notes/client-side-tools-ai-assistants&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
    <item>
      <title>I Built a Voice AI Assistant for Lab Results Notifications</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Thu, 30 Jul 2026 18:22:31 +0000</pubDate>
      <link>https://dev.to/botoclock/i-built-a-voice-ai-assistant-for-lab-results-notifications-159d</link>
      <guid>https://dev.to/botoclock/i-built-a-voice-ai-assistant-for-lab-results-notifications-159d</guid>
      <description>&lt;p&gt;Lab results are one of those healthcare workflows where the phone call sounds simple, but the product boundaries matter a lot.&lt;/p&gt;

&lt;p&gt;A patient wants to know whether results are back. The clinic needs to verify identity. The system should disclose only what is appropriate. And if the patient needs the full report, the best answer is usually a secure portal link, not reading everything out loud or putting lab values in an SMS.&lt;/p&gt;

&lt;p&gt;So I built the sample as a notification assistant, not a clinical assistant.&lt;/p&gt;

&lt;p&gt;The Telnyx code example is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-lab-results-notification-voice-python" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-lab-results-notification-voice-python&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It uses Telnyx AI Assistants with a small Python + Flask app for the screen-share dashboard and optional backend extension points.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flow
&lt;/h2&gt;

&lt;p&gt;The caller dials a Telnyx phone number that is assigned directly to the AI Assistant.&lt;/p&gt;

&lt;p&gt;The assistant asks for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Full legal name&lt;/li&gt;
&lt;li&gt;Date of birth&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only after those match a mock record does it disclose the lab result summary.&lt;/p&gt;

&lt;p&gt;For the normal result path, the assistant says the result is back and within the expected range, then asks whether the patient wants a secure portal link by text.&lt;/p&gt;

&lt;p&gt;If the patient says yes, the assistant uses Telnyx native messaging to send a portal link. The text does not include lab values.&lt;/p&gt;

&lt;p&gt;For abnormal or borderline examples, the assistant gives follow-up language and points the patient toward a nurse callback or clinical staff.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part I Care About
&lt;/h2&gt;

&lt;p&gt;The AI is not practicing medicine.&lt;/p&gt;

&lt;p&gt;The AI is not interpreting raw lab values.&lt;/p&gt;

&lt;p&gt;The AI is not replacing the provider.&lt;/p&gt;

&lt;p&gt;It is doing the operational part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;verify identity
give a short provider-approved summary
send the patient to the secure portal
route follow-up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That distinction matters in healthcare AI demos. A good demo should show what the system can do, but also show what it refuses to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Telnyx AI Assistants Here
&lt;/h2&gt;

&lt;p&gt;The cleanest version of this demo does not need a custom tunnel in the call path.&lt;/p&gt;

&lt;p&gt;The phone number is assigned directly to the Telnyx AI Assistant. The assistant is provisioned from &lt;code&gt;provision_assistant.py&lt;/code&gt;. The SMS link uses the assistant's native &lt;code&gt;send_message&lt;/code&gt; tool.&lt;/p&gt;

&lt;p&gt;That means the live demo path is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telnyx number
  -&amp;gt; Telnyx AI Assistant
  -&amp;gt; native SMS link
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Flask app is still useful, but mostly for the demo dashboard and optional backend extension points.&lt;/p&gt;

&lt;p&gt;That keeps the call experience simple enough to explain on screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dashboard
&lt;/h2&gt;

&lt;p&gt;I also added a local Flask UI at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;call flow&lt;/li&gt;
&lt;li&gt;mock patient test identities&lt;/li&gt;
&lt;li&gt;HIPAA compliance safeguards&lt;/li&gt;
&lt;li&gt;local audit and callback state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not show API keys, real PHI, the raw assigned phone number, or raw transcripts.&lt;/p&gt;

&lt;p&gt;That screen is useful for a video because it gives viewers something concrete to look at while the phone call is happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  HIPAA Compliance Safeguards
&lt;/h2&gt;

&lt;p&gt;This sample is designed around the safeguards expected in a HIPAA-compliant lab results notification workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mock data only&lt;/li&gt;
&lt;li&gt;identity verification before result disclosure&lt;/li&gt;
&lt;li&gt;minimum necessary spoken result summary&lt;/li&gt;
&lt;li&gt;no lab values in SMS&lt;/li&gt;
&lt;li&gt;recording disabled&lt;/li&gt;
&lt;li&gt;assistant data retention disabled&lt;/li&gt;
&lt;li&gt;no diagnosis or treatment advice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For production, pair these controls with the required BAA, role-based access controls, storage controls, audit logs, retention settings, monitoring, and legal/compliance review.&lt;/p&gt;

&lt;p&gt;That is the deployment posture this demo is built to show: Telnyx AI for a privacy-sensitive healthcare workflow, with PHI kept out of SMS and result access routed through a secure portal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It Locally
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/team-telnyx/telnyx-code-examples.git
&lt;span class="nb"&gt;cd &lt;/span&gt;telnyx-code-examples/ai-lab-results-notification-voice-python
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TELNYX_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;KEY...
&lt;span class="nv"&gt;TELNYX_PHONE_NUMBER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+18005551234
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Provision the assistant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python provision_assistant.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then assign a Telnyx number directly to the assistant in the Portal and call it.&lt;/p&gt;

&lt;p&gt;For the happy path, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: maya rivera
date of birth: november 22 1984
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ask for the secure portal link by text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Notes
&lt;/h2&gt;

&lt;p&gt;For a production version, I would move patient verification and result lookup out of the prompt and into a backend connected to the real patient portal or EHR workflow.&lt;/p&gt;

&lt;p&gt;I would also keep the assistant prompt conservative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;verify identity
disclose minimum necessary
do not diagnose
do not interpret beyond provider note
do not put lab values in sms
route clinical questions to staff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the shape I like for this kind of workflow. AI handles a narrow phone interaction, while the secure patient portal remains the place where patients access the full report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code example: &lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-lab-results-notification-voice-python" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-lab-results-notification-voice-python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Telnyx AI Assistant quickstart: &lt;a href="https://developers.telnyx.com/docs/inference/ai-assistants/no-code-voice-assistant" rel="noopener noreferrer"&gt;https://developers.telnyx.com/docs/inference/ai-assistants/no-code-voice-assistant&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI Assistant interruption settings: &lt;a href="https://developers.telnyx.com/docs/inference/ai-assistants/interruption-settings" rel="noopener noreferrer"&gt;https://developers.telnyx.com/docs/inference/ai-assistants/interruption-settings&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Architecting for HIPAA on Telnyx: &lt;a href="https://telnyx.com/resources/architecting-hipaa-telnyx" rel="noopener noreferrer"&gt;https://telnyx.com/resources/architecting-hipaa-telnyx&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Telnyx Developer Docs: &lt;a href="https://developers.telnyx.com" rel="noopener noreferrer"&gt;https://developers.telnyx.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
    <item>
      <title>I Built a Voice AI Payment Collection Demo That Keeps Card Details Out of the Assistant</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Wed, 29 Jul 2026 23:55:26 +0000</pubDate>
      <link>https://dev.to/botoclock/i-built-a-voice-ai-payment-collection-demo-that-keeps-card-details-out-of-the-assistant-41km</link>
      <guid>https://dev.to/botoclock/i-built-a-voice-ai-payment-collection-demo-that-keeps-card-details-out-of-the-assistant-41km</guid>
      <description>&lt;p&gt;Voice AI payment collection is tricky because the most impressive part of the demo is also the part you should not let the model handle.&lt;/p&gt;

&lt;p&gt;An assistant can verify a caller, explain an account balance, answer billing questions, and negotiate a payment plan. But once the caller is ready to enter card details, the assistant should step aside.&lt;/p&gt;

&lt;p&gt;I built a Telnyx code example that does exactly that.&lt;/p&gt;

&lt;p&gt;The code example:&lt;br&gt;
&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-pci-protected-payment-collection-python" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-pci-protected-payment-collection-python&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Flow
&lt;/h2&gt;

&lt;p&gt;A caller dials a Telnyx number for a billing line. Telnyx sends the inbound call event to a Flask webhook. The app answers with Voice API and starts a Telnyx AI Assistant.&lt;/p&gt;

&lt;p&gt;The assistant explains what the line can do: account status, billing questions, payment plans, and secure keypad payment collection. Then it verifies the caller by full name and date of birth.&lt;/p&gt;

&lt;p&gt;After verification, the assistant can disclose the account balance and help the caller choose a payment plan. If the caller agrees to pay, the assistant calls a backend webhook tool called &lt;code&gt;start_secure_payment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That tool starts Telnyx Pay over Voice. From that point on, the caller enters card details with the phone keypad. The assistant does not ask for the numbers, does not hear the numbers, and does not receive the keypad digits.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;The pattern is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI handles the conversation&lt;/li&gt;
&lt;li&gt;Pay over Voice handles card entry&lt;/li&gt;
&lt;li&gt;The app records only sanitized payment status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That split is the point of the sample.&lt;/p&gt;

&lt;p&gt;If you let a general voice agent collect card numbers, you immediately create a logging, recording, transcript, and storage problem. Even if the assistant is "just helping," the raw payment data can end up in places it does not belong.&lt;/p&gt;

&lt;p&gt;Pay over Voice moves that sensitive step into a purpose-built IVR flow. Telnyx prompts for card number, expiration date, billing ZIP, and security code. The caller enters those values by keypad, and Telnyx sends payment progress/completion events back to the app.&lt;/p&gt;

&lt;p&gt;The local dashboard in the sample is intentionally boring in the right way. It shows high-level proof that the secure payment flow started and completed. It does not show raw PAN, CVV, expiration date, billing ZIP, or raw DTMF.&lt;/p&gt;
&lt;h2&gt;
  
  
  What The Demo Shows
&lt;/h2&gt;

&lt;p&gt;The sample includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flask webhook handling for inbound Voice API events&lt;/li&gt;
&lt;li&gt;AI Assistant startup with &lt;code&gt;ai_assistant_start&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Assistant webhook tools&lt;/li&gt;
&lt;li&gt;Pay Connector provisioning&lt;/li&gt;
&lt;li&gt;Pay over Voice start command&lt;/li&gt;
&lt;li&gt;Mock payment processor endpoint&lt;/li&gt;
&lt;li&gt;Sanitized event dashboard&lt;/li&gt;
&lt;li&gt;Test-mode payment flow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The demo customer is Jordan Lee, with a past-due balance of &lt;code&gt;$342.50&lt;/code&gt;. A good demo path is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;caller: jordan lee
caller: march fifteenth nineteen ninety
caller: can i do forty dollars a week?
caller: yes, start the secure payment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then Pay over Voice takes over. For test mode, enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4111111111111111
0827
94111
123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run It Locally
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/team-telnyx/telnyx-code-examples.git
&lt;span class="nb"&gt;cd &lt;/span&gt;telnyx-code-examples/ai-pci-protected-payment-collection-python
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
ngrok http 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set your public URL and API key in &lt;code&gt;.env&lt;/code&gt;, then provision the assistant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python provision_assistant.py
python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point your Voice API application webhook to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;ngrok-id&amp;gt;.ngrok-free.app/webhooks/voice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call your Telnyx number and walk through the billing flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where To Take It Next
&lt;/h2&gt;

&lt;p&gt;For a production implementation, replace the mock payment processor with your actual processor, review PCI scope with your compliance team, verify webhook signatures, and store only the sanitized payment outcome your business process needs.&lt;/p&gt;

&lt;p&gt;The assistant should keep the same boundary: explain, verify, plan, and hand off. It should never ask for card details out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-pci-protected-payment-collection-python" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-pci-protected-payment-collection-python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pay over Voice: &lt;a href="https://developers.telnyx.com/docs/voice/programmable-voice/pay" rel="noopener noreferrer"&gt;https://developers.telnyx.com/docs/voice/programmable-voice/pay&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Voice API commands: &lt;a href="https://developers.telnyx.com/docs/voice/programmable-voice/voice-api-commands-and-resources" rel="noopener noreferrer"&gt;https://developers.telnyx.com/docs/voice/programmable-voice/voice-api-commands-and-resources&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Start AI Assistant: &lt;a href="https://developers.telnyx.com/api-reference/call-commands/start-ai-assistant" rel="noopener noreferrer"&gt;https://developers.telnyx.com/api-reference/call-commands/start-ai-assistant&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Telnyx Portal: &lt;a href="https://portal.telnyx.com" rel="noopener noreferrer"&gt;https://portal.telnyx.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
    <item>
      <title>I Built a Voice AI Agent for Pre-Visit Insurance Clearance</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Wed, 22 Jul 2026 22:51:50 +0000</pubDate>
      <link>https://dev.to/botoclock/i-built-a-voice-ai-agent-for-pre-visit-insurance-clearance-57fh</link>
      <guid>https://dev.to/botoclock/i-built-a-voice-ai-agent-for-pre-visit-insurance-clearance-57fh</guid>
      <description>&lt;p&gt;Pre-visit insurance clearance is one of those workflows that sounds simple until you see how many phone calls it creates.&lt;/p&gt;

&lt;p&gt;A patient calls before an appointment. They need to know whether a procedure, test, or medication requires prior authorization. Staff need to identify the patient, understand what they are asking for, collect payer and provider context, decide how urgent it is, and route the request to the right billing queue.&lt;/p&gt;

&lt;p&gt;I built a Telnyx code example that turns that call into a structured intake ticket.&lt;/p&gt;

&lt;p&gt;The code example:&lt;br&gt;
&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-pre-visit-clearance-voice-agent-python" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-pre-visit-clearance-voice-agent-python&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Flow
&lt;/h2&gt;

&lt;p&gt;A patient calls a Telnyx number. Telnyx sends the inbound call event to a Flask webhook. The app answers with Call Control, then starts &lt;code&gt;gather_using_ai&lt;/code&gt; to collect the patient's spoken pre-clearance request as structured fields.&lt;/p&gt;

&lt;p&gt;When Telnyx finishes collecting those fields, the app receives &lt;code&gt;call.ai_gather.ended&lt;/code&gt;. From there, the app classifies the request with AI Inference, confirms the details with the patient, creates a staff-facing ticket, sends the patient an SMS confirmation, and optionally alerts billing staff in Slack.&lt;/p&gt;

&lt;p&gt;The agent is non-clinical. It doesn't give medical advice, approve or deny coverage, or diagnose. It collects administrative data and routes it — which is the bottleneck in a lot of healthcare revenue-cycle work.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Pattern Matters
&lt;/h2&gt;

&lt;p&gt;The main Telnyx primitive here is Gather Using AI.&lt;/p&gt;

&lt;p&gt;The app asks Telnyx to gather the fields it needs directly inside the call. The webhook response gives the backend something structured enough to use in a workflow.&lt;/p&gt;

&lt;p&gt;That makes the rest of the app cleaner:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Call Control handles the inbound call lifecycle&lt;/li&gt;
&lt;li&gt;Gather Using AI collects the spoken intake details&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call.ai_gather.ended&lt;/code&gt; advances the workflow&lt;/li&gt;
&lt;li&gt;AI Inference classifies urgency and type&lt;/li&gt;
&lt;li&gt;Messaging sends the patient a confirmation&lt;/li&gt;
&lt;li&gt;Slack gives billing staff a queue signal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation patterns it covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOB fallback when caller ID is unknown&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;command_id&lt;/code&gt; on Call Control commands&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;send_silence_when_idle&lt;/code&gt; when answering&lt;/li&gt;
&lt;li&gt;structured JSON classification for procedure, urgency, and request type&lt;/li&gt;
&lt;li&gt;keyword-based urgency override for phrases like "ASAP" or "can't wait"&lt;/li&gt;
&lt;li&gt;confirmation before ticket creation&lt;/li&gt;
&lt;li&gt;partial ticket creation if the caller hangs up after providing useful context&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Run It Locally
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/team-telnyx/telnyx-code-examples.git
&lt;span class="nb"&gt;cd &lt;/span&gt;telnyx-code-examples/ai-pre-visit-clearance-voice-agent-python
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python app.py
ngrok http 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Point your Telnyx Call Control Application webhook to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;your-ngrok-domain&amp;gt;/webhooks/voice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seed a patient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:5000/patients &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"patient_id":"P001","name":"Jordan Lee","phone":"+15551112233","dob":"03/15/1990","insurance":"Blue Cross","provider":"Dr. Smith"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call the number and say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need clearance for an MRI on my lower back.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app collects the request, classifies it, confirms the details, creates a ticket, and sends an SMS confirmation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where To Take It Next
&lt;/h2&gt;

&lt;p&gt;Replace in-memory storage with your EHR, PMS, or billing system integration. Wire ticket completion into real prior auth submission APIs. Add privacy review and HIPAA-ready operational controls before production use. Queue Slack and SMS side effects so webhook responses stay fast.&lt;/p&gt;

&lt;p&gt;The key boundary should stay the same: the agent collects and routes. Staff and payer systems make the actual coverage decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-pre-visit-clearance-voice-agent-python" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-pre-visit-clearance-voice-agent-python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gather Using AI: &lt;a href="https://developers.telnyx.com/api-reference/call-commands/gather-using-ai" rel="noopener noreferrer"&gt;https://developers.telnyx.com/api-reference/call-commands/gather-using-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call.ai_gather.ended&lt;/code&gt;: &lt;a href="https://developers.telnyx.com/api-reference/callbacks/call-ai-gather-ended" rel="noopener noreferrer"&gt;https://developers.telnyx.com/api-reference/callbacks/call-ai-gather-ended&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Voice API commands: &lt;a href="https://developers.telnyx.com/docs/voice/programmable-voice/voice-api-commands-and-resources" rel="noopener noreferrer"&gt;https://developers.telnyx.com/docs/voice/programmable-voice/voice-api-commands-and-resources&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI Inference docs: &lt;a href="https://developers.telnyx.com/docs/inference" rel="noopener noreferrer"&gt;https://developers.telnyx.com/docs/inference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Messaging docs: &lt;a href="https://developers.telnyx.com/docs/messaging" rel="noopener noreferrer"&gt;https://developers.telnyx.com/docs/messaging&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Telnyx Portal: &lt;a href="https://portal.telnyx.com" rel="noopener noreferrer"&gt;https://portal.telnyx.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
    <item>
      <title>I Built a Voice AI Assistant for Prescription Refill Intake</title>
      <dc:creator>anusha</dc:creator>
      <pubDate>Tue, 21 Jul 2026 23:35:10 +0000</pubDate>
      <link>https://dev.to/botoclock/i-built-a-voice-ai-assistant-for-prescription-refill-intake-1cj6</link>
      <guid>https://dev.to/botoclock/i-built-a-voice-ai-assistant-for-prescription-refill-intake-1cj6</guid>
      <description>&lt;p&gt;Prescription refill calls are one of those workflows that sound simple until you look at the operational details.&lt;/p&gt;

&lt;p&gt;The caller needs to explain which medication they are calling about. The pharmacy or clinic may need the preferred pharmacy, the callback number, how soon the refill is needed, and whether the request needs manual review.&lt;/p&gt;

&lt;p&gt;But there is also a hard boundary: the AI should not approve a prescription, deny a refill, change dosage instructions, diagnose anything, or make clinical decisions.&lt;/p&gt;

&lt;p&gt;So I built the workflow as an intake assistant, not a decision-maker.&lt;/p&gt;

&lt;p&gt;The Telnyx code example is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-prescription-refill-intake-voice-assistant-python" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-prescription-refill-intake-voice-assistant-python&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It uses Telnyx AI Assistants with a small Python + Flask backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flow
&lt;/h2&gt;

&lt;p&gt;The caller dials a Telnyx phone number. Telnyx sends a &lt;code&gt;call.initiated&lt;/code&gt; webhook to the Flask app.&lt;/p&gt;

&lt;p&gt;The app does two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Answers the call&lt;/li&gt;
&lt;li&gt;Starts the configured Telnyx AI Assistant with &lt;code&gt;ai_assistant_start&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that, the assistant owns the live conversation. It gives an emergency reminder, then asks one question at a time.&lt;/p&gt;

&lt;p&gt;The assistant has three backend tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;create_refill_request&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flag_manual_review&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;queue_callback&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the assistant has enough information, it calls &lt;code&gt;create_refill_request&lt;/code&gt;. If the request mentions a controlled substance, dosage change, side effects, urgent access issue, or similar follow-up trigger, it calls &lt;code&gt;flag_manual_review&lt;/code&gt;. If the caller wants a callback, it calls &lt;code&gt;queue_callback&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part I Care About
&lt;/h2&gt;

&lt;p&gt;The AI is not the system of record.&lt;/p&gt;

&lt;p&gt;The AI is not approving the refill.&lt;/p&gt;

&lt;p&gt;The AI is collecting enough information for staff to do the next step.&lt;/p&gt;

&lt;p&gt;That distinction matters a lot in healthcare voice AI. A useful assistant should reduce the intake burden without pretending to replace a pharmacist, clinician, or compliance process.&lt;/p&gt;

&lt;p&gt;The backend is where the workflow is enforced. It stores the request, masks caller identifiers, checks the assistant tool secret, and exposes a review endpoint for staff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Telnyx AI Assistants Here
&lt;/h2&gt;

&lt;p&gt;You could build this as a manual voice state machine with gather and speak commands. That works, but the code gets focused on conversation plumbing.&lt;/p&gt;

&lt;p&gt;With Telnyx AI Assistants, the shape is cleaner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call Control answers the phone
AI Assistant handles the conversation
assistant tools call the Flask backend
backend stores refill workflow state
staff reviews the request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The assistant prompt is also provisioned from code in &lt;code&gt;provision_assistant.py&lt;/code&gt;, which means someone can recreate the assistant from the repo instead of manually copying settings from a portal screenshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It Locally
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/team-telnyx/telnyx-code-examples.git
&lt;span class="nb"&gt;cd &lt;/span&gt;telnyx-code-examples/ai-prescription-refill-intake-voice-assistant-python
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the required environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TELNYX_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;KEY...
&lt;span class="nv"&gt;PUBLIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-ngrok-url.ngrok-free.app
&lt;span class="nv"&gt;TOOL_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replace_with_a_random_shared_secret
&lt;span class="nv"&gt;TELNYX_PHONE_NUMBER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+18005551234
&lt;span class="nv"&gt;TELNYX_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_telnyx_public_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expose it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ngrok http 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Provision the assistant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python provision_assistant.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then set your Call Control Application webhook URL to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;your-ngrok-domain&amp;gt;/webhooks/voice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call the Telnyx number and ask for a prescription refill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What To Watch In The Code
&lt;/h2&gt;

&lt;p&gt;The main app is &lt;code&gt;app.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The inbound webhook answers the call and starts the assistant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TELNYX_API_BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/calls/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;call_control_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/actions/ai_assistant_start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;telnyx_headers&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TELNYX_ASSISTANT_ID&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The assistant setup is in &lt;code&gt;provision_assistant.py&lt;/code&gt;. That file creates or updates the assistant, sets the model, adds the prompt, configures dynamic variables, and registers the webhook tools.&lt;/p&gt;

&lt;p&gt;The tool endpoints are intentionally narrow. They accept structured data and return workflow state. That makes the assistant useful without letting it become the approval layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Notes
&lt;/h2&gt;

&lt;p&gt;The sample keeps state in memory because it is designed to be easy to run locally.&lt;/p&gt;

&lt;p&gt;For production, I would add encrypted database storage, staff authentication, audit logging, retention policies, monitoring, stricter PHI controls, and a full compliance review.&lt;/p&gt;

&lt;p&gt;I would also keep the assistant instructions conservative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;collect intake
route to staff
do not approve or deny
do not change medication instructions
do not diagnose
send emergency callers to 9-1-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the useful shape for this kind of healthcare AI workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code example: &lt;a href="https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-prescription-refill-intake-voice-assistant-python" rel="noopener noreferrer"&gt;https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-prescription-refill-intake-voice-assistant-python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Telnyx AI Assistant quickstart: &lt;a href="https://developers.telnyx.com/docs/inference/ai-assistants/no-code-voice-assistant" rel="noopener noreferrer"&gt;https://developers.telnyx.com/docs/inference/ai-assistants/no-code-voice-assistant&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Attach an AI Assistant to a Call: &lt;a href="https://developers.telnyx.com/docs/voice/programmable-voice/ai-assistant-start" rel="noopener noreferrer"&gt;https://developers.telnyx.com/docs/voice/programmable-voice/ai-assistant-start&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI Assistant dynamic variables: &lt;a href="https://developers.telnyx.com/docs/inference/ai-assistants/dynamic-variables" rel="noopener noreferrer"&gt;https://developers.telnyx.com/docs/inference/ai-assistants/dynamic-variables&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Telnyx Developer Docs: &lt;a href="https://developers.telnyx.com" rel="noopener noreferrer"&gt;https://developers.telnyx.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>telnyx</category>
      <category>ai</category>
      <category>devrel</category>
    </item>
  </channel>
</rss>
