n4nAI

How to connect an AI sales agent to Salesforce

Hands-on tutorial to connect an AI sales agent to Salesforce with Python, simple-salesforce, and LLM function calling. Runnable code and expected output.

n4n Team3 min read566 words

Audio narration

Coming soon — every post will get a voice note here.

Connecting an AI sales agent to Salesforce is less about prompt engineering and more about disciplined tool boundaries. In this tutorial you’ll build a minimal agent that authenticates to Salesforce, creates a lead, and queries opportunities using function calling. We’ll use Python, simple-salesforce, and an OpenAI-compatible endpoint to connect AI sales agent Salesforce without a heavy framework.

Prerequisites

  • Python 3.11 or newer
  • A Salesforce org with API access (Developer Edition or sandbox is fine)
  • simple-salesforce and openai Python packages
  • Environment variables for Salesforce credentials and an LLM API key
pip install simple-salesforce openai python-dotenv

Create a .env file:

SF_USERNAME=you@domain.com
SF_PASSWORD=yourpassword
SF_SECURITY_TOKEN=XXXXXX
SF_DOMAIN=test  # use "login" for production
LLM_API_KEY=sk-...
LLM_BASE_URL=https://api.openai.com/v1  # or your gateway

The security token is required unless your IP is allowlisted. For production, set SF_DOMAIN=login.

Step 1: Authenticate to Salesforce

Use simple-salesforce to obtain a client. Fail fast if credentials are wrong.

import os
from dotenv import load_dotenv
from simple_salesforce import Salesforce

load_dotenv()

sf = Salesforce(
    username=os.environ["SF_USERNAME"],
    password=os.environ["SF_PASSWORD"],
    security_token=os.environ["SF_SECURITY_TOKEN"],
    domain=os.environ["SF_DOMAIN"],
)

org_id = sf.query("SELECT Id FROM Organization LIMIT 1")["records"][0]["Id"]
print("Connected to org:", org_id)

Expected output:

Connected to org: 00D5f0000012345ABC

If you see SalesforceAuthenticationFailed, verify the token and that you’re hitting the right domain. A common mistake is using test for a production org.

Step 2: Define Salesforce tools

The agent should only touch well-scoped functions. Below are three minimal operations that map to standard objects.

def create_lead(first_name: str, last_name: str, company: str, email: str) -> dict:
    result = sf.Lead.create({
        "FirstName": first_name,
        "LastName": last_name,
        "Company": company,
        "Email": email,
    })
    return {"id": result["id"], "success": result["success"]}

def get_open_opportunities(min_amount: float) -> list:
    query = f"SELECT Id, Name, Amount FROM Opportunity WHERE IsClosed = false AND Amount > {min_amount}"
    records = sf.query(query)["records"]
    return [{"id": r["Id"], "name": r["Name"], "amount": r["Amount"]} for r in records]

def update_lead_status(lead_id: str, status: str) -> dict:
    sf.Lead.update(lead_id, {"Status": status})
    return {"updated": lead_id, "status": status}

These wrappers return plain dicts so the LLM can parse them without SOAP envelopes or nested attributes. Never return the raw simple_salesforce response; it carries metadata the model doesn’t need.

Step 3: Describe tools for the LLM

OpenAI-style function calling expects JSON schemas. Keep descriptions imperative and parameter lists tight.

[
  {
    "type": "function",
    "function": {
      "name": "create_lead",
      "description": "Create a new lead in Salesforce with name, company, email",
      "parameters": {
        "type": "object",
        "properties": {
          "first_name": {"type": "string"},
          "last_name": {"type": "string"},
          "company": {"type": "string"},
          "email": {"type": "string"}
        },
        "required": ["last_name", "company"]
      }
    }
  },
  {
    "type": "function",
    "function": {
      "name": "get_open_opportunities",
      "description": "List open opportunities with amount greater than min_amount",
      "parameters": {
        "type": "object",
        "properties": {"min_amount": {"type": "number"}},
        "required": ["min_amount"]
      }
    }
  }
]

Only expose what the agent needs. If you add update_lead_status, include it here as well.

Step 4: Build the agent loop

We use the openai client. When you connect AI sales agent Salesforce to an LLM gateway, point the client at a single OpenAI-compatible base URL. If you point it at n4n.ai’s OpenAI-compatible endpoint, you get automatic fallback when a provider is rate-limited, which matters for unattended sales agents.

import json
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["LLM_API_KEY"],
    base_url=os.environ.get("LLM_BASE_URL"),
)

tools = json.load(open("tools.json"))  # from Step 3
functions = {"create_lead": create_lead, "get_open_opportunities": get_open_opportunities}

messages = [{"role": "user", "content": "Create a lead for Jane Doe at Acme, jane@acme.com, then list open opportunities above 10000"}]

while True:
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )
    msg = resp.choices[0].message
    if not msg.tool_calls:
        print("Agent:", msg.content)
        break
    messages.append(msg)
    for call in msg.tool_calls:
        fn = functions[call.function.name]
        args = json.loads(call.function.arguments)
        output = fn(**args)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(output),
        })

This loop terminates when the model returns text instead of a tool call. Keep the model small (gpt-4o-mini or equivalent) for deterministic orchestration.

Step 5: Run the task and verify

Execute the script. Expected intermediate tool output (truncated):

Agent: Created lead 00Q5f000001abcDEF. Open opportunities above 10000: [{'id': '0065f00000xyz', 'name': 'Acme Expansion', 'amount': 25000}]

If the agent skips the lead creation, tighten the prompt or set tool_choice to force the first call. Function calling is not guaranteed to be ordered unless you constrain it.

Step 6: Error handling and idempotency

Salesforce throws on duplicate emails if you configured a unique rule. Wrap calls:

def create_lead_safe(**kwargs):
    try:
        return create_lead(**kwargs)
    except Exception as e:
        return {"error": str(e)}

The agent should surface "error" fields to the user rather than hallucinate success. When you connect AI sales agent Salesforce to live data, treat every write as a side effect that needs confirmation in high-value objects like Opportunity. Use Id lookups before updates.

Step 7: Smoke test the integration

Mock Salesforce to verify the agent calls tools in the right order without hitting the API.

from unittest.mock import patch

def test_agent_calls_lead_first():
    with patch("__main__.create_lead") as mock_lead, \
         patch("__main__.get_open_opportunities") as mock_opp:
        mock_lead.return_value = {"id": "00Q", "success": True}
        mock_opp.return_value = []
        # run loop with a mocked client.chat.completions.create
        # assert mock_lead called before mock_opp

A real test would mock client.chat.completions.create to return a scripted tool call sequence. This catches prompt regressions before they touch CRM data.

Production considerations

  • Cache Salesforce object metadata (fields, picklists) to avoid repeated describe calls.
  • Use a named integration user with least-privilege permission sets; never embed admin creds in the agent process.
  • For asynchronous agents, queue tool calls and reconcile with Salesforce Change Data Capture events.
  • Honor client routing directives if your gateway supports them; n4n.ai forwards provider cache-control hints, which can reduce redundant token spend on long system prompts that repeat the Salesforce schema.
  • Log every tool call with the Salesforce record Id and the model’s rationale. Auditing is non-negotiable in sales contexts.

Building the connection is straightforward; keeping it safe is the real work.

Tagsai-sales-agentssalesforcecrmintegrations

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All ai agents in sales & crm posts →