Loading the notebook…
Loading the notebook…
Notebooks · Specialist agents · 49
End-to-end workflow:
Its imports and arguments are checked against shipit-agent 3.0. The outputs were saved when it was written, for an earlier release.
from pathlib import Path
def _find_notebooks_dir() -> Path:
cwd = Path.cwd().resolve()
if cwd.name == 'notebooks':
return cwd
candidate = cwd / 'notebooks'
return candidate if candidate.is_dir() else cwd
WORKSPACE = _find_notebooks_dir() / '_sales_workspace'
WORKSPACE.mkdir(parents=True, exist_ok=True)
print('workspace:', WORKSPACE)
# from shipit_agent.llms import build_llm_from_settings
# llm = build_llm_from_settings({'provider': 'bedrock',
# 'model': 'bedrock/anthropic.claude-sonnet-4-5-v2:0'}, provider='bedrock')
# llm = build_llm_from_settings({'provider': 'litellm',
# 'model': 'openai/gpt-4o-mini'}, provider='litellm')
# from shipit_agent.llms import LiteLLMProxyChatLLM
# llm = LiteLLMProxyChatLLM(model='gpt-4o-mini',
# api_base='https://litellm.internal', api_key='sk-proxy')
from shipit_agent.llms import SimpleEchoLLM
llm = SimpleEchoLLM()
print('llm:', type(llm).__name__)
Salesforce needs a base_url (the org's instance URL) in the credential record — otherwise the tool returns missing_instance_url before any stub gets a chance to run. LinkedIn similarly needs base_url. Gmail uses google-api-python-client under the hood, not the HTTP connector base, so we override .run directly with a canned response.
from shipit_agent.integrations import CredentialRecord, InMemoryCredentialStore
from shipit_agent.tools.base import ToolContext, ToolOutput
from shipit_agent.tools.salesforce import SalesforceTool
from shipit_agent.tools.linkedin import LinkedInSearchTool
from shipit_agent.tools.gmail import GmailTool
store = InMemoryCredentialStore()
store.set(CredentialRecord(
key='salesforce', provider='salesforce',
secrets={'token': 'sf-demo'},
metadata={'base_url': 'https://acme.my.salesforce.com'},
))
store.set(CredentialRecord(
key='linkedin', provider='linkedin',
secrets={'token': 'li-demo'},
metadata={'base_url': 'https://nubela.co/proxycurl/api',
'auth_mode': 'bearer'},
))
store.set(CredentialRecord(
key='gmail', provider='gmail',
secrets={'token': 'gmail-demo'},
))
sf = SalesforceTool(credential_store=store, allow_writes=True)
li = LinkedInSearchTool(credential_store=store)
gm = GmailTool(credential_store=store)
# ---- Salesforce stub --------------------------------------------
def _fake_sf(*, record, method, path, query=None, body=None):
if '/sobjects/Task' in path and method.upper() == 'POST':
return {'id': '00T5f000001abcdEAA', 'success': True}
if path.endswith('/query'):
return {'totalSize': 0, 'done': True, 'records': []}
return {}
sf._request_json = _fake_sf
# ---- LinkedIn stub ----------------------------------------------
def _fake_linkedin(*, record, method, path, query=None, body=None):
return {
'full_name': 'Priya Menon',
'headline': 'VP Engineering at Northwind Cloud',
'company': 'Northwind Cloud',
'location': 'London, United Kingdom',
'experience': [
{'company': 'Northwind Cloud', 'title': 'VP Engineering',
'from': '2023-01', 'to': 'present'},
{'company': 'Stripe', 'title': 'Eng Manager',
'from': '2019-04', 'to': '2022-12'},
],
}
li._request_json = _fake_linkedin
# ---- Gmail stub (google-api based — override .run directly) -----
def _fake_gmail_run(context, **kwargs):
# Demo-only: pretend no prior thread exists for this contact.
return ToolOutput(
text='No Gmail messages found for: [email]',
metadata={'provider': 'gmail', 'connected': True,
'action': 'search', 'count': 0, 'items': []},
)
gm.run = _fake_gmail_run # type: ignore[assignment]
print('tools ready:', sf.name, li.name, gm.name)
A marketing-qualified lead just landed. We have a name, email, and LinkedIn URL — nothing else yet.
lead = {
'name': 'Priya Menon',
'email': '[email]',
'linkedin_url': 'https://www.linkedin.com/in/priyamenon',
'company': 'Northwind Cloud',
}
print(lead)
ctx = ToolContext(prompt='lead enrichment', state={'credential_store': store})
li_out = li.run(ctx, action='lookup_profile', profile_url=lead['linkedin_url'])
print(li_out.text)
profile = li_out.metadata.get('item') or li_out.metadata.get('profile') or {}
# Fall back to the raw stub payload shape — the tool formats it into
# `item`/`profile` depending on the upstream vendor.
profile = profile or _fake_linkedin(record=None, method='GET', path='/', query=None, body=None)
print()
print('headline:', profile.get('headline'))
print('location:', profile.get('location'))
gmail_out = gm.run(ctx, action='search',
query=f'from:{lead["email"]} OR to:{lead["email"]}')
print(gmail_out.text)
has_prior_thread = bool(gmail_out.metadata.get('count', 0))
print('has_prior_thread:', has_prior_thread)
In a real run you'd call the LLM to draft the email — here we just template it from the enrichment data so the notebook is deterministic without external model calls.
subject = f'{lead["company"]} engineering scaling — quick question'
body = (
f"Hi {lead['name'].split()[0]},\n\n"
f"Saw you're VP Eng at {lead['company']}. Given your Stripe "
f"background, curious how you're thinking about agent "
f"orchestration as your team scales.\n\n"
f"Would a 15-min call next week be useful? We just shipped "
f"shipit-agent v1.0.7 — fan-out + budget-gated long runs.\n\n"
f"— Rahul"
)
print('Subject:', subject)
print()
print(body)
log_activity is always allowed on SalesforceTool — other writes are gated by allow_writes=True.
sf_out = sf.run(ctx, action='log_activity',
subject=subject,
description=body,
related_to_id='001xx000003DHPiAAO')
print(sf_out.text)
print('task id:', sf_out.metadata.get('id'))
docs-app/content/source/tools/linkedin.md for the exact boundary.docs-app/content/source/tools/salesforce.md — OAuth instance URL + session token.Autopilot(..., goal=Goal('Enrich new leads ' 'from inbound form')) on a 15-minute cron.