Voice AI

Build a Telnyx Agent That Can Send SMS and Place Calls with LLM Tool Calling

The most useful communications agents do more than answer questions. They take action.

This Telnyx code example shows that pattern on Edge Compute. A user writes a normal sentence, GLM-5.2 decides whether the request needs an SMS, a phone call, or a status check, and a durable ToolAgent executes the selected Telnyx tool.

The code example is here:

https://github.com/team-telnyx/telnyx-code-examples/tree/main/agent-with-tool-calling

You need a Telnyx API key, a Telnyx number, Messaging setup, a Call Control application, and the code sample.

What This Example Builds

The sample is a Node.js and TypeScript app running on Telnyx Edge Compute.

It defines three tools:

send_sms(to, body)
make_call(to)
check_status(what)

The user experience is intentionally simple:

User: Text +13125550001 hi from my tool-calling agent
Agent: sends the SMS, then replies with a short confirmation

User: Call me at +13125550001
Agent: places the call, then replies with a short confirmation

User: Did the SMS send?
Agent: checks the latest send_sms result and summarizes it

That is the point of the sample. The user does not need to click a button for "SMS" or "Call." The model chooses the tool from the request.

Architecture

user message
  -> edge function receives request
  -> ToolAgent appends user message to history
  -> Telnyx Inference receives the conversation and tool definitions
  -> GLM-5.2 returns a tool call
  -> ToolAgent dispatches send_sms, make_call, or check_status
  -> tool result is appended with the same toolCallId
  -> model is called again with the tool result
  -> assistant returns a normal final message

The important detail is the second model call. A tool call is not the final answer. It is an instruction to the application.

The agent has to:

  • save the assistant tool-call message
  • execute the selected tool once
  • append the tool result using the same toolCallId
  • send the updated conversation back to the model
  • stop when the model returns a normal assistant message

That gives you a clean, inspectable loop.

Why This Pattern Matters

Tool calling is where an LLM becomes useful inside a communications workflow.

Without tools, the model can say:

I can send that text for you.

With tools, the model can choose send_sms, pass the phone number and body, and let the application call Telnyx Messaging.

Without tools, the model can say:

You should call the customer now.

With tools, the model can choose make_call, pass the destination number, and let the application call Telnyx Call Control.

That is a better developer pattern because the model handles intent and argument extraction, while your code owns execution, validation, logging, and error handling.

The Tool Definitions

The sample sends tool definitions to Telnyx Inference alongside the conversation.

The SMS tool accepts a destination number and message body:

send_sms({
  to: "+13125550001",
  body: "hi from my tool-calling agent"
})

The call tool accepts a destination number:

make_call({
  to: "+13125550001"
})

The status tool checks the agent's local ledger:

check_status({
  what: "send_sms"
})

These schemas give the model a small action surface. It can choose from approved functions, not arbitrary code.

The Core Implementation

The ToolAgent extends the Telnyx Agent SDK:

export class ToolAgent extends Agent<Env, ToolState> {
  protected override initialState(): ToolState {
    return { from: "", to: "", iterations: 0, lastReply: "", updatedAt: 0 };
  }
}

When a user message arrives, the agent saves it and queues work:

await this.messages.add("user", text);
await this.queue("process");

Inside process(), the agent calls Telnyx Inference through the Edge binding:

await this.env.TELNYX.ai.openai.chat.createCompletion({
  model,
  messages,
  tools,
  tool_choice: "auto",
});

If the model chooses send_sms, the app calls Telnyx Messaging:

await this.env.TELNYX.messages.send({
  from: fromNumber,
  to,
  text: body,
});

If the model chooses make_call, the app calls Telnyx Call Control:

await this.env.TELNYX.calls.dial({
  connection_id: callControlAppId,
  from: fromNumber,
  to,
  command_id: toolCallId,
});

The command_id uses the tool call ID, which makes the call easy to correlate with the agent ledger.

The Tool Ledger

The sample logs each tool call with:

  • tool name
  • toolCallId
  • normalized arguments
  • result
  • status
  • timestamp

That ledger is useful in demos and in debugging. You can prove that the model chose the right tool, the app executed it, and the tool result went back into the conversation before the final assistant response.

Run The Example

Clone the repo:

git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/agent-with-tool-calling
npm install

Run the checks:

npm run typecheck
npm run types

Configure your Telnyx number, Messaging Profile webhook, Call Control application, and Edge function settings. Then deploy:

npm run ship

Try commands like:

Text +13125550001 running five minutes late
Call me at +13125550001
Did the call go through?

Why This Is Useful

This same shape works for many communications agents:

  • appointment reminders
  • delivery updates
  • support escalations
  • lead response
  • customer callbacks
  • field service notifications
  • internal operations alerts

The model understands the request. Your code validates the arguments, calls Telnyx, records the result, and gives the model enough context to answer the user clearly.

That separation is the main lesson of the sample.

Resources

  • Code example: https://github.com/team-telnyx/telnyx-code-examples/tree/main/agent-with-tool-calling

Ready to build with low-latency voice AI?

Join developers building the future of real-time conversations