Fewer agent tool calls: a discovery cache for MCP tool schemas, a trajectory cache for whole multi-step call sequences with a one-line @smartflow_tool decorator, a tool-call-reduction benchmark, and a verified compression quality delta. See the Tool-Call Efficiency guide →
What is Smartflow?
Smartflow is an enterprise AI orchestration layer that sits between your application and AI providers (OpenAI, Anthropic, Google, and others). It provides:
- Intelligent Routing — Automatically route requests to the best provider based on cost, latency, and availability
- 4-Phase Semantic Cache — 60–80% cost reduction with intent fingerprinting, exact-key lookup, and Phase 4 VectorLite BERT KNN search (all-MiniLM-L6-v2, 384-dim, cosine similarity ≥ 0.90)
- ML-Powered Compliance — Real-time PII detection with adaptive learning and behavioral analysis
- Complete Audit Trail — Every AI interaction logged for compliance, debugging, and analytics
- Automatic Failover — Zero-downtime provider switching when issues occur
- MCP Tool Gateway — Register and invoke external MCP tools with shared auth, budgeting, and audit
- A2A Agent Orchestration — Route tasks to registered A2A agents with full traceability
┌─────────────────────────────────────────────────────────────────┐
│ YOUR APPLICATION │
│ │ │
│ pip install smartflow-sdk │
│ │ │
│ ┌────────▼────────┐ │
│ │ Smartflow SDK │ │
│ └────────┬────────┘ │
└─────────────────────────────┼───────────────────────────────────┘
│
┌──────────▼──────────┐
│ SMARTFLOW PROXY │
│ ┌────────────────┐ │
│ │ MetaCache │ │ ← 60-80% cost savings
│ │ ML Compliance │ │ ← Adaptive PII detection
│ │ Smart Routing │ │ ← Best provider selection
│ │ VAS Logging │ │ ← Complete audit trail
│ │ MCP Gateway │ │ ← Tool orchestration
│ │ A2A Gateway │ │ ← Agent-to-agent tasks
│ └────────────────┘ │
└──────────┬──────────┘
│
┌───────────┬───────┴───────┬───────────┬───────────┐
▼ ▼ ▼ ▼ ▼
┌───────┐ ┌─────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│OpenAI │ │Anthropic│ │ Gemini │ │ Cohere │ │ Local │
└───────┘ └─────────┘ └────────┘ └────────┘ └────────┘
Dual-Mode Operation — v0.4.0
Smartflow SDK v0.4.0 introduces a dual-mode architecture that gives developers the freedom to start building immediately — even without a deployed Smartflow instance — and seamlessly upgrade to full enterprise gateway mode whenever they are ready.
Connect to a deployed Smartflow instance for the full feature set.
- ✓ BERT KNN semantic cache (55–75% cost savings)
- ✓ Real-time policy engine & PII detection
- ✓ SSO identity (Entra ID, LDAP, SAML, OIDC)
- ✓ Full VAS per-user audit trail
- ✓ MCP gateway + A2A orchestration
- ✓ Prometheus metrics + management dashboard
Call AI providers directly — same API surface, no infrastructure required.
- ✓ OpenAI, Anthropic, Gemini, Ollama
- ✓ Multi-provider routing via model prefix
- ✓ Streaming responses
- ✓ Embeddings API
- ~ Basic in-memory stats (no semantic cache)
- ~ Gateway-only features raise
DirectModeError
Mode Selection Logic
Mode is selected automatically in priority order — no manual configuration required:
# Priority 1: Explicit URL argument (backward compatible — always gateway mode)
sf = SmartflowClient("https://yourco.langsmart.app", api_key="sk-sf-...")
# Priority 2: SMARTFLOW_GATEWAY_URL environment variable
# export SMARTFLOW_GATEWAY_URL="https://yourco.langsmart.app"
sf = SmartflowClient() # detects env var → gateway mode
# Priority 3: ~/.smartflow/config.yaml (written by `smartflow configure`)
sf = SmartflowClient() # reads config file → gateway or direct mode
# Priority 4: No gateway configured → direct mode (calls providers directly)
sf = SmartflowClient() # direct mode with env var keys (OPENAI_API_KEY, etc.)
# Check which mode you're in
print(sf.mode) # "gateway" or "direct"
print(sf.is_gateway_mode()) # True / False
First-Run Setup
Run the interactive wizard once to configure your environment. It saves settings to
~/.smartflow/config.yaml and is read automatically on every subsequent
SmartflowClient() instantiation.
# CLI wizard (recommended — asks gateway URL or provider keys)
smartflow configure
# Check current config and test connectivity
smartflow status
# Quick chat test
smartflow chat "Hello, which mode am I using?"
Or trigger the wizard from Python:
import smartflow
smartflow.configure() # same interactive wizard
Provider Routing (Direct Mode)
In direct mode, the model string controls which provider is called. The same prefix notation works in gateway mode too (the gateway translates it).
sf = SmartflowClient() # or SmartflowClient("https://gateway...")
# OpenAI (default)
await sf.chat("Hello", model="gpt-4o")
await sf.chat("Hello", model="openai/gpt-4o") # explicit prefix
# Anthropic Claude
await sf.chat("Hello", model="claude-sonnet-4-6")
await sf.chat("Hello", model="anthropic/claude-3-5-haiku-20241022")
# Google Gemini (via openai-compat endpoint)
await sf.chat("Hello", model="gemini-1.5-pro")
await sf.chat("Hello", model="gemini/gemini-2.0-flash")
# Ollama (local — OLLAMA_BASE_URL env var or default http://localhost:11434)
await sf.chat("Hello", model="ollama/llama3")
await sf.chat("Hello", model="ollama/mistral")
# Custom OpenAI-compat server (LOCAL_BASE_URL env var)
await sf.chat("Hello", model="local/my-fine-tuned-model")
anthropic/claude-*, gemini/...,
ollama/...) also works in gateway mode — the Smartflow proxy translates
and routes automatically. The same code works in both modes.
Gateway-Only Features in Direct Mode
Calling a gateway-only method (compliance scan, VAS logs, routing override, etc.) in
direct mode raises a clear DirectModeError with instructions:
from smartflow import SmartflowClient
from smartflow.direct import DirectModeError
sf = SmartflowClient() # direct mode
try:
logs = await sf.get_logs()
except DirectModeError as e:
print(e)
# VAS audit logs requires a Smartflow Enterprise gateway.
# Run `smartflow configure` to connect to a gateway and unlock:
# • BERT KNN semantic cache (55–75% cost savings)
# • Real-time policy engine (PII detection, jailbreak guard)
# • SSO identity integration (Entra ID, LDAP, SAML)
# • Full VAS audit trail (per-user request logging)
# • MCP gateway + A2A orchestration
Installation
pip install smartflow-sdkOptional — sync client in async environments (Jupyter notebooks):
pip install nest_asyncioQuick Start
Async (Recommended)
import asyncio
from smartflow import SmartflowClient
async def main():
async with SmartflowClient("https://yourco.langsmart.app", api_key="sk-sf-...") as sf: # gateway mode
# Automatic caching, compliance scanning, multi-provider failover,
# and complete audit logging on every call.
response = await sf.chat("Explain quantum computing in simple terms")
print(response)
asyncio.run(main())Synchronous — Scripts and Notebooks
from smartflow import SyncSmartflowClient
sf = SyncSmartflowClient() # reads ~/.smartflow/config.yaml (gateway or direct)
response = sf.chat("What is machine learning?")
print(response)
stats = sf.get_cache_stats()
print(f"Cache hit rate: {stats.hit_rate:.1%}")
print(f"Tokens saved: {stats.tokens_saved:,}")
sf.close()Optional — Direct Mode Provider Support
To use direct mode (no gateway), install the provider packages you need:
# OpenAI + Anthropic (most common)
pip install "smartflow-sdk[all]"
# Just OpenAI
pip install "smartflow-sdk[openai]"
# Just Anthropic
pip install "smartflow-sdk[anthropic]"
# Sync client in Jupyter notebooks
pip install "smartflow-sdk[nest]"
In gateway mode, no provider packages are required — the gateway handles provider calls server-side. httpx is the only dependency.
For Jupyter notebooks with an existing event loop:
import nest_asyncio
nest_asyncio.apply()OpenAI Drop-in Replacement
Zero code changes required — just update the base URL:
from openai import OpenAI
# Before: client = OpenAI()
# After: through Smartflow — caching, compliance, logging all apply transparently
client = OpenAI(
base_url="http://your-smartflow:7775/v1",
api_key="sk-sf-your-virtual-key"
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}]
)Feature Availability by Mode
| Feature | Gateway Mode | Direct Mode |
|---|---|---|
chat() / chat_completions() | ✓ Full | ✓ Full |
stream_chat() | ✓ Full | ✓ Full |
embeddings() | ✓ Full | ✓ OpenAI only |
claude_message() | ✓ Via proxy | ✓ Direct Anthropic |
| Multi-provider routing | ✓ 37+ providers | ✓ OpenAI / Anthropic / Gemini / Ollama |
| Semantic BERT cache (55–75%) | ✓ 4-phase MetaCache | ✗ Not available |
check_compliance() / intelligent_scan() | ✓ ML-powered | ✗ Gateway only |
get_logs() (VAS audit trail) | ✓ Per-user SSO | ✗ Gateway only |
get_cache_stats() | ✓ Live MetaCache stats | ✓ Client-side counts |
| SSO / Enterprise Identity | ✓ Entra ID / LDAP / SAML | ✗ Gateway only |
| MCP Gateway / A2A Orchestration | ✓ Full | ✗ Gateway only |
| Prometheus metrics | ✓ Native /metrics | ✗ Gateway only |
| Policy engine / guardrails | ✓ Visual editor + no-code | ✗ Gateway only |
| Required dependencies | httpx only | openai / anthropic (optional) |
SmartflowClient
Primary async client.
class SmartflowClient(
base_url: Optional[str] = None, # Gateway URL or None (direct/config mode)
api_key: Optional[str] = None, # Virtual key (sk-sf-...)
timeout: float = 30.0, # Request timeout in seconds
management_port: int = 7778, # Management API port
compliance_port: int = 7777, # Compliance API port
bridge_port: int = 3500, # Hybrid bridge port
)Use as a context manager for automatic cleanup:
async with SmartflowClient("http://smartflow:7775", api_key="sk-sf-...") as sf:
...
# Or manual lifecycle
sf = SmartflowClient("http://smartflow:7775")
await sf._ensure_client()
# ... use sf ...
await sf.close()Core AI Methods
chat()
Send a message, receive the reply as a plain string.
async def chat(
message: str,
model: str = "gpt-4o",
system_prompt: Optional[str] = None,
temperature: float = 0.7,
max_tokens: Optional[int] = None,
**kwargs,
) -> strasync with SmartflowClient("http://smartflow:7775") as sf:
# Simple
response = await sf.chat("Explain Docker containers")
# With options
response = await sf.chat(
message="Write a Python function to sort a list",
model="gpt-4o",
system_prompt="You are an expert Python developer. Write clean, documented code.",
temperature=0.3,
max_tokens=1000,
)chat_completions()
Full OpenAI-compatible completions. Returns a structured
AIResponse.
async def chat_completions(
messages: List[Dict[str, str]],
model: str = "gpt-4o",
temperature: float = 0.7,
max_tokens: Optional[int] = None,
stream: bool = False,
**kwargs,
) -> AIResponseresponse = await sf.chat_completions(
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is REST API?"},
],
model="gpt-4o",
)
print(response.content)
print(f"Tokens used: {response.usage.total_tokens}")
print(f"Cached: {response.cached}") # True if served from MetaCachestream_chat()
Async generator that yields text delta strings as they stream.
async def stream_chat(
message: str,
model: str = "gpt-4o",
system_prompt: Optional[str] = None,
temperature: float = 0.7,
max_tokens: Optional[int] = None,
**kwargs,
) -> AsyncIterator[str]async for chunk in sf.stream_chat("Tell me a story about a robot"):
print(chunk, end="", flush=True)
print()claude_message()
Send a message to Claude using the Anthropic Messages API native
path. The proxy injects the API key automatically — no
anthropic_key required in production.
async def claude_message(
message: str,
model: str = "claude-sonnet-4-6",
max_tokens: int = 1024,
system: Optional[str] = None,
anthropic_key: Optional[str] = None,
) -> strresponse = await sf.claude_message(
message="Analyze this code for security vulnerabilities",
model="claude-sonnet-4-6",
max_tokens=2000,
system="You are a senior security engineer.",
)Routes to /anthropic/v1/messages (native Anthropic
format). For multi-turn or multimodal use, call
chat_completions() with
model="claude-sonnet-4-6" using the OpenAI-compatible
format.
embeddings()
Generate vector embeddings.
async def embeddings(
input: Union[str, List[str]],
model: str = "text-embedding-3-small",
encoding_format: str = "float",
dimensions: Optional[int] = None,
input_type: Optional[str] = None,
**kwargs,
) -> Dict[str, Any]# Single text
result = await sf.embeddings("Hello, world!")
vector = result["data"][0]["embedding"]
# Batch
result = await sf.embeddings([
"First document",
"Second document",
"Third document",
])
vectors = [item["embedding"] for item in result["data"]]
# Cohere with input_type
result = await sf.embeddings(
["search query", "document text"],
model="cohere/embed-english-v3.0",
input_type="search_document",
)
# Reduce dimensions (OpenAI text-embedding-3+)
result = await sf.embeddings("Hello", model="text-embedding-3-large", dimensions=256)image_generation()
Generate images.
async def image_generation(
prompt: str,
model: str = "dall-e-3",
n: int = 1,
size: str = "1024x1024",
quality: Optional[str] = None,
response_format: str = "url",
style: Optional[str] = None,
**kwargs,
) -> Dict[str, Any]result = await sf.image_generation(
"A futuristic city at sunrise",
model="dall-e-3",
size="1792x1024",
quality="hd",
style="vivid",
)
print(result["data"][0]["url"])audio_transcription()
Transcribe audio. Accepts a file-like object.
async def audio_transcription(
file: Any,
model: str = "whisper-1",
language: Optional[str] = None,
prompt: Optional[str] = None,
response_format: str = "json",
temperature: float = 0.0,
filename: str = "audio.mp3",
**kwargs,
) -> Dict[str, Any]with open("recording.mp3", "rb") as f:
result = await sf.audio_transcription(f, model="whisper-1")
print(result["text"])
# Groq (faster, free tier available)
with open("recording.mp3", "rb") as f:
result = await sf.audio_transcription(f, model="groq/whisper-large-v3")text_to_speech()
Synthesize speech. Returns raw audio bytes.
async def text_to_speech(
input: str,
model: str = "tts-1",
voice: str = "alloy",
response_format: str = "mp3",
speed: float = 1.0,
**kwargs,
) -> bytesaudio = await sf.text_to_speech("Hello, this is Smartflow.", voice="nova")
with open("output.mp3", "wb") as f:
f.write(audio)rerank()
Rerank documents by relevance to a query.
async def rerank(
query: str,
documents: List[str],
model: str = "rerank-english-v3.0",
top_n: Optional[int] = None,
**kwargs,
) -> Dict[str, Any]result = await sf.rerank(
"What is the return policy?",
["We accept returns within 30 days.", "Contact support@example.com."],
top_n=1,
)list_models()
List available models across all enabled providers.
async def list_models() -> List[Dict[str, Any]]models = await sf.list_models()
for m in models:
print(m["id"])chatbot_query()
Query Smartflow’s built-in system chatbot for operational information. Answers natural-language questions about VAS logs, cache stats, cost analysis, and system health.
async def chatbot_query(query: str) -> Dict[str, Any]result = await sf.chatbot_query("show me today's cache stats")
print(result["response"])
result = await sf.chatbot_query("which provider had the most errors this week?")
result = await sf.chatbot_query("what did we spend on OpenAI yesterday?")Provider Prefix Reference
All methods that accept a model parameter support
provider prefix routing. Prefix the model name with
provider/ to route to a specific provider. For the primary
providers, no prefix is needed — model name is detected
automatically.
Automatic detection (no prefix needed):
# OpenAI — detected from gpt-*, o1-*, o3-*, chatgpt-*, whisper-*, dall-e-*
reply = await sf.chat("Hello", model="gpt-4o")
reply = await sf.chat("Hello", model="gpt-4o-mini")
reply = await sf.chat("Hello", model="o3-mini")
# Anthropic — detected from claude-*
reply = await sf.chat("Hello", model="claude-sonnet-4-6")
reply = await sf.chat("Hello", model="claude-3-opus-20240229")
# Google Gemini — detected from gemini-*
reply = await sf.chat("Hello", model="gemini-1.5-pro")
reply = await sf.chat("Hello", model="gemini-2.0-flash")Explicit prefix required:
reply = await sf.chat("Hello", model="xai/grok-2-latest")
reply = await sf.chat("Hello", model="mistral/mistral-large-latest")
reply = await sf.chat("Hello", model="cohere/command-r-plus")
reply = await sf.chat("Hello", model="groq/llama-3.1-70b-versatile")
reply = await sf.chat("Hello", model="openrouter/meta-llama/llama-3.1-405b")
reply = await sf.chat("Hello", model="ollama/llama3.2")
reply = await sf.chat("Hello", model="azure/my-gpt4o-deployment")
# Force native Anthropic Messages API path
reply = await sf.claude_message("Hello", model="claude-sonnet-4-6")Full prefix table:
| Prefix | Provider | API Key Env Var |
|---|---|---|
| (none) | OpenAI | OPENAI_API_KEY |
anthropic/ |
Anthropic | ANTHROPIC_API_KEY |
xai/ |
xAI (Grok) | XAI_API_KEY |
gemini/ |
Google Gemini | GEMINI_API_KEY |
vertex_ai/ |
Google Vertex AI | VERTEXAI_API_KEY |
openrouter/ |
OpenRouter | OPENROUTER_API_KEY |
azure/ |
Azure OpenAI | AZURE_API_KEY + AZURE_API_BASE |
mistral/ |
Mistral AI | MISTRAL_API_KEY |
cohere/ |
Cohere | COHERE_API_KEY |
nvidia_nim/ |
NVIDIA NIM | NVIDIA_NIM_API_KEY |
huggingface/ |
HuggingFace | HUGGINGFACE_API_KEY |
groq/ |
Groq | GROQ_API_KEY |
deepgram/ |
Deepgram | DEEPGRAM_API_KEY |
fireworks/ |
Fireworks AI | FIREWORKS_API_KEY |
novita/ |
Novita AI | NOVITA_API_KEY |
together/ |
Together AI | TOGETHER_API_KEY |
perplexity/ |
Perplexity AI | PERPLEXITY_API_KEY |
replicate/ |
Replicate | REPLICATE_API_KEY |
vercel_ai_gateway/ |
Vercel AI Gateway | VERCEL_AI_GATEWAY_API_KEY |
ollama/ |
Ollama (local) | (none required) |
Intelligent Compliance Engine
Smartflow’s ML-powered compliance engine goes beyond regex. It learns and adapts based on user behavior and organizational baselines.
┌─────────────────────────────────────────────────────────────────┐
│ INTELLIGENT COMPLIANCE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ LAYER 1 │ │ LAYER 2 │ │ LAYER 3 │ │
│ │ Regex/Rules │ → │ ML Embeddings│ → │ Behavioral │ │
│ │ │ │ │ │ Analysis │ │
│ │ SSN │ │ Semantic │ │ User │ │
│ │ Credit Card │ │ similarity │ │ patterns │ │
│ │ Email │ │ Context │ │ Org │ │
│ │ Phone │ │ awareness │ │ baselines │ │
│ │ MRN │ │ Learned │ │ Anomaly │ │
│ │ Passport │ │ patterns │ │ detection │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ └─────────────────┼──────────────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ CORRELATION │ │
│ │ ENGINE │ │
│ │ │ │
│ │ Composite Risk │ │
│ │ Score + Action │ │
│ └──────────────────┘ │
│ │ │
│ ▼ │
│ Allow | AllowAndLog | Review | Block │
└─────────────────────────────────────────────────────────────────┘
intelligent_scan()
async def intelligent_scan(
content: str,
user_id: Optional[str] = None,
org_id: Optional[str] = None,
context: Optional[str] = None,
) -> IntelligentScanResultresult = await sf.intelligent_scan(
content="Please send payment to card 4111-1111-1111-1111",
)
print(f"Has violations: {result.has_violations}") # True
print(f"Risk score: {result.risk_score:.2f}") # 0.0 – 1.0
print(f"Action: {result.recommended_action}") # Allow/AllowAndLog/Block/Review
print(f"Explanation: {result.explanation}")
for v in result.regex_violations:
print(f" - {v['violation_type']}: {v['severity']}")Enable behavioral analysis with user context:
result = await sf.intelligent_scan(
content="Customer email: john.doe@example.com",
user_id="support_agent_42", # Track individual behavior
org_id="acme_corporation", # Compare against org baseline
context="customer_support", # Context for better detection
)check_compliance()
Rule-based compliance scan.
async def check_compliance(
content: str,
policy: str = "enterprise_standard",
) -> ComplianceResultresult = await sf.check_compliance("My SSN is 123-45-6789")
if result.has_violations:
print(f"Risk: {result.risk_level}")
print(f"PII: {result.pii_detected}")
print(f"Safe text: {result.redacted_content}")redact_pii()
async def redact_pii(content: str) -> strsafe = await sf.redact_pii("Call me at 555-867-5309, email john@example.com")
# "Call me at [PHONE], email [EMAIL]"submit_compliance_feedback()
Submit a true/false-positive correction to retrain the ML model.
async def submit_compliance_feedback(
scan_id: str,
is_false_positive: bool,
user_id: Optional[str] = None,
notes: Optional[str] = None,
) -> Dict[str, Any]# Scan content, store the scan response dict to get the scan_id
response = await sf._post(
f"{sf.compliance_url}/api/compliance/intelligent/scan",
{"content": "Call me at 555-0100"}
)
scan_id = response.get("scan_id")
if scan_id:
await sf.submit_compliance_feedback(
scan_id=scan_id,
is_false_positive=True,
user_id="admin_user",
notes="555-0100 is a known test number, not real PII",
)get_learning_summary()
Organization-wide learning progress.
async def get_learning_summary() -> LearningSummarysummary = await sf.get_learning_summary()
print(f"Total users tracked: {summary.total_users}")
print(f"Users with complete baselines: {summary.users_learning_complete}")
print(f"Learning period: {summary.config_learning_days} days")get_learning_status()
Adaptive learning status for a specific user.
async def get_learning_status(user_id: str) -> LearningStatusstatus = await sf.get_learning_status("user-alice")
print(f"Days tracked: {status.days_tracked}")
print(f"Progress: {status.progress_percent}%")
print(f"Complete: {status.learning_complete}")get_ml_stats()
ML compliance engine statistics.
async def get_ml_stats() -> MLStatsml_stats = await sf.get_ml_stats()
print(f"Total patterns: {ml_stats.total_patterns}")
print(f"Learned patterns: {ml_stats.learned_patterns}")
print(f"Pattern categories: {ml_stats.patterns_by_category}")
print(f"Average confidence: {ml_stats.average_confidence:.2f}")get_org_baseline()
Organization behavioral baseline used for anomaly detection.
async def get_org_baseline(org_id: str) -> OrgBaselinebaseline = await sf.get_org_baseline("acme-corp")
print(f"Users: {baseline.user_count}")
print(f"Violation rate: {baseline.violation_rate:.2%}")
print(f"Top violations: {baseline.top_violation_types}")Other Compliance Methods
| Method | Returns | Description |
|---|---|---|
get_org_summary() |
Dict |
Organization-level aggregate compliance stats |
get_persistence_stats() |
PersistenceStats |
Redis persistence stats for compliance data |
save_compliance_data() |
Dict |
Trigger manual flush of compliance data to Redis |
get_intelligent_health() |
Dict |
Health status of ML engine and embedding service |
MCP Tool Invocation
As of v0.4.0 the SDK has first-class MCP methods. Earlier versions
had none, so the only option was hand-rolling JSON-RPC over
httpx. That still works, but the methods below handle the
envelope, auth, and error mapping for you, and every call is logged to
the VAS audit trail like any other request.
Everything in this section is gateway-only. In direct mode these
raise DirectModeError.
Registering a Server
await sf.register_mcp_server(
server_id="github-tools",
url="https://mcp.example.com/github",
transport="streamable_http",
description="GitHub issue and PR tooling",
)
servers = await sf.list_mcp_servers()
for s in servers:
print(f"{s['server_id']:20s} {s['url']}")register_mcp_server() posts to /api/admin/mcp/servers.
The live handler requires name, ignores
id/server_id, and always mints
mcp-{millis}. Pass name= in **fields
or the call 400s. It is a create, not an upsert.
Discovering Tools Before You Register
discover_mcp_tools() probes a server and returns what it
advertises without adding anything to the registry. Useful for reviewing
a third-party server before you let your org call it.
probe = await sf.discover_mcp_tools("https://mcp.example.com/github")
for tool in probe["tools"]:
print(f"{tool['name']}: {tool['description']}")For well-known public servers, list_mcp_connectors()
returns preset definitions you can register by id rather than typing the
URL and transport by hand:
presets = await sf.list_mcp_connectors()
await sf.register_mcp_connector("github", scopes=["repo:read"])Calling a Tool
result = await sf.call_mcp_tool(
server_id="github-tools",
name="create_issue",
arguments={
"repo": "my-org/my-repo",
"title": "Bug: login fails on mobile",
"body": "Steps to reproduce...",
},
)
print(result["result"]["content"])The gateway applies the same policy checks to tool arguments that it applies to prompts, so a tool call carrying PII is caught before it reaches the upstream server.
call_mcp_tool currently posts JSON-RPC to
/{server_id}/mcp, which is not on the ingress allowlist (nginx 404).
Until the client is patched, post to POST /api/mcp with
x-mcp-server: {id}. Unsigned JSON-RPC is 402
unsigned_request while PREMIUM_MCP_GATE_ENABLED is on
(x-aperion-attestation).
Searching the Tool Catalog
The catalog is semantically indexed, so search works on intent rather than exact tool names. This is what you want when an agent has to pick a tool at runtime.
matches = await sf.search_mcp_tools("create a github issue", k=3)
for tool in matches:
print(f"{tool['server_id']}.{tool['name']}: {tool['description']}")
catalog = await sf.get_mcp_catalog() # every server
catalog = await sf.get_mcp_catalog("github-tools") # one serverTrust and Usage
Each registered server carries a trust record.
evaluate_mcp_trust() re-runs the evaluation and refreshes
the score.
trust = await sf.get_mcp_trust("github-tools")
print(trust["score"], trust["signals"])
await sf.evaluate_mcp_trust("github-tools")
usage = await sf.get_mcp_usage() # per-server call counts and cost totalsMCP Method Reference
| Method | Purpose |
|---|---|
list_mcp_servers() |
Registered servers |
register_mcp_server(server_id, url, transport, **fields) |
Register or update a server |
remove_mcp_server(server_id) |
Remove a server |
discover_mcp_tools(url, transport, **fields) |
Probe a URL without registering |
list_mcp_connectors() |
Known public connector presets |
register_mcp_connector(preset_id, **fields) |
Register a preset by id |
list_mcp_skills() |
Registered MCP skills |
get_mcp_catalog(server_id=None) |
Tool catalog, all servers or one |
search_mcp_tools(query, k=5) |
Semantic search across the catalog |
call_mcp_tool(server_id, name, arguments, request_id=1) |
Governed tool invocation |
get_mcp_trust(server_id=None) |
Trust registry records |
evaluate_mcp_trust(server_id) |
Re-run a trust evaluation |
get_mcp_usage() |
Call counts and cumulative cost |
A2A Agent Invocation
A2A tasks also have SDK methods now. send_agent_task()
builds the task envelope, routes it through the gateway, and returns the
parsed result.
Registering and Listing Agents
await sf.register_agent(
agent_id="summarizer-agent",
url="https://agents.internal/summarizer",
description="Long-document summarisation",
)
for agent in await sf.list_agents():
print(agent["agent_id"], agent["url"])Sending a Task
result = await sf.send_agent_task(
agent_id="summarizer-agent",
text="Summarise the Q4 earnings report.",
trace_id="trace-abc-123",
)
print(result["result"]["parts"][0]["text"])trace_id maps to the x-a2a-trace-id header
and is passed through every hop of a multi-agent chain, so you can pull
the logs for an entire chain with one id. Omit it and the gateway
generates one.
Task History and Capability Cards
tasks = await sf.list_agent_tasks("summarizer-agent")
detail = await sf.get_agent_task(tasks[0]["id"])
card = await sf.get_agent_card("summarizer-agent")
print(card["name"], card["capabilities"])| Method | Purpose |
|---|---|
list_agents() |
Registered A2A agents |
register_agent(agent_id, url, **fields) |
Register an agent |
get_agent(agent_id) /
remove_agent(agent_id) |
Read or remove one agent |
send_agent_task(agent_id, text, task_id=None, trace_id=None) |
Send a governed task |
list_agent_tasks(agent_id) |
Tasks handled by an agent |
get_agent_task(task_id) |
Status and result of one task |
get_agent_card(agent_id) |
Capability card (.well-known/agent.json) |
Agent Identity (AIDA)
AIDA gives each agent a cryptographic identity: an Ed25519 credential that proves which agent made a request and what it was allowed to do. Think of it as a virtual ID badge that a downstream service can verify without calling back to Smartflow.
Scopes are the delegated authority. An agent’s scopes should be a subset of what the human on whose behalf it acts can do.
Issuing a Credential
cred = await sf.issue_agent_credential(
agent_id="summarizer-agent",
scopes=["documents:read", "summaries:write"],
expires_in=86400,
)
print(cred["credential_id"])
print(cred["token"]) # give this to the agent; it is shown onceVerifying
check = await sf.verify_agent_credential(token, required_scope="documents:read")
if not check["valid"]:
raise PermissionError(check.get("reason", "invalid credential"))For verification outside Smartflow, publish the JWKS and let the other service check the signature offline. No network call to the gateway, no shared secret.
jwks = await sf.get_aida_jwks() # standard JWKS document
pub = await sf.get_aida_pubkey() # raw Ed25519 public keyRevoking
for c in await sf.get_agent_credentials_for("summarizer-agent"):
await sf.revoke_agent_credential(c["credential_id"])| Method | Purpose |
|---|---|
issue_agent_credential(agent_id, scopes=None, **fields) |
Issue an Ed25519 credential |
verify_agent_credential(token, required_scope=None) |
Verify and optionally enforce a scope |
list_agent_credentials() |
All issued credentials |
get_agent_credential(cred_id) |
One credential |
get_agent_credentials_for(agent_id) |
Credentials for one agent |
revoke_agent_credential(cred_id) |
Revoke a credential |
get_aida_pubkey() / get_aida_jwks() |
Issuer public key / JWKS |
aida_health() |
Subsystem health |
Policy Engine
Two layers, and they serve different jobs.
Guardrail policies are the enforcement primitives
the proxy evaluates on every request. Policy Perfect is
the builder service on port 7782 behind the dashboard’s
drag-and-drop UI, where policies get assigned to users, groups, roles,
and regions.
If you are enforcing, use the guardrail methods. If you are building tooling that mirrors the dashboard, use the Policy Perfect methods.
Guardrail Policies and Attachments
A policy defines what to check. An attachment says who it applies to.
await sf.create_policy({
"name": "block-ssn-outbound",
"description": "Reject prompts containing US SSNs",
"rules": [{"match": "pii.ssn", "action": "block"}],
})
await sf.attach_policy({
"policy_name": "block-ssn-outbound",
"scope_type": "group",
"scope_id": "contact-center",
})
for a in await sf.list_policy_attachments():
print(f"{a['policy_name']} -> {a['scope_type']}:{a['scope_id']}")resolve_policies() answers “what would apply to this
request?” without sending one. It is the fastest way to debug why a
policy did or didn’t fire.
resolution = await sf.resolve_policies({
"user_id": "jane@acme.com",
"groups": ["contact-center"],
"model": "gpt-4o",
})
print(resolution["policies"])
print(resolution["active_frameworks"])Policy Perfect Assignments
policies = await sf.list_builder_policies()
presets = await sf.list_policy_presets()
await sf.assign_policy(
policy_ids=["gdpr-baseline", "no-secrets-outbound"],
subject_type="group",
subject_id="eu-engineering",
)
for a in await sf.list_policy_assignments():
print(a["subject_type"], a["subject_id"], a["policy_ids"])subject_type accepts user,
group, role, and region. Group
and role values come from your identity provider, so they line up with
whatever Entra or Okta already returns.
Generating Policies From a Document
Point it at a regulation, a vendor DPA, or an internal standard and it drafts policies from the text. Generation is asynchronous — you get a job id back and poll it.
job = await sf.generate_policies_from_document(
text=open("data-handling-standard.md").read(),
filename="data-handling-standard.md",
)
status = await sf.get_document_job(job["job_id"])
if status["state"] == "completed":
drafts = await sf.get_document_job_results(job["job_id"])
for d in drafts["policies"]:
print(d["name"], "-", d["rationale"])Drafts are proposals, not live policy. Nothing takes effect until you create and attach it.
| Method | Purpose |
|---|---|
list_policies() / get_policy(name) |
Guardrail policy definitions |
create_policy(policy) /
delete_policy(name) |
Create/update or delete a policy |
list_guardrails() |
Available guardrail primitives |
list_policy_attachments() |
Policy → scope bindings |
attach_policy(attachment) /
detach_policy(attachment_id) |
Bind or unbind |
resolve_policies(context) |
What applies to a given context |
list_builder_policies() /
create_builder_policy(policy) |
Policy Perfect policies |
assign_policy(policy_ids, subject_type, subject_id, **fields) |
Assign to user/group/role/region |
list_policy_assignments() /
delete_policy_assignment(id) |
Manage assignments |
list_policy_presets() |
Preset templates |
generate_policies_from_document(text=None, file=None, ...) |
Draft policies from a document |
get_document_job(job_id) /
get_document_job_results(job_id) |
Poll and collect drafts |
Governance and Audit Evidence
These methods produce the artifacts an auditor or regulator asks for. They read from the same VAS traces the proxy already writes, so evidence is a query rather than a separate collection exercise.
AI System Inventory
Most AI regulation starts with the same question: what AI systems do
you run? get_detected_models() compares live traffic
against the inventory and returns what’s in use but undeclared — shadow
AI, effectively.
await sf.add_ai_inventory({
"name": "Claims Triage Assistant",
"purpose": "Route inbound claims by severity",
"risk_tier": "high",
"owner": "claims-ops@acme.com",
"models": ["gpt-4o"],
})
for m in await sf.get_detected_models():
print(f"undeclared: {m['model']} ({m['request_count']} requests)")EU AI Act Conformity
summary = await sf.get_conformity_summary()
print(f"{summary['covered']}/{summary['total']} articles covered")
for art in await sf.list_conformity_articles():
if art["status"] != "covered":
print(f"gap: Article {art['article']} - {art['title']}")
detail = await sf.get_conformity_article("13") # transparency obligationsExamination Reports
report = await sf.generate_examination_report({
"framework": "eu_ai_act",
"start": "2026-04-01",
"end": "2026-06-30",
})
full = await sf.get_examination_report(report["report_id"])Tamper-Evident Audit Chain
Audit entries are hash-chained. verify_audit_chain()
walks the chain and confirms nothing was altered or removed after the
fact, which is the property that makes the log usable as evidence rather
than just a log.
integrity = await sf.verify_audit_chain()
print(integrity["valid"], integrity["entries_verified"])
entries = await sf.get_audit_logs(limit=500, user_id="jane@acme.com")Information Barriers
Barriers stop data crossing between groups that are supposed to stay separated — research and trading, or two clients with a conflict.
await sf.create_barrier({
"name": "research-trading",
"side_a": ["research"],
"side_b": ["trading"],
"mode": "block",
})
violations = await sf.list_barrier_violations()
attestation = await sf.get_barrier_attestation()| Method | Purpose |
|---|---|
list_ai_inventory() /
add_ai_inventory(entry) |
AI system inventory |
get_detected_models() |
Models in traffic but not declared |
generate_examination_report(spec) |
Build a regulatory report |
list_examination_reports() /
get_examination_report(id) |
Retrieve reports |
get_conformity_summary() |
EU AI Act posture |
list_conformity_articles() /
get_conformity_article(article) |
Article coverage |
get_audit_logs(limit=100, **params) |
Audit-chain entries |
verify_audit_chain() |
Verify chain integrity |
list_barriers() /
create_barrier(barrier) |
Information barriers |
list_barrier_violations() |
Barrier violations |
get_barrier_attestation() |
Barrier attestation report |
Vector Stores and RAG
Retrieval runs through the gateway, so documents you ingest get the same compliance scanning and audit logging as prompts. That matters more than it sounds: RAG pipelines are a common way for regulated data to reach a model without anyone noticing.
store = await sf.create_vector_store(name="policy-docs")
await sf.add_vector_store_file(
store["id"],
content=open("employee-handbook.md").read(),
filename="employee-handbook.md",
)
hits = await sf.search_vector_store(store["id"], "parental leave", top_k=5)
for h in hits["results"]:
print(f"{h['score']:.3f} {h['text'][:80]}")The rag_* methods are the higher-level path — chunking,
embedding, retrieval, and generation in one call.
await sf.rag_ingest(content=open("q4-report.md").read(), filename="q4-report.md")
answer = await sf.rag_query("What drove the Q4 margin change?", top_k=4)
print(answer["answer"])
for src in answer["sources"]:
print(" -", src["filename"])| Method | Purpose |
|---|---|
create_vector_store(name, **fields) |
Create a store |
list_vector_stores() /
get_vector_store(id) |
List or read stores |
delete_vector_store(id) |
Delete a store |
add_vector_store_file(store_id, **fields) |
Add a document |
search_vector_store(store_id, query, top_k=5) |
Semantic search |
rag_ingest(**fields) |
Chunk and embed a document |
rag_query(query, **fields) |
Retrieval-augmented query |
SSO and Identity Configuration
Identity is what makes the rest of the governance stack meaningful. Without it, audit logs say “someone” and policies can only be applied globally. With Entra or Okta connected, every VAS log entry carries a real user, and policies bind to the groups your directory already maintains.
await sf.set_sso_config({
"provider": "entra",
"issuer": "https://login.microsoftonline.com/<tenant-id>/v2.0",
"client_id": "<client-id>",
"client_secret": "<client-secret>",
"group_claim": "groups",
})
status = await sf.get_sso_status()
print(status["enabled"], status["provider"])
for team in await sf.list_sso_teams():
print(team["id"], team["name"])list_sso_teams() returns groups pulled from the
provider. Feed those ids straight into assign_policy() as
the subject_id and policy assignment stays in sync with
your directory instead of drifting into a second copy of your org
chart.
Secrets are write-only. get_sso_config() returns the
configuration with client_secret redacted.
| Method | Purpose |
|---|---|
get_sso_config() /
set_sso_config(config) |
Read or write identity config |
get_sso_status() |
Whether SSO is active, and which provider |
list_sso_teams() |
Groups from the identity provider |
Monitoring and Analytics
Cache Performance
stats = await sf.get_cache_stats()
print("=== CACHE PERFORMANCE ===")
print(f"Hit Rate: {stats.hit_rate:.1%}")
print(f"")
print(f"Layer Breakdown:")
print(f" L1 (Memory): {stats.l1_hits:,} hits")
print(f" L2 (Semantic): {stats.l2_hits:,} hits")
print(f" L3 (Exact): {stats.l3_hits:,} hits")
print(f"")
print(f"Savings:")
print(f" Tokens saved: {stats.tokens_saved:,}")
print(f" Cost saved: ${stats.cost_saved_cents / 100:.2f}")CacheStats fields: hits,
misses, hit_rate, tokens_saved,
cost_saved_cents, l1_hits,
l2_hits, l3_hits, entries
Provider Health
providers = await sf.get_provider_health()
print("=== PROVIDER STATUS ===")
for p in providers:
print(f"{p.provider}")
print(f" Status: {p.status}")
print(f" Latency: {p.latency_ms:.0f}ms")
print(f" Success: {p.success_rate:.1%}")
print(f" Requests: {p.requests_total:,}")ProviderHealth fields: provider,
status, latency_ms, success_rate,
error_rate, requests_total,
last_updated
System Health
health = await sf.health_comprehensive()
print(health.status) # "healthy" | "degraded" | "unhealthy"
print(health.uptime_seconds)
print(health.version)Quick liveness check:
status = await sf.health()
assert status["status"] == "ok"Audit Logs (VAS)
async def get_logs(
limit: int = 100,
offset: int = 0,
provider: Optional[str] = None,
model: Optional[str] = None,
days: int = 30,
) -> List[VASLog]Retrieve VAS audit logs from the hot Redis tier (recent) and cold MongoDB tier (archived). Logs are returned newest-first.
logs = await sf.get_logs(limit=50, provider="openai", days=7)
print("=== RECENT AI INTERACTIONS ===")
for log in logs:
route = log.routing_strategy or "direct"
cached_badge = f"[CACHE:{log.metacache.tokens_saved} tokens saved]" if log.metacache.hit else ""
print(f"[{log.timestamp}] {log.provider}/{log.model} {cached_badge}")
print(f" Latency: {log.latency_ms}ms | Tokens: {log.tokens_used} | Routing: {route}")
print(f" Compliance: {log.compliance_status} | Conv: {log.conversation_id}")
if log.compliance_violations:
print(f" Violations: {log.compliance_violations}")Pagination example:
page_size = 100
page = 0
while True:
batch = await sf.get_logs(limit=page_size, offset=page * page_size)
if not batch:
break
process(batch)
page += 1Analytics
async def get_analytics(
start_date: Optional[str] = None,
end_date: Optional[str] = None,
) -> Dict[str, Any]data = await sf.get_analytics(
start_date="2026-02-01",
end_date="2026-02-19",
)Routing
get_routing_status()
Current routing state: active provider, fallback chain, last failure.
status = await sf.get_routing_status()force_provider()
Force all routing to a specific provider for a duration.
async def force_provider(
provider: str,
duration_seconds: int = 300,
) -> Dict[str, Any]# Force to OpenAI for 10 minutes during an Anthropic outage
await sf.force_provider("openai", duration_seconds=600)SmartflowAgent
Higher-level agent with conversation memory, compliance scanning, and tool support.
from smartflow import SmartflowClient, SmartflowAgent
async with SmartflowClient("http://smartflow:7775") as sf:
agent = SmartflowAgent(
client=sf,
name="TechSupport",
model="gpt-4o",
system_prompt="""You are a senior technical support engineer.
Guidelines:
- Be patient and thorough
- Ask clarifying questions when needed
- Provide step-by-step solutions
- Never ask for or repeat sensitive information""",
temperature=0.7,
compliance_policy="enterprise_standard",
enable_compliance_scan=True, # Auto-scan inputs and outputs
user_id="support_session_123",
org_id="tech_company",
)
# Conversation with full context memory
print(await agent.chat("My application keeps crashing"))
print(await agent.chat("It's a Python web app using Flask"))
print(await agent.chat("Here's the error: MemoryError"))
print(f"Messages exchanged: {agent.message_count}")
agent.clear_history()| Method | Description |
|---|---|
chat(message, scan_input=True, scan_output=True) |
Send message; raises ComplianceError if blocked |
clear_history() |
Reset conversation, preserve system prompt |
get_history() |
Return copy of message history |
message_count |
Number of messages in history |
SmartflowWorkflow
Chain AI operations with branching logic.
from smartflow import SmartflowClient, SmartflowWorkflow
async with SmartflowClient("http://smartflow:7775") as sf:
workflow = SmartflowWorkflow(sf, name="ContentPipeline")
workflow.add_step(
name="analyze",
action="chat",
config={
"prompt": "Analyze the tone and intent of this text: {input}",
"model": "gpt-4o-mini",
},
next_steps=["compliance_check"],
)
workflow.add_step(
name="compliance_check",
action="compliance_check",
config={"content": "{input}"},
next_steps=["route"],
)
workflow.add_step(
name="route",
action="condition",
config={
"field": "output",
"cases": {
"positive": "enhance",
"negative": "review",
"neutral": "publish",
},
},
)
result = await workflow.execute({"input": "This product exceeded my expectations!"})
print(f"Success: {result.success}")
print(f"Path taken: {' -> '.join(result.steps_executed)}")
print(f"Execution time: {result.execution_time_ms:.0f}ms")
print(f"Total tokens: {result.total_tokens}")Step actions:
| Action | Config fields | Description |
|---|---|---|
"chat" |
prompt, model,
temperature |
Chat completion; {input} / {output} are
template variables |
"compliance_check" |
content |
Rule-based compliance scan |
"condition" |
field, cases, default |
Branch on a context value |
SyncSmartflowClient
Synchronous wrapper. Every async method is available without
await.
from smartflow import SyncSmartflowClient
sf = SyncSmartflowClient("http://smartflow:7775", api_key="sk-sf-...")
reply = sf.chat("Hello!")
emb = sf.embeddings("Hello", model="text-embedding-3-small")
img = sf.image_generation("A sunset", model="dall-e-3")
transcript = sf.audio_transcription(open("audio.mp3", "rb"), model="whisper-1")
audio = sf.text_to_speech("Hello!", voice="nova")
ranked = sf.rerank("What is the return policy?", ["doc1", "doc2"])
stats = sf.get_cache_stats()
logs = sf.get_logs(limit=20)
sf.close()Configuration Reference
Client Options
sf = SmartflowClient(
base_url="http://smartflow:7775", # Proxy endpoint
api_key="sk-sf-...", # Virtual key for authentication
timeout=30.0, # Request timeout in seconds
management_port=7778, # Health, metrics, routing API
compliance_port=7777, # Compliance API
bridge_port=3500, # Hybrid bridge (cross-instance logs)
)From Environment Variables
import os
from smartflow import SmartflowClient
sf = SmartflowClient(
base_url=os.environ["SMARTFLOW_URL"],
api_key=os.environ.get("SMARTFLOW_API_KEY"),
)Error Handling
from smartflow import (
SmartflowClient,
SmartflowError,
ConnectionError,
ComplianceError,
RateLimitError,
TimeoutError,
)
import asyncio
try:
async with SmartflowClient("http://smartflow:7775") as sf:
response = await sf.chat("Hello!")
except ConnectionError:
print("Cannot connect to Smartflow proxy")
except ComplianceError as e:
print(f"Blocked by compliance policy: {e}")
except RateLimitError:
print("Rate limited — backing off")
await asyncio.sleep(60)
except TimeoutError:
print("Request timed out")
except SmartflowError as e:
print(f"Smartflow error: {e}")| Exception | Condition |
|---|---|
SmartflowError |
Base class for all SDK errors |
ConnectionError |
Cannot connect to proxy |
AuthenticationError |
401 — invalid or missing key |
RateLimitError |
429 — rate limit hit |
ComplianceError |
403 — request blocked by compliance policy |
ProviderError |
Upstream provider error |
TimeoutError |
Request timeout |
Real-World Use Cases
Use Case 1: Secure Customer Support Bot
Challenge: Build a customer support chatbot that handles sensitive information while maintaining PCI-DSS and GDPR compliance.
import asyncio
from smartflow import SmartflowClient, SmartflowAgent
class SecureCustomerSupportBot:
"""
Customer support bot with built-in PII protection.
- Automatic PII detection and blocking
- Conversation memory
- Audit trail for compliance
- Behavioral analysis per customer
"""
def __init__(self, smartflow_url: str):
self.sf_url = smartflow_url
async def handle_customer_session(self, customer_id: str, organization: str):
async with SmartflowClient(self.sf_url) as sf:
agent = SmartflowAgent(
client=sf,
name="SecureSupport",
model="gpt-4o",
system_prompt="""You are a helpful customer support agent for a financial services company.
CRITICAL RULES:
1. NEVER ask customers for full credit card numbers, SSNs, or passwords
2. If a customer shares sensitive info, acknowledge receipt but do not repeat it
3. For account verification, use last 4 digits only
4. Always offer secure channels for sensitive transactions""",
compliance_policy="pci_dss_strict",
enable_compliance_scan=True,
user_id=f"customer_{customer_id}",
org_id=organization,
)
print("SecureSupport: Hello! How can I help you today?")
while True:
user_input = input("Customer: ")
if user_input.lower() == "quit":
break
try:
response = await agent.chat(user_input)
print(f"SecureSupport: {response}")
except Exception as e:
if "compliance" in str(e).lower():
print("SecureSupport: I noticed you shared some sensitive information.")
print("For your protection, please use our secure verification process.")
else:
print(f"Error: {e}")
async def main():
bot = SecureCustomerSupportBot("http://smartflow:7775")
await bot.handle_customer_session(customer_id="12345", organization="fintech_corp")
# asyncio.run(main())What This Demonstrates: - PII detection blocks sensitive data before it reaches the AI provider - Behavioral tracking learns normal patterns per customer - Complete audit trail for compliance audits - Graceful handling of compliance violations
Use Case 2: Cost-Optimized Content Generation Pipeline
Challenge: Generate thousands of product descriptions daily while minimizing API costs.
import asyncio
from dataclasses import dataclass
from typing import List
from smartflow import SmartflowClient
@dataclass
class Product:
id: str
name: str
category: str
features: List[str]
price: float
class ContentGenerationPipeline:
"""
High-volume content generation with intelligent caching.
Cost optimization:
1. Similar products hit the semantic cache (60-80% savings)
2. Smaller models for simple tasks
3. Structured prompts to maximize cache hit potential
"""
def __init__(self, smartflow_url: str):
self.sf_url = smartflow_url
async def generate_description(self, sf: SmartflowClient, product: Product) -> dict:
# Structure prompt to maximize cache hits across similar products
prompt = f"""Write a compelling product description.
Category: {product.category}
Product: {product.name}
Key Features: {', '.join(product.features)}
Price Point: ${product.price:.2f}
Requirements:
- 2-3 sentences
- Highlight key benefits
- Include call-to-action
- Professional tone"""
response = await sf.chat_completions(
messages=[{"role": "user", "content": prompt}],
model="gpt-4o-mini",
temperature=0.7,
)
return {
"product_id": product.id,
"description": response.content,
"cached": response.cached,
"tokens": response.usage.total_tokens,
}
async def process_catalog(self, products: List[Product]) -> dict:
async with SmartflowClient(self.sf_url) as sf:
initial_stats = await sf.get_cache_stats()
results = []
cached_count = 0
total_tokens = 0
for i, product in enumerate(products):
result = await self.generate_description(sf, product)
results.append(result)
if result["cached"]:
cached_count += 1
total_tokens += result["tokens"]
if (i + 1) % 10 == 0:
print(f"Processed {i + 1}/{len(products)} products...")
final_stats = await sf.get_cache_stats()
tokens_saved = final_stats.tokens_saved - initial_stats.tokens_saved
cost_saved = final_stats.cost_saved_cents - initial_stats.cost_saved_cents
cache_hit_rate = cached_count / len(products) if products else 0
return {
"results": results,
"summary": {
"total_products": len(products),
"cache_hit_rate": f"{cache_hit_rate:.1%}",
"tokens_used": total_tokens,
"tokens_saved": tokens_saved,
"cost_saved": f"${cost_saved / 100:.2f}",
},
}
async def main():
pipeline = ContentGenerationPipeline("http://smartflow:7775")
products = [
Product("SKU001", "Wireless Bluetooth Headphones", "Electronics",
["Noise cancelling", "40hr battery", "Premium sound"], 149.99),
Product("SKU002", "Wireless Earbuds Pro", "Electronics",
["Active noise cancelling", "36hr battery", "Hi-Fi audio"], 129.99),
Product("SKU003", "Over-Ear Gaming Headset", "Electronics",
["7.1 surround", "Noise isolation", "RGB lighting"], 89.99),
]
result = await pipeline.process_catalog(products)
print(f"Products processed: {result['summary']['total_products']}")
print(f"Cache hit rate: {result['summary']['cache_hit_rate']}")
print(f"Tokens saved: {result['summary']['tokens_saved']:,}")
print(f"Cost saved: {result['summary']['cost_saved']}")
# asyncio.run(main())What This Demonstrates: - Semantic caching
recognizes similar products and reuses responses - Structured prompts
maximize cache hit potential - Real-time cost tracking via
cost_saved_cents - Batch processing for high-volume
workloads
Use Case 3: Multi-Agent Research and Report Generation
Challenge: Coordinate multiple specialized AI agents to produce a polished, auditable research report.
import asyncio
from datetime import datetime
from smartflow import SmartflowClient, SmartflowAgent
class ResearchOrchestrator:
"""
Multi-agent research system.
Agents:
1. Researcher — gathers and summarizes information
2. Analyst — identifies patterns and insights
3. Writer — produces polished executive report
4. Editor — reviews for accuracy and clarity (deep mode only)
All interactions logged for full auditability.
"""
def __init__(self, smartflow_url: str):
self.sf_url = smartflow_url
async def research_topic(self, topic: str, depth: str = "standard") -> dict:
async with SmartflowClient(self.sf_url) as sf:
timestamp = datetime.now().isoformat()
researcher = SmartflowAgent(
client=sf, name="Researcher", model="gpt-4o",
system_prompt="""You are a thorough research analyst.
Provide structured findings covering: current state, recent developments,
key players, and challenges. Output as organized bullet points.""",
user_id="research_system", org_id="analytics_dept",
)
print(f"Researcher: Investigating '{topic}'...")
research_data = await researcher.chat(
f"Research this topic: {topic}\n\n"
f"Cover: current state and facts, recent developments, "
f"key players, challenges and opportunities."
)
if depth == "quick":
return {"topic": topic, "timestamp": timestamp,
"report": research_data, "agents_used": ["Researcher"]}
analyst = SmartflowAgent(
client=sf, name="Analyst", model="gpt-4o",
system_prompt="""You are a strategic analyst.
Identify non-obvious patterns, provide data-driven insights,
make predictions, and highlight risks and opportunities.""",
user_id="research_system", org_id="analytics_dept",
)
print("Analyst: Analyzing findings...")
analysis = await analyst.chat(
f"Analyze this research and provide strategic insights:\n\n"
f"{research_data}\n\nFocus on hidden patterns, future implications, "
f"and strategic recommendations."
)
writer = SmartflowAgent(
client=sf, name="Writer", model="gpt-4o",
system_prompt="""You are an expert business writer.
Synthesize research and analysis into a coherent executive narrative.
Lead with key findings and recommendations.""",
user_id="research_system", org_id="analytics_dept",
)
print("Writer: Composing report...")
draft_report = await writer.chat(
f"Write an executive report from this research and analysis:\n\n"
f"RESEARCH:\n{research_data}\n\nANALYSIS:\n{analysis}\n\n"
f"Include: Executive Summary, Key Findings, "
f"Strategic Analysis, Recommendations, Conclusion."
)
if depth == "standard":
return {"topic": topic, "timestamp": timestamp,
"report": draft_report,
"agents_used": ["Researcher", "Analyst", "Writer"]}
# Deep mode: add Editor
editor = SmartflowAgent(
client=sf, name="Editor", model="gpt-4o",
system_prompt="""You are a senior business editor.
Review for accuracy and clarity, improve flow, ensure consistent tone,
fact-check against source research, polish for executive presentation.""",
temperature=0.3,
user_id="research_system", org_id="analytics_dept",
)
print("Editor: Polishing final report...")
final_report = await editor.chat(
f"Edit and polish this report:\n\n{draft_report}\n\n"
f"Source research for fact-checking:\n{research_data}"
)
logs = await sf.get_logs(limit=10)
return {
"topic": topic,
"timestamp": timestamp,
"report": final_report,
"agents_used": ["Researcher", "Analyst", "Writer", "Editor"],
"audit_trail": [
{"timestamp": log.timestamp, "model": log.model,
"tokens": log.tokens_used, "cached": log.cached}
for log in logs
],
}
async def main():
orchestrator = ResearchOrchestrator("http://smartflow:7775")
result = await orchestrator.research_topic(
topic="The impact of AI agents on enterprise software development in 2026",
depth="deep",
)
print("=" * 60)
print(f"Topic: {result['topic']}")
print(f"Generated: {result['timestamp']}")
print(f"Agents: {' -> '.join(result['agents_used'])}")
print("=" * 60)
print(result["report"])
print("\nAUDIT TRAIL:")
for entry in result.get("audit_trail", []):
cached = "[CACHED]" if entry["cached"] else ""
print(f" [{entry['timestamp']}] {entry['model']} — {entry['tokens']} tokens {cached}")
# asyncio.run(main())What This Demonstrates: - Coordinated multi-agent workflows with specialized roles - Progressive refinement through an agent chain - Complete per-request audit trail for every AI interaction - Organizational context tracking for behavioral analysis
Response Types
AIResponse
| Field | Type | Description |
|---|---|---|
content |
str |
First choice text |
choices |
list |
Full choices array |
usage |
Usage |
Token usage (prompt_tokens,
completion_tokens, total_tokens) |
model |
str |
Model used |
id |
str |
Response ID |
cached |
bool |
True if served from MetaCache |
cache_hit_type |
str |
"exact", "semantic" (Phase 4 VectorLite
BERT KNN hit), or None |
provider |
str |
Provider that served the request |
CacheStats
| Field | Type |
|---|---|
hit_rate |
float |
hits / misses |
int |
l1_hits / l2_hits /
l3_hits |
int |
tokens_saved |
int |
cost_saved_cents |
int |
entries |
int |
ComplianceResult
| Field | Type |
|---|---|
has_violations |
bool |
compliance_score |
float |
violations |
list[str] |
pii_detected |
list[str] |
risk_level |
str — "low" / "medium" /
"high" / "critical" |
recommendations |
list[str] |
redacted_content |
str \| None |
IntelligentScanResult
| Field | Type |
|---|---|
has_violations |
bool |
risk_score |
float — 0.0 to 1.0 |
recommended_action |
str — "Allow" / "AllowAndLog"
/ "Review" / "Block" |
explanation |
str |
regex_violations |
list |
ml_violations |
list |
behavior_deviations |
list |
processing_time_us |
int |
VASLog
| Field | Type | Description |
|---|---|---|
request_id |
str |
Unique ID — matches x-smartflow-request-id response
header |
timestamp |
str |
ISO-8601 UTC |
provider |
str |
openai, anthropic, google,
etc. |
model |
str |
Actual model returned by provider (or requested model for cache hits) |
model_provider |
str |
Provider name only — never contains API key material |
tokens_used |
int |
Total tokens (prompt + completion) |
cost |
float |
Estimated USD cost |
latency_ms |
int |
End-to-end proxy latency. 5 for cache hits. |
processing_time_ms |
int |
Total processing time |
content_type |
str |
chat, completion, image,
etc. |
user_id |
str \| None |
Extracted from JWT, x-smartflow-user-id, or
x-user-id header |
conversation_id |
str \| None |
Set when x-conversation-id or x-session-id
header is present |
conversation_stage |
str \| None |
Conversation lifecycle stage |
routing_strategy |
str \| None |
"direct", "cache", or configured strategy
("latency", "tag", etc.) |
routing_reason |
str \| None |
Human-readable reason, e.g. "provider:openai" or
"cache_hit:tier=L1" |
compliance_status |
str \| None |
"compliant" or "violated" |
compliance_violations |
str \| None |
JSON-encoded list of violation objects when
compliance_status == "violated" |
compliance |
ComplianceInfo |
Full compliance object including data_classification,
compliance_score, violations_details,
regulatory_frameworks |
metacache |
MetacacheData |
Cache hit info: hit (bool), query (str),
tokens_saved (int) |
metrics |
ProviderMetrics |
Provider-level metrics: prompt_tokens,
completion_tokens, processing_time_ms,
success |
ProviderHealth
| Field | Type |
|---|---|
provider |
str |
status |
str — "healthy" / "degraded"
/ "unhealthy" |
latency_ms |
float |
success_rate |
float |
error_rate |
float |
requests_total |
int |
last_updated |
str |
SystemHealth
| Field | Type |
|---|---|
status |
str |
uptime_seconds |
int |
version |
str |
providers |
dict |
cache |
dict |
timestamp |
str |
WorkflowResult
| Field | Type |
|---|---|
success |
bool |
output |
str |
steps_executed |
list[str] |
errors |
list |
total_tokens |
int |
total_cost_cents |
int |
execution_time_ms |
float |
Summary
| Feature | Benefit |
|---|---|
| Semantic Cache (3-tier) | 60–80% cost reduction, no external vector DB |
| ML Compliance Engine | Real-time PII protection with adaptive learning |
| Smart Routing | Latency, cost, or priority-based provider selection |
| Full Audit Trail (VAS) | Complete compliance visibility across every request |
| MCP Tool Gateway | Register and invoke external tools with shared auth and budgeting |
| A2A Agent Orchestration | Route tasks across agents with full traceability |
| Agent Builder | Production-ready conversational AI with memory and compliance |
| Workflow Orchestration | Multi-step AI pipelines with branching and error handling |
Resources
- PyPI: https://pypi.org/project/smartflow-sdk/
- Documentation: https://docs.aperion.ai/smartflow-sdk-reference.html
Support
- Email: support@smartflow.ai
Changelog
v0.5.0 — 2026-08-07
Agent tool-call efficiency, built on top of dual-mode operation from v0.4.0. Full write-up: Tool-Call Efficiency guide →
- New
@smartflow_tooldecorator — wraps an existing tool function with almost no code change and participates automatically in trajectory caching. - New
smartflow_task()context manager — groups a sequence of tool calls under one cacheable task key. get_tool_call_benchmark()— live tool-call-reduction percentage computed from real MCP cache hit/miss counts.discover_tools(),get_discovery_cache_stats()— tool schemas served from a discovery cache instead of re-querying the origin MCP server on every call.lookup_trajectory(),start_trajectory(),record_trajectory_step(),commit_trajectory(),discard_trajectory(),get_trajectory_stats()— cache and replay a whole multi-step tool-call sequence, not just individual calls.compress_text()gained an optionalquality_checkflag — scores whether lossy semantic compression stayed faithful to the original via an HHEM grounding check, returned asquality_delta.
v0.4.0 — 2026-07-27
Published to PyPI. The packaged version had been stuck at
0.2.0 while the code moved on, so this release
re-synchronises the wheel, the code, and this page.
Dual-mode operation
SmartflowClient()now works with no arguments. Mode resolves in priority order: explicitbase_url, thenSMARTFLOW_GATEWAY_URL, then~/.smartflow/config.yaml, then direct mode.- Direct mode calls providers straight from the client — OpenAI,
Anthropic, Gemini, Ollama, and any OpenAI-compatible
local/endpoint. Chat, streaming, and embeddings all work with no extra dependencies. - Gateway-only features raise
DirectModeErrornaming the feature, instead of failing with a connection error. - Added
sf.mode,sf.is_gateway_mode(),sf.is_direct_mode(). - Added the
smartflowCLI:configure,status,chat.
MCP — list_mcp_servers(),
register_mcp_server(), remove_mcp_server(),
discover_mcp_tools(), list_mcp_connectors(),
register_mcp_connector(), list_mcp_skills(),
get_mcp_catalog(), search_mcp_tools(),
get_mcp_usage(), call_mcp_tool(),
get_mcp_trust(), evaluate_mcp_trust(). MCP
calls no longer require hand-written JSON-RPC.
A2A — list_agents(),
register_agent(), get_agent(),
remove_agent(), list_agent_tasks(),
get_agent_task(), get_agent_card(),
send_agent_task().
AIDA agent identity —
issue_agent_credential(),
verify_agent_credential(),
list_agent_credentials(),
get_agent_credential(),
get_agent_credentials_for(),
revoke_agent_credential(), get_aida_pubkey(),
get_aida_jwks(), aida_health().
Policy engine — list_policies(),
create_policy(), get_policy(),
delete_policy(), list_guardrails(),
list_policy_attachments(), attach_policy(),
detach_policy(), resolve_policies(). Policy
Perfect on port 7782: list_builder_policies(),
create_builder_policy(), assign_policy(),
list_policy_assignments(),
delete_policy_assignment(),
list_policy_presets(),
generate_policies_from_document(),
get_document_job(),
get_document_job_results(). New client keyword
policy_perfect_port=7782.
Governance and audit —
list_ai_inventory(), add_ai_inventory(),
get_detected_models(),
generate_examination_report(),
list_examination_reports(),
get_examination_report(),
get_conformity_summary(),
list_conformity_articles(),
get_conformity_article(), get_audit_logs(),
verify_audit_chain(), list_barriers(),
create_barrier(), list_barrier_violations(),
get_barrier_attestation().
Vector stores and RAG —
create_vector_store(), list_vector_stores(),
get_vector_store(), delete_vector_store(),
add_vector_store_file(),
search_vector_store(), rag_ingest(),
rag_query().
SSO — get_sso_config(),
set_sso_config(), get_sso_status(),
list_sso_teams().
Fixed
health()called/healthon the proxy, which the proxy does not serve. It now hits/health/liveliness. Addedreadiness()for/health/readiness. Management-API health is stillhealth_comprehensive().- Package metadata URLs pointed at
docs.smartflow.aiand dead GitHub repositories. They now point ataperion.aiand this documentation. - The
0.3.0media methods (embeddings(),image_generation(),audio_transcription(),text_to_speech(),stream_chat(),rerank(),list_models()) existed in code but were never packaged. They ship here.
v0.3.1 — 2026
- Added
chatbot_query()— natural-language operational queries - Added
get_logs_hybrid()— unified audit log across all instances via hybrid bridge - Added
submit_compliance_feedback()— true/false-positive corrections for ML model retraining - Added
get_learning_status(),get_learning_summary()— adaptive learning progress - Added
get_ml_stats()— ML engine pattern counts and accuracy - Added
get_org_summary(),get_org_baseline()— organizational compliance baselines - Added
get_persistence_stats(),save_compliance_data(),get_intelligent_health() - Documented MCP tool invocation and catalog search patterns
- Documented A2A task invocation and capability card retrieval
- Corrected
IntelligentScanResultfield names (latency_msnotavg_latency_ms,cost_saved_centsnotcost_saved_usd) - Corrected
get_analytics()signature:start_date/end_dateparameters - Corrected
SystemHealthfield names:status,uptime_seconds,providers
v0.3.0
- Added
image_generation()— multi-provider image generation - Added
audio_transcription()— multipart upload, Groq/Deepgram/Fireworks routing - Added
text_to_speech()— returns raw audio bytes - Added
stream_chat()— async SSE iterator - Added
rerank()— Cohere-compatible document reranking - Extended
embeddings()withencoding_format,dimensions,input_type - New providers: Groq, Deepgram, Fireworks AI
v0.2.0
- Added
SmartflowAgentwith compliance scanning and conversation memory - Added
SmartflowWorkflowfor multi-step AI pipelines - Added
intelligent_scan,submit_compliance_feedback - Added
get_provider_health,get_cache_stats,health_comprehensive
v0.1.0
- Initial release:
chat,chat_completions,embeddings,claude_message - VAS audit logging,
SyncSmartflowClient
© 2026 Langsmart, Inc. All rights reserved. Smartflow is a trademark of Langsmart, Inc.