Building a Salesforce AI agent with LangChain and Groq in Google Colab
This post walks through building a conversational agent that answers questions about your Salesforce org in plain English, so nobody has to write SOQL, dig through setup menus, or build a report to get a number out.
It combines LangChain's agent framework, Groq's low-latency LLM inference, and the langchain-salesforce integration. The agent reads the question, generates valid SOQL, runs it against your org, checks the result through its own reasoning loop, and hands back a readable answer along with the structured data behind it.
The problem with traditional programmatic Salesforce access
A normal integration means writing the SOQL by hand, knowing the object model and the namespace prefixes, and handling pagination, governor limits and error recovery yourself. That is a high bar for a business analyst, a sales operations team, or an executive who only wants the data.
A LangChain agent changes that. Give it a set of Salesforce tools and it can:
- Accept questions in plain English.
- Reason about which objects and fields are relevant.
- Work out the correct API names, namespace resolution included.
- Estimate query code before execution.
- Return the answer as a formatted table.
What follows covers the build itself with the langchain-salesforce package, the design patterns that handle Salesforce-specific problems such as namespace prefixes, governor limits and SOQL syntax validation, and the practices around error handling, rate limit management and conversational memory. Groq's LPU-powered inference is what keeps an interactive agent loop under a second. The Colab notebook runs as-is, so developers and business users alike can query Salesforce data without writing SOQL.
By the end you can ask it things like:
- "Show me the top five accounts by created date."
- "How many open opportunities were created this month?"
Scope
This sticks to standard SOQL data queries against the Salesforce REST API. It does not cover:
- DML operations (INSERT/UPDATE/DELETE).
- Bulk API, Streaming API, Tooling API, GraphQL API, Apex execution, or Salesforce Flow automation.
Architecture overview: the ReAct pattern
The agent runs a ReAct (reasoning + acting) loop. It reasons about the question, picks a tool, runs it, and repeats until it has a final answer.

- Interface: people ask through the
ask()function in a Colab cell, or through the optional Gradio web UI. - Agent and memory: the LangGraph agent takes the question and keeps the conversation in
MemorySaver, so follow-up questions work. - Reasoning: the agent sends the question, the history and the available tools to the Groq LLM, which decides the next action.
- Tool execution: the agent calls whichever tool it picked, such as
find_object_api_nameorexecute_soql, against the Salesforce API. - Result processing: the result goes back to the LLM. If the question still is not answered, it calls another tool; otherwise it writes the final answer.
- Output: the answer is displayed and the Salesforce data lands in a global
pandasDataFrame calledlast_df, ready for further analysis.
Implementation procedure
Prerequisites
- A Google account for Colab.
- Salesforce credentials:
- Username
- Password
- Security token (obtain from Setup → My Personal Information → Reset Security Token).
- A Groq API key (free tier at https://console.groq.com/keys).
- Salesforce permissions:
- "API Enabled"
- Read access to the objects you query.
- Tooling API access, for metadata queries.
Step-by-step implementation
Step 1: environment setup (cell 1)
Install the packages and check that the imports work:
!pip install -q langchain langchain_groq langgraph langchain-core langchain-salesforce pandas gradio
import sys
print(f"Python {sys.version.split()[0]}")
try:
from langchain_salesforce import SalesforceTool
print("langchain-salesforce imported successfully")
except ImportError as e:
print(f"langchain-salesforce import failed: {e}")
Key libraries:
- LangChain: the core agent orchestration framework.
- langchain_groq: the LangChain adapter for Groq's fast inference API.
- LangGraph: the graph-based agent executor that runs the ReAct loop and holds state.
- langchain-salesforce: the official Salesforce integration, wrapping the REST API and handling authentication.
- pandas: turns Salesforce records into DataFrames for structured display and analysis.
- Gradio: an optional web UI for handing the agent to non-technical users.
Step 2: connecting to Salesforce (cell 2)
This cell opens the single SalesforceTool connection that every other tool uses, then proves it works with a minimal query. SalesforceTool wraps the simple-salesforce library underneath, and authenticates with username, password and security token against Salesforce's OAuth endpoint.
Keep the credentials out of the notebook. Open Colab's Secrets panel (the key icon in the left sidebar) and define these:
| Name | Description | Notebook Access |
|---|---|---|
src_username |
Your Salesforce username. | True |
src_password |
Your Salesforce password. | True |
src_token |
Your Salesforce security token. | True |
COLAB_GROK_API_KEY |
Your API key from Groq. | True |
The notebook reads them at runtime, so the values never sit in a cell where they can be exposed by accident.
from langchain_salesforce import SalesforceTool
from google.colab import userdata
import json, pandas as pd
def init_salesforce() -> SalesforceTool:
"""Initialise SalesforceTool and verify connectivity with a test query."""
print("Connecting to Salesforce...")
try:
sf_tool = SalesforceTool(
username=userdata.get('src_username'),
password=userdata.get('src_password'),
security_token=userdata.get('src_token'),
domain="test" # Change to "" for production
)
# Minimal connectivity check
test = sf_tool.invoke({"operation": "query", "query": "SELECT Id, Name FROM Account LIMIT 1"})
records = test.get("records", [])
print(f"Connected → {len(records)} test record(s) fetched")
return sf_tool
except Exception as e:
msg = str(e)
print(f"\n Connection failed: {type(e).__name__}")
if "INVALID_LOGIN" in msg:
print(" Fix: wrong username/password/token combination")
elif "INVALID_SESSION_ID" in msg:
print(" Fix: regenerate security token in Salesforce Setup")
elif "INSUFFICIENT_ACCESS" in msg:
print(" Fix: grant 'API Enabled' permission to your user")
else:
print(f" Debug: {msg}")
raise
sf = init_salesforce()
Step 3: initialising the LLM (cell 3)
This is the model that does the agent's reasoning. Two of them are configured, a primary and a fallback, so the agent keeps answering when the primary hits its daily rate limit and switches over on its own.
- Primary model:
llama-3.3-70b-versatile(100k tokens/day free tier) - Fallback model:
meta-llama/llama-4-scout-17b-16e-instruct(1M tokens/day free tier)
Three parameters do the work of keeping behaviour reliable:
temperature=0makes the output deterministic, so the same question always produces the same SOQL and the same query.max_tokens=2048stops the tool-call JSON getting truncated mid-generation.max_retries=2covers transient network errors.
from langchain_core.tools import tool
from langchain_groq import ChatGroq
import os, re, json
os.environ["GROQ_API_KEY"] = userdata.get('COLAB_GROK_API_KEY')
GROQ_MODEL_PRIMARY = "llama-3.3-70b-versatile"
GROQ_MODEL_FALLBACK = "meta-llama/llama-4-scout-17b-16e-instruct"
GROQ_MODEL_ACTIVE = GROQ_MODEL_PRIMARY
# updated by ask() on rate-limit switch
def make_llm(model: str) -> ChatGroq:
"""Create a ChatGroq instance. Called at init and when switching fallback model."""
return ChatGroq(
model=model,
temperature=0, # deterministic — same question always generates same SOQL
max_tokens=2048, # cap output to prevent mid-JSON truncation → tool_use_failed
max_retries=2, # retry transient HTTP errors (rate limits handled separately)
)
llm = make_llm(GROQ_MODEL_PRIMARY)
print(f" LLM initialised: {llm.model_name}")
print(f" temperature=0 | max_tokens=2048")
print(f" Fallback model: {GROQ_MODEL_FALLBACK}")
Step 4: defining the agent tools (cell 4)
The agent's capabilities are Python functions with an @tool decorator on them. The example below, get_object_fields, returns every field on a Salesforce object through the standard describe endpoint.
# ══════════════════════════════════════════════════════════════════════════════
# TOOL 2 — get_object_fields
# ══════════════════════════════════════════════════════════════════════════════
# Returns ALL fields for a given sObject via the standard describe endpoint.
# For each field it returns:
# name, label, type, nillable, calculated (formula), length, updateable
@tool
def get_object_fields(object_name: str) -> str:
"""Get ALL fields for a Salesforce object using the standard describe endpoint.
Use when the user asks 'what fields does X have' or 'show me the columns of X'.
Returns a DataFrame displayed inline and stored in last_df."""
global last_df
try:
# Regex guard: Salesforce API names are alphanumeric
if not re.match(r'^[a-zA-Z0-9_]+$', object_name):
raise ValueError("Invalid object name format.")
response = sf.describe(object_name)
fields_data = []
for field in response.get('fields', []):
fields_data.append({
'Name': field.get('name'),
'Label': field.get('label'),
'Type': field.get('type'),
'Nillable': field.get('nillable'),
'Calculated': field.get('calculated'),
'Length': field.get('length'),
'Updateable': field.get('updateable')
})
last_df = pd.DataFrame(fields_data)
return last_df.to_markdown(index=False)
except Exception as e:
print(f"Error describing object '{object_name}': {e}")
return f"Could not retrieve fields for {object_name}. Check API name and permissions."
Leave a Comment