Python Client Libraries Integration¶
Build Python applications and agents directly on Amazon Bedrock models with stdapi.ai, using the same LangChain, pydantic-ai and OpenAI Agents SDK client classes you would use against OpenAI or Anthropic directly—three client-side changes: the base URL, the API key, and — where the name differs from what the client already sends — the model name, now picked from every provider in the catalogue rather than one vendor's list.
At a glance¶
-
Standard Client Classes, No Fork
ChatOpenAI,OpenAIEmbeddings,ChatAnthropic, pydantic-ai'sOpenAIChatModeland the Agents SDK'sOpenAIResponsesModelall accept a custom base URL directly—no gateway-specific SDK to install. -
Access Amazon Bedrock Models
Claude, Nova, DeepSeek, Qwen, and 100+ models, called through the same classes your code already imports. -
Tool Calling and Structured Output
bind_tools,with_structured_output, and pydantic-ai's typed tool registration all work end to end against Bedrock models. -
Pay-Per-Use Pricing
No per-request markup. Pay only Amazon Bedrock rates for the calls your application makes.
%%{init: {'flowchart': {'htmlLabels': true}} }%%
flowchart LR
app["<img src='../styles/logo_python.svg' style='height:64px;width:auto;vertical-align:middle;' /> Your Python App<br/>(LangChain, pydantic-ai, Agents SDK)"] --> stdapi["<img src='../styles/logo.svg' style='height:64px;width:auto;vertical-align:middle;' /> stdapi.ai"]
stdapi --> bedrock["<img src='../styles/logo_amazon_bedrock.svg' style='height:64px;width:auto;vertical-align:middle;' /> Amazon Bedrock"] About These Libraries¶
🔗 Links: LangChain | pydantic-ai | OpenAI Agents SDK | Ollama Python
These are among the most widely used Python libraries for building LLM-backed applications and agents. Every one of them accepts a custom base URL and API key as constructor arguments—no plugin, wrapper, or extension needed.
What you can build:
- Custom agents - Tool-calling loops, structured output, and multi-turn conversations in your own Python code
- RAG applications - Combine chat models with
OpenAIEmbeddingsfor retrieval, see RAG Pipelines - Stateful and voice agents - Server-side conversations, hosted retrieval and spoken sessions through the OpenAI Agents SDK
- Internal services - Backend applications and scripts that call Bedrock models without a UI or CLI in between
- Ollama-native code - Applications already written against the Ollama client, repointed at the gateway without changing their calls
Connect Your Own Instance¶
Point any Python process—wherever it runs—at your stdapi.ai gateway. Nothing below requires the AWS sample in Part 2.
Prerequisites¶
Before you start
- ✓ stdapi.ai deployed - See deployment guide or run locally with Docker; see Part 2 for a Terraform-deployed gateway
- ✓ Your stdapi.ai URL - e.g.,
https://api.example.comorhttp://localhost:8000for local - ✓ Your API key - From Terraform output or configuration (optional for local development)
LangChain¶
Chat — langchain-openai¶
ChatOpenAI takes the gateway's /v1 base URL directly. .invoke(), .stream(), bind_tools(), and with_structured_output() all work unchanged against Bedrock models.
ChatOpenAI
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="anthropic.claude-fable-5",
base_url="https://YOUR_STDAPI_URL/v1",
api_key="YOUR_STDAPI_KEY",
)
response = model.invoke("Name the largest planet in the solar system.")
print(response.content)
See Chat Completions API for the full parameter and model reference.
Embeddings — langchain-openai¶
OpenAIEmbeddings also takes the /v1 base URL, but its default behavior needs one extra setting.
Disable client-side tokenization
OpenAIEmbeddings tokenizes its input with tiktoken by default and sends the gateway a token-ID array instead of text—an artifact the embeddings endpoint rejects with a 400 error rather than silently embedding something other than what was asked for. Set check_embedding_ctx_length=False to send plain text instead:
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(
model="amazon.titan-embed-text-v2:0",
base_url="https://YOUR_STDAPI_URL/v1",
api_key="YOUR_STDAPI_KEY",
check_embedding_ctx_length=False,
)
vector = embeddings.embed_query("Your text here")
Without this setting, every call to embed_query or embed_documents fails—this is the first thing to check if OpenAIEmbeddings returns a 400 against stdapi.ai but works against OpenAI directly.
See Embeddings API for supported models.
Chat — langchain-anthropic¶
ChatAnthropic takes the gateway's /anthropic base URL and works with every model the route serves, not only Claude.
ChatAnthropic
from langchain_anthropic import ChatAnthropic
model = ChatAnthropic(
model_name="anthropic.claude-fable-5",
base_url="https://YOUR_STDAPI_URL/anthropic",
api_key="YOUR_STDAPI_KEY",
)
response = model.invoke("Name the largest planet in the solar system.")
print(response.content)
See Anthropic Messages API for the full parameter and model reference.
pydantic-ai¶
pydantic-ai's OpenAIChatModel reaches the gateway through an OpenAIProvider carrying the base URL and API key, and works through the same Chat Completions API route as ChatOpenAI above—including reasoning models and multi-turn tool-calling loops.
Agent with a custom base URL
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider
model = OpenAIChatModel(
"anthropic.claude-fable-5",
provider=OpenAIProvider(
base_url="https://YOUR_STDAPI_URL/v1", api_key="YOUR_STDAPI_KEY"
),
)
agent = Agent(model, system_prompt="You are a helpful assistant.")
result = agent.run_sync("Name the largest planet in the solar system.")
print(result.output)
Reasoning-capable models (Claude, DeepSeek, and others) work through the same agent, including a full tool-calling loop that reasons on one turn and calls a registered tool on the next. Request a reasoning effort level per call with model_settings:
from pydantic_ai.models.openai import OpenAIChatModelSettings
result = agent.run_sync(
"Call the registered tool, then answer.",
model_settings=OpenAIChatModelSettings(openai_reasoning_effort="low"),
)
OpenAI Agents SDK¶
The OpenAI Agents SDK speaks the Responses API natively, so pointing its client at the gateway also hands its agents the server-side surfaces the Responses route serves — stored conversations, hosted retrieval and the realtime voice session included.
An agent bound to the gateway
from agents import Agent, Runner, set_tracing_disabled
from agents.models.openai_responses import OpenAIResponsesModel
from openai import AsyncOpenAI
set_tracing_disabled(True)
client = AsyncOpenAI(base_url="https://YOUR_STDAPI_URL/v1", api_key="YOUR_STDAPI_KEY")
agent = Agent(
name="assistant",
instructions="Answer in one short sentence.",
model=OpenAIResponsesModel(model="anthropic.claude-fable-5", openai_client=client),
)
result = Runner.run_sync(agent, "Name the largest planet in the solar system.")
print(result.final_output)
set_tracing_disabled(True) matters here: left on, the SDK exports every run to OpenAI's tracing backend with its own key, which is exactly the third party this deployment exists to remove.
Server-Side Sessions¶
OpenAIConversationsSession keeps an agent's turns in a conversation on the gateway instead of in the process, so a second run replays what the gateway stored rather than a history you carried:
from agents.memory import OpenAIConversationsSession
session = OpenAIConversationsSession(openai_client=client)
Pass it as Runner.run_sync(..., session=session); the conversation id is what makes a run resumable from another process.
Hosted Retrieval¶
FileSearchTool(vector_store_ids=[...]) attaches a vector store to the agent, and the search happens inside the response — the SDK's own loop never sees a tool call. Build the store first, as in the RAG Pipelines guide, then:
from agents import FileSearchTool
agent = Agent(
name="librarian",
instructions="Answer only from the attached notes.",
tools=[FileSearchTool(vector_store_ids=["vs_abc123"], include_search_results=True)],
)
Voice Agents¶
RealtimeRunner opens a spoken session against WS /v1/realtime: give its model_config the wss://YOUR_STDAPI_URL/v1/realtime?model=<id> URL and either the API key or a minted client secret. Sessions last up to 8 minutes and call no tools — see the Realtime API for what a session does and does not emit.
Ollama Python Client¶
The official ollama client reaches the gateway's Ollama-compatible endpoints. Point Client at your gateway and pass the API key as a bearer header — the same shape the client uses for any authenticated Ollama endpoint:
ollama.Client
import ollama
client = ollama.Client(
host="https://YOUR_STDAPI_URL/ollama",
headers={"Authorization": "Bearer YOUR_STDAPI_KEY"},
)
response = client.chat(
model="anthropic.claude-fable-5",
messages=[
{"role": "user", "content": "Name the largest planet in the solar system."}
],
)
print(response.message.content)
The host carries OLLAMA_ROUTES_PREFIX (/ollama by default): the client appends /api/chat, /api/tags and the rest itself. client.generate(), client.embed(), client.list(), client.show() and client.ps() all work against the same instance; client.pull() reports success immediately, because every model the gateway offers is hosted and available as soon as it appears in the model list.
Models are not stored here
create, copy, push and delete are refused with a 403: the gateway serves hosted models and keeps no model store to write to. Reporting success would tell your code a model changed when it did not.
See Ollama Chat API, Ollama Generate API, Ollama Embed API and Ollama Models API for the full parameter and model reference.
Deploy the Gateway on AWS¶
There is no application sample to deploy here — your LangChain, pydantic-ai, OpenAI Agents SDK or Ollama client process is code you already own. The Terraform sample below is one worked example of a credible AWS deployment for the gateway itself, not the only architecture that works: it is a normal HTTPS service, and where your Python process runs relative to its VPC is your choice, not a fixed part of the architecture.
Architecture¶
The decision that shapes this diagram is where your application process runs relative to the gateway's VPC: deployed as another ECS Fargate service (or any other workload) inside the same VPC, it reaches the gateway over private DNS and needs no public endpoint at all; running anywhere else — a laptop, a different account, another cloud — it reaches the gateway through a public Application Load Balancer instead.
%%{init: {'flowchart': {'htmlLabels': true, 'nodeSpacing': 20, 'rankSpacing': 40, 'subGraphTitleMargin': {'top': 8, 'bottom': 10}}} }%%
flowchart TB
extapp["<img src='../styles/logo_python.svg' style='height:40px;width:auto;vertical-align:middle;' /> Your Python App<br/>(outside the VPC)"]
subgraph public["Your VPC · public subnets"]
alb["<img src='../styles/logo_amazon_load_balancing.svg' style='height:40px;width:auto;vertical-align:middle;' /> Application Load Balancer<br/>HTTPS · ACM certificate<br/>WAF (optional)"]
end
subgraph private["Your VPC · private app subnets — no inbound route from the internet"]
intapp["<img src='../styles/logo_python.svg' style='height:40px;width:auto;vertical-align:middle;' /> Your Python App<br/>(in-VPC, e.g. ECS Fargate)"]
stdapi["<img src='../styles/logo.svg' style='height:40px;width:auto;vertical-align:middle;' /> stdapi.ai<br/>ECS Fargate"]
egress["<img src='../styles/logo_amazon_vpc.svg' style='height:40px;width:auto;vertical-align:middle;' /> NAT gateways · one per AZ<br/>+ free S3 gateway endpoint"]
end
subgraph regional["AWS service endpoints · your account, the regions you configure"]
bedrock["<img src='../styles/logo_amazon_bedrock.svg' style='height:40px;width:auto;vertical-align:middle;' /> Amazon Bedrock"]
s3["<img src='../styles/logo_amazon_s3.svg' style='height:40px;width:auto;vertical-align:middle;' /> Amazon S3<br/>SSE-KMS"]
cw["<img src='../styles/logo_amazon_cloudwatch.svg' style='height:40px;width:auto;vertical-align:middle;' /> Amazon CloudWatch<br/>logs · metrics · traces"]
end
extapp -->|"HTTPS · TLS 1.2+ · bearer token"| alb
alb -->|"HTTP · private subnet"| stdapi
alb ~~~ intapp
intapp -.->|"OpenAI/Anthropic API · bearer token<br/>Cloud Map private DNS, no public endpoint"| stdapi
stdapi --> egress
egress -->|"HTTPS · SigV4"| bedrock
egress -->|"S3 gateway endpoint"| s3
egress --> cw The solid path is the public one: an application outside the VPC has no route to the private subnets, so it can only reach the gateway through the ALB, over HTTPS. The dotted path is the in-VPC alternative: an application co-located in the private app subnets resolves the gateway through AWS Cloud Map private DNS and never touches the ALB — for that path, the ALB, its ACM certificate and any WAF module do not need to exist at all. A deployment picks one path or the other for a given application; both are shown here only because the choice is yours to make, not because both run at once.
What Each AWS Service Does Here¶
| AWS service | Role in this integration | Where it is configured |
|---|---|---|
| Amazon ECS on AWS Fargate | Runs the stdapi.ai gateway container, and — on the in-VPC path — your Python application as its own service | Terraform module (default) |
| Elastic Load Balancing | Public entry point on the out-of-VPC path only; terminates TLS with an ACM certificate | alb_enabled, alb_public |
| AWS Cloud Map | Private DNS name your in-VPC application resolves instead of a public endpoint | service_discovery_dns_namespace_id, service_discovery_dns_name |
| Amazon Bedrock | Chat completions, embeddings, tool calling and reasoning for every client library on this page | AWS_BEDROCK_REGIONS |
| Amazon S3 | Temporary storage for multimodal request and response payloads | Terraform module (default) |
| AWS KMS | Customer-managed key encrypting the S3 bucket | Terraform module (default) |
| AWS Secrets Manager / SSM Parameter Store | Holds the API key when one is generated or referenced | api_key_create, api_key_ssm_parameter, api_key_secretsmanager_secret |
| Amazon CloudWatch | Gateway request logs, EMF usage metrics, and OpenTelemetry trace export | Logging & monitoring |
| AWS IAM | Least-privilege task role for the gateway; scoped to the model and AI-service actions it invokes | IAM permissions |
Security Measures in This Flow¶
- Authentication — every client class on this page sends its
api_keyargument as anAuthorization: Bearerheader, whichever mechanism validates it on the gateway: a stdapi.ai API key, an Amazon Cognito user-pool token the gateway verifies itself, or a token from an OIDC / IAM Identity Center flow terminated at the ALB before the request reaches stdapi.ai. AWS IAM SigV4 is available only through an API Gateway integration in front of the gateway — these SDKs' bearer-token clients cannot sign a SigV4 request themselves. - Encryption in transit — HTTPS from an out-of-VPC application to the ALB; private-subnet HTTP from the ALB to the gateway container, or Cloud Map DNS with no ALB hop at all on the in-VPC path; HTTPS with SigV4 from the gateway to every AWS service it calls.
- Encryption at rest — SSE-KMS on the S3 bucket that holds multimodal payloads, with a customer-managed key.
- Least privilege — the gateway's ECS task role carries only the model and AI-service actions its configuration enables, not a blanket Bedrock or S3 grant.
- Content policy — a Bedrock guardrail configured on the gateway applies to every chat request your application sends, independent of which client library issued it.
- Data handling — the gateway is stateless and holds request bodies in memory only; no third party sits between your application and the models it calls, so a Bedrock request made through stdapi.ai carries no telemetry back to another vendor unless your own client library adds it — see the
set_tracing_disabled(True)note under OpenAI Agents SDK above.
What It Costs to Run¶
| Charge | Driver |
|---|---|
| stdapi.ai licence | $0.10 per gateway container-hour, metered through AWS Marketplace, with a 14-day free trial on the licence |
| ECS Fargate | The gateway service, sized and auto-scaled independently of your application's own compute |
| Load balancing and networking | An ALB plus the NAT gateways the private subnets egress through, next to the S3 gateway endpoint, which carries no charge — the ALB drops out entirely when your application runs in the same VPC and reaches the gateway over Cloud Map private DNS |
| Model and AI-service usage | Amazon Bedrock at AWS rates, billed to your account with no markup |
Read a model's price before your application sends anything to it with GET /model_pricing. Setting COST_TRACKING=true additionally puts a per-request cost on each usage entry — estimated from published AWS prices, not read back from your invoice.
What to Watch¶
The gateway writes one structured request event per call to CloudWatch, carrying the request id, path, status code, execution_time_ms, the model that served it, and the token counts AWS billed; streaming calls add a matching request_stream event. When OTEL_ENABLED=true and OTEL_EXPORTER_ENDPOINT point at a collector, the gateway also exports OpenTelemetry traces for the same calls. Because your application is the client here, the most direct link between the two is the x-request-id response header: read it from every gateway response and log it alongside your own request handling, so a failure your application sees can be traced straight back to the gateway event that produced it.
fields id, path, model_id, status_code
| filter type = "request" and status_code >= 400
| stats count(*) as errors by path, model_id, status_code
| sort errors desc
Pair this with your application's own logs, keyed on the x-request-id it received, to tell a gateway-side failure apart from one your code introduced before or after the call.
Next Steps¶
- Getting Started — Deploy stdapi.ai to AWS with Terraform
- Local Development — Run stdapi.ai locally with Docker
- RAG Pipelines — Combine embeddings and reranking in a retrieval pipeline
- More Use Cases — Explore other integrations and tools