LangChain integration for Google Sheets as database with conversational memory and RAG retriever
Project description
๐ง Google Apps Script - Complete Setup
Why Apps Script Mode?
Apps Script Mode unlocks full CRUD operations (Create, Update, Delete) while still bypassing the Google Cloud API:
- โ No Google Cloud project needed
- โ No API quotas or billing
- โ Deploy once, use forever
- โ 2-minute setup (copy-paste script)
Step-by-Step Setup
1. Open Apps Script Editor
- Open your Google Sheet
- Click Extensions โ Apps Script
- Delete any default code
- Copy the script below
2. Copy This Complete Script
/**
SheetBase Apps Script - Full CRUD API
Provides REST API for Google Sheets with:
GET: Search and retrieve records
POST: Create, update, delete records
Author: Charles Lepoittevin (SheetBase)
Version: 2.0.0
License: MIT */
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ // Configuration // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const CONFIG = { SHEET_NAME: 'Sheet1', // Change to your sheet tab name MAX_RESULTS: 1000, // Maximum records to return FUZZY_THRESHOLD: 0.2, // Search sensitivity (0.0-1.0) ENABLE_LOGGING: true // Enable detailed logging };
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ // GET Request Handler - Search & Retrieve // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
/**
Handle GET requests (search and retrieval)
Query parameters:
q: Search keywords (space-separated, OR supported)
user_id: Filter by user ID (for memory operations)
limit: Maximum results (default: 100) */ function doGet(e) { try { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.SHEET_NAME);
if (!sheet) { return jsonResponse({ success: false, error: Sheet "${CONFIG.SHEET_NAME}" not found }, 404); }
const data = sheet.getDataRange().getValues();
if (data.length === 0) { return jsonResponse({ success: true, data: [], count: 0 }); }
const headers = data; let records = data.slice(1).map((row, idx) => { const record = { __row: idx + 2 }; headers.forEach((header, i) => { record[header] = row[i]; }); return record; });
const params = e.parameter || {}; const query = params.q || ''; const userId = params.user_id || ''; const limit = parseInt(params.limit || '100');
if (query) { const keywords = query.toLowerCase().split(' ').filter(k => k.length > 0); records = records.filter(record => { const text = JSON.stringify(record).toLowerCase(); return keywords.some(kw => text.includes(kw)); }); }
if (userId && records.length > 0) { records = records.filter(record => { return record['user_id'] === userId || record['UserID'] === userId; }); }
if (limit > 0 && limit < records.length) { records = records.slice(0, limit); }
logInfo(GET request: Found ${records.length} records (query="${query}", user_id="${userId}"));
return jsonResponse({ success: true, data: records, count: records.length });
} catch (error) { logError('GET request failed', error); return jsonResponse({ success: false, error: error.toString() }, 500); } }
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ // POST Request Handler - Create, Update, Delete // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
/**
Handle POST requests (CRUD operations)
Request body examples:
Create: {"operation": "create", "values": {"Product": "USB Cable", "Price": "9.99"}}
Update: {"operation": "update", "rowNumber": 5, "updates": {"Price": "29.99"}}
Delete: {"operation": "delete", "rowNumber": 5} */ function doPost(e) { try { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.SHEET_NAME);
if (!sheet) { return jsonResponse({ success: false, error: Sheet "${CONFIG.SHEET_NAME}" not found }, 404); }
const body = JSON.parse(e.postData.contents); const operation = body.operation;
if (!operation) { return jsonResponse({ success: false, error: 'Missing "operation" field in request body' }, 400); }
switch (operation.toLowerCase()) { case 'create': return handleCreate(sheet, body);
case 'update': return handleUpdate(sheet, body);
case 'delete': return handleDelete(sheet, body);
default: return jsonResponse({ success: false, error: Unknown operation: ${operation}. Use "create", "update", or "delete" }, 400); }
} catch (error) { logError('POST request failed', error); return jsonResponse({ success: false, error: error.toString() }, 500); } }
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ // CRUD Operation Handlers // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function handleCreate(sheet, body) { if (!body.values || typeof body.values !== 'object') { return jsonResponse({ success: false, error: 'Missing or invalid "values" field' }, 400); }
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues(); const newRow = headers.map(header => body.values[header] || '');
sheet.appendRow(newRow); const rowNumber = sheet.getLastRow();
logInfo(CREATE: Added row ${rowNumber} with data: ${JSON.stringify(body.values)});
return jsonResponse({ success: true, message: 'Record created successfully', rowNumber: rowNumber, data: body.values }); }
function handleUpdate(sheet, body) { if (!body.rowNumber || typeof body.rowNumber !== 'number') { return jsonResponse({ success: false, error: 'Missing or invalid "rowNumber" field' }, 400); }
if (!body.updates || typeof body.updates !== 'object') { return jsonResponse({ success: false, error: 'Missing or invalid "updates" field' }, 400); }
const rowNumber = body.rowNumber; const lastRow = sheet.getLastRow();
if (rowNumber < 2 || rowNumber > lastRow) { return jsonResponse({ success: false, error: Invalid row number: ${rowNumber}. Must be between 2 and ${lastRow} }, 400); }
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
Object.keys(body.updates).forEach(columnName => { const colIndex = headers.indexOf(columnName); if (colIndex !== -1) { sheet.getRange(rowNumber, colIndex + 1).setValue(body.updates[columnName]); } });
logInfo(UPDATE: Modified row ${rowNumber} with updates: ${JSON.stringify(body.updates)});
return jsonResponse({ success: true, message: Row ${rowNumber} updated successfully, rowNumber: rowNumber, updates: body.updates }); }
function handleDelete(sheet, body) { if (!body.rowNumber || typeof body.rowNumber !== 'number') { return jsonResponse({ success: false, error: 'Missing or invalid "rowNumber" field' }, 400); }
const rowNumber = body.rowNumber; const lastRow = sheet.getLastRow();
if (rowNumber < 2 || rowNumber > lastRow) { return jsonResponse({ success: false, error: Invalid row number: ${rowNumber}. Must be between 2 and ${lastRow} }, 400); }
sheet.deleteRow(rowNumber);
logInfo(DELETE: Removed row ${rowNumber});
return jsonResponse({ success: true, message: Row ${rowNumber} deleted successfully, rowNumber: rowNumber }); }
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ // Utility Functions // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function jsonResponse(data, statusCode = 200) { return ContentService .createTextOutput(JSON.stringify(data)) .setMimeType(ContentService.MimeType.JSON); }
function logInfo(message) { if (CONFIG.ENABLE_LOGGING) { console.log([SheetBase] ${message}); } }
function logError(message, error) { console.error([SheetBase ERROR] ${message}:, error); }
3. Deploy as Web App
- Click Deploy โ New deployment
- Click the gear icon โ๏ธ โ Select Web app
- Fill in the form:
- Description:
SheetBase API v1 - Execute as: Me (your Google account)
- Who has access: Anyone โ ๏ธ (Important!)
- Description:
- Click Deploy
- Click Authorize access
- Select your Google account
- Click Advanced โ Go to [Project Name] (unsafe)
- Click Allow
- Copy the Web App URL (looks like): https://script.google.com/macros/s/AKfycbxxxxxxxxxxxxx/exec
4. Test Your Deployment
Test GET (search): curl "https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec?q=laptop&limit=5"
Expected response: { "success": true, "data": [ {"__row": 2, "Product": "Gaming Laptop", "Price": "999"}, {"__row": 3, "Product": "Laptop Stand", "Price": "29"} ], "count": 2 }
Test POST (create): curl -X POST "https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec" -H "Content-Type: application/json" -d '{"operation": "create", "values": {"Product": "USB Cable", "Price": "9.99"}}'
5. Use in Python
from langchain_sheetbase import create_sheetbase_tools
tools = create_sheetbase_tools( mode='appsscript', apps_script_url='https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec' )
Now you have full CRUD operations!
Configuration Options
Edit the CONFIG object at the top of the script:
const CONFIG = { SHEET_NAME: 'Sheet1', // Change to your sheet tab name MAX_RESULTS: 1000, // Limit search results FUZZY_THRESHOLD: 0.2, // Search sensitivity (0.0-1.0) ENABLE_LOGGING: true // Set to false in production };
Troubleshooting
Error: "Script function not found: doGet"
- Solution: Make sure you deployed as Web app, not API executable
Error: "Authorization required"
- Solution: Redeploy with "Who has access" set to Anyone
Error: "Sheet not found"
- Solution: Update
CONFIG.SHEET_NAMEto match your sheet tab name
No data returned
- Solution: Ensure your sheet has a header row (row 1) with column names
Updating the Script
When you modify the script:
- Save changes (Ctrl+S / Cmd+S)
- Click Deploy โ Manage deployments
- Click โ๏ธ Edit icon
- Change Version to New version
- Click Deploy
โ ๏ธ The URL stays the same - no need to update your Python code!
Memory Schema (Optional)
If using SheetBase for conversational memory, add these columns to your sheet:
| timestamp | user_id | user_request | agent_response |
|---|---|---|---|
| 2025-10-27T20:00:00Z | user_123 | What laptops? | We have 5 gaming... |
The SheetBaseMemory class will automatically use this schema.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langchain_sheetbase-0.1.0.tar.gz.
File metadata
- Download URL: langchain_sheetbase-0.1.0.tar.gz
- Upload date:
- Size: 16.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.12.3 Linux/6.8.0-86-generic
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5754973c58a6dbb1e6cc267f58d2b467182f96403550279bfd1ab953ef974a68
|
|
| MD5 |
ad6fa6ac1b9352f733b4c18b8a482204
|
|
| BLAKE2b-256 |
cc30da68141dbf9c6593f16bf0e7c30cacc793ffdbbb60157448f4f512eb9649
|
File details
Details for the file langchain_sheetbase-0.1.0-py3-none-any.whl.
File metadata
- Download URL: langchain_sheetbase-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.12.3 Linux/6.8.0-86-generic
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
183826935c26ed8b721e5cd73484cd1c0a7369f04d49872b8f93308d35020314
|
|
| MD5 |
c9484954685c8c0d1430564dfb2bc548
|
|
| BLAKE2b-256 |
d1fb4b31e34e090642de9d02a798fa989d3b83c144579405b392b644e294b10a
|