# Authenticate
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/authenticate
https://api.host.langchain.com/openapi.json post /v2/auth/authenticate
Get OAuth token or start authentication flow if needed.
# Check Oauth Token Exists
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/check-oauth-token-exists
https://api.host.langchain.com/openapi.json get /v2/auth/tokens/exists
Return whether the current user has any tokens for a given provider (across agents).
# Check Workspace Slack Tokens Exist
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/check-workspace-slack-tokens-exist
https://api.host.langchain.com/openapi.json get /v2/auth/tokens/workspace/slack/exists
Check if the workspace has any Slack tokens.
# Create Mcp Oauth Provider
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/create-mcp-oauth-provider
https://api.host.langchain.com/openapi.json post /v2/auth/providers/mcp-discover
Create an OAuth provider via MCP auto-discovery.
# Create Oauth Provider
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/create-oauth-provider
https://api.host.langchain.com/openapi.json post /v2/auth/providers
Create a new OAuth provider manually.
# Delete Oauth Provider
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/delete-oauth-provider
https://api.host.langchain.com/openapi.json delete /v2/auth/providers/{provider_id}
Delete an OAuth provider.
# Delete Oauth Tokens For User
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/delete-oauth-tokens-for-user
https://api.host.langchain.com/openapi.json delete /v2/auth/tokens
Delete all tokens for the current user for the given provider (across agents).
# Get Oauth Provider
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/get-oauth-provider
https://api.host.langchain.com/openapi.json get /v2/auth/providers/{provider_id}
Get a specific OAuth provider.
# List Oauth Providers
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/list-oauth-providers
https://api.host.langchain.com/openapi.json get /v2/auth/providers
List OAuth providers.
# Oauth Callback
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/oauth-callback
https://api.host.langchain.com/openapi.json post /v2/auth/callback/{provider_id}
# Oauth Callback Get
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/oauth-callback-get
https://api.host.langchain.com/openapi.json get /v2/auth/callback/{provider_id}
Handle OAuth callback redirect from OAuth providers.
Processes the OAuth token exchange, then redirects to the frontend callback
page for a consistent UI experience.
# Oauth Setup Callback
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/oauth-setup-callback
https://api.host.langchain.com/openapi.json get /v2/auth/setup/{provider_id}
Handle OAuth setup callback redirect from GitHub Apps.
This endpoint handles the "Setup URL" callback from GitHub Apps, which is
triggered when a user installs or updates their GitHub App installation.
For "update" actions (user modified repo access via GitHub), we just show
a success page since no token exchange is needed.
For new installations with code/state, we process similar to the regular
OAuth callback.
# Revoke All Slack Tokens For Workspace
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/revoke-all-slack-tokens-for-workspace
https://api.host.langchain.com/openapi.json delete /v2/auth/tokens/workspace/slack
Revoke ALL Slack tokens for the workspace. Admin-only action that disconnects Slack entirely.
This is a destructive operation that:
- Revokes all Slack tokens on Slack's side for all users in the workspace
- Deletes all Slack tokens from the database
# Update Oauth Provider
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/update-oauth-provider
https://api.host.langchain.com/openapi.json patch /v2/auth/providers/{provider_id}
Update an OAuth provider.
# Wait For Auth Completion
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/auth-service-v2/wait-for-auth-completion
https://api.host.langchain.com/openapi.json get /v2/auth/wait/{auth_id}
Wait for OAuth authentication completion.
# Create Deployment
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/deployments-v2/create-deployment
https://api.host.langchain.com/openapi.json post /v2/deployments
Create a new deployment.
# Delete Deployment
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/deployments-v2/delete-deployment
https://api.host.langchain.com/openapi.json delete /v2/deployments/{deployment_id}
Delete a deployment by ID.
# Delete Deployments
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/deployments-v2/delete-deployments
https://api.host.langchain.com/openapi.json delete /v2/deployments
Delete multiple deployments with partial success support.
Returns:
- 200: All deployments deleted successfully
- 207: Some deployments deleted successfully, some failed
# Get Deployment
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/deployments-v2/get-deployment
https://api.host.langchain.com/openapi.json get /v2/deployments/{deployment_id}
Get a deployment by ID.
# Get Revision
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/deployments-v2/get-revision
https://api.host.langchain.com/openapi.json get /v2/deployments/{deployment_id}/revisions/{revision_id}
Get a revision by ID for a deployment.
# List Deployments
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/deployments-v2/list-deployments
https://api.host.langchain.com/openapi.json get /v2/deployments
List all deployments.
# List Revisions
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/deployments-v2/list-revisions
https://api.host.langchain.com/openapi.json get /v2/deployments/{deployment_id}/revisions
List all revisions for a deployment.
# Patch Deployment
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/deployments-v2/patch-deployment
https://api.host.langchain.com/openapi.json patch /v2/deployments/{deployment_id}
Patch a deployment by ID.
# Redeploy Revision
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/deployments-v2/redeploy-revision
https://api.host.langchain.com/openapi.json post /v2/deployments/{deployment_id}/revisions/{revision_id}/redeploy
Redeploy a specific revision ID.
# List GitHub Integrations
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/integrations-v1/list-github-integrations
https://api.host.langchain.com/openapi.json get /v1/integrations/github/install
List available GitHub integrations for LangGraph Platfom Cloud SaaS.
# List GitHub Repositories
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/integrations-v1/list-github-repositories
https://api.host.langchain.com/openapi.json get /v1/integrations/github/{integration_id}/repos
List available GitHub repositories for an integration that are available to deploy to LangSmith Deployment.
# Create Listener
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/listeners-v2/create-listener
https://api.host.langchain.com/openapi.json post /v2/listeners
Create a listener.
Creating a listener is only allowed for LangSmith organizations with self-hosted enterprise plans.
# Delete Listener
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/listeners-v2/delete-listener
https://api.host.langchain.com/openapi.json delete /v2/listeners/{listener_id}
Delete a listener by ID.
# Get Listener
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/listeners-v2/get-listener
https://api.host.langchain.com/openapi.json get /v2/listeners/{listener_id}
Get a listener by ID.
# List Listeners
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/listeners-v2/list-listeners
https://api.host.langchain.com/openapi.json get /v2/listeners
List all listeners.
# Patch Listener
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/listeners-v2/patch-listener
https://api.host.langchain.com/openapi.json patch /v2/listeners/{listener_id}
Patch a listener by ID.
# Create a Sandbox
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/create-a-sandbox
https://api.host.langchain.com/openapi.json post /v2/sandboxes/boxes
Create a new SandboxClaim in tenant's namespace.
If wait_for_ready is True (default), this will block until the sandbox
is ready or the timeout is reached.
The sandbox creation can fail for several reasons:
- Image pull errors (invalid image, registry auth issues)
- Container crashes (bad entrypoint, missing dependencies, health check failures)
- Scheduling failures (no suitable nodes available)
Sandbox creation is subject to quota limits (count, CPU, memory)
configured via Metronome org config.
On failure, the claim is automatically cleaned up and a descriptive error
is returned.
# Create a Sandbox Pool
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/create-a-sandbox-pool
https://api.host.langchain.com/openapi.json post /v2/sandboxes/pools
Create a new Sandbox Pool in tenant's namespace.
Pools pre-provision sandboxes from a template for faster claim binding.
Requirements:
- The referenced template must exist
- The template must not have any volume mounts (volumes are stateful)
Pool creation is subject to quota limits. The total resource usage
(replicas * per-replica resources) is checked against the organization's
quota limits.
# Create a SandboxTemplate
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/create-a-sandboxtemplate
https://api.host.langchain.com/openapi.json post /v2/sandboxes/templates
Create a new SandboxTemplate in tenant's namespace.
# Create a Volume
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/create-a-volume
https://api.host.langchain.com/openapi.json post /v2/sandboxes/volumes
Create a new persistent volume in the tenant's sandbox namespace.
This creates both a PersistentVolume (PV) and PersistentVolumeClaim (PVC).
The volume can then be referenced in sandbox templates.
Volume creation is subject to quota limits (count and total storage)
configured via Metronome org config.
If wait_for_ready is True (default), this blocks until the PVC is bound
or the timeout is reached.
# Delete a Registry
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/delete-a-registry
https://api.host.langchain.com/openapi.json delete /v2/sandboxes/registries/{name}
Delete a private container registry.
This will fail if any templates reference this registry. Delete or update
those templates first before deleting the registry.
# Delete a Sandbox
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/delete-a-sandbox
https://api.host.langchain.com/openapi.json delete /v2/sandboxes/boxes/{name}
Delete a SandboxClaim from tenant's namespace.
# Delete a Sandbox Pool
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/delete-a-sandbox-pool
https://api.host.langchain.com/openapi.json delete /v2/sandboxes/pools/{name}
Delete a Sandbox Pool from tenant's namespace.
This will terminate all sandboxes in the pool.
# Delete a SandboxTemplate
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/delete-a-sandboxtemplate
https://api.host.langchain.com/openapi.json delete /v2/sandboxes/templates/{name}
Delete a SandboxTemplate from tenant's namespace.
Deletion will fail if the template is in use by any sandboxes or pools.
# Delete a Volume
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/delete-a-volume
https://api.host.langchain.com/openapi.json delete /v2/sandboxes/volumes/{name}
Delete a persistent volume (both PV and PVC) from tenant's namespace.
This will fail if any templates reference this volume. Delete or update
those templates first before deleting the volume.
# Get a Registry
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/get-a-registry
https://api.host.langchain.com/openapi.json get /v2/sandboxes/registries/{name}
Get a specific registry by name.
Returns metadata only -- credentials are never included in responses.
# Get a Sandbox
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/get-a-sandbox
https://api.host.langchain.com/openapi.json get /v2/sandboxes/boxes/{name}
Get a specific Sandbox by name in tenant's namespace.
This endpoint queries the database for fast performance.
# Get a Sandbox Pool
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/get-a-sandbox-pool
https://api.host.langchain.com/openapi.json get /v2/sandboxes/pools/{name}
Get a specific Sandbox Pool by name in tenant's namespace.
This endpoint queries the database for fast performance.
# Get a SandboxTemplate
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/get-a-sandboxtemplate
https://api.host.langchain.com/openapi.json get /v2/sandboxes/templates/{name}
Get a specific SandboxTemplate by name in tenant's namespace.
This endpoint queries the database for fast performance.
# Get a Volume
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/get-a-volume
https://api.host.langchain.com/openapi.json get /v2/sandboxes/volumes/{name}
Get a specific volume by name in the tenant's sandbox namespace.
This endpoint queries the database for fast performance.
# Get sandbox resource usage and quota limits
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/get-sandbox-resource-usage-and-quota-limits
https://api.host.langchain.com/openapi.json get /v2/sandboxes/usage
Return current resource consumption and organisation quota limits.
Sandbox counts include both direct claims and warmpool replicas.
For self-hosted deployments limit fields are 0 (quotas not enforced).
# List all Registries
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/list-all-registries
https://api.host.langchain.com/openapi.json get /v2/sandboxes/registries
List all registered private container registries for the tenant.
Returns metadata only -- credentials are never included in responses.
Supports optional pagination via `limit` and `offset` query parameters.
# List all Sandbox Pools
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/list-all-sandbox-pools
https://api.host.langchain.com/openapi.json get /v2/sandboxes/pools
List all Sandbox Pools in the tenant's namespace.
This endpoint queries the database for fast performance.
Supports optional pagination via `limit` and `offset` query parameters.
# List all Sandboxes
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/list-all-sandboxes
https://api.host.langchain.com/openapi.json get /v2/sandboxes/boxes
List all Sandboxes in the tenant's namespace.
This endpoint queries the database for fast performance.
Supports optional pagination via `limit` and `offset` query parameters.
# List all SandboxTemplates
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/list-all-sandboxtemplates
https://api.host.langchain.com/openapi.json get /v2/sandboxes/templates
List all SandboxTemplates in the tenant's sandbox namespace.
This endpoint queries the database for fast performance.
Supports optional pagination via `limit` and `offset` query parameters.
# List all Volumes
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/list-all-volumes
https://api.host.langchain.com/openapi.json get /v2/sandboxes/volumes
List all persistent volumes in the tenant's sandbox namespace.
This endpoint queries the database for fast performance.
Supports optional pagination via `limit` and `offset` query parameters.
# Register a Private Container Registry
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/register-a-private-container-registry
https://api.host.langchain.com/openapi.json post /v2/sandboxes/registries
Register a private container registry for pulling sandbox images.
Provide the registry URL, username, and password (or access token).
The backend builds the K8s dockerconfigjson secret internally.
The registry can then be referenced in sandbox templates via the
`registry_name` field to pull images from private registries.
# Update a Registry
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/update-a-registry
https://api.host.langchain.com/openapi.json patch /v2/sandboxes/registries/{name}
Update a registry's display name and/or credentials.
You can update:
- **name**: New display name (must be unique within the organization)
- **url**, **username**, **password**: New credentials (all three must be
provided together)
# Update a Sandbox
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/update-a-sandbox
https://api.host.langchain.com/openapi.json patch /v2/sandboxes/boxes/{name}
Update a Sandbox's display name.
Currently only the display name can be updated.
# Update a Sandbox Pool
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/update-a-sandbox-pool
https://api.host.langchain.com/openapi.json patch /v2/sandboxes/pools/{name}
Update a Sandbox Pool's display name and/or replica count.
You can update:
- **name**: New display name (must be unique within the organization)
- **replicas**: New replica count (scaling up is subject to quota limits)
The template reference cannot be changed after creation.
Set replicas to 0 to pause the pool without deleting it.
# Update a SandboxTemplate
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/update-a-sandboxtemplate
https://api.host.langchain.com/openapi.json patch /v2/sandboxes/templates/{name}
Update a SandboxTemplate's display name.
Currently only the display name can be updated. The image, resources,
and volume mounts cannot be changed after creation.
# Update a Volume
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/api-reference/sandboxes-v2/update-a-volume
https://api.host.langchain.com/openapi.json patch /v2/sandboxes/volumes/{name}
Update a volume's display name and/or storage size.
You can update:
- **name**: New display name (must be unique within the organization)
- **size**: New storage size (only increases allowed, cannot decrease)
The storage increase is subject to quota limits configured via
Metronome org config.
# null
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/index
Documentation
LangChain is the platform for agent engineering. AI teams at Replit, Clay, Rippling, Cloudflare, Workday, and more trust LangChain's products to engineer reliable agents.
LangSmith
[**LangSmith**](/langsmith/home) is a platform that helps AI teams use live production data for continuous testing and improvement. LangSmith provides:
See exactly how your agent thinks and acts with detailed tracing and aggregate trend metrics.
Test and score agent behavior on production data or offline datasets to continuously improve performance.
Iterate on prompts with version control, prompt optimization, and collaboration features.
Ship your agent in one click, using scalable infrastructure built for long-running tasks.
LangSmith meets the highest standards of data security and privacy with HIPAA, SOC 2 Type 2, and GDPR compliance. For more information, see the [Trust Center](https://trust.langchain.com/).
LangSmith Agent Builder
Create helpful AI agents without code. Start from a template, connect your accounts, and let the agent handle routine work while you stay in control.
Open source agent frameworks
Quickly build agents that can tackle any task.
Customize the building blocks of your agents.
Control every step of your custom agent with low-level orchestration, memory, and human-in-the-loop support.
Quickly build agents that can tackle any task.
Customize the building blocks of your agents.
Control every step of your custom agent with low-level orchestration, memory, and human-in-the-loop support.
Get started
Additional resources
Ask questions, share solutions, and discuss best practices.
Connect with other builders and get quick help.
Submit tickets and track support requests.
Real-time status of LangSmith services and APIs.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/index.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Attribute-based access control
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/abac
This reference explains LangSmith's Attribute-Based Access Control (ABAC) system, which enables fine-grained access control based on resource attributes, complementing [RBAC](/langsmith/rbac). For automated user provisioning into roles, see [SCIM](/langsmith/user-management#set-up-scim-for-your-organization).
ABAC complements [Role-Based Access Control (RBAC)](/langsmith/rbac) by adding tag-based conditions to access decisions. While RBAC grants blanket permissions based on a user's role (e.g., "can read all projects"), ABAC lets you restrict or grant access based on resource tags (e.g., "can only read projects tagged with Environment=Development").
ABAC (Attribute-Based Access Control) is an Enterprise feature in private beta. If you are interested in upgrading to Enterprise, [contact our sales team](https://www.langchain.com/contact-sales).
Roles and resource tags can be managed via the UI or API. ABAC policies are currently only configurable via the [API](https://api.smith.langchain.com/docs#/access_policies).
## Before you begin
* [Set up resource tags](/langsmith/set-up-resource-tags) in your workspace.
* ABAC currently only supports `resource_tag_key` as an `attribute_name` in policies, for evaluating against resource tags. No other attributes are supported yet.
## Access policy structure
An access policy defines conditions under which access is granted or denied. Here's the structure:
```json theme={null}
{
"name": "Policy Name",
"description": "Optional description",
"effect": "allow | deny",
"condition_groups": [
{
"permission": "projects:read",
"resource_type": "project",
"conditions": [
{
"attribute_name": "resource_tag_key",
"attribute_key": "Environment",
"operator": "equals",
"attribute_value": "Production"
}
]
}
],
"role_ids": [""]
}
```
### Effect
The `effect` determines what happens when conditions match:
* **`allow`** - Grant access when conditions match
* **`deny`** - Block access when conditions match
Deny policies always take precedence. If both an allow and deny policy match, access is denied.
### Condition groups
The `condition_groups` array contains one or more condition groups. Multiple condition groups are evaluated with **OR logic** - if any group matches, the policy applies.
Each condition group specifies:
* **`permission`** - The permission this group applies to
* **`resource_type`** - The resource type to match
* **`conditions`** - Array of conditions (evaluated with **AND logic** within the group)
#### Resource types and permissions
| Resource type | Supported permissions |
| ------------- | ----------------------------------------------------------------------- |
| `project` | `projects:read`, `runs:read` |
| `prompt` | `prompts:read`, `prompts:update`, `prompts:delete` |
| `dataset` | `datasets:read`, `datasets:update`, `datasets:delete`, `datasets:share` |
Runs don't have their own tags. Run permissions (`runs:read`) are evaluated against the parent project's tags.
#### Conditions
Each condition in the `conditions` array specifies:
* **`attribute_name`** - Currently only `resource_tag_key` is supported
* **`attribute_key`** - The tag key to match (e.g., `Environment`, `Team`)
* **`operator`** - The comparison operator
* **`attribute_value`** - The value to compare against
##### Operators
| Operator | Description |
| ------------------------ | ------------------------------------------------ |
| `equals` | Exact match (case sensitive) |
| `not_equals` | Values differ (case sensitive) |
| `equals_ignore_case` | Exact match (case insensitive) |
| `not_equals_ignore_case` | Values differ (case insensitive) |
| `matches` | Glob pattern matching with `*` and `?` wildcards |
| `not_matches` | Match when value doesn't match glob pattern |
##### `_if_exists` variants
Each operator has an `_if_exists` variant that matches by default when the tag key is absent, or evaluates the condition normally when the tag exists:
| Operator | Description |
| ---------------------------------- | ----------------------------------------------------------------- |
| `equals_if_exists` | Exact match (case sensitive), or if tag key absent |
| `not_equals_if_exists` | Values differ (case sensitive), or if tag key absent |
| `equals_ignore_case_if_exists` | Exact match (case insensitive), or if tag key absent |
| `not_equals_ignore_case_if_exists` | Values differ (case insensitive), or if tag key absent |
| `matches_if_exists` | Glob pattern match, or if tag key absent |
| `not_matches_if_exists` | Match when value doesn't match glob pattern, or if tag key absent |
In an **allow** policy, `_if_exists` variants grant access to resources that either match the condition or don't have the specified tag key. In a **deny** policy, they block resources that either match the condition or don't have the tag key.
### Roles
The `role_ids` array specifies which workspace roles the policy applies to. When a user with that role accesses a resource, the policy conditions are evaluated.
Policies can be attached to roles when creating the policy, or attached later via the API.
## Managing access policies
Access policies are managed via the LangSmith API by [Organization Admins](/langsmith/rbac#organization-admin). Before creating policies, [set up resource tags](/langsmith/set-up-resource-tags) in your workspace.
## How ABAC works with RBAC
[RBAC](/langsmith/rbac) permissions and ABAC policies are both considered when determining access to resources:
* ABAC **deny** policies override RBAC permissions
* ABAC **allow** policies can grant access even without RBAC permissions
* If no ABAC policies match, the system falls back to RBAC
### Policy evaluation outcomes
**Feature combinations:**
| RBAC enabled | ABAC enabled | Behavior |
| ------------ | ------------ | --------------------------------------------------- |
| ✗ | ✗ | All workspace members have Admin-level access |
| ✓ | ✗ | Standard RBAC - access based on role permissions |
| ✓ | ✓ | RBAC + ABAC - fine-grained tag-based access control |
**When both RBAC and ABAC are enabled:**
| RBAC permits | Allow policy matches | Deny policy matches | Result |
| ------------ | -------------------- | ------------------- | -------------------------------- |
| ✓ | ✓ | ✗ | **Allowed** |
| ✓ | ✗ | ✗ | **Allowed** (RBAC fallback) |
| ✓ | ✓ | ✓ | **Denied** (deny wins) |
| ✓ | ✗ | ✓ | **Denied** (deny wins) |
| ✗ | ✓ | ✗ | **Allowed** (ABAC grants access) |
| ✗ | ✗ | ✗ | **Denied** |
| ✗ | ✓ | ✓ | **Denied** (deny wins) |
## Example scenarios
### 1. Annotator team assignment
Allow annotators to only access datasets tagged for their team:
```json theme={null}
{
"name": "Annotator Team A Access",
"effect": "allow",
"condition_groups": [{
"permission": "datasets:read",
"resource_type": "dataset",
"conditions": [{
"attribute_name": "resource_tag_key",
"attribute_key": "Annotation-Team",
"operator": "equals",
"attribute_value": "Team-A"
}]
}]
}
```
### 2. Block sensitive data
Deny access to datasets containing PII. Since deny policies override allow policies, this blocks access even for users with RBAC permissions:
```json theme={null}
{
"name": "Block PII Datasets",
"effect": "deny",
"condition_groups": [{
"permission": "datasets:read",
"resource_type": "dataset",
"conditions": [{
"attribute_name": "resource_tag_key",
"attribute_key": "Contains-PII",
"operator": "equals",
"attribute_value": "true"
}]
}]
}
```
### 3. Application-based access with wildcards
Allow engineers to access projects for any application in the "chatbot" family using glob patterns:
```json theme={null}
{
"name": "Chatbot Apps Access",
"effect": "allow",
"condition_groups": [{
"permission": "projects:read",
"resource_type": "project",
"conditions": [{
"attribute_name": "resource_tag_key",
"attribute_key": "Application",
"operator": "matches",
"attribute_value": "chatbot-*"
}]
}]
}
```
### 4. Client and purpose isolation (AND logic)
Grant access only if both conditions are met - dataset is for training AND belongs to a specific client:
```json theme={null}
{
"name": "Client Training Data Access",
"effect": "allow",
"condition_groups": [{
"permission": "datasets:read",
"resource_type": "dataset",
"conditions": [
{
"attribute_name": "resource_tag_key",
"attribute_key": "Purpose",
"operator": "equals",
"attribute_value": "Training"
},
{
"attribute_name": "resource_tag_key",
"attribute_key": "Client",
"operator": "equals",
"attribute_value": "Acme-Corp"
}
]
}]
}
```
### 5. Client data plus resources without a `Client` tag using `_if_exists`
Consultants don't have RBAC `datasets:read` permission, but this policy grants them access to datasets tagged `Client=Acme-Corp`, as well as datasets that don't have a `Client` tag at all. Datasets tagged with a different client (e.g., `Client=Other-Corp`) remain blocked:
```json theme={null}
{
"name": "Acme Consultant Access",
"effect": "allow",
"condition_groups": [{
"permission": "datasets:read",
"resource_type": "dataset",
"conditions": [{
"attribute_name": "resource_tag_key",
"attribute_key": "Client",
"operator": "equals_if_exists",
"attribute_value": "Acme-Corp"
}]
}]
}
```
## Troubleshooting
**Access unexpectedly denied?**
* Check if a deny policy is matching (deny always takes precedence)
* Check if the user has RBAC permissions or a matching allow policy
* Verify the resource has the expected tag and value
* Deny policies with `_if_exists` operators block resources missing that tag key
* For case-sensitive operators (`equals`, `not_equals`), check for case mismatches
* With multiple conditions in a group, all must match (AND logic)
**Access unexpectedly granted?**
* Review RBAC permissions (users may have access via their role)
* Check if an allow policy is too broad (e.g., using wildcards)
* `_if_exists` operators match resources missing that tag key
**Policy not taking effect?**
* Confirm the policy is attached to the correct role
* Verify the user has that role in the workspace
* Check that `resource_type` and `permission` match the resource being accessed
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/abac.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Connect an authentication provider
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/add-auth-server
In [the last tutorial](/langsmith/resource-auth), you added resource authorization to give users private conversations. However, you are still using hard-coded tokens for authentication, which is not secure. Now you'll replace those tokens with real user accounts using [OAuth2](/langsmith/deployment-quickstart).
You'll keep the same [`Auth`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth) object and [resource-level access control](/langsmith/auth#single-owner-resources), but upgrade authentication to use Supabase as your identity provider. While Supabase is used in this tutorial, the concepts apply to any OAuth2 provider. You'll learn how to:
1. Replace test tokens with real JWT tokens
2. Integrate with OAuth2 providers for secure user authentication
3. Handle user sessions and metadata while maintaining our existing authorization logic
## Background
OAuth2 involves three main roles:
1. **Authorization server**: The identity provider (e.g., Supabase, Auth0, Google) that handles user authentication and issues tokens
2. **Application backend**: Your LangGraph application. This validates tokens and serves protected resources (conversation data)
3. **Client application**: The web or mobile app where users interact with your service
A standard OAuth2 flow works something like this:
```mermaid theme={null}
sequenceDiagram
participant User
participant Client
participant AuthServer
participant Agent Server
User->>Client: Initiate login
User->>AuthServer: Enter credentials
AuthServer->>Client: Send tokens
Client->>Agent Server: Request with token
Agent Server->>AuthServer: Validate token
AuthServer->>Agent Server: Token valid
Agent Server->>Client: Serve request (e.g., run agent or graph)
```
## Prerequisites
Before you start this tutorial, ensure you have:
* The [bot from the second tutorial](/langsmith/resource-auth) running without errors.
* A [Supabase project](https://supabase.com/dashboard) to use its authentication server.
## 1. Install dependencies
Install the required dependencies. Start in your `custom-auth` directory and ensure you have the `langgraph-cli` installed:
```bash pip theme={null}
cd custom-auth
pip install -U "langgraph-cli[inmem]"
```
```bash uv theme={null}
cd custom-auth
uv add "langgraph-cli[inmem]"
```
## 2. Set up the authentication provider
Next, fetch the URL of your auth server and the private key for authentication.
Since you're using Supabase for this, you can do this in the Supabase dashboard:
1. In the left sidebar, click on t️⚙ Project Settings" and then click "API"
2. Copy your project URL and add it to your `.env` file
```shell theme={null}
echo "SUPABASE_URL=your-project-url" >> .env
```
3. Copy your service role secret key and add it to your `.env` file:
```shell theme={null}
echo "SUPABASE_SERVICE_KEY=your-service-role-key" >> .env
```
4. Copy your "anon public" key and note it down. This will be used later when you set up our client code.
```bash theme={null}
SUPABASE_URL=your-project-url
SUPABASE_SERVICE_KEY=your-service-role-key
```
## 3. Implement token validation
In the previous tutorials, you used the [`Auth`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth) object to [validate hard-coded tokens](/langsmith/set-up-custom-auth) and [add resource ownership](/langsmith/resource-auth).
Now you'll upgrade your authentication to validate real JWT tokens from Supabase. The main changes will all be in the [`@auth.authenticate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth.authenticate) decorated function:
* Instead of checking against a hard-coded list of tokens, you'll make an HTTP request to Supabase to validate the token.
* You'll extract real user information (ID, email) from the validated token.
* The existing resource authorization logic remains unchanged.
Update `src/security/auth.py` to implement this:
```python {highlight={8-9,20-30}} title="src/security/auth.py" theme={null}
import os
import httpx
from langgraph_sdk import Auth
auth = Auth()
# This is loaded from the `.env` file you created above
SUPABASE_URL = os.environ["SUPABASE_URL"]
SUPABASE_SERVICE_KEY = os.environ["SUPABASE_SERVICE_KEY"]
@auth.authenticate
async def get_current_user(authorization: str | None):
"""Validate JWT tokens and extract user information."""
assert authorization
scheme, token = authorization.split()
assert scheme.lower() == "bearer"
try:
# Verify token with auth provider
async with httpx.AsyncClient() as client:
response = await client.get(
f"{SUPABASE_URL}/auth/v1/user",
headers={
"Authorization": authorization,
"apiKey": SUPABASE_SERVICE_KEY,
},
)
assert response.status_code == 200
user = response.json()
return {
"identity": user["id"], # Unique user identifier
"email": user["email"],
"is_authenticated": True,
}
except Exception as e:
raise Auth.exceptions.HTTPException(status_code=401, detail=str(e))
# ... the rest is the same as before
# Keep our resource authorization from the previous tutorial
@auth.on
async def add_owner(ctx, value):
"""Make resources private to their creator using resource metadata."""
filters = {"owner": ctx.user.identity}
metadata = value.setdefault("metadata", {})
metadata.update(filters)
return filters
```
The most important change is that we're now validating tokens with a real authentication server. Our authentication handler has the private key for our Supabase project, which we can use to validate the user's token and extract their information.
## 4. Test authentication flow
Let's test out the new authentication flow. You can run the following code in a file or notebook. You will need to provide:
* A valid email address
* A Supabase project URL (from [above](#setup-auth-provider))
* A Supabase anon **public key** (also from [above](#setup-auth-provider))
```python theme={null}
import os
import httpx
from getpass import getpass
from langgraph_sdk import get_client
# Get email from command line
email = getpass("Enter your email: ")
base_email = email.split("@")
password = "secure-password" # CHANGEME
email1 = f"{base_email[0]}+1@{base_email[1]}"
email2 = f"{base_email[0]}+2@{base_email[1]}"
SUPABASE_URL = os.environ.get("SUPABASE_URL")
if not SUPABASE_URL:
SUPABASE_URL = getpass("Enter your Supabase project URL: ")
# This is your PUBLIC anon key (which is safe to use client-side)
# Do NOT mistake this for the secret service role key
SUPABASE_ANON_KEY = os.environ.get("SUPABASE_ANON_KEY")
if not SUPABASE_ANON_KEY:
SUPABASE_ANON_KEY = getpass("Enter your public Supabase anon key: ")
async def sign_up(email: str, password: str):
"""Create a new user account."""
async with httpx.AsyncClient() as client:
response = await client.post(
f"{SUPABASE_URL}/auth/v1/signup",
json={"email": email, "password": password},
headers={"apiKey": SUPABASE_ANON_KEY},
)
assert response.status_code == 200
return response.json()
# Create two test users
print(f"Creating test users: {email1} and {email2}")
await sign_up(email1, password)
await sign_up(email2, password)
```
⚠️ Before continuing: Check your email and click both confirmation links. Supabase will reject `/login` requests until after you have confirmed your users' email.
Now test that users can only see their own data. Make sure the server is running (run `langgraph dev`) before proceeding. The following snippet requires the "anon public" key that you copied from the Supabase dashboard while [setting up the auth provider](#setup-auth-provider) previously.
```python theme={null}
async def login(email: str, password: str):
"""Get an access token for an existing user."""
async with httpx.AsyncClient() as client:
response = await client.post(
f"{SUPABASE_URL}/auth/v1/token?grant_type=password",
json={
"email": email,
"password": password
},
headers={
"apikey": SUPABASE_ANON_KEY,
"Content-Type": "application/json"
},
)
assert response.status_code == 200
return response.json()["access_token"]
# Log in as user 1
user1_token = await login(email1, password)
user1_client = get_client(
url="http://localhost:2024", headers={"Authorization": f"Bearer {user1_token}"}
)
# Create a thread as user 1
thread = await user1_client.threads.create()
print(f"✅ User 1 created thread: {thread['thread_id']}")
# Try to access without a token
unauthenticated_client = get_client(url="http://localhost:2024")
try:
await unauthenticated_client.threads.create()
print("❌ Unauthenticated access should fail!")
except Exception as e:
print("✅ Unauthenticated access blocked:", e)
# Try to access user 1's thread as user 2
user2_token = await login(email2, password)
user2_client = get_client(
url="http://localhost:2024", headers={"Authorization": f"Bearer {user2_token}"}
)
try:
await user2_client.threads.get(thread["thread_id"])
print("❌ User 2 shouldn't see User 1's thread!")
except Exception as e:
print("✅ User 2 blocked from User 1's thread:", e)
```
The output should look like this:
```shell theme={null}
✅ User 1 created thread: d6af3754-95df-4176-aa10-dbd8dca40f1a
✅ Unauthenticated access blocked: Client error '403 Forbidden' for url 'http://localhost:2024/threads'
✅ User 2 blocked from User 1's thread: Client error '404 Not Found' for url 'http://localhost:2024/threads/d6af3754-95df-4176-aa10-dbd8dca40f1a'
```
Your authentication and authorization are working together:
1. Users must log in to access the bot
2. Each user can only see their own threads
All users are managed by the Supabase auth provider, so you don't need to implement any additional user management logic.
## Next steps
You've successfully built a production-ready authentication system for your LangGraph application! Let's review what you've accomplished:
1. Set up an authentication provider (Supabase in this case)
2. Added real user accounts with email/password authentication
3. Integrated JWT token validation into your Agent Server
4. Implemented proper authorization to ensure users can only access their own data
5. Created a foundation that's ready to handle your next authentication challenge
Now that you have production authentication, consider:
1. Building a web UI with your preferred framework (see the [Custom Auth](https://github.com/langchain-ai/custom-auth) template for an example)
2. Learn more about the other aspects of authentication and authorization in the [conceptual guide on authentication](/langsmith/auth).
3. Customize your handlers and setup further after reading the [reference docs](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/add-auth-server.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Human-in-the-loop using server API
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/add-human-in-the-loop
To review, edit, and approve tool calls in an agent or workflow, use LangGraph's [human-in-the-loop](/oss/python/langgraph/interrupts) features.
## Dynamic interrupts
```python {highlight={2,34}} theme={null}
from langgraph_sdk import get_client
from langgraph_sdk.schema import Command
client = get_client(url=)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
# create a thread
thread = await client.threads.create()
thread_id = thread["thread_id"]
# Run the graph until the interrupt is hit.
result = await client.runs.wait(
thread_id,
assistant_id,
input={"some_text": "original text"} # (1)!
)
print(result['__interrupt__']) # (2)!
# > [
# > {
# > 'value': {'text_to_revise': 'original text'},
# > 'resumable': True,
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
# > 'when': 'during'
# > }
# > ]
# Resume the graph
print(await client.runs.wait(
thread_id,
assistant_id,
command=Command(resume="Edited text") # (3)!
))
# > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3\. The graph is resumed with a `Command(resume=...)`, injecting the human's input and continuing execution.
```javascript {highlight={32}} theme={null}
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: });
// Using the graph deployed with the name "agent"
const assistantID = "agent";
// create a thread
const thread = await client.threads.create();
const threadID = thread["thread_id"];
// Run the graph until the interrupt is hit.
const result = await client.runs.wait(
threadID,
assistantID,
{ input: { "some_text": "original text" } } # (1)!
);
console.log(result['__interrupt__']); # (2)!
// > [
# > {
# > 'value': {'text_to_revise': 'original text'},
# > 'resumable': True,
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
# > 'when': 'during'
# > }
# > ]
// Resume the graph
console.log(await client.runs.wait(
threadID,
assistantID,
{ command: { resume: "Edited text" }} # (3)!
));
# > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3. The graph is resumed with a `{ resume: ... }` command object, injecting the human's input and continuing execution.
Create a thread:
```bash theme={null}
curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Run the graph until the interrupt is hit.:
```bash theme={null}
curl --request POST \
--url /threads//runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"some_text\": \"original text\"}
}"
```
Resume the graph:
```bash theme={null}
curl --request POST \
--url /threads//runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": \"Edited text\"
}
}"
```
This is an example graph you can run in the Agent Server.
See [LangSmith quickstart](/langsmith/deployment-quickstart) for more details.
```python {highlight={7,13}} theme={null}
from typing import TypedDict
import uuid
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.constants import START
from langgraph.graph import StateGraph
from langgraph.types import interrupt, Command
class State(TypedDict):
some_text: str
def human_node(state: State):
value = interrupt( # (1)!
{
"text_to_revise": state["some_text"] # (2)!
}
)
return {
"some_text": value # (3)!
}
# Build the graph
graph_builder = StateGraph(State)
graph_builder.add_node("human_node", human_node)
graph_builder.add_edge(START, "human_node")
graph = graph_builder.compile()
```
1. `interrupt(...)` pauses execution at `human_node`, surfacing the given payload to a human.
2. Any JSON serializable value can be passed to the [`interrupt`](https://reference.langchain.com/python/langgraph/types/#langgraph.types.interrupt) function. Here, a dict containing the text to revise.
3. Once resumed, the return value of `interrupt(...)` is the human-provided input, which is used to update the state.
Once you have a running Agent Server, you can interact with it using
[LangGraph SDK](/langsmith/langgraph-python-sdk)
```python {highlight={2,34}} theme={null}
from langgraph_sdk import get_client
from langgraph_sdk.schema import Command
client = get_client(url=)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
# create a thread
thread = await client.threads.create()
thread_id = thread["thread_id"]
# Run the graph until the interrupt is hit.
result = await client.runs.wait(
thread_id,
assistant_id,
input={"some_text": "original text"} # (1)!
)
print(result['__interrupt__']) # (2)!
# > [
# > {
# > 'value': {'text_to_revise': 'original text'},
# > 'resumable': True,
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
# > 'when': 'during'
# > }
# > ]
# Resume the graph
print(await client.runs.wait(
thread_id,
assistant_id,
command=Command(resume="Edited text") # (3)!
))
# > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3\. The graph is resumed with a `Command(resume=...)`, injecting the human's input and continuing execution.
```javascript {highlight={32}} theme={null}
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: });
// Using the graph deployed with the name "agent"
const assistantID = "agent";
// create a thread
const thread = await client.threads.create();
const threadID = thread["thread_id"];
// Run the graph until the interrupt is hit.
const result = await client.runs.wait(
threadID,
assistantID,
{ input: { "some_text": "original text" } } # (1)!
);
console.log(result['__interrupt__']); # (2)!
# > [
# > {
# > 'value': {'text_to_revise': 'original text'},
# > 'resumable': True,
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
# > 'when': 'during'
# > }
# > ]
// Resume the graph
console.log(await client.runs.wait(
threadID,
assistantID,
{ command: { resume: "Edited text" }} # (3)!
));
# > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3. The graph is resumed with a `{ resume: ... }` command object, injecting the human's input and continuing execution.
Create a thread:
```bash theme={null}
curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Run the graph until the interrupt is hit:
```bash theme={null}
curl --request POST \
--url /threads//runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"some_text\": \"original text\"}
}"
```
Resume the graph:
```bash theme={null}
curl --request POST \
--url /threads//runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": \"Edited text\"
}
}"
```
## Static interrupts
Static interrupts (also known as static breakpoints) are triggered either before or after a node executes.
Static interrupts are **not** recommended for human-in-the-loop workflows. They are best used for debugging and testing.
You can set static interrupts by specifying `interrupt_before` and `interrupt_after` at compile time:
```python {highlight={1,2,3}} theme={null}
graph = graph_builder.compile( # (1)!
interrupt_before=["node_a"], # (2)!
interrupt_after=["node_b", "node_c"], # (3)!
)
```
1. The breakpoints are set during `compile` time.
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
Alternatively, you can set static interrupts at run time:
```python {highlight={1,5,6}} theme={null}
await client.runs.wait( # (1)!
thread_id,
assistant_id,
inputs=inputs,
interrupt_before=["node_a"], # (2)!
interrupt_after=["node_b", "node_c"] # (3)!
)
```
1. `client.runs.wait` is called with the `interrupt_before` and `interrupt_after` parameters. This is a run-time configuration and can be changed for every invocation.
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
```javascript {highlight={1,6,7}} theme={null}
await client.runs.wait( // (1)!
threadID,
assistantID,
{
input: input,
interruptBefore: ["node_a"], // (2)!
interruptAfter: ["node_b", "node_c"] // (3)!
}
)
```
1. `client.runs.wait` is called with the `interruptBefore` and `interruptAfter` parameters. This is a run-time configuration and can be changed for every invocation.
2. `interruptBefore` specifies the nodes where execution should pause before the node is executed.
3. `interruptAfter` specifies the nodes where execution should pause after the node is executed.
```bash theme={null}
curl --request POST \
--url /threads//runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"interrupt_before\": [\"node_a\"],
\"interrupt_after\": [\"node_b\", \"node_c\"],
\"input\":
}"
```
The following example shows how to add static interrupts:
```python theme={null}
from langgraph_sdk import get_client
client = get_client(url=)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
# create a thread
thread = await client.threads.create()
thread_id = thread["thread_id"]
# Run the graph until the breakpoint
result = await client.runs.wait(
thread_id,
assistant_id,
input=inputs # (1)!
)
# Resume the graph
await client.runs.wait(
thread_id,
assistant_id,
input=None # (2)!
)
```
1. The graph is run until the first breakpoint is hit.
2. The graph is resumed by passing in `None` for the input. This will run the graph until the next breakpoint is hit.
```js theme={null}
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: });
// Using the graph deployed with the name "agent"
const assistantID = "agent";
// create a thread
const thread = await client.threads.create();
const threadID = thread["thread_id"];
// Run the graph until the breakpoint
const result = await client.runs.wait(
threadID,
assistantID,
{ input: input } # (1)!
);
// Resume the graph
await client.runs.wait(
threadID,
assistantID,
{ input: null } # (2)!
);
```
1. The graph is run until the first breakpoint is hit.
2. The graph is resumed by passing in `null` for the input. This will run the graph until the next breakpoint is hit.
Create a thread:
```bash theme={null}
curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Run the graph until the breakpoint:
```bash theme={null}
curl --request POST \
--url /threads//runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\":
}"
```
Resume the graph:
```bash theme={null}
curl --request POST \
--url /threads//runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\"
}"
```
## Learn more
* [Human-in-the-loop conceptual guide](/oss/python/langgraph/interrupts): learn more about LangGraph human-in-the-loop features.
* [Common patterns](/oss/python/langgraph/interrupts#common-patterns): learn how to implement patterns like approving/rejecting actions, requesting user input, tool call review, and validating human input.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/add-human-in-the-loop.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Add metadata and tags to traces
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/add-metadata-tags
LangSmith supports sending arbitrary metadata and tags along with traces.
Tags are strings that can be used to categorize or label a trace. Metadata is a dictionary of key-value pairs that can be used to store additional information about a trace.
Both are useful for associating additional information with a trace, such as the environment in which it was executed, the user who initiated it, or an internal correlation ID. For more information on tags and metadata, see the [Concepts](/langsmith/observability-concepts#tags) page. For information on how to query traces and runs by metadata and tags, see the [Filter traces in the application](/langsmith/filter-traces-in-application) page.
```python Python theme={null}
import openai
import langsmith as ls
from langsmith.wrappers import wrap_openai
client = openai.Client()
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
]
# You can set metadata & tags **statically** when decorating a function
# Use the @traceable decorator with tags and metadata
# Ensure that the LANGSMITH_TRACING environment variables are set for @traceable to work
@ls.traceable(
run_type="llm",
name="OpenAI Call Decorator",
tags=["my-tag"],
metadata={"my-key": "my-value"}
)
def call_openai(
messages: list[dict], model: str = "gpt-4.1-mini"
) -> str:
# You can also dynamically set metadata on the parent run:
rt = ls.get_current_run_tree()
rt.metadata["some-conditional-key"] = "some-val"
rt.tags.extend(["another-tag"])
return client.chat.completions.create(
model=model,
messages=messages,
).choices[0].message.content
call_openai(
messages,
# To add at **invocation time**, when calling the function.
# via the langsmith_extra parameter
langsmith_extra={"tags": ["my-other-tag"], "metadata": {"my-other-key": "my-value"}}
)
# Alternatively, you can use the context manager
with ls.trace(
name="OpenAI Call Trace",
run_type="llm",
inputs={"messages": messages},
tags=["my-tag"],
metadata={"my-key": "my-value"},
) as rt:
chat_completion = client.chat.completions.create(
model="gpt-4.1-mini",
messages=messages,
)
rt.metadata["some-conditional-key"] = "some-val"
rt.end(outputs={"output": chat_completion})
# You can use the same techniques with the wrapped client
patched_client = wrap_openai(
client, tracing_extra={"metadata": {"my-key": "my-value"}, "tags": ["a-tag"]}
)
chat_completion = patched_client.chat.completions.create(
model="gpt-4.1-mini",
messages=messages,
langsmith_extra={
"tags": ["my-other-tag"],
"metadata": {"my-other-key": "my-value"},
},
)
```
```typescript TypeScript theme={null}
import OpenAI from "openai";
import { traceable, getCurrentRunTree } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers";
const client = wrapOpenAI(new OpenAI());
const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello!" },
];
const traceableCallOpenAI = traceable(
async (messages: OpenAI.Chat.ChatCompletionMessageParam[]) => {
const completion = await client.chat.completions.create({
model: "gpt-4.1-mini",
messages,
});
const runTree = getCurrentRunTree();
runTree.extra.metadata = {
...runTree.extra.metadata,
someKey: "someValue",
};
runTree.tags = [...(runTree.tags ?? []), "runtime-tag"];
return completion.choices[0].message.content;
},
{
run_type: "llm",
name: "OpenAI Call Traceable",
tags: ["my-tag"],
metadata: { "my-key": "my-value" },
}
);
// Call the traceable function
await traceableCallOpenAI(messages);
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/add-metadata-tags.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Overview
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/administration-overview
This overview covers topics related to managing users, organizations, workspaces, and applications within LangSmith.
## Resource hierarchy
### Organizations
An organization is a logical grouping of users within LangSmith that defines shared settings applying across all of its workspaces. These settings govern organization-wide concerns rather than individual projects within a workspace. Common organization-level configurations include user management, single sign-on (SSO), OAuth provider configuration, custom role creation, billing, and usage tracking. Typically, there is one organization per company. An organization can have multiple workspaces. For more details, see the [setup guide](/langsmith/set-up-hierarchy#set-up-an-organization).
When you log in for the first time, a personal organization will be created for you automatically. If you'd like to collaborate with others, you can create a separate organization and invite your team members to join. There are a few important differences between your personal organization and shared organizations:
| Feature | Personal | Shared |
| ------------------- | ------------------- | -------------------------------------------------------------------------------------------- |
| Maximum workspaces | 1 | Variable, depending on plan (see [pricing page](https://www.langchain.com/pricing-langsmith) |
| Collaboration | Cannot invite users | Can invite users |
| Billing: paid plans | Developer plan only | All other plans available |
### Workspaces
Workspaces were formerly called Tenants. Some code and APIs may still reference the old name for a period of time during the transition.
A workspace is a logical grouping of users and resources within an organization. Workspaces are commonly used to isolate teams or business units, providing separation between projects and their associated resources. A workspace separates trust boundaries for resources and access control. Users are granted permissions at the workspace level, which determine their access to resources in that workspace, including tracing projects, datasets, annotation queues, and prompts. For details on setup, see the [setup guide](/langsmith/set-up-hierarchy#set-up-a-workspace) and for details on permissions see [Workspaces (RBAC)](/langsmith/administration-overview#workspace-roles-rbac).
It is recommended to create a separate workspace for each team within your organization. To organize resources even further, you can use [Applications](#applications) to group resources within a workspace. For guidance on different workspace organization models based on your team's isolation requirements, refer to [Workload isolation](/langsmith/workload-isolation).
The following image shows a sample workspace settings page:
### Applications
An application is a logical grouping of resources within a workspace. Applications are often agents, but they can be used for any project within a team. Applications keep the UI organized by only surfacing the resources associated with the application currently in context.
Applications are built on top of [resource tags](/langsmith/administration-overview#resource-tags) and can be used to control resource access using [ABAC (private beta)](/langsmith/organization-workspace-operations#access-policies).
The following image shows how to manage and switch applications in the main navbar:
Any resource can be created without being tagged to an application. These resources will be visible when the `Show all applications` option is selected.
### Resources
Resources are the concrete entities used to build, run, and observe applications and agents, such as tracing projects, prompts, datasets, and deployments. Resources are scoped to a specific application.
### Additional info
The following diagram explains the relationship between organizations, workspaces, applications, and resources:
See the table below for details on which features are available in which scope(s):
| Resource/Setting | Scope |
| --------------------------------------------------------------------------- | ------------------------ |
| Trace Projects | Workspace or Application |
| Annotation Queues | Workspace or Application |
| Deployments | Workspace or Application |
| Datasets & Experiments | Workspace or Application |
| Prompts | Workspace or Application |
| Resource Tags | Workspace |
| API Keys | Workspace |
| Settings including Secrets, Feedback config, Models, Rules, and Shared URLs | Workspace |
| User management: Invite User to Workspace | Workspace |
| RBAC: Assigning Workspace Roles | Workspace |
| Data Retention, Usage Limits | Workspace\* |
| Plans and Billing, Credits, Invoices | Organization |
| User management: Invite User to Organization | Organization\*\* |
| Adding Workspaces | Organization |
| Assigning Organization Roles | Organization |
| RBAC: Creating/Editing/Deleting Custom Roles | Organization |
\* Data retention settings and usage limits will be available soon for the organization level as well \*\* Self-hosted installations may enable workspace-level invites of users to the organization via a feature flag. See the [self-hosted user management docs](/langsmith/self-host-user-management) for details.
### Resource tags
Resource tags allow you to further segregate resources within a workspace for use with [ABAC](/langsmith/organization-workspace-operations#access-policies). Each tag is a key-value pair that can be assigned to a resource.
LangSmith resource tags are very similar to tags in cloud services like [AWS](https://docs.aws.amazon.com/tag-editor/latest/userguide/tagging.html).
## User management and RBAC
### Users
A user is a person who has access to LangSmith. Users can be members of one or more organizations and workspaces within those organizations.
Organization members are managed in organization settings:
And workspace members are managed in workspace settings:
### API keys
We ended support for legacy API keys prefixed with `ls__` on October 22, 2024 in favor of personal access tokens (PATs) and service keys. We require using PATs and service keys for all new integrations. API keys prefixed with `ls__` will no longer work as of October 22, 2024.
#### Expiration dates
When you create an API key, you have the option to set an expiration date. Adding an expiration date to keys enhances security and minimizes the risk of unauthorized access. For example, you may set expiration dates on keys for temporary tasks that require elevated access.
By default, keys never expire. Once expired, an API key is no longer valid and cannot be reactivated or have its expiration modified.
#### Personal access tokens (PATs)
Personal Access Tokens (PATs) are used to authenticate requests to the LangSmith API. They are created by users and scoped to a user. The PAT will have the same permissions as the user that created it. We recommend not using these to authenticate requests from your application, but rather using them for personal scripts or tools that interact with the LangSmith API. If the user associated with the PAT is removed from the organization, the PAT will no longer work.
PATs are prefixed with `lsv2_pt_`
#### Service keys
Service keys are similar to PATs, but are used to authenticate requests to the LangSmith API on behalf of a service account. Only admins can create service keys. We recommend using these for applications / services that need to interact with the LangSmith API, such as LangGraph agents or other integrations. Service keys may be scoped to a single workspace, multiple workspaces, or the entire organization, and can be used to authenticate requests to the LangSmith API for whichever workspace(s) it has access to.
Service keys are prefixed with `lsv2_sk_`
Use the `X-Tenant-Id` header to specify the target workspace.
* **When using PATs**: If this header is omitted, requests will run against the default workspace associated with the key.
* **When using organization-scoped service keys**: You must include the `X-Tenant-Id` header when accessing workspace-scoped resources. Without it, the request will fail with a `403 Forbidden` error.
To see how to create a service key or Personal Access Token, see the [setup guide](/langsmith/create-account-api-key)
### Organization roles
Organization roles are distinct from the [Enterprise feature workspace RBAC](#workspace-roles-rbac) and are used in the context of multiple [workspaces](#workspaces). Your organization role determines your workspace membership characteristics and your [organization-level permissions](/langsmith/organization-workspace-operations).
The organization role selected also impacts workspace membership as described here:
* [Organization Admin](/langsmith/rbac#organization-admin) grants full access to manage all organization configuration, users, billing, and workspaces.
* An Organization Admin has `Admin` access to all workspaces in an organization.
* [Organization User](/langsmith/rbac#organization-user) may read organization information but cannot execute any write actions at the organization level. An Organization User may create [Personal Access Tokens](#personal-access-tokens-pats).
* An Organization User can be added to a subset of workspaces and assigned workspace roles as usual (if RBAC is enabled), which specify permissions at the workspace level.
* [Organization Viewer](/langsmith/rbac#organization-viewer) is equivalent to Organization User, but **cannot** create Personal Access Tokens. (for self-hosted, available in Helm chart version 0.11.25+).
The Organization User and Organization Viewer roles are only available in organizations on [plans](https://langchain.com/pricing) with multiple workspaces. In organizations limited to a single workspace, all users have the Organization Admin role.
See [security settings](/langsmith/manage-organization-by-api#security-settings) for instructions on how to disable PAT creation for the entire organization.
For more information on setting up organizations and workspaces, refer to the [organization setup guide](/langsmith/set-up-hierarchy#organization-roles) for more information.
The following table provides an overview of organization level permissions:
| | Organization Viewer | Organization User | Organization Admin |
| ------------------------------------------- | ------------------- | ----------------- | ------------------ |
| View organization configuration | ✅ | ✅ | ✅ |
| View organization roles | ✅ | ✅ | ✅ |
| View organization members | ✅ | ✅ | ✅ |
| View data retention settings | ✅ | ✅ | ✅ |
| View usage limits | ✅ | ✅ | ✅ |
| Create personal access tokens (PATs) | ❌ | ✅ | ✅ |
| Admin access to all workspaces | ❌ | ❌ | ✅ |
| Manage billing settings | ❌ | ❌ | ✅ |
| Create workspaces | ❌ | ❌ | ✅ |
| Create, edit, and delete organization roles | ❌ | ❌ | ✅ |
| Invite new users to organization | ❌ | ❌ | ✅ |
| Delete user invites | ❌ | ❌ | ✅ |
| Remove users from an organization | ❌ | ❌ | ✅ |
| Update data retention settings | ❌ | ❌ | ✅ |
| Update usage limits | ❌ | ❌ | ✅ |
For a comprehensive list of required permissions along with the operations and roles that can perform them, refer to the [Organization and workspace reference](/langsmith/organization-workspace-operations).
### Workspace roles (RBAC)
RBAC (Role-Based Access Control) is a feature that is only available to Enterprise customers. If you are interested in this feature, [contact our sales team](https://www.langchain.com/contact-sales). Other plans default to using the Admin role for all users.
Roles are used to define the set of permissions that a user has within a workspace. There are three built-in system roles that cannot be edited:
* [Workspace Admin](/langsmith/rbac#workspace-admin) has full access to all resources within the workspace.
* [Workspace Editor](/langsmith/rbac#workspace-editor) has full permissions except for workspace management (adding/removing users, changing roles, configuring service keys).
* [Workspace Viewer](/langsmith/rbac#workspace-viewer) has read-only access to all resources within the workspace.
[Organization admins](/langsmith/rbac#organization-admin) can also create/edit custom roles with specific permissions for different resources.
Roles can be managed in **Organization Settings** under the **Roles** tab:
* For comprehensive documentation on roles and permissions, refer to the [Role-based access control](/langsmith/rbac) guide.
* For more details on assigning and creating roles, refer to the [User Management](/langsmith/user-management) guide.
* For a comprehensive list of required permissions along with the operations and roles that can perform them, refer to the [Organization and workspace reference](/langsmith/organization-workspace-operations).
## Best practices
### Environment separation
Use [resource tags](#resource-tags) to organize resources by environment using the default tag key `Environment` and different values for the environment (e.g., `dev`, `staging`, `prod`). We do not recommend using separate workspaces for environment separation because resources cannot be shared across workspaces, which would prevent you from promoting resources (like prompts) between environments.
**Resource tags vs. commit tags for prompt management**
While both types of tags can use environment terminology like `dev`, `staging`, and `prod`, they serve different purposes:
* **Resource tags** (`Environment: prod`): Use these to *organize and filter* resources across your workspace. Apply resource tags to tracing projects, datasets, and other resources (including prompts) to group them by environment, which enables filtering in the UI.
* [Commit tags](/langsmith/manage-prompts#commit-tags) (`prod` tag): Use these to manage which [prompt version](/langsmith/prompt-engineering) your code references. Commit tags are labels that point to specific commits in a prompt's history. When your code pulls a prompt by tag name (e.g., `client.pull_prompt("prompt-name:prod")`), it retrieves whichever commit that tag currently points to. To promote a prompt from `staging` to `prod`, move the commit tag to point to the desired version.
Resource tags organize **which resources** belong to an environment. Commit tags let you control **which version** of a prompt your code references without changing the code itself.
## Usage and billing
### Data retention
This section covers how data retention works and how it's priced in LangSmith.
#### Why retention matters
* **Privacy**: Many data privacy regulations, such as GDPR in Europe or CCPA in California, require organizations to delete personal data once it's no longer necessary for the purposes for which it was collected. Setting retention periods aids in compliance with such regulations.
* **Cost**: LangSmith charges less for traces that have low data retention. See our tutorial on how to [optimize spend](/langsmith/billing#optimize-your-tracing-spend) for details.
#### How it works
LangSmith has two tiers of traces based on Data Retention with the following characteristics:
| | Base | Extended |
| -------------------- | --------------------------------------------------------------- | --------------------------------------------------------------- |
| **Price** | [See pricing page](https://www.langchain.com/pricing-langsmith) | [See pricing page](https://www.langchain.com/pricing-langsmith) |
| **Retention Period** | 14 days | 400 days |
**Data deletion after retention ends**
After the specified retention period, traces are no longer accessible in the tracing project UI or via the API. All user data associated with the trace (e.g. inputs and outputs) is deleted from our internal systems within a day thereafter. Some metadata associated with each trace may be retained indefinitely for analytics and billing purposes.
**Data retention auto-upgrades**
Auto upgrades can have an impact on your bill. Please read this section carefully to fully understand your estimated LangSmith tracing costs.
When you use certain features with `base` tier traces, their data retention will be automatically upgraded to `extended` tier. This will increase both the retention period, and the cost of the trace.
The complete list of scenarios in which a trace will upgrade when:
* **Feedback** is added to any run on the trace (or any trace in the thread), whether through [manual annotation](/langsmith/annotate-traces-inline#annotate-traces-and-runs-inline), automatically with [an online evaluator](/langsmith/online-evaluations-llm-as-judge), or programmatically [via the SDK](/langsmith/attach-user-feedback#log-user-feedback-using-the-sdk).
* An **[annotation queue](/langsmith/annotation-queues#assign-runs-to-an-annotation-queue)** receives any run from the trace.
* An **[automation rule](/langsmith/rules#set-up-automation-rules)** matches any run within a trace.
**Why auto-upgrade traces?**
We have two reasons behind the auto-upgrade model for tracing:
1. We think that traces that match any of these conditions are fundamentally more interesting than other traces, and therefore it is good for users to be able to keep them around longer.
2. We philosophically want to charge customers an order of magnitude lower for traces that may not be interacted with meaningfully. We think auto-upgrades align our pricing model with the value that LangSmith brings, where only traces with meaningful interaction are charged at a higher rate.
If you have questions or concerns about our pricing model, please feel free to contact support via [support.langchain.com](https://support.langchain.com) and let us know your thoughts!
**How does data retention affect downstream features?**
* **Annotation Queues, Run Rules, and Feedback**: Traces that use these features will be [auto-upgraded](#data-retention-auto-upgrades).
* **Monitoring**: The monitoring tab will continue to work even after a base tier trace's data retention period ends. It is powered by trace metadata that exists for >30 days, meaning that your monitoring graphs will continue to stay accurate even on `base` tier traces.
* **Datasets**: Datasets have an indefinite data retention period. Restated differently, if you add a trace's inputs and outputs to a dataset, they will never be deleted. We suggest that if you are using LangSmith for data collection, you take advantage of the datasets feature.
#### Billing model
**Billable metrics**
On your LangSmith invoice, you will see two metrics that we charge for:
* LangSmith Traces (Base Charge)
* LangSmith Traces (Extended Data Retention Upgrades).
The first metric includes all traces, regardless of tier. The second metric just counts the number of extended retention traces.
**Why measure all traces + upgrades instead of base and extended traces?**
A natural question to ask when considering our pricing is why not just show the number of `base` tier and `extended` tier traces directly on the invoice?
While we understand this would be more straightforward, it doesn't fit trace upgrades properly. Consider a `base` tier trace that was recorded on June 30, and upgraded to `extended` tier on July 3. The `base` tier trace occurred in the June billing period, but the upgrade occurred in the July billing period. Therefore, we need to be able to measure these two events independently to properly bill our customers.
If your trace was recorded as an extended retention trace, then the `base` and `extended` metrics will both be recorded with the same timestamp.
**Cost breakdown**
The Base Charge for a trace is .05¢ per trace. We priced the upgrade such that an `extended` retention trace costs 10x the price of a base tier trace (.50¢ per trace) including both metrics. Thus, each upgrade costs .45¢.
### Rate limits
LangSmith has rate limits which are designed to ensure the stability of the service for all users.
To ensure access and stability, LangSmith will respond with HTTP Status Code 429 indicating that rate or usage limits have been exceeded under the following circumstances:
#### Temporary throughput limit over a 1 minute period at our application load balancer
This 429 is the result of exceeding a fixed number of API calls over a 1 minute window on a per API key/access token basis. The start of the window will vary slightly — it is not guaranteed to start at the start of a clock minute — and may change depending on application deployment events.
After the max events are received we will respond with a 429 until 60 seconds from the start of the evaluation window has been reached and then the process repeats.
This 429 is thrown by our application load balancer and is a mechanism in place for all LangSmith users independent of plan tier to ensure continuity of service for all users.
| Method | Endpoints | Limit | Window |
| ----------------- | ------------- | ----- | -------- |
| `DELETE` | `/sessions*` | 30 | 1 minute |
| `POST` OR `PATCH` | `/runs*` | 5000 | 1 minute |
| `GET` | `/runs/:id` | 30 | 1 minute |
| `POST` | `/feedbacks*` | 5000 | 1 minute |
| `*` | `*` | 2000 | 1 minute |
The LangSmith SDK takes steps to minimize the likelihood of reaching these limits on run-related endpoints by batching up to 100 runs from a single session ID into a single API call.
#### Plan-level hourly trace event limit
This 429 is the result of reaching your maximum hourly events ingested and is evaluated in a fixed window starting at the beginning of each clock hour in UTC and resets at the top of each new hour.
An event in this context is the creation or update of a run. So if run is created, then subsequently updated in the same hourly window, that will count as 2 events against this limit.
This is thrown by our application and varies by plan tier, with organizations on our Startup/Plus and Enterprise plan tiers having higher hourly limits than our Free and Developer Plan Tiers which are designed for personal use.
| Plan | Limit | Window |
| -------------------------------- | -------------- | ------ |
| Developer (no payment on file) | 50,000 events | 1 hour |
| Developer (with payment on file) | 250,000 events | 1 hour |
| Startup/Plus | 500,000 events | 1 hour |
| Enterprise | Custom | Custom |
#### Plan-level hourly trace data ingest limit
This 429 is the result of reaching the maximum amount of data ingested across your trace inputs, outputs, and metadata and is evaluated in a fixed window starting at the beginning of each clock hour in UTC and resets at the top of each new hour.
Typically, inputs, outputs, and metadata are send on both run creation and update events. So if a run is created and is 2.0MB in size at creation, and 3.0MB in size when updated in the same hourly window, that will count as 5.0MB of storage against this limit.
This is thrown by our application and varies by plan tier, with organizations on our Startup/Plus and Enterprise plan tiers having higher hourly limits than our Free and Developer Plan Tiers which are designed for personal use.
| Plan | Limit | Window |
| -------------------------------- | ------ | ------ |
| Developer (no payment on file) | 500MB | 1 hour |
| Developer (with payment on file) | 2.5GB | 1 hour |
| Startup/Plus | 5.0GB | 1 hour |
| Enterprise | Custom | Custom |
#### Plan-level monthly unique traces limit
This 429 is the result of reaching your maximum monthly traces ingested and is evaluated in a fixed window starting at the beginning of each calendar month in UTC and resets at the beginning of each new month.
This is thrown by our application and applies only to the Developer Plan Tier when there is no payment method on file.
| Plan | Limit | Window |
| ------------------------------ | ------------ | ------- |
| Developer (no payment on file) | 5,000 traces | 1 month |
#### Self-configured monthly usage limits
This 429 is the result of reaching your usage limit as configured by your organization admin and is evaluated in a fixed window starting at the beginning of each calendar month in UTC and resets at the beginning of each new month.
This is thrown by our application and varies by organization based on their configured settings.
#### Handling 429s responses in your application
Since some 429 responses are temporary and may succeed on a successive call, if you are directly calling the LangSmith API in your application we recommend implementing retry logic with exponential backoff and jitter.
For convenience, LangChain applications built with the LangSmith SDK has this capability built-in.
It is important to note that if you are saturating the endpoints for extended periods of time, retries may not be effective as your application will eventually run large enough backlogs to exhaust all retries.
If that is the case, we would like to discuss your needs more specifically. Please contact support via [LangSmith Support](https://support.langchain.com) with details about your applications throughput needs and sample code and we can work with you to better understand whether the best approach is fixing a bug, changes to your application code, or a different LangSmith plan.
### Usage limits
LangSmith lets you configure usage limits on tracing. Note that these are *usage* limits, not *spend* limits, which mean they let you limit the quantity of occurrences of some event rather than the total amount you will spend.
LangSmith lets you set two different monthly limits, mirroring our Billable Metrics discussed in the aforementioned data retention guide:
* All traces limit
* Extended data retention traces limit
These let you limit the number of total traces, and extended data retention traces respectively.
#### Properties of usage limiting
Usage limiting is approximate, meaning that we do not guarantee the exactness of the limit. In rare cases, there may be a small period of time where additional traces are processed above the limit threshold before usage limiting begins to apply.
#### Side effects of extended data retention traces limit
The extended data retention traces limit has side effects. If the limit is already reached, any feature that could cause an auto-upgrade of tracing tiers becomes inaccessible. This is because an auto-upgrade of a trace would cause another extended retention trace to be created, which in turn should not be allowed by the limit. Therefore, you can no longer:
1. match run rules
2. add feedback to traces
3. add runs to annotation queues
Each of these features may cause an auto upgrade, so we shut them off when the limit is reached.
#### Updating usage limits
Usage limits can be updated from the `Settings` page under `Usage and Billing`. Limit values are cached, so it may take a minute or two before the new limits apply.
### Related content
* Tutorial on how to [optimize spend](/langsmith/billing#optimize-your-tracing-spend)
## Additional resources
* **[Release Versions](/langsmith/release-versions)**: Learn about LangSmith's version support policy, including Active, Critical, End of Life, and Deprecated support levels.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/administration-overview.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Set up Agent Auth (Beta)
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-auth
Enable secure access from agents to any system using OAuth 2.0 credentials with Agent Auth.
Agent Auth is in **Beta** and under active development. To provide feedback or use this feature, reach out to the [LangChain team](https://forum.langchain.com/c/help/langsmith/).
## Installation
```bash pip theme={null}
pip install langchain-auth
```
```bash uv theme={null}
uv add langchain-auth
```
```bash theme={null}
npm install @langchain/auth
```
## Quickstart
### 1. Initialize the client
```python theme={null}
from langchain_auth import Client
client = Client(api_key="your-langsmith-api-key")
```
```javascript theme={null}
import { Client } from '@langchain/auth';
const client = new Client({ apiKey: 'your-langsmith-api-key' });
```
#### Self-hosted configuration
For self-hosted LangSmith instances, specify the API URL using the `/api-host` path on your instance.
```bash theme={null}
export LANGSMITH_API_URL="https://your-langsmith-instance.com/api-host"
```
Then initialize the client normally:
```python theme={null}
client = Client(api_key="your-langsmith-api-key")
```
```python theme={null}
client = Client(
api_key="your-langsmith-api-key",
api_url="https://your-langsmith-instance.com/api-host"
)
```
```javascript theme={null}
const client = new Client({
apiKey: 'your-langsmith-api-key',
apiUrl: 'https://your-langsmith-instance.com/api-host'
});
```
### 2. Set up OAuth providers
Before agents can authenticate, you need to configure an OAuth provider using the following process:
1. Select a unique identifier for your OAuth provider to use in LangChain's platform (e.g., "github-local-dev", "google-workspace-prod").
2. Go to your OAuth provider's developer console and create a new OAuth application.
3. Set the callback URL in your OAuth provider:
```
https://smith.langchain.com/host-oauth-callback/{provider_id}
```
For example, if your provider\_id is "github-local-dev", use:
```
https://smith.langchain.com/host-oauth-callback/github-local-dev
```
```
https://{your-langsmith-instance}/host-oauth-callback/{provider_id}
```
For example, if your instance is `langsmith.example.com` and provider\_id is "github", use:
```
https://langsmith.example.com/host-oauth-callback/github
```
4. Use `client.create_oauth_provider()` with the credentials from your OAuth app:
```python theme={null}
new_provider = await client.create_oauth_provider(
provider_id="{provider_id}", # Provide any unique ID
name="{provider_display_name}", # Provide any display name
client_id="{your_client_id}",
client_secret="{your_client_secret}",
auth_url="{auth_url_of_your_provider}",
token_url="{token_url_of_your_provider}",
)
```
```javascript theme={null}
const newProvider = await client.createOAuthProvider({
providerId: '{provider_id}', // Provide any unique ID
name: '{provider_display_name}', // Provide any display name
clientId: '{your_client_id}',
clientSecret: '{your_client_secret}',
authUrl: '{auth_url_of_your_provider}',
tokenUrl: '{token_url_of_your_provider}',
});
```
### 3. Authenticate from an agent
The client `authenticate()` API is used to get OAuth tokens from pre-configured providers. On the first call, it takes the caller through an OAuth 2.0 auth flow.
#### In LangGraph context
By default, tokens are scoped to the calling agent using the Assistant ID parameter.
```python theme={null}
auth_result = await client.authenticate(
provider="{provider_id}",
scopes=["scopeA"],
user_id="your_user_id" # Any unique identifier to scope this token to the human caller
)
# Or explicitly specify an agent_id for agent-scoped tokens
auth_result = await client.authenticate(
provider="{provider_id}",
scopes=["scopeA"],
user_id="your_user_id",
agent_id="specific-agent-id" # Optional: explicitly set agent scope
)
```
During execution, if authentication is required, the SDK will throw an [interrupt](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/add-human-in-the-loop/#pause-using-interrupt). The agent execution pauses and presents the OAuth URL to the user:
After the user completes OAuth authentication and we receive the callback from the provider, they will see the auth success page.
The agent then resumes execution from the point it left off at, and the token can be used for any API calls. We store and refresh OAuth tokens so that future uses of the service by either the user or agent do not require an OAuth flow.
```python theme={null}
token = auth_result.token
```
#### Outside LangGraph context
Provide the `auth_url` to the user for out-of-band OAuth flows.
```python theme={null}
auth_result = await client.authenticate(
provider="{provider_id}",
scopes=["scopeA"],
user_id="your_user_id"
)
if auth_result.status == "pending":
print(f"Complete OAuth at: {auth_result.url}")
# Wait for user to complete OAuth
completed_auth = await client.wait_for_completion(auth_result.auth_id)
print("Authentication completed!")
else:
token = auth_result.token
print(f"Already authenticated, token: {token}")
```
```javascript theme={null}
const authResult = await client.authenticate({
provider: '{provider_id}',
scopes: ['scopeA'],
userId: 'your_user_id'
});
if (authResult.status === 'pending') {
console.log(`Complete OAuth at: ${authResult.authUrl}`);
// Wait for user to complete OAuth
const completedAuth = await client.waitForCompletion(authResult.authId);
console.log('Authentication completed!');
} else {
const token = authResult.token;
console.log(`Already authenticated, token: ${token}`);
}
```
## Troubleshooting
### Self-hosted: 405 Method Not Allowed
If you receive a `405 Method Not Allowed` error, ensure `LANGSMITH_API_URL` points to the `/api-host` path:
```bash theme={null}
export LANGSMITH_API_URL="https://your-instance.com/api-host"
```
### Self-hosted: Malformed OAuth callback URL
Ensure your OAuth provider's redirect URI matches your LangSmith instance URL:
```
https://your-instance.com/host-oauth-callback/{provider_id}
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-auth.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith Agent Builder
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder
Create helpful AI agents without code. Start from a template, connect your accounts, and let the agent handle routine work while you stay in control.
## What you can do
* Automate everyday tasks like drafting emails, summarizing updates, and organizing information.
* Connect your favorite apps to bring context into your agent's work.
* Use in chat or where you work (e.g., Slack) to get help in the flow.
* Stay in control with simple approvals for important actions.
## Start building
Pick a ready-made starter (e.g., email assistant or team updates) and customize.
Describe your goal in plain English and let AI draft your agent's configuration. Review and edit before running.
## Get started
Sign up for a [LangSmith account](https://smith.langchain.com/agents?skipOnboarding=true).
Start from a ready-to-use template, or describe your goal and let AI draft your agent's instructions. You can edit details before running. [Browse templates](https://www.langchain.com/templates).
Securely sign in to the services you want the agent to use.
Run the agent and iterate on its instructions in a few clicks.
## Privacy policy and disclaimers
The LangSmith Agent Builder App for Slack collects, manages, and stores third-party data in accordance with our privacy policy. For full details on how your data is handled, please see [our privacy policy](https://www.langchain.com/privacy-policy).
Agent Builder uses the following approach to AI:
* **Model**: Uses LLMs provided through the LangSmith platform
* **Data retention**: User data is retained according to LangSmith's data retention policies
* **Data tenancy**: Data is handled according to your LangSmith organization settings
* **Data residency**: Data residency follows your LangSmith configuration
Disclaimers:
* **AI-generated content**: All responses from agents are generated by AI and may contain errors or inaccuracies. Always verify important information.
* **Data usage**: Slack data is not used to train LLMs. Your workspace data remains private and is only used to provide agent functionality.
* **Transparency**: The Agent Builder is transparent about the actions it will take once added to your workspace, as outlined in the permissions section above.
## Learn more
* [Essentials: connections, automation, memory, approvals](/langsmith/agent-builder-essentials)
* [Create from a template](/langsmith/agent-builder-templates)
* [Set up your workspace](/langsmith/agent-builder-setup)
* [Connect apps and services](/langsmith/agent-builder-tools) and [use remote connections](/langsmith/agent-builder-mcp-framework)
* [Choose between workspace and private agents](/langsmith/agent-builder-manage-agent-settings)
* [Authorize accounts when prompted](/langsmith/agent-builder-auth-format)
* [Call agents from your app](/langsmith/agent-builder-code)
**Self-hosting for Agent Builder is available in Beta.** For more information, see [Enable Agent Builder](/langsmith/agent-builder-self-hosted).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Auth-aware tool responses
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-auth-format
Format tool responses to trigger OAuth flows and resume execution automatically.
Some [tools](/langsmith/agent-builder-tools) require user authorization (for example, Google, Slack, GitHub). Agent Builder includes middleware to detect when a tool needs authorization and to pause the run with a clear prompt to the user. After the user completes auth, the same tool call is retried automatically.
## Return shape to request auth
If a tool detects missing authorization, return a JSON string containing the following fields:
```json theme={null}
{
"auth_required": true,
"auth_url": "https://auth.example.com/start",
"auth_id": "opaque-tracking-id"
}
```
* `auth_required`: set to `true` to signal an interrupt is needed.
* `auth_url`: where the user should be redirected to authorize.
* `auth_id`: optional correlation ID to track the auth session.
When Agent Builder detects this response, it interrupts the run, displays the authentication UI to the user, and automatically retries the tool call once authorization completes.
If you want your custom tools to reuse the same authentication required interrupt + UI, ensure your tools return the same shape of JSON.
Return only this JSON as the tool's output. Avoid including additional text or content. Agent Builder parses the response to trigger the authentication flow.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-auth-format.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Call agents from code
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-code
Invoke Agent Builder agents from Python, JavaScript, or any language through the REST API.
You can invoke Agent Builder agents from your applications using the [LangGraph SDK](/langsmith/reference) or the REST API. Agent Builder agents run on [Agent Server](/langsmith/agent-server), so you can use the same API methods as any other [LangSmith deployment](/langsmith/deployments).
The REST API lets you call your agent from any language or platform that supports HTTP requests.
## Prerequisites
* A LangSmith account with an Agent Builder agent
* A [Personal Access Token (PAT)](/langsmith/create-account-api-key) for authentication
* (SDK only) The [LangGraph SDK](/langsmith/reference) installed:
```bash Python theme={null}
pip install langgraph-sdk python-dotenv
```
```bash TypeScript theme={null}
yarn add @langchain/langgraph-sdk
```
## Authentication
To authenticate with your agent's Agent Builder deployment, provide a LangSmith [Personal Access Token (PAT)](/langsmith/create-account-api-key) to the `api_key` argument when instantiating the LangGraph SDK client, or via the `X-API-Key` header. If using `X-API-Key`, you must also set the `X-Auth-Scheme` header to `langsmith-api-key`.
If the PAT you pass is not tied to the owner of the agent, your request will be rejected with a `404 Not Found` error.
If the agent you're trying to invoke is a workspace agent and you're not the owner, you can perform all the same operations as you would in the UI (read-only).
## 1. Get the agent ID and URL
To get your agent's `agent_id` and `api_url`:
1. In the [LangSmith UI](https://smith.langchain.com), navigate to your agent's inbox.
2. Next to the agent name, click the **Edit Agent** icon.
3. Click the **Settings** icon in the top right corner.
4. Click **View code snippets** to see pre-populated values for your agent.
Copy the code below and replace `agent_id` and `api_url` with the values from your agent's code snippets.
Create a `.env` file in your project root with your [Personal Access Token](/langsmith/create-account-api-key):
```bash .env theme={null}
LANGGRAPH_API_KEY=your-personal-access-token
```
## 2. Fetch agent configuration
Verify your connection by fetching your agent's configuration:
```python theme={null}
import os
from dotenv import load_dotenv
from langgraph_sdk.client import get_client
load_dotenv()
agent_id = "your-agent-id"
api_key = os.getenv("LANGGRAPH_API_KEY")
api_url = ".us.langgraph.app"
client = get_client(
url=api_url,
api_key=api_key,
headers={
"X-Auth-Scheme": "langsmith-api-key",
},
)
async def get_assistant(agent_id: str):
agent = await client.assistants.get(agent_id)
print(agent)
if __name__ == "__main__":
import asyncio
asyncio.run(get_assistant(agent_id))
```
```ts theme={null}
import "dotenv/config";
import { Client } from "@langchain/langgraph-sdk";
const agentId = "your-agent-id";
const apiKey = process.env.LANGGRAPH_API_KEY;
const apiUrl = ".us.langgraph.app";
const client = new Client({
apiUrl,
apiKey,
defaultHeaders: {
"X-Auth-Scheme": "langsmith-api-key",
},
});
async function main(agentId: string) {
const agent = await client.assistants.get(agentId);
console.log(agent);
}
main(agentId).catch(console.error);
```
```bash theme={null}
curl --request GET \
--url ".us.langgraph.app/assistants/your-agent-id" \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: your-personal-access-token' \
--header 'X-Auth-Scheme: langsmith-api-key'
```
Use a [Personal Access Token (PAT)](/langsmith/create-account-api-key) tied to your LangSmith account. Set the `X-Auth-Scheme` header to `langsmith-api-key` for authentication. If you implemented custom authentication, pass the user's token in headers so the agent can use user-scoped tools. See [Add custom authentication](/langsmith/custom-auth).
## 3. Invoke agent
The examples below show how to send a message to your agent and receive a response. You can use either a **stateless** run (no thread, no conversation history) or a **stateful** run (with a thread to maintain conversation history across multiple turns).
### Stateless run
A stateless run sends a single request and returns the full response. No conversation history is persisted. This is the simplest way to call your agent:
```python theme={null}
import os
from dotenv import load_dotenv
from langgraph_sdk.client import get_client
load_dotenv()
agent_id = "your-agent-id"
api_key = os.getenv("LANGGRAPH_API_KEY")
api_url = "https://.us.langgraph.app"
client = get_client(
url=api_url,
api_key=api_key,
headers={
"X-Auth-Scheme": "langsmith-api-key",
},
)
result = await client.runs.wait(
None,
agent_id,
input={
"messages": [
{"role": "user", "content": "What can you help me with?"}
]
},
)
print(result)
```
```ts theme={null}
import "dotenv/config";
import { Client } from "@langchain/langgraph-sdk";
const agentId = "your-agent-id";
const apiKey = process.env.LANGGRAPH_API_KEY;
const apiUrl = ".us.langgraph.app";
const client = new Client({
apiUrl,
apiKey,
defaultHeaders: {
"X-Auth-Scheme": "langsmith-api-key",
},
});
const result = await client.runs.wait(
null,
agentId,
{
input: {
messages: [
{ role: "user", content: "What can you help me with?" }
]
}
}
);
console.log(result);
```
```bash theme={null}
curl --request POST \
--url ".us.langgraph.app/runs/wait" \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: your-personal-access-token' \
--header 'X-Auth-Scheme: langsmith-api-key' \
--data '{
"assistant_id": "your-agent-id",
"input": {
"messages": [
{
"role": "user",
"content": "What can you help me with?"
}
]
}
}'
```
### Stateless streaming run
To stream the response as it is generated rather than waiting for the full result, use the streaming endpoint:
```python theme={null}
async for chunk in client.runs.stream(
None,
agent_id,
input={
"messages": [
{"role": "user", "content": "What can you help me with?"}
]
},
stream_mode="updates",
):
if chunk.data and "run_id" not in chunk.data:
print(chunk.data)
```
```ts theme={null}
const streamResponse = client.runs.stream(
null,
agentId,
{
input: {
messages: [
{ role: "user", content: "What can you help me with?" }
]
},
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && !("run_id" in chunk.data)) {
console.log(chunk.data);
}
}
```
```bash theme={null}
curl --request POST \
--url ".us.langgraph.app/runs/stream" \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: your-personal-access-token' \
--header 'X-Auth-Scheme: langsmith-api-key' \
--data '{
"assistant_id": "your-agent-id",
"input": {
"messages": [
{
"role": "user",
"content": "What can you help me with?"
}
]
},
"stream_mode": [
"updates"
]
}'
```
### Stateful run with a thread
To maintain conversation history across multiple interactions, first create a thread and then run your agent on it. Each subsequent run on the same thread has access to the full message history:
```python theme={null}
import os
from dotenv import load_dotenv
from langgraph_sdk.client import get_client
load_dotenv()
agent_id = "your-agent-id"
api_key = os.getenv("LANGGRAPH_API_KEY")
api_url = ".us.langgraph.app"
client = get_client(
url=api_url,
api_key=api_key,
headers={
"X-Auth-Scheme": "langsmith-api-key",
},
)
thread = await client.threads.create()
async for chunk in client.runs.stream(
thread["thread_id"],
agent_id,
input={
"messages": [
{"role": "user", "content": "Hi, my name is Alice."}
]
},
stream_mode="updates",
):
if chunk.data and "run_id" not in chunk.data:
print(chunk.data)
async for chunk in client.runs.stream(
thread["thread_id"],
agent_id,
input={
"messages": [
{"role": "user", "content": "What is my name?"}
]
},
stream_mode="updates",
):
if chunk.data and "run_id" not in chunk.data:
print(chunk.data)
```
```ts theme={null}
import "dotenv/config";
import { Client } from "@langchain/langgraph-sdk";
const agentId = "your-agent-id";
const apiKey = process.env.LANGGRAPH_API_KEY;
const apiUrl = ".us.langgraph.app";
const client = new Client({
apiUrl,
apiKey,
defaultHeaders: {
"X-Auth-Scheme": "langsmith-api-key",
},
});
const thread = await client.threads.create();
let streamResponse = client.runs.stream(
thread["thread_id"],
agentId,
{
input: {
messages: [
{ role: "user", content: "Hi, my name is Alice." }
]
},
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && !("run_id" in chunk.data)) {
console.log(chunk.data);
}
}
streamResponse = client.runs.stream(
thread["thread_id"],
agentId,
{
input: {
messages: [
{ role: "user", content: "What is my name?" }
]
},
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && !("run_id" in chunk.data)) {
console.log(chunk.data);
}
}
```
First, create a thread:
```bash theme={null}
curl --request POST \
--url ".us.langgraph.app/threads" \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: your-personal-access-token' \
--header 'X-Auth-Scheme: langsmith-api-key' \
--data '{}'
```
Use the `thread_id` from the response to send messages on the thread:
```bash theme={null}
curl --request POST \
--url ".us.langgraph.app/threads//runs/stream" \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: your-personal-access-token' \
--header 'X-Auth-Scheme: langsmith-api-key' \
--data '{
"assistant_id": "your-agent-id",
"input": {
"messages": [
{
"role": "user",
"content": "Hi, my name is Alice."
}
]
},
"stream_mode": [
"updates"
]
}'
```
Send a follow-up message on the same thread:
```bash theme={null}
curl --request POST \
--url ".us.langgraph.app/threads//runs/stream" \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: your-personal-access-token' \
--header 'X-Auth-Scheme: langsmith-api-key' \
--data '{
"assistant_id": "your-agent-id",
"input": {
"messages": [
{
"role": "user",
"content": "What is my name?"
}
]
},
"stream_mode": [
"updates"
]
}'
```
## REST API reference
The table below summarizes the key endpoints. Replace `` with your agent's deployment URL.
| Operation | Method | Endpoint |
| ------------------------------------------------------------------------------------------------------------------------ | ------ | ------------------------------------------- |
| [Get agent info](/langsmith/agent-server-api/assistants/get-assistant) | `GET` | `/assistants/` |
| [Create a thread](/langsmith/agent-server-api/threads/create-thread) | `POST` | `/threads` |
| [Run (wait for result)](https://docs.langchain.com/langsmith/agent-server-api/stateless-runs/create-run-wait-for-output) | `POST` | `/runs/wait` |
| [Run (streaming)](/langsmith/agent-server-api/stateless-runs/create-run-stream-output) | `POST` | `/runs/stream` |
| [Run on thread (wait)](/langsmith/agent-server-api/thread-runs/create-run-wait-for-output) | `POST` | `/threads//runs/wait` |
| /langsmith/agent-server-api/thread-runs/create-run-stream-output | `POST` | `/threads//runs/stream` |
All endpoints require the following headers:
* `Content-Type: application/json`
* `X-Api-Key:` your [Personal Access Token](/langsmith/create-account-api-key)
* `X-Auth-Scheme: langsmith-api-key`
For the full API specification, see the [Agent Server API reference](/langsmith/server-api-ref).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-code.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Essentials
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-essentials
Agent Builder's core features
Agent Builder essentials are the core features that make up the foundation of your agents. They include tools, triggers, memory, sub-agents, and approvals.
## Tools
Tools let your agents interact with your apps and services. Your agents can send emails, create calendar events, post messages, search the web, and more. Choose from built-in tools for Gmail, Slack, Google Calendar, GitHub, and many others.
See [Supported tools](/langsmith/agent-builder-tools) for a complete list.
## Triggers
Triggers define when your agent should start running. You can connect your agent to external tools or time-based schedules, letting it respond automatically to messages, emails, or recurring events.
Here are some popular ways to trigger your agent:
Activate your agent when messages are received in specific Slack channels.
Trigger your agent when emails are received.
Run your agent on a time-based schedule for recurring tasks.
## Threads
Threads are conversations between you and your agent. Each thread contains messages, agent responses, and any actions the agent takes.
To view threads, navigate to your agent in the [LangSmith UI](https://smith.langchain.com). The inbox shows all threads for that agent. Click on a thread to view the conversation.
### Read and unread status
How threads are marked depends on whether the agent uses triggers:
* **Chat agents (no trigger):** Responses mark the thread as **unread**. Viewing the thread marks it as read.
* **Trigger-based agents:** Responses keep the thread as **read** by default.
You can manually mark any thread as read or unread at any time.
## Skills
Skills are a way to bundle capabilities and provide more specific information in situations where the context is not universally relevant.
Using skills can help:
* Save on token usage by only providing the context that is relevant to the current task.
* Prevent the agent from having too much context in the system prompt, which can lead to hallucinations and incorrect responses.
For more information, see [Skills](/oss/python/deepagents/skills).
## Memory and updates
Agents remember important information from previous conversations and can update themselves to work better.
* Memory: Agents remember relevant details from past interactions, so they can make better decisions in future conversations.
* Self-updates: Agents can add new tools, remove ones they don't need, or adjust their instructions to improve how they work.
* What stays the same: Agents can't change their name, description, or the triggers that start them.
For more information, see [How we built Agent Builder's memory system](https://www.langchain.com/conceptual-guides/how-we-built-agent-builders-memory).
## Custom models
Agent Builder supports custom models. You can connect any LLM API that supports the **OpenAI chat completions spec** or **Anthropic chat spec**.
Common use cases include:
* **LLM proxies**: Route requests through services like LiteLLM, Portkey, or your own proxy.
* **Self-hosted models**:Connect to models running on your own infrastructure.
* **Alternative providers**: Use any provider with a compatible API.
To add a custom model:
1. In the [LangSmith UI](https://smith.langchain.com), navigate to the agent you want to edit.
2. Click on the settings icon in the top right corner.
3. In the **Model** section, select **+ Add custom model**.
4. Enter the model ID, display name, base URL, and API key name and value.
5. Click **Save**.
Custom models must be accessible through a public API endpoint. LangSmith cannot connect to models hosted on private networks, behind VPNs, or on machines that are not exposed to the internet.
## Sub-agents
Build complex agents by breaking big tasks into smaller, specialized helpers. Think of sub-agents as a team of specialists—each one handles a specific part of the job while working together with your main agent.
This approach makes it easier to build sophisticated systems. Instead of one agent trying to do everything, you can have specialized helpers that each excel at their part of the task.
Here are some ways you might use sub-agents:
* Split into sub-tasks: Have one agent fetch data, another summarize it, and a third format the results.
* Specialized tools: Give different agents access to different tools based on what they need to do.
* Independent work: Let sub-agents work on their own, then bring their results back to the main agent.
## Human-in-the-loop
Stay in control of important decisions. You can set up your agent to pause and ask for your approval before taking certain actions. This ensures your agent handles most tasks automatically, while you retain oversight.
### Setting up approval steps
When setting up your agent, choose the tool or action you want to review before it runs.
Find the approval option for that tool and switch it on.
When your agent reaches that step, it will pause and wait for your approval before continuing.
### What you can do when your agent pauses
When your agent stops to ask for approval, you have three options:
Give the green light and let your agent proceed with its plan.
Modify the agent's message or parameters before allowing it to continue.
Share feedback to help your agent learn and improve.
## Traces
Traces are a series of steps that your agent takes to go from input to output. You can use [LangSmith](/langsmith/home) to visualize these execution steps.
To view all traces for your agent:
1. In the [LangSmith UI](https://smith.langchain.com), navigate to your agent's inbox.
2. Next to the agent name, click the **View Agent Traces** icon.
To view a trace for a specific thread:
1. In the [LangSmith UI](https://smith.langchain.com), navigate to your agent's inbox.
2. Right-click on the thread you want to trace and select **View trace**.
For more information, see [LangSmith Observability](/langsmith/observability).
## Next steps
* [Set up your workspace](/langsmith/agent-builder-setup)
* [Connect apps and services](/langsmith/agent-builder-tools)
* [Use remote servers for tools](/langsmith/agent-builder-remote-mcp-servers)
* [Choose between workspace and private agents](/langsmith/agent-builder-manage-agent-settings)
* [Call agents from your app](/langsmith/agent-builder-code)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-essentials.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Manage agent settings
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-manage-agent-settings
Manage your agents in Agent Builder.
This page explains how to manage the settings for your agents in Agent Builder.
## Change the model
To change the model for your agent:
1. In the [LangSmith UI](https://smith.langchain.com), navigate to your agent's inbox.
2. Next to the agent name, click the **Edit Agent** icon.
3. In the top right corner, click the **Settings** icon.
4. Select the **Model** you want to use.
5. Enter the API key for the model.
For information on how to add a custom model, see [Custom models](/langsmith/agent-builder-essentials#custom-models).
## Reconnect tool integrations
To reconnect a tool integration to an agent:
1. In the [LangSmith UI](https://smith.langchain.com), navigate to your agent's inbox.
2. Next to the agent name, click the **Edit Agent** icon.
3. In the top right corner, click the **Settings** icon.
4. In the **Connected Integrations** section, click the **Connect** button next to the tool you want to reconnect.
## Download agent files
To download the files for your agent, click the **Settings** icon in the top right corner of the agent and select **Download ZIP**. This will export the agent configuration as a ZIP file.
## Change access to the agent
Agents can either be private to the creator or shared within a LangSmith workspace.
| Feature | Private agents | [Workspace agents](#workspace-scoped-agent-details) |
| ------------------------ | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **Ownership and access** | Only visible to creator | Visible to anyone within the same LangSmith workspace |
| **OAuth authentication** | OAuth credentials are scoped to creator | OAuth credentials are scoped to each user; new users cloning workspace agents must re-authenticate with selected tools |
| **Secrets** | Uses workspace-scoped LangSmith secrets | Uses workspace-scoped LangSmith secrets (same as private agents) |
To change the agent visibility, click the **Visibility settings** icon in the top right corner of the agent and select either **Only me** or **Workspace**.
### Workspace-scoped agent details
While workspace-scoped agents are shared, some details are public, while others are private:
* **Threads are always user-scoped**, so even if an agent is workspace-scoped, the chat history created within that agent will always be private and only accessible to the specific user who created them.
* **The system prompt, selected tools, and sub-agents will be public on workspace-scoped agents.** Users will not be able to modify these fields on the original workspace-scoped agent, but can make changes once they've cloned the agent.
* **The trigger type on workspace-scoped agents is public** (for example, Slack message received), but the specific connection with the trigger (for example, the Slack channel, or Gmail address) is not shared. This way, users know what trigger to use when cloning an agent, but can't gain unauthorized access to any connections the original user has set up.
## Update memory
Your agent can remember information from previous conversations and use it to make better decisions in future conversations. By default, your agent will require approval to update its memories folder.
To disable this requirement for your agent:
1. In the [LangSmith UI](https://smith.langchain.com), navigate to your agent's inbox.
2. Next to the agent name, click the **Edit Agent** icon.
3. In the top right corner, click the **Settings** icon.
4. In the **Memory** section, toggle **Require approval to update memories** to off.
## Use the agent programmatically
You can use the [LangGraph SDK](/langsmith/reference) to connect to your agent through code. To view the code snippets needed to call your agent programmatically:
1. In the [LangSmith UI](https://smith.langchain.com), navigate to your agent's inbox.
2. Next to the agent name, click the **Edit Agent** icon.
3. In the top right corner, click the **Settings** icon.
4. Click the **View code snippets** button.
5. Copy the pre-populated code snippets for your agent.
For more information, see [Call agents from code](/langsmith/agent-builder-code).
## Pause agent
To pause an agent, pause its trigger:
1. In the [LangSmith UI](https://smith.langchain.com), navigate to your agent's inbox.
2. Next to the agent name, click the **Edit Agent** icon.
3. In the graph view, click the **Pause** button in the **Triggers** box.
4. Click **Save Changes**.
To resume, click the **Resume triggers** button in the **Triggers** box.
## Delete agent
To permanently delete an agent:
1. In the [LangSmith UI](https://smith.langchain.com), navigate to your agent's inbox.
2. Next to the agent name, click the **Edit Agent** icon.
3. In the top right corner, click the **Settings** icon.
4. Click the **Delete Agent** button.
5. To confirm the deletion, click the **Delete** button.
This action cannot be undone. It will permanently delete the agent, all threads linked to the agent, and unlink any attached triggers.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-manage-agent-settings.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith Tool Server
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-mcp-framework
The LangSmith Tool Server is a standalone MCP framework for building and deploying tools with built-in authentication and authorization. Use the Tool Server when you want to:
* [Create custom tools](#create-a-custom-toolkit) that integrate with LangSmith's [Agent Auth](/langsmith/agent-auth) for OAuth authentication
* [Build an MCP gateway](#use-as-an-mcp-gateway) for agents you're building yourself (outside of Agent Builder)
If you're using [Agent Builder](/langsmith/agent-builder), you don't need to interact with the Tool Server directly. Agent Builder provides [built-in tools](/langsmith/agent-builder-tools) and supports [remote MCP servers](/langsmith/agent-builder-tools#using-remote-mcp-servers) without requiring Tool Server setup.
However, you can configure the associated tool server instance as an MCP server, which will allow you to use your custom MCP servers in your agent.
Download the [PyPi package](https://pypi.org/project/langsmith-tool-server/) to get started.
## Create a custom toolkit
Install the LangSmith Tool Server and LangChain CLI:
```bash theme={null}
pip install langsmith-tool-server
pip install langchain-cli-v2
```
Create a new toolkit:
```bash theme={null}
langchain tools new my-toolkit
cd my-toolkit
```
This creates a toolkit with the following structure:
```
my-toolkit/
├── pyproject.toml
├── toolkit.toml
└── my_toolkit/
├── __init__.py
├── auth.py
└── tools/
├── __init__.py
└── ...
```
Define your tools using the `@tool` decorator:
```python theme={null}
from langsmith_tool_server import tool
@tool
def hello(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
@tool
def add(x: int, y: int) -> int:
"""Add two numbers."""
return x + y
TOOLS = [hello, add]
```
Run the server:
```bash theme={null}
langchain tools serve
```
Your tool server will start on `http://localhost:8000`.
## Call tools via MCP protocol
Below is an example that lists available tools and calls the `add` tool:
```python theme={null}
import asyncio
import aiohttp
async def mcp_request(url: str, method: str, params: dict = None):
async with aiohttp.ClientSession() as session:
payload = {"jsonrpc": "2.0", "method": method, "params": params or {}, "id": 1}
async with session.post(f"{url}/mcp", json=payload) as response:
return await response.json()
async def main():
url = "http://localhost:8000"
tools = await mcp_request(url, "tools/list")
print(f"Tools: {tools}")
result = await mcp_request(url, "tools/call", {"name": "add", "arguments": {"a": 5, "b": 3}})
print(f"Result: {result}")
asyncio.run(main())
```
## Use as an MCP gateway
The LangSmith Tool Server can act as an MCP gateway, aggregating tools from multiple MCP servers into a single endpoint. Configure MCP servers in your `toolkit.toml`:
```toml theme={null}
[toolkit]
name = "my-toolkit"
tools = "./my_toolkit/__init__.py:TOOLS"
[[mcp_servers]]
name = "weather"
transport = "streamable_http"
url = "http://localhost:8001/mcp/"
[[mcp_servers]]
name = "math"
transport = "stdio"
command = "python"
args = ["-m", "mcp_server_math"]
```
All tools from connected MCP servers are exposed through your server's `/mcp` endpoint. MCP tools are prefixed with their server name to avoid conflicts (e.g., `weather_get_forecast`, `math_add`).
## Authenticate
### OAuth for third-party APIs
For tools that need to access third-party APIs (like Google, GitHub, Slack, etc.), you can use OAuth authentication with [Agent Auth](/langsmith/agent-auth).
Before using OAuth in your tools, you'll need to configure an OAuth provider in your LangSmith workspace settings. See the [Agent Auth documentation](/langsmith/agent-auth) for setup instructions.
Once configured, specify the `auth_provider` in your tool decorator:
```python theme={null}
from langsmith_tool_server import tool, Context
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
@tool(
auth_provider="google",
scopes=["https://www.googleapis.com/auth/gmail.readonly"],
integration="gmail"
)
async def read_emails(context: Context, max_results: int = 10) -> str:
"""Read recent emails from Gmail."""
credentials = Credentials(token=context.token)
service = build('gmail', 'v1', credentials=credentials)
# ... Gmail API calls
return f"Retrieved {max_results} emails"
```
Tools with `auth_provider` must:
* Have `context: Context` as the first parameter
* Specify at least one scope
* Use `context.token` to make authenticated API calls
### Custom request authentication
Custom authentication allows you to validate requests and integrate with your identity provider. Define an authentication handler in your `auth.py` file:
```python theme={null}
from langsmith_tool_server import Auth
auth = Auth()
@auth.authenticate
async def authenticate(authorization: str = None) -> dict:
"""Validate requests and return user identity."""
if not authorization or not authorization.startswith("Bearer "):
raise auth.exceptions.HTTPException(
status_code=401,
detail="Unauthorized"
)
token = authorization.replace("Bearer ", "")
# Validate token with your identity provider
user = await verify_token_with_idp(token)
return {"identity": user.id}
```
The handler runs on every request and must return a dict with `identity` (and optionally `permissions`).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-mcp-framework.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Pricing FAQ
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-pricing-faq
View our [Pricing](https://www.langchain.com/pricing) page for more information on our pricing plans.
## What is an Agent Builder run?
An Agent Builder Run is a complete invocation of an Agent Builder agent, initiated by the receipt of a message or event, whether triggered automatically or sent manually. An Agent Builder Run includes any message delivered to the agent through configured triggers (including third-party integrations or other connected services) as well as messages sent directly through the Agent Builder user interface (for example, via the chat interface). An Agent Builder Run is counted if the invocation succeeds or is interrupted. Resuming execution after a human-in-the-loop interruption constitutes a separate Agent Builder Run. An execution failure does not count as an Agent Builder Run.
## Are model costs included with Agent Builder pricing?
No. Model usage is billed separately by your model provider. Agent Builder lets you choose the model you want (for example, Anthropic, OpenAI, Gemini, or another provider) and connect it using your provider API key. For more information on configuring models, see the [Agent Builder setup](/langsmith/agent-builder-setup#required-model-key) page.
## Are agent tools included with Agent Builder pricing?
Agent Builder includes built-in tools and supports connecting third-party tools via a remote MCP server. Third-party tools typically require you to authenticate with your account, and any fees or usage charges are billed by the third-party provider. For more information on setting up tool keys, see the [Agent Builder setup](/langsmith/agent-builder-setup#optional-tool-keys) page.
## Are Agent Builder runs automatically traced in LangSmith, and do they count toward billing?
Yes. Every Agent Builder run is automatically traced in LangSmith. These traces are attributed to the user's account and count toward usage-based billing under the customer's LangSmith plan.
## How do I disable Agent Builder?
If you are on a Self-Hosted LangSmith instance, you can disable Agent Builder by setting the following to `false` in your `values.yaml`:
```yaml theme={null}
backend:
agentBootstrap:
enabled: false
config:
agentBuilder:
enabled: false
agentBuilderToolServer:
enabled: false
agentBuilderTriggerServer:
enabled: false
```
If you are on cloud LangSmith, [reach out to our Support team](https://support.langchain.com/) to disable Agent Builder.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-pricing-faq.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Quickstart
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-quickstart
Build an agent from a template
In this quickstart, you'll use the pre-defined **Email Assistant** [template](/langsmith/agent-builder-templates) that organizes and manages your inbox for you.
* Select a different template.
You'll interact with your agent through chat, just like texting a helpful assistant.
## Before you start
You'll need:
* A LangSmith account ([sign up here](https://smith.langchain.com/agents?skipOnboarding=true)).
* A Gmail account.
* A Google calendar.
* An OpenAI or Anthropic API key (Step 1 will show you how to get one).
## 1. Get your model API key
Your agent needs an API key to connect to an AI model. The AI model is what allows your agent to understand and respond to your requests.
1. Go to [platform.openai.com/api-keys](https://platform.openai.com/api-keys).
2. Click **Create new secret key**.
3. Give it a name like "Agent Builder".
4. Copy the key (it starts with `sk-`).
5. Save it somewhere safe, you'll need it in Step 2.
1. Go to [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys).
2. Click **Create Key**.
3. Give it a name like "Agent Builder".
4. Copy the key (it starts with `sk-ant-`).
5. Save it somewhere safe, you'll need it in Step 2.
Both services charge based on usage.
## 2. Add your API key to LangSmith
Now you'll add your API key to LangSmith so your agents can use it:
1. Go to [smith.langchain.com](https://smith.langchain.com).
2. Click the **Settings** icon in the bottom left.
Click the **Secrets** tab at the top.
1. Click **Add secret**.
2. For **Key**, enter:
* `OPENAI_API_KEY` (if using OpenAI)
* `ANTHROPIC_API_KEY` (if using Anthropic)
3. For **Value**, paste the API key you copied in Step 1.
4. Click **Save secret**.
Your agent now has access to an AI model to understand and respond to your requests. Next, you'll create your agent.
## 3. Create your agent
1. In the [LangSmith UI](https://smith.langchain.com), click **Switch to Agent Builder** at the top of the left-hand navigation.
1. Select **Templates** in the left-hand navigation.
2. Select **Email Assistant** template.
3. Click **Use this template**.
If you don't want to start with a template, you have two other options. From the **+ New Agent** page:
* **Chat**: Use the chat interface to describe your agent, and it will help you create it step-by-step.
* **Manually**: Select **Create manually instead** to build your agent without any pre-filled responses on the configuration page.
Your agent will ask you to connect your Google accounts:
1. Click **Connect**.
2. Sign in with your Google account.
3. Review permissions and click **Allow**.
4. You'll be redirected back to LangSmith where your agent will be created.
Your agent only accesses your accounts when working on tasks you give it. You can revoke access anytime in your Google account settings.
## 4. View the agent template
At this point, you can review the template instructions for the email assistant. If needed, you can make adjustments to the instructions.
If you made any changes, click **Save changes**.
1. In the right-hand panel of the configuration page, select the **Test Chat** tab.
2. Try out the email assistant in the chat interface, for example:
> *Apply a "Review" label to emails that I receive, which require some kind of review from me*
Your agent will start work and provide a **Continue** option for each step that requires your approval.
3. As you test out the agent, you can make edits to the instructions, or add tools that you may need. Click **Save changes** when you're happy with the results.
## Edit your agent
You may want to update your agent's instructions or include more tools. You can directly chat with your agent to ask for updates, or you can:
1. From **My Agents** in the left-hand navigation, select the agent you want to edit.
2. Select **Edit Agent**.
From the agent's edit page, you can:
* Add tools with **+ Add tool** to connect more apps and services like Slack, GitHub, or Linear.
* Add further helpers with **+ Add sub-agent** to break complex tasks into specialized sub-tasks.
* Request pauses for reviews on existing tools.
* Modify existing tools.
* Explore features that can trigger your agent to start a task.
## Next steps
Now that you've created your first agent, here's what to explore:
Explore pre-built agents for common tasks
Run your agent automatically with triggers (Slack, email, schedules)
Add Slack, GitHub, Linear, and more
Use sub-agents to break down big tasks
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-quickstart.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Remote MCP servers
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-remote-mcp-servers
Connect Agent Builder to popular remote MCP servers
You can connect Agent Builder to remote MCP servers to extend your agents with additional tools and integrations. This page covers how to add custom MCP servers and provides configuration details for popular remote servers.
An [*MCP (Model Context Protocol) server*](https://modelcontextprotocol.io/docs/getting-started/intro) exposes tools that an agent can call at runtime.
A remote MCP server:
* Runs outside of LangSmith (usually over HTTPS).
* Owns its own authentication and authorization.
* Acts as a bridge between your agent and an external system.
LangSmith Agent Builder doesn't execute these tools itself, it forwards requests to the MCP server and returns the results to the agent.
### How it works
* Agent Builder discovers tools from remote MCP servers via the standard MCP protocol.
* Headers configured in your workspace are automatically attached when fetching tools or calling them. Headers are key-value pairs sent with every HTTP request to your MCP server. They're commonly used for authentication (like API keys or bearer tokens), but can also provide configuration information, content types, or custom metadata.
* Tools from remote servers are available alongside built-in tools in Agent Builder.
**Runtime**: Agent Builder automatically connects to your MCP server and uses its tools.
```mermaid theme={null}
sequenceDiagram
participant Agent as Agent Builder
participant MCP as Remote MCP Server
Agent->>MCP: Discover available tools (with configured headers)
MCP-->>Agent: Return tool list
Note over Agent,MCP: Later, when agent needs a tool...
Agent->>MCP: Call tool (with configured headers)
MCP-->>Agent: Return result
```
The following sections show you how to connect a remote MCP server to Agent Builder:
* [General configuration](#general-configuration): Step-by-step instructions for connecting any remote MCP server with authentication headers. Use this if you're familiar with MCP servers and want a quick reference.
* [Example: Connecting a custom MCP server](#example-connecting-a-custom-mcp-server): A detailed walkthrough using a GitHub-based MCP server as an example. Use this if you want to see a complete end-to-end example with specific authentication details.
## Add a remote MCP server
Configure remote MCP servers in your LangSmith [workspace](/langsmith/administration-overview#workspaces):
In the [LangSmith UI](https://smith.langchain.com), select **Settings** in the lower left sidebar, then choose the **MCP Servers** tab.
You can also add MCP servers by navigating to the **Agent Builder > Tools** tab and clicking the **+ Add service** button.
Click **Add server** and enter:
* **Name**: A descriptive name for the server
* **URL**: The MCP server endpoint URL
Select an authentication type based on the server's requirements:
* **Headers**: Add key-value pairs sent with every request. The most common pattern is using an Authorization bearer token:
* **Key**: `Authorization`
* **Value**: `Bearer API_KEY`
You can add multiple headers if your MCP server requires additional authentication or configuration parameters. Each header key-value pair is sent with every request to the server.
* **OAuth 2.1 (Auto)**: Select this for servers that support OAuth via dynamic client registration. You'll be prompted to log in with your account for that service.
* **OAuth 2.1 (Manual)**: Select this for servers that support OAuth, but require specifying the client ID/secret beforehand. OAuth providers used in this flow must have **PKCE** enabled.
For specific authentication details, see the configuration details for each [supported server](#supported-servers).
Click **Save server**. Agent Builder will automatically discover available tools from your MCP server and make them available in your agents. The configured headers are applied to both tool discovery requests and tool execution requests.
## Update your MCP server URL
Changing the URL of a custom MCP server will break any agents that use tools from that server.
Agent Builder stores tool references by MCP server URL. If you update the URL of a custom MCP server, existing agents will fail when attempting to call those tools because the stored URL no longer matches.
To update an MCP server URL:
1. Update your MCP server URL in the workspace settings.
2. For each agent using tools from that server:
* Remove the affected tools from the agent configuration.
* Re-add the tools (they will now reference the new URL).
3. Test the agent to confirm tools work correctly.
## Example: Connect a custom MCP server
Here's a practical example of connecting Agent Builder to a GitHub MCP server that requires authentication:
The MCP server needs permission to access GitHub on your behalf. You’ll do this using a **GitHub Personal Access Token (PAT)**.
1. Go to **GitHub → Settings → Developer settings**.
2. Open **Personal access tokens**.
3. Create a **Fine-grained token** (recommended).
Grant **read-only** permissions:
* **Contents**: Read
* **Issues**: Read
* **Pull requests**: Read
Once created, **copy the token**. You won’t be able to see it again.
Treat this token like a password. You can rotate or revoke it at any time without changing your agent.
Remote MCP servers typically authenticate using HTTP headers.
For the GitHub MCP server, the required headers look like this:
```http theme={null}
Authorization: Bearer ghp_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
User-Agent: langsmith-agent-builder
```
* Authorization proves who you are.
* The MCP server validates the token.
* Every tool call from the agent includes these headers.
Agent Builder will attach these headers automatically once configured.
In the [LangSmith UI](https://smith.langchain.com):
1. Navigate to **Settings** > **Workspaces** > **Secrets**.
2. Click **Add secret**.
3. Name: `GITHUB_TOKEN` (or any descriptive name).
4. Value: Your authentication token.
5. Save the secret.
In **Settings** > **MCP Servers**:
1. Click **Add server**.
2. Add a **Name** for the MCP server.
3. **URL**: Enter your MCP server URL (e.g., `https://mcp-github.example.com`)
4. Add authentication header:
* Key: `Authorization`
* Value: `Bearer {{GITHUB_TOKEN}}`
5. Save the configuration.
The tools from your MCP server are now available in Agent Builder. When you create or edit an agent, you'll see these tools alongside the built-in tools. All requests to your MCP server will include the authentication header automatically.
Once connected, the workflow for this example looks like this:
1. The agent decides it needs GitHub data.
2. It selects a tool exposed by the MCP server.
3. LangSmith forwards the request to the remote MCP server.
4. The server authenticates using your token.
5. GitHub data is fetched and returned.
6. The agent receives structured results and continues reasoning.
Different MCP servers may use different authentication methods:
* `Authorization: Bearer {{TOKEN}}` (most common)
* `X-API-Key: {{API_KEY}}`
* Custom headers specific to your implementation
* Multiple headers for authentication
Always check your MCP server's documentation for the correct authentication header format.
## Supported servers
### Arcade
[Arcade](https://arcade.dev) provides a gateway for connecting to various tools and services.
**Configuration details**
| Setting | Value |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| **URL** | Dynamic. Create an MCP gateway in your [Arcade dashboard](https://arcade.dev), select the tools you want, then copy the generated URL. |
| **Auth type** | Headers or OAuth 2.1 (Auto). See below for specific details. |
**Option 1: Headers authentication**
Add the following headers:
| Header | Value |
| ---------------- | ----------------------- |
| `Authorization` | `Bearer ARCADE_API_KEY` |
| `Arcade-User-Id` | Your email address |
**Option 2: OAuth authentication**
1. Select **OAuth 2.1 (Auto)** as the auth type when adding the server.
2. Log in with your Arcade account when prompted.
To learn more about Arcade MCP integration, see the [Arcade documentation](https://docs.arcade.dev/en/resources/integrations).
### Parallel
[Parallel](https://parallel.ai) provides search and task automation tools.
**Configuration details**
| Setting | Value |
| ------------- | ------------------------------------ |
| **URL** | `https://search-mcp.parallel.ai/mcp` |
| **Auth type** | Headers |
Add the following header:
| Header | Value |
| --------------- | ------------------------- |
| `Authorization` | `Bearer PARALLEL_API_KEY` |
For more information, see the [Parallel documentation for search MCP](https://docs.parallel.ai/integrations/mcp/search-mcp) and [task MCP](https://docs.parallel.ai/integrations/mcp/task-mcp).
### Notion
[Notion](https://notion.com) provides tools for interacting with your Notion workspace.
**Configuration details**
| Setting | Value |
| ------------- | ---------------------------- |
| **URL** | `https://mcp.notion.com/mcp` |
| **Auth type** | OAuth 2.1 (Auto) |
1. Select **OAuth 2.1 (Auto)** as the auth type when adding the server.
2. Log in with your Notion account when prompted.
For more information, see the [Notion MCP documentation](https://developers.notion.com/docs/mcp).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-remote-mcp-servers.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Manage Agent Builder
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-self-hosted
[Agent Builder](/langsmith/agent-builder) allows users to create, deploy, and manage AI agents directly within LangSmith with no code. An agent created in Agent Builder is essentially an assistant on a LangSmith Deployment. This page explains how to manage Agent Builder on a [self-hosted LangSmith instance](/langsmith/self-hosted).
Agent Builder is available on [LangSmith Self-Hosted v0.13](https://changelog.langchain.com/announcements/langsmith-self-hosted-v0-13) and later. Self-hosted is an add-on to the Enterprise plan. For more details, refer to [Pricing](https://www.langchain.com/pricing). [Contact our sales team](https://www.langchain.com/contact-sales) if you want to get a license key to trial LangSmith in your environment.
## Prerequisites
Before enabling Agent Builder, you must complete the following setup steps:
1. Install the base LangSmith platform:
* [Install on Kubernetes](/langsmith/kubernetes).
* [Install on Docker](/langsmith/docker).
2. [Enable LangSmith Deployment](/langsmith/deploy-self-hosted-full-platform) (agent deployment capabilities).
## Components
Agent Builder consists of the following components:
* `agentBootstrap`: Job that deploys the LangSmith Deployment (agent) needed for Agent Builder.
* `agentBuilder`
* `toolServer`: Provides MCP tool execution for agents.
* `triggerServer`: Handles webhooks and scheduled triggers.
* `agent`: The main agent that will handle agent generation and where all the assistants will be created.
## Enable Agent Builder
To enable Agent Builder, add the following to your `values.yaml`:
```yaml theme={null}
backend:
agentBootstrap:
enabled: true
config:
agentBuilder:
enabled: true
encryptionKey: ""
agentBuilderToolServer:
enabled: true
agentBuilderTriggerServer:
enabled: true
```
### Generate an encryption key
Agent Builder requires a Fernet encryption key to securely store secrets. Generate one using Python:
```bash theme={null}
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
```
You can store the encryption key in a predefined Kubernetes secret using the `agent_builder_encryption_key` parameter. See [Use an existing secret](/langsmith/self-host-using-an-existing-secret#parameters) for details.
## Enable OAuth tools and triggers (optional)
To enable OAuth-based tools (like Gmail, Slack, GitHub), configure the `oauthProviderOrgId` and add provider IDs for each integration you want to enable. You can enable any combination of providers.
### Available providers
| Provider | Tools enabled | Trigger enabled | Setup guide |
| ----------------------- | ----------------------------------------------- | --------------- | ------------------------------------- |
| `githubOAuthProvider` | GitHub | - | [See below](#github-oauth-provider) |
| `googleOAuthProvider` | Gmail, Google Calendar, Google Sheets, BigQuery | Gmail | [See below](#google-oauth-provider) |
| `linearOAuthProvider` | Linear | - | [See below](#linear-oauth-provider) |
| `linkedinOAuthProvider` | LinkedIn | - | [See below](#linkedin-oauth-provider) |
| `slackOAuthProvider` | Slack | Slack | [See below](#slack-oauth-provider) |
### General configuration
Add the following to your `values.yaml`. Include only the providers you need.
```yaml theme={null}
backend:
agentBootstrap:
enabled: true
config:
agentBuilder:
# Organization ID where OAuth providers are configured
oauthProviderOrgId: ""
oauth:
# Add provider IDs for integrations you want to enable,
# Slack requires additional configuration
slackOAuthProvider: ""
slackSigningSecret: ""
slackBotId: ""
googleOAuthProvider: ""
linkedinOAuthProvider: ""
linearOAuthProvider: ""
githubOAuthProvider: ""
```
The provider ID must be unique and cannot end with `-agent-builder` or `-oauth-provider`.
### Provider setup guides
To enable GitHub OAuth for Agent Builder, you need to create a GitHub OAuth app and configure it with the required permissions.
Go to [GitHub Settings > Developer settings > OAuth Apps](https://github.com/settings/developers) and click **New OAuth App**.
Fill in the application details. You can name it whatever you like and leave the default checkbox settings.
Give the app read/write permissions to **Pull Requests** and **Issues**.
Set the callback URL, replacing `` with your LangSmith hostname and `` with your provider ID:
```
https:///host-oauth-callback/
```
Click **Generate a new client secret** and copy both the **Client ID** (shown at the top of the app page) and the **Client Secret**.
In LangSmith, go to **Settings > OAuth Providers** and add a new provider:
* **Client ID**: from GitHub app
* **Client Secret**: from GitHub app
* **Authorization URL**: `https://github.com/login/oauth/authorize`
* **Token URL**: `https://github.com/login/oauth/access_token`
* **Provider ID**: Unique string, for example: `github`
Add the following to your `values.yaml` and deploy:
```yaml theme={null}
config:
agentBuilder:
oauthProviderOrgId: ""
oauth:
githubOAuthProvider: ""
```
After creating the app, you need to:
1. Authenticate the app to your GitHub account.
2. Go to **Settings > Applications > Installed GitHub Apps** and install the app on the repositories you want it to access.
For private repositories, you must explicitly install the app on each repository you want Agent Builder to access.
To enable Google OAuth for Agent Builder, you need to create an OAuth client in GCP and configure it with the required URLs and credentials.
Create a new OAuth client app (Web application) in [Google Cloud Console](https://console.cloud.google.com/apis/credentials).
Add the following URLs to your OAuth client, replacing `` with your LangSmith hostname and `` with the provider ID you'll use (e.g., `google`):
**Authorized JavaScript origins:**
* `https://`
**Authorized redirect URIs:**
* `https:///api-host/v2/auth/callback/`
* `https:///host-oauth-callback/`
Copy the **Client ID** and **Client Secret** from the GCP OAuth app.
In LangSmith, go to **Settings > OAuth Providers** and add a new provider:
* **Client ID**: Find in GCP
* **Client Secret**: Find in GCP
* **Authorization URL**: `https://accounts.google.com/o/oauth2/auth`
* **Token URL**: `https://oauth2.googleapis.com/token`
* **Provider ID**: Unique string, for example: `google`
Add the LangSmith OAuth provider ID to your `values.yaml` and deploy:
```yaml theme={null}
config:
agentBuilder:
oauthProviderOrgId: ""
oauth:
googleOAuthProvider: ""
```
To enable Linear OAuth for Agent Builder, you need to create a Linear OAuth app and configure it with the required credentials.
Go to [Linear Settings > API > Applications](https://linear.app/settings/api/applications/new) and create a new OAuth application.
Set the callback URL, replacing `` with your LangSmith hostname and `` with your provider ID:
```
https:///host-oauth-callback/
```
After creating the app, copy the **Client ID** and **Client Secret**.
In LangSmith, go to **Settings > OAuth Providers** and add a new provider:
* **Client ID**: from Linear app
* **Client Secret**: from Linear app
* **Authorization URL**: `https://linear.app/oauth/authorize`
* **Token URL**: `https://api.linear.app/oauth/token`
* **Provider ID**: Unique string, for example: `linear`
Add the following to your `values.yaml` and deploy:
```yaml theme={null}
config:
agentBuilder:
oauthProviderOrgId: ""
oauth:
linearOAuthProvider: ""
```
To enable LinkedIn OAuth for Agent Builder, you need to create a LinkedIn OAuth app and configure it with the required credentials.
Go to [linkedin.com/developers/apps](https://www.linkedin.com/developers/apps/) and create a new app.
In your app settings, go to the **Auth** tab.
Add the following redirect URI, replacing `` with your LangSmith hostname and `` with your provider ID:
```
https:///host-oauth-callback/
```
Copy the **Client ID** and **Client Secret** from the Auth tab.
In LangSmith, go to **Settings > OAuth Providers** and add a new provider:
* **Client ID**: from LinkedIn app
* **Client Secret**: from LinkedIn app
* **Authorization URL**: `https://www.linkedin.com/oauth/v2/authorization`
* **Token URL**: `https://www.linkedin.com/oauth/v2/accessToken`
* **Provider ID**: Unique string, for example: `linkedin`
Add the following to your `values.yaml` and deploy:
```yaml theme={null}
config:
agentBuilder:
oauthProviderOrgId: ""
oauth:
linkedinOAuthProvider: ""
```
To enable Slack OAuth for Agent Builder, you need to create a Slack app and configure it with the required scopes, credentials, and event subscriptions.
Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App**.
In **OAuth & Permissions**, add the following scopes:
* `channels:history`
* `channels:read`
* `chat:write`
* `groups:history`
* `groups:read`
* `im:history`
* `im:read`
* `im:write`
* `mpim:history`
* `team:read`
* `users:read`
* `users:read.email`
Copy the **Client ID**, **Client Secret**, and **Signing Secret** from the Slack app settings.
In LangSmith, go to **Settings > OAuth Providers** and add a new provider:
* **Client ID**: Find in Slack app
* **Client Secret**: Find in Slack app
* **Authorization URL**: `https://slack.com/oauth/v2/authorize`
* **Token URL**: `https://slack.com/api/oauth.v2.access`
* **Provider ID**: Unique string, for example: `slack`
Add the following redirect URI to your Slack app under **OAuth & Permissions > Redirect URLs**, replacing `` with your LangSmith hostname and `` with your provider ID (e.g., `slack`):
```
https:///host-oauth-callback/
```
1. Get the bot token from **OAuth & Permissions** in your Slack app.
2. Run the following command:
```bash theme={null}
curl -X POST https://slack.com/api/auth.test \
-H "Authorization: Bearer "
```
3. Copy the `bot_id` from the response.
Add the following to your `values.yaml` and deploy:
```yaml theme={null}
config:
agentBuilder:
oauthProviderOrgId: ""
oauth:
slackOAuthProvider: ""
slackSigningSecret: ""
slackBotId: ""
```
1. After deployment, go to **Event Subscriptions** in your Slack app and enable events.
2. Set the **Request URL** to:
```
https:///v1/triggers/webhooks/d809e66e-0000-4000-8000-000000000002
```
3. Add the following bot events:
* `message.channels`
* `message.groups`
* `message.im`
* `message.mpim`
1. Add the Slack bot to the channel you want it to read from.
2. When configuring the Slack tool or trigger in Agent Builder, provide the **channel ID** and **channel name**.
## Disable Agent Builder
To disable Agent Builder, set the following to `false` in your `values.yaml`:
```yaml theme={null}
config:
agentBuilder:
enabled: false
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-self-hosted.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Workspace setup
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-setup
Add required workspace secrets for models and tools used by Agent Builder.
This page lists the workspace secrets you need to add before using Agent Builder. Add these in your LangSmith workspace settings under Secrets. Keep values scoped to your workspace and avoid placing credentials in prompts or code.
## Add workspace secrets
In the [LangSmith UI](https://smith.langchain.com), ensure that you have an LLM API key set as a [workspace secret](/langsmith/administration-overview#workspace-secrets) (either Anthropic or OpenAI API key).
1. Navigate to **Settings** and then move to the **Secrets** tab.
2. Select **Add secret** and enter either `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` as the **name**, and your API key as the **value**.
3. Select **Save secret**.
When adding workspace secrets in the LangSmith UI, make sure the secret keys match the environment variable names expected by your model provider.
## Secret types
### Required model key
For Agent Builder to make API calls to LLMs, you need to set an OpenAI or Anthropic API key as a workspace secret. The agent graphs load this key from workspace secrets for inference.
Agent Builder supports custom models per agent. See [Custom models](/langsmith/agent-builder-essentials#custom-models) for more information.
### Agent builder specific secrets
Secrets prefixed with `AGENT_BUILDER_` are prioritized over workspace secrets within Agent Builder. This way, you can better track the usage of Agent Builder vs other parts of LangSmith which use the same secrets.
If you have both `OPENAI_API_KEY` and `AGENT_BUILDER_OPENAI_API_KEY`, the `AGENT_BUILDER_OPENAI_API_KEY` secret will be used.
### Optional tool keys
Add keys for any tools you enable. These are read from workspace secrets at runtime.
* `EXA_API_KEY`: Required for Exa search tools (general web and LinkedIn profile search).
* `TAVILY_API_KEY`: Required for Tavily web search.
* `TWITTER_API_KEY` and `TWITTER_API_KEY_SECRET`: Required for Twitter/X read operations (app-only bearer). Posting/media upload is not enabled.
### MCP server configuration
Agent Builder can pull tools from one or more remote [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers. Configure MCP servers and headers in your [workspace](/langsmith/administration-overview#workspaces) settings. Agent Builder automatically discovers tools and applies the configured headers when calling them.
For more information, refer to the [Remote MCP servers](/langsmith/agent-builder-remote-mcp-servers) page.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-setup.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Integrate Slack with an agent
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-slack-app
Connect the LangSmith Agent Builder to your Slack workspace to let your agents communicate with users in Slack.
The LangSmith Agent Builder App for Slack integrates your agents with Slack for secure, context-aware communication inside your Slack workspace.
After integrating, your agents will be able to:
* Send direct messages.
* Post to channels.
* Read thread messages.
* Reply in threads.
* Read conversation history.
The LangSmith Agent Builder App for Slack itself does not have any direct pricing. However, agent runs and traces are billed through the [LangSmith platform](https://smith.langchain.com) according to your organization's plan.
For current pricing information, see the [LangSmith pricing page](https://www.langchain.com/pricing).
## How it works
When a message is posted in your monitored Slack channel:
1. Slack sends an event to LangSmith.
2. The trigger activates your agent with the message content.
3. Your agent processes the message and uses Slack tools to respond.
4. The response appears in Slack (in the same thread if replying to a thread).
Your agent receives the full thread context when responding to threaded messages, so it can provide contextually relevant responses.
## Prerequisites
* An existing agent in Agent Builder (see [Quickstart](/langsmith/agent-builder-quickstart) to create one)
* Admin access to a Slack workspace or permission to install apps
## Connect to Slack
First, authorize LangSmith to access your Slack workspace and invite the bot to your channel.
1. In LangSmith, navigate to **Agent Builder**.
2. Select your agent from **My Agents**.
3. Click **Edit Agent**.
1. Scroll down to the **Triggers** section.
2. Click **+ Add trigger**.
3. Select **Slack - Message Received**.
1. Click **Connect** to authorize the Slack connection.
2. Sign in to your Slack workspace.
3. Review the permissions and click **Allow**.
4. You'll be redirected back to LangSmith.
Agent Builder requires the following permissions to your Slack workspace:
* **Send messages** - Send direct messages and post to channels
* **Read messages** - Read channel history and thread messages
* **View channels** - Access basic channel information
* **View users** - Look up user information for messaging
These permissions enable agents to communicate effectively within your Slack workspace.
Before selecting a channel, invite the bot to the channel you want to use. In Slack, go to that channel and type:
```txt theme={null}
/invite @LangSmith Agent Builder
```
1. Back in LangSmith, choose the Slack channel you invited the bot to.
2. Click **Save**.
Your agent will now be triggered whenever a message is posted in that channel.
## Add Slack tools
Tools let your agent take actions. To respond in Slack, you need to add Slack tools.
You can also ask your agent to add these tools itself! In the agent chat, try: "Add the Slack tools so you can respond to messages."
1. In the agent editor, scroll to the **Tools** section.
2. Click **+ Add tool**.
3. Search for "Slack" and add the tools you need:
* **slack\_send\_channel\_message** - Post messages to a channel
* **slack\_reply\_to\_message** - Reply in a thread
* **slack\_write\_private\_message** - Send direct messages
* **slack\_read\_channel\_history** - Read recent messages
* **slack\_read\_thread\_messages** - Read thread replies
If prompted, click **Connect** to authorize the Slack tools. This may use the same authorization from Step 1.
Click **Save changes** to save your agent configuration.
## Configure agent behavior (optional)
Your agent needs to know how to handle incoming Slack messages. You can update its instructions by prompting it directly.
In the agent chat, try something like:
```
Update your instructions to handle the Slack Trigger and Slack Tools
for bidirectional communication
```
Adjust the instructions based on your use case - for example, you might want the agent to only respond to certain types of questions, or to pull information from specific sources before replying.
## Test the integration
In your Slack channel, send a message to test the agent:
> *Hello, can you help me?*
Your agent should respond in the channel. You can view the agent's activity in the **Feed** tab in Agent Builder.
If your agent doesn't respond:
* Verify the bot was invited to the channel
* Check the **Feed** tab for any errors
* Ensure the trigger is not paused (check the toggle in the Triggers section)
## Next steps
Connect additional services to your agent
Set up email, schedule, or webhook triggers
Start from a pre-built agent template
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-slack-app.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Templates
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-templates
Start faster with curated Agent Builder templates and customize tools, prompts, and triggers.
Agent Builder includes [starter templates](https://www.langchain.com/templates) to help you create agents quickly. Templates include predefined instructions, [tools](/langsmith/agent-builder-tools), and [triggers](/langsmith/agent-builder-essentials#triggers) (if applicable) for common use cases. You can use templates as-is, or as a baseline to customize.
If you're new to Agent Builder, start with the step-by-step [quickstart](/langsmith/agent-builder-quickstart) to build your first agent using a template.
## Features
Templates are pre-configured agents designed for specific use cases. Each template includes the following components:
### Pre-configured tools
Templates come with a curated set of [tools](/langsmith/agent-builder-essentials#tools) that enable the agent to perform specific actions. For example, an email assistant template includes tools for reading, sending, and organizing emails. Tools connect to external services through OAuth authentication, allowing your agent to interact with apps like Gmail, Slack, or Linear. For a complete list, refer to [Supported tools](/langsmith/agent-builder-tools).
### System instructions
Each template includes a *system prompt* (also called *instructions*) that defines the agent's behavior, personality, and capabilities. The system prompt guides how the agent interprets user requests and uses its available tools. You can customize these instructions to match your specific needs.
### Triggers (optional)
Some templates include [triggers](/langsmith/agent-builder-essentials#triggers) that allow agents to respond to external events automatically. For example, a Slack bot template might include a trigger that activates when someone mentions the agent in a channel. Triggers enable proactive agent behavior beyond chat-based interactions.
### Cloning and customization
Templates serve as starting points that you clone to create your own agent. When you clone a template, you create an independent copy that you can customize without affecting the original. You can modify prompts, add or remove tools, attach different triggers, and switch models to tailor the agent to your requirements.
## Available templates
A daily agent that scans your calendar and delivers a concise briefing with meeting details and important context.
Automate email triage with an agent that flags important emails, drafts and sends replies, and schedules meetings.
Automate recruiting with an agent that digests candidate requirements, adapts to feedback, and outputs a candidate list.
An agent that tracks top AI discussions from X lists and Hacker News, and delivers a daily Slack message with important updates.
For more information, see [Templates](https://www.langchain.com/templates).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-templates.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Built-in tools
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-tools
You can access a variety of tools in Agent Builder. Use built-in tools to give your agents access to email, calendars, chat, project management, code hosting, spreadsheets/BI, search, social, and general web utilities.
Google, Slack, Linear, GitHub, and LinkedIn use OAuth. Exa, Tavily, Pylon, and Twitter/X use workspace secrets (API keys).
Math and logic
Math operations
Generate random numbers
Analytics
Execute SQL queries
Search Deep Wiki
Read wiki structure and contents
Ask questions
Search our documentation
PRs, issues, and content
List pull requests
Get pull request details
Create issues and pull requests
Comment on issues and pull requests
Read repository files and list directories
Read and send email
Read emails (optionally include body, filter with search)
Send email or reply to an existing message
Create draft emails
Mark messages as read
Get a conversation thread
Apply or create labels
List mailbox labels
Manage events
List events for a date
Get event details
Create new events
Update events
Delete events
Manage docs
Create new docs
Read docs
Append text
Replace text
Spreadsheets
Get spreadsheets
Create spreadsheets
Read ranges
Write ranges
Append rows
Clear ranges
Manage issues and teams
List teams and team members
List issues with filters
Get issue details
List labels
Create, update, or delete issues
Post to profile
Publish a post with optional image or link
Manage incidents
List alert group settings and get details
List incidents and get details
Get outlier incidents, past incidents, and related incidents
List services and get details
List teams, members, schedules, and users, and get details
List oncalls
Manage team members
Manage schedules
Manage event orchestration routers
Issue management
List issues
Get issue details
Update issues
Exa web search (optionally fetch page contents)
Exa LinkedIn profile search
Tavily web search
Send and read messages
Send a direct message to a user
Post a message to a channel
Reply in a thread
Read channel history
Read thread messages
Manage payments
Create and list customers, products, payments, and prices
Create, list, and finalize invoices
List, update, and delete subscriptions
Create and list coupons
Create list, and update disputes
Search and fetch Stripe resources
Search documentation
Read a tweet by ID
Read recent posts from a list
Read webpage text content
Extract image URLs and metadata
Notify user (for confirmations/updates)
You can also connect to remote MCP servers to give your agents access to additional tools. See [Remote MCP servers](/langsmith/agent-builder-remote-mcp-servers) for more information.
## Disconnect a tool
To remove a tool from your agent:
In the [LangSmith UI](https://smith.langchain.com), hover over **My Agents** in the left sidebar and click the settings icon.
In the integrations section, locate the connected app you want to remove.
Click **Disconnect** for that integration.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-tools.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Agent Builder webhooks
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-builder-webhooks
Integrate agent publishing with external systems, CI/CD pipelines, or custom deployment workflows.
When triggered, a webhook sends a complete package of your agent's configuration and files to the specified endpoint.
**Security notes:**
* Webhook URLs must use HTTPS.
* Custom headers (e.g., API keys) are stored encrypted.
* Publisher identity is included for audit trails.
* Webhooks are only visible to agent owners.
## Set up a webhook
1. Navigate to **Settings > Agent Builder Webhooks**.
2. Click **Add Webhook**.
3. Configure:
* **Name**: A descriptive name (e.g., "Publish Agent", "Deploy to Production").
* **URL**: Your HTTPS endpoint that will receive the webhook.
* **Headers** (optional): Custom headers for authentication (stored encrypted).
* **Form Schema** (optional): Define custom input fields users must fill when triggering.
## Trigger a webhook
1. Open your agent in the Agent Builder editor.
2. Click the **Settings** menu (gear icon).
3. Under **Webhooks**, click the webhook name.
4. Fill in any custom fields defined in the form schema.
5. Click **Run Webhook**.
## Webhook payload
The webhook payload is a JSON object with the following fields:
| Field | Description |
| --------------------------------------------------- | ------------------------------------------------------------------ |
| `action` | The name of the webhook. |
| `input` | Values from custom form fields (empty object if no custom fields). |
| `publisher` | User ID and email of the person triggering the webhook. |
| `agent` | Agent name and description. |
| [`tool_auth_requirements`](#tool-auth-requirements) | Authentication requirements for each tool the agent uses. |
| [`files`](#zip-file-structure) | Base64-encoded ZIP containing all agent files. |
| [`fields`](#custom-input-fields) | Custom input fields. |
For example:
```json theme={null}
{
"action": "Webhook Name",
"input": {
"notes": "User-provided value",
"environment": "prod",
"dry_run": true
},
"publisher": {
"user_id": "uuid-of-publishing-user",
"email": "user@example.com"
},
"agent": {
"name": "My Agent",
"description": "Agent description text"
},
"tool_auth_requirements": [
{
"tool_name": "tavily_web_search",
"auth_type": "api_key",
"required_env_vars": ["TAVILY_API_KEY"]
},
{
"tool_name": "google_calendar",
"auth_type": "oauth",
"auth_provider": "google",
"scopes": ["calendar.readonly"]
}
],
"files": {
"type": "zip",
"filename": "My_Agent.zip",
"content_base64": ""
},
"fields": [
{
"name": "notes",
"label": "Deployment Notes",
"type": "textarea"
}
]
}
```
### Tool auth requirements
The `tool_auth_requirements` array describes authentication needed for each tool:
| Auth Type | Fields | Description |
| --------- | ------------------------- | ------------------------------------------------ |
| `none` | - | Tool requires no authentication |
| `api_key` | `required_env_vars` | Tool needs API key(s) in environment variables |
| `oauth` | `auth_provider`, `scopes` | Tool requires OAuth tokens with specified scopes |
Use this information to configure your deployment environment with the necessary credentials.
### ZIP file structure
The `files.content_base64` field contains a ZIP archive with the following structure:
```
.
├── AGENTS.md # Agent system prompt and instructions
├── config.json # Agent metadata (name, description, visibility)
├── tools.json # Tool configurations and interrupt settings
├── skills/ # Optional skill definitions
│ └── skill-name/
│ └── SKILL.md
└── subagents/ # Optional subagent configurations
└── research_worker/
├── AGENTS.md
└── tools.json
```
The `config.json` file and `tools.json` files are structured as follows:
```json theme={null}
{
"name": "My Agent",
"description": "Agent description",
"visibility_scope": "tenant",
"triggers_paused": false
}
```
```json theme={null}
{
"tools": [
{
"name": "tavily_web_search",
"mcp_server_url": "http://localhost:8084",
"mcp_server_name": "Agent Builder",
"display_name": "tavily_web_search"
}
],
"interrupt_config": {
"http://localhost:8084::tavily_web_search::Agent Builder": false
}
}
```
### Custom input fields
You can define custom input fields to collect information when the webhook is triggered. Supported field types are as follows:
| Type | Description |
| ---------- | --------------------------------- |
| `string` | Single-line text input (default). |
| `number` | Numeric input. |
| `boolean` | Checkbox (true/false). |
| `textarea` | Multi-line text input. |
| `json` | JSON editor. |
| `select` | Dropdown with predefined options. |
For example:
```json theme={null}
{
"fields": [
{
"name": "notes",
"label": "Deployment Notes",
"type": "textarea"
},
{
"name": "environment",
"label": "Environment",
"type": "select",
"options": [
{ "label": "Development", "value": "dev" },
{ "label": "Staging", "value": "staging" },
{ "label": "Production", "value": "prod" }
]
},
{
"name": "dry_run",
"label": "Dry Run",
"type": "boolean",
"default": true
}
]
}
```
## Example: Webhook server
The following is an example webhook server in Python:
```python theme={null}
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import base64
import zipfile
import io
class WebhookHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['Content-Length'])
body = json.loads(self.rfile.read(content_length))
action = body.get("action")
input_data = body.get("input", {})
publisher = body.get("publisher", {})
agent = body.get("agent", {})
tool_auth = body.get("tool_auth_requirements", [])
files = body.get("files", {})
print(f"Webhook: {action}")
print(f"Publisher: {publisher.get('email')}")
print(f"Agent: {agent.get('name')}")
print(f"Custom Input: {input_data}")
# Extract ZIP contents
if files.get("content_base64"):
zip_bytes = base64.b64decode(files["content_base64"])
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
print(f"Files: {zf.namelist()}")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"status": "ok"}).encode())
HTTPServer(("", 8000), WebhookHandler).serve_forever()
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-builder-webhooks.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Agent Server
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server
LangSmith Deployment's **Agent Server** offers an API for creating and managing agent-based applications. It is built on the concept of [assistants](/langsmith/assistants), which are agents configured for specific tasks, and includes built-in [persistence](/oss/python/langgraph/persistence#memory-store) and a **task queue**. This versatile API supports a wide range of agentic application use cases, from background processing to real-time interactions.
Use Agent Server to create and manage [assistants](/langsmith/assistants), [threads](/oss/python/langgraph/persistence#threads), [runs](/langsmith/assistants#execution), [cron jobs](/langsmith/cron-jobs), [webhooks](/langsmith/use-webhooks), and more.
**API reference**
For detailed information on the API endpoints and data models, refer to the [Agent Server API reference](/langsmith/server-api-ref).
## Application structure
To deploy an Agent Server application, you need to specify the graph(s) you want to deploy, as well as any relevant configuration settings, such as dependencies and environment variables.
Read the [application structure](/langsmith/application-structure) guide to learn how to structure your LangGraph application for deployment.
## Parts of a deployment
When you deploy Agent Server, you are deploying one or more [graphs](#graphs), a database for [persistence](/oss/python/langgraph/persistence), and a task queue.
### Graphs
When you deploy a graph with Agent Server, you are deploying a "blueprint" for an [Assistant](/langsmith/assistants).
An [Assistant](/langsmith/assistants) is a graph paired with specific configuration settings. You can create multiple assistants per graph, each with unique settings to accommodate different use cases
that can be served by the same graph.
Upon deployment, Agent Server will automatically create a default assistant for each graph using the graph's default configuration settings.
We often think of a graph as implementing an [agent](/oss/python/langgraph/workflows-agents), but a graph does not necessarily need to implement an agent. For example, a graph could implement a simple
chatbot that only supports back-and-forth conversation, without the ability to influence any application control flow. In reality, as applications get more complex, a graph will often implement a more complex flow that may use [multiple agents](/oss/python/langchain/multi-agent) working in tandem.
### Persistence and task queue
Agent Server leverages a database for [persistence](/oss/python/langgraph/persistence) and a task queue.
[PostgreSQL](https://www.postgresql.org/) is supported as a database for Agent Server and [Redis](https://redis.io/) as the task queue.
If you're deploying using [LangSmith cloud](/langsmith/cloud), these components are managed for you. If you're deploying Agent Server on your [own infrastructure](/langsmith/self-hosted), you'll need to set up and manage these components yourself.
For more information on how these components are set up and managed, review the [hosting options](/langsmith/platform-setup) guide.
### How to deploy
Agent Server can be deployed using different methods depending on your infrastructure:
* [Cloud](/langsmith/deploy-to-cloud): Deploy from GitHub repositories with fully managed infrastructure.
* [Hybrid or self-hosted with control plane](/langsmith/deploy-with-control-plane): Build Docker images and deploy via the UI.
* [Standalone servers](/langsmith/deploy-standalone-server): Deploy Agent Servers directly without the control plane.
Cloud deployments are available on all LangSmith plans. Hybrid and self-hosted options require an Enterprise plan and license key. To acquire a license key, [contact our sales team](https://www.langchain.com/contact-sales).
## Runtime architecture
The following description applies to the non-distributed runtime variant of LangSmith Deployment.
### Container architecture
A typical deployment consists of two kinds of long-running containers, both built from the same Docker image (a base image with your project code installed on top):
* **API servers** handle client requests (creating runs, reading thread state, streaming results) but do not execute agent code themselves.
* **Queue workers** are the execution engine. They listen to the durable task queue, execute your graph code, and write checkpoints.
Containers are \**stateless* but persistent. At least 1 queue worker must listen to the task queue at any time to ensure no runs are orphaned. The containers can serve many runs over their lifetime.
API servers and queue workers are separate container pools and [scale independently](/langsmith/data-plane#autoscaling).
### Run execution lifecycle
When you invoke a run, the request flows through several components:
1. A client sends a request to an API server, which creates a pending run in the durable task queue.
2. A queue worker picks up the run, acquires a lease on it, loads the appropriate graph, and begins execution. The queue enforces that at most 1 run can be executed for a given thread at one time.
3. As the graph executes, the worker writes checkpoints to the persistence layer (the frequency depends on the [durability mode](/oss/python/langgraph/durable-execution#durability-modes)) and broadcasts streaming events over the configured pubsub provider.
4. If the client opened a `/stream` connection, the API server subscribes to the pubsub channel and forwards events to the client via server-sent events in real time.
5. When execution completes, the worker updates the run status and releases its slot for the next run.
Each worker handles up to [`N_JOBS_PER_WORKER`](/langsmith/env-var#n-jobs-per-worker) runs concurrently (default: 10), so a single worker container serves many runs in parallel. See [Configure Agent Server for scale](/langsmith/agent-server-scale) for tuning guidance.
### Graph loading and compilation
How and when your graph is compiled depends on how you register it in your [application structure](/langsmith/application-structure):
1. **Compiled graph**: If you export an already-compiled graph (a `CompiledGraph` instance), it is loaded once at container startup and reused for every run. This is the most efficient path.
2. **Factory function**: If you export an agent factory function, the server invokes it each time it needs the graph. Factories receive the run's configuration, enabling per-run graph customization (for example, choosing different models or tools based on the assistant config). Keep factory functions lightweight for best performance.
In both cases, the server automatically injects the checkpointer and memory store configured for that deployment at runtime. **You should not configure these in your graph code**, since the server needs to manage these for other operations.
## Learn more
* [Application Structure](/langsmith/application-structure) guide explains how to structure your application for deployment.
* The [API Reference](https://docs.langchain.com/langsmith/server-api-ref) provides detailed information on the API endpoints and data models.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-server.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# A2A JSON-RPC
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/a2a/a2a-json-rpc
langsmith/agent-server-openapi.json post /a2a/{assistant_id}
Communicate with an assistant using the Agent-to-Agent (A2A) Protocol over JSON-RPC 2.0.
This endpoint accepts a JSON-RPC envelope and dispatches based on `method`.
**Supported Methods:**
- `message/send`: Send a message and wait for the final Task result.
- `message/stream`: Send a message and receive Server-Sent Events (SSE) JSON-RPC responses.
- `tasks/get`: Fetch the current state of a Task by ID.
- `tasks/cancel`: Request cancellation (currently not supported; returns an error).
**LangGraph Mapping:**
- `message.contextId` maps to LangGraph `thread_id`.
**Notes:**
- Only `text` and `data` parts are supported; `file` parts are not.
- If `message.contextId` is omitted, a new context is created.
- Text parts require the assistant input schema to include a `messages` field.
# Count Assistants
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/count-assistants
langsmith/agent-server-openapi.json post /assistants/count
Get the count of assistants matching the specified criteria.
# Create Assistant
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/create-assistant
langsmith/agent-server-openapi.json post /assistants
Create an assistant.
An initial version of the assistant will be created and the assistant is set to that version. To change versions, use the `POST /assistants/{assistant_id}/latest` endpoint.
# Delete Assistant
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/delete-assistant
langsmith/agent-server-openapi.json delete /assistants/{assistant_id}
Delete an assistant by ID.
All versions of the assistant will be deleted as well.
# Get Assistant
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/get-assistant
langsmith/agent-server-openapi.json get /assistants/{assistant_id}
Get an assistant by ID.
# Get Assistant Graph
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/get-assistant-graph
langsmith/agent-server-openapi.json get /assistants/{assistant_id}/graph
Get an assistant by ID.
# Get Assistant Schemas
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/get-assistant-schemas
langsmith/agent-server-openapi.json get /assistants/{assistant_id}/schemas
Get an assistant by ID.
# Get Assistant Subgraphs
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/get-assistant-subgraphs
langsmith/agent-server-openapi.json get /assistants/{assistant_id}/subgraphs
Get an assistant's subgraphs.
# Get Assistant Subgraphs by Namespace
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/get-assistant-subgraphs-by-namespace
langsmith/agent-server-openapi.json get /assistants/{assistant_id}/subgraphs/{namespace}
Get an assistant's subgraphs filtered by namespace.
# Get Assistant Versions
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/get-assistant-versions
langsmith/agent-server-openapi.json post /assistants/{assistant_id}/versions
Get all versions of an assistant.
# Patch Assistant
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/patch-assistant
langsmith/agent-server-openapi.json patch /assistants/{assistant_id}
Update an assistant.
# Search Assistants
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/search-assistants
langsmith/agent-server-openapi.json post /assistants/search
Search for assistants.
This endpoint also functions as the endpoint to list all assistants.
# Set Latest Assistant Version
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/assistants/set-latest-assistant-version
langsmith/agent-server-openapi.json post /assistants/{assistant_id}/latest
Set the latest version for an assistant.
# Count Crons
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/crons-plus-tier/count-crons
langsmith/agent-server-openapi.json post /runs/crons/count
Get the count of crons matching the specified criteria.
# Create Cron
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/crons-plus-tier/create-cron
langsmith/agent-server-openapi.json post /runs/crons
Create a cron to schedule runs on new threads.
# Create Thread Cron
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/crons-plus-tier/create-thread-cron
langsmith/agent-server-openapi.json post /threads/{thread_id}/runs/crons
Create a cron to schedule runs on a thread.
# Delete Cron
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/crons-plus-tier/delete-cron
langsmith/agent-server-openapi.json delete /runs/crons/{cron_id}
Delete a cron by ID.
# Search Crons
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/crons-plus-tier/search-crons
langsmith/agent-server-openapi.json post /runs/crons/search
Search all active crons
# Update Cron
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/crons-plus-tier/update-cron
langsmith/agent-server-openapi.json patch /runs/crons/{cron_id}
Update a cron job by ID.
# MCP Get
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/mcp/mcp-get
langsmith/agent-server-openapi.json get /mcp/
Implemented according to the Streamable HTTP Transport specification.
# MCP Post
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/mcp/mcp-post
langsmith/agent-server-openapi.json post /mcp/
Implemented according to the Streamable HTTP Transport specification.
Sends a JSON-RPC 2.0 message to the server.
- **Request**: Provide an object with `jsonrpc`, `id`, `method`, and optional `params`.
- **Response**: Returns a JSON-RPC response or acknowledgment.
**Notes:**
- Stateless: Sessions are not persisted across requests.
# Terminate Session
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/mcp/terminate-session
langsmith/agent-server-openapi.json delete /mcp/
Implemented according to the Streamable HTTP Transport specification.
Terminate an MCP session. The server implementation is stateless, so this is a no-op.
# Create Background Run
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/stateless-runs/create-background-run
langsmith/agent-server-openapi.json post /runs
Create a run and return the run ID immediately. Don't wait for the final run output.
# Create Run Batch
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/stateless-runs/create-run-batch
langsmith/agent-server-openapi.json post /runs/batch
Create a batch of runs and return immediately.
# Create Run, Stream Output
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/stateless-runs/create-run-stream-output
langsmith/agent-server-openapi.json post /runs/stream
Create a run and stream the output.
# Create Run, Wait for Output
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/stateless-runs/create-run-wait-for-output
langsmith/agent-server-openapi.json post /runs/wait
Create a run, wait for the final output and then return it.
# Delete an item.
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/store/delete-an-item
langsmith/agent-server-openapi.json delete /store/items
# List namespaces with optional match conditions.
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/store/list-namespaces-with-optional-match-conditions
langsmith/agent-server-openapi.json post /store/namespaces
# Retrieve a single item.
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/store/retrieve-a-single-item
langsmith/agent-server-openapi.json get /store/items
# Search for items within a namespace prefix.
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/store/search-for-items-within-a-namespace-prefix
langsmith/agent-server-openapi.json post /store/items/search
# Store or update an item.
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/store/store-or-update-an-item
langsmith/agent-server-openapi.json put /store/items
# Health Check
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/system/health-check
langsmith/agent-server-openapi.json get /ok
Check the health status of the server. Optionally check database connectivity.
# Server Information
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/system/server-information
langsmith/agent-server-openapi.json get /info
Get server version information, feature flags, and metadata.
# System Metrics
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/system/system-metrics
langsmith/agent-server-openapi.json get /metrics
Get system metrics in Prometheus or JSON format for monitoring and observability.
# Cancel Run
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/thread-runs/cancel-run
langsmith/agent-server-openapi.json post /threads/{thread_id}/runs/{run_id}/cancel
# Cancel Runs
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/thread-runs/cancel-runs
langsmith/agent-server-openapi.json post /runs/cancel
Cancel one or more runs. Can cancel runs by thread ID and run IDs, or by status filter.
# Create Background Run
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/thread-runs/create-background-run
langsmith/agent-server-openapi.json post /threads/{thread_id}/runs
Create a run in existing thread, return the run ID immediately. Don't wait for the final run output.
# Create Run, Stream Output
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/thread-runs/create-run-stream-output
langsmith/agent-server-openapi.json post /threads/{thread_id}/runs/stream
Create a run in existing thread. Stream the output.
# Create Run, Wait for Output
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/thread-runs/create-run-wait-for-output
langsmith/agent-server-openapi.json post /threads/{thread_id}/runs/wait
Create a run in existing thread. Wait for the final output and then return it.
# Delete Run
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/thread-runs/delete-run
langsmith/agent-server-openapi.json delete /threads/{thread_id}/runs/{run_id}
Delete a run by ID.
# Get Run
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/thread-runs/get-run
langsmith/agent-server-openapi.json get /threads/{thread_id}/runs/{run_id}
Get a run by ID.
# Join Run
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/thread-runs/join-run
langsmith/agent-server-openapi.json get /threads/{thread_id}/runs/{run_id}/join
Wait for a run to finish.
# Join Run Stream
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/thread-runs/join-run-stream
langsmith/agent-server-openapi.json get /threads/{thread_id}/runs/{run_id}/stream
Join a run stream. This endpoint streams output in real-time from a run similar to the /threads/__THREAD_ID__/runs/stream endpoint. If the run has been created with `stream_resumable=true`, the stream can be resumed from the last seen event ID.
# List Runs
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/thread-runs/list-runs
langsmith/agent-server-openapi.json get /threads/{thread_id}/runs
List runs for a thread.
# Copy Thread
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/copy-thread
langsmith/agent-server-openapi.json post /threads/{thread_id}/copy
Create a new thread with a copy of the state and checkpoints from an existing thread.
# Count Threads
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/count-threads
langsmith/agent-server-openapi.json post /threads/count
Get the count of threads matching the specified criteria.
# Create Thread
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/create-thread
langsmith/agent-server-openapi.json post /threads
Create a thread.
# Delete Thread
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/delete-thread
langsmith/agent-server-openapi.json delete /threads/{thread_id}
Delete a thread by ID.
# Get Thread
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/get-thread
langsmith/agent-server-openapi.json get /threads/{thread_id}
Get a thread by ID.
# Get Thread History
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/get-thread-history
langsmith/agent-server-openapi.json get /threads/{thread_id}/history
Get all past states for a thread.
# Get Thread History Post
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/get-thread-history-post
langsmith/agent-server-openapi.json post /threads/{thread_id}/history
Get all past states for a thread.
# Get Thread State
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/get-thread-state
langsmith/agent-server-openapi.json get /threads/{thread_id}/state
Get state for a thread.
The latest state of the thread (i.e. latest checkpoint) is returned.
# Get Thread State At Checkpoint
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/get-thread-state-at-checkpoint
langsmith/agent-server-openapi.json get /threads/{thread_id}/state/{checkpoint_id}
Get state for a thread at a specific checkpoint.
# Get Thread State At Checkpoint
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/get-thread-state-at-checkpoint-1
langsmith/agent-server-openapi.json post /threads/{thread_id}/state/checkpoint
Get state for a thread at a specific checkpoint.
# Join Thread Stream
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/join-thread-stream
langsmith/agent-server-openapi.json get /threads/{thread_id}/stream
This endpoint streams output in real-time from a thread. The stream will include the output of each run executed sequentially on the thread and will remain open indefinitely. It is the responsibility of the calling client to close the connection.
# Patch Thread
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/patch-thread
langsmith/agent-server-openapi.json patch /threads/{thread_id}
Update a thread.
# Prune Threads
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/prune-threads
langsmith/agent-server-openapi.json post /threads/prune
Prune threads by ID. The 'delete' strategy removes threads entirely. The 'keep_latest' strategy prunes old checkpoints but keeps threads and their latest state (requires FF_USE_CORE_API=true).
# Search Threads
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/search-threads
langsmith/agent-server-openapi.json post /threads/search
Search for threads.
This endpoint also functions as the endpoint to list all threads.
# Update Thread State
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-api/threads/update-thread-state
langsmith/agent-server-openapi.json post /threads/{thread_id}/state
Add state to a thread.
# Agent Server changelog
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-changelog
**Subscribe**: Our changelog includes an [RSS feed](https://docs.langchain.com/langsmith/agent-server-changelog/rss.xml) that can integrate with [Slack](https://slack.com/help/articles/218688467-Add-RSS-feeds-to-Slack), [email](https://zapier.com/apps/email/integrations/rss/1441/send-new-rss-feed-entries-via-email), Discord bots like [Readybot](https://readybot.io/) or [RSS Feeds to Discord Bot](https://rss.app/en/bots/rssfeeds-discord-bot), and other subscription tools.
[Agent Server](/langsmith/agent-server) is an API platform for creating and managing agent-based applications. It provides built-in persistence, a task queue, and supports deploying, configuring, and running assistants (agentic workflows) at scale. This changelog documents all notable updates, features, and fixes to Agent Server releases.
## v0.7.46
* Structured error payloads in webhooks now include `error` and `message` fields, replacing the previous flat string format, which may affect systems parsing the `error` field.
* Expanded store auth tests with namespace-rewriting to enhance namespace handling and cross-user isolation.
## v0.7.45
* Replaced null bytes with U+FFFD in all handling paths to prevent key collisions.
## v0.7.44
* Increase flexibility of database URI parser
* Add additional validation on some payloads
## v0.7.40
* Fixed a regression in assistant creation by ensuring `metadata` and `config` are populated as empty objects `{}` instead of `null` when not provided.
## v0.7.39
* Ensured auth configuration is passed correctly in distributed runtime operations to improve executor functionality.
* Added support for Red Hat UBI-9 based Docker images for enterprise customers using RHEL-based containers.
* Added graceful shutdown handoff for distributed runtime, allowing in-flight runs to transfer to the next pod without using a retry attempt.
## v0.7.38
* Added `state_updated_at` field to threads for tracking meaningful state changes, allowing filtering and sorting based on these changes.
* Added support for scheduling crons within the core system.
* Ensured accurate display of the `https` protocol in agent cards using the x-forwarded-proto header for proper A2A client functionality.
## v0.7.37
* Added generic fallbacks for `acopy_thread`, `aprune`, and `adelete_for_runs` in the BYOC checkpointer adapter, simplifying implementation for custom checkpointers.
## v0.7.36
* Updated A2A protocol support to v1.0 RC, renamed JSON-RPC methods, added a ListTasks handler, and enhanced role, state, and part formats for improved integration and compliance.
* Improved authentication filtering for `Crons.search()` and `Crons.count()` to prevent unauthorized thread information access.
* Fixed gaps in BYOC checkpointer for copy, rollback, and namespace filtering operations, ensuring proper handling across different storage backends.
## v0.7.35
* Added an optional `context` parameter to MCP `tools/call` and A2A `message/send` and `message/stream` endpoints, enabling middleware to inject runtime context from headers.
## v0.7.33
* Enhanced the Redis fixture by removing custom checkpointer test skips, improving typed serialization, and adding missing Redis methods.
* Resolved a stored XSS vulnerability in the handle\_ui endpoint by sanitizing message names in single-quoted HTML onload attributes.
* Fixed an authorization bypass issue in `put_item` to ensure correct namespace rewrite by the auth handler.
* Enforced assistant ownership checks during run creation, preventing execution on unowned assistants while ensuring system assistants remain accessible to all authenticated users.
* Implemented AES encryption for checkpoint blobs and writes in the Go checkpointer when using LANGGRAPH\_AES\_KEY.
* Implemented initial checkpointer gRPC servicer with all necessary methods and conversion helpers.
## v0.7.32
* Sanitized error messages in streams and A2A responses to protect sensitive information like database connection strings and internal hostnames.
* Fixed a bug in `join_run_stream` to correctly handle multiple `stream_mode` parameters, ensuring proper parsing of stringified JSON lists.
* Added build, test, and publish processes for Node.js 24 images, supporting the latest LTS version.
* Enhanced custom checkpointer adapter with new capabilities and improved metadata enrichment for consistent API responses.
* Added stricter version constraints for langgraph libraries in executor Docker images to prevent unintended upgrades.
* Enhanced security by sanitizing SSE event and id fields to prevent CR/LF injection.
* Fixed an issue causing cron-created runs to use default encryption contexts instead of properly propagating the specified ones.
## v0.7.31
* Corrected metadata reading functionality to ensure accurate data processing.
## v0.7.30
* Propagated cron metadata for more comprehensive scheduling information.
* Merges cron metadata on `PATCH` requests to align with other endpoints by preserving existing data.
## v0.7.29
* Refined authentication semantics for cron creation to prevent privilege escalation and ensure independent filtering for crons, threads, and assistants.
* Validated tar file entries to prevent directory traversal vulnerabilities in the cloudflared download process.
* Added an IDs filter to the `SearchThreadsRequest` to streamline thread endpoint operations.
* Updated the fallback mechanism to use a Python Postgres connection for thread state, fixing issues with worker completion checkpoints.
* Introduced a feature-flagged initial version of the Redis queue implementation with ongoing updates.
## v0.7.28
* Internal maintenance and stability improvements for MCP and gRPC.
## v0.7.27
* Improved MCP tool input schemas by removing common message types for cleaner tool definitions.
* Added name sanitization for MCP tools to ensure valid tool names.
## v0.7.26
* Added validation for system keys on ingress.
## v0.7.25
* Switched the Python queue worker to use core go `Runs.next()`.
* Fixed a monitoring issue in the long query monitor
## v0.7.24
* Optimized Postgres connection handling to prevent hitting connection limits under high load and removed unnecessary error logs.
* Switched to a new backend for runs management and streaming using gRPC.
## v0.7.23
* Corrected the unmarshaling process for the `input` field in `RunCommand` to ensure accurate data mapping and enable a previously gated JS test.
* Ensured race condition handling for run streaming by fully subscribing before execution starts, with added support for the `FF_LOG_DROPPED_EVENTS` environment variable.
## v0.7.22
* Ensured `get_store()` works in custom routes, enabling Store access from user-defined Starlette endpoints.
## v0.7.21
* Support for PATCH /crons/
## v0.7.19
* Custom encryption improvements for core API.
## v0.7.18
* Update thread streaming for core API usage.
## v0.7.17
* Instrumentation for OTEL now requires explicit opt-in with `LS_APM_OTEL_ENABLED=true` for improved control.
## v0.7.16
* Switched threads streaming to the new gRPC backend for improved performance.
* Introduced replica tracing in DR for enhanced evaluation capabilities in Studio.
## v0.7.15
* Added support for pausing crons with a new `is_enabled` field, allowing only enabled crons to be executed.
* Introduced gRPC server support for JSON encryption and decryption operations.
## v0.7.14
* Ensured selected system fields are excluded from custom encryption to prevent unnecessary encryption of non-sensitive data.
* Introduced a custom checkpointer adapter with unit tests to validate implementation checks.
## v0.7.13
* Fixed a bug where the app state was not properly preserved through requests when a mount prefix was set.
## v0.7.11
* Added configuration to control which payload fields can be exposed to webhooks.
* Updated all dependencies in the `/api/langgraph_api/js` group, including `@langchain/core`, `hono`, `@types/react`, and `prettier`, to the latest versions for improved performance and security.
* Upgraded `hono` from version 4.11.4 to 4.11.7 to address multiple security vulnerabilities in the middleware.
## v0.7.10
* Increased the gRPC server startup timeout to 1 minute to prevent occasional connection timeouts with the core server.
* Updated @langchain/langgraph from version 1.1.0 to 1.1.2, introducing mixed schema support for StateGraph and type bag patterns for GraphNode and ConditionalEdgeRouter utilities.
## v0.7.9
* A2A `messageId` is now mapped to LangChain message IDs for proper message tracking across protocols.
## v0.7.7
* Ensured preservation of custom configurable fields in checkpoint metadata during gRPC serialization.
## v0.7.5
* Enforced custom encryption for values, interrupts, and errors when setting a thread's status, resolving previous inconsistencies.
* Added A2A validation checks in `message/stream` and `message/send` routes for `parts`, `role`, and `messageId` fields.
* Added native A2A interrupt support: `input-required` state is now returned when graphs are interrupted. Use the new `command` parameter in `message/stream` and `message/send` requests to resume with a `Command` payload.
* Mounted `.well-known/agent-card.json` under `/a2a/{assistant_id}/` for A2A agent discovery.
* Added proper A2A error codes for task existence checks in `tasks/cancel`.
## v0.7.4
* Fixed a bug with Redis URL parsing for `ssl_cert_reqs` field, ensuring compatibility with redis-go.
* Added a gRPC client for streaming runs, controlled by the `FF_USE_CORE_API` feature flag.
## v0.7.2
* Updated `@langchain/langgraph` to version 1.1.0, introducing type utilities for graph nodes and conditional edges for enhanced TypeScript ergonomics.
## v0.7.0
* Switched to using the Go assistants implementation by default for improved performance.
* Added `LANGGRAPH_AES_JSON_KEYS` configuration to enable AES encryption for specified JSON fields using a key name allowlist.
## v0.6.39
* Added gRPC client support for `Threads.State()` to the Python `core-api`, improving thread ID and run counting operations.
## v0.6.36
* Validated the length of `$and` and `$or` in auth filters and optimized unnecessary root-level filters.
## v0.6.35
* Unified the error format by removing the `code` field and standardizing all errors to return JSON with a `detail` field.
## v0.6.34
* Small fixes for feature-flagged internal environments (unreleased).
## v0.6.33
* Small fixes for feature-flagged internal environments (unreleased).
## v0.6.32
* Small fixes for feature-flagged internal environments (unreleased).
## v0.6.31
* Properly respected the disable\_a2a setting to ensure accurate configuration handling.
## v0.6.29
* Fix minor bugs.
## v0.6.28
* Added support for `ParentCommand` to correctly propagate control to parent graphs, enhancing command handling and navigation.
* Added a Python gRPC client for managing run operations, enhancing consistency between Go and Python implementations.
## v0.6.27
* Fixed a regression issue in handling empty thread metadata.
## v0.6.26
* Fixed the port configuration issue for the persistence gRPC server.
## v0.6.25
* Ran the core-api gRPC server in the executor tier to support loopback API calls in graphs and removed unnecessary configuration for disabling the server.
## v0.6.24
* Fixed the behavior of the liveness probe in the executor tier, addressing issues from version 0.6.23.
## v0.6.23
* Integrated gRPC server health check with `/ok` endpoint in liveness probe to ensure proper startup coordination.
* Reverted the previous change to disable the checkpointer and added a condition to enable RemoteCheckpointer only during testing.
* Suppressed `langgraph_auth_*` and `langgraph_request_id` fields in checkpoint metadata to prevent inclusion of transient user data.
## v0.6.22
* Resolved an error caused by missing encryption contexts when using blob-only custom encryption, ensuring proper function without errors.
## v0.6.21
* Introduced a Python gRPC client for run operations, including `Search`, `Get`, `Delete`, `Cancel`, `Stats`, and `Sweep`, with updated API implementation and a new unit test suite for enum mappings.
## v0.6.19
* Reproduced OSS implementations of `get_state` and `update_state` in the engine server and re-enabled `test_weather_subgraph`.
## v0.6.18
* Added functionality to enforce specific license claims for self-hosted Enterprise users, enabling remote disabling of the Agent Builder product.
* Added a new Prune endpoint for better resource management.
* Merged graph configuration with invoke configuration in Pregel, giving precedence to invoke settings.
* Introduced the `include=ttl` query parameter to the GET /threads/ endpoint for optional TTL information retrieval without affecting standard read performance.
* Introduced a `keep_latest` TTL strategy to preserve the latest state while pruning older checkpoints via the core API.
## v0.6.17
* Ensured ongoing runs are stopped when an agent is deleted to prevent lingering processes.
## v0.6.16
* Streamlined and consolidated run operations in the Go persistence layer, improving efficiency and consistency across packages.
## v0.6.15
* Improved the utility converting custom route docstrings to OpenAPI schema content by adding error handling when parsing docstrings, applicable for users with custom Starlette apps.
## v0.6.12
* Improved resolve\_embeddings to be more robust, enabling multiple calls without errors.
* Updated `@langchain/langgraph` from version 1.0.4 to 1.0.7, adding support for resumableStreams on remote graphs and undeprecating toolsCondition.
* Implemented `RemoteCheckpointer` to enable subgraph checkpointing, enhancing task execution reliability.
## v0.6.11
* Made the maximum number of retries configurable for enhanced customization.
## v0.6.10
* Ensured run cancellation only processes 'message' type Redis events, improving pubsub client reliability.
* Added custom encryption for the Store API `value` field, allowing users to choose which keys to encrypt for enhanced security.
* Enabled streaming for subgraph custom events by updating TeeStream to handle event types separately.
## v0.6.9
* Enforced stable JSON keys for custom encryption, removed model-type-specific custom JSON functions, and improved error handling for double-encryption scenarios.
## v0.6.8
* Added profiling feature to enhance performance analysis and monitoring.
## v0.6.7
* Logged server startup time for improved monitoring and diagnostics.
## v0.6.5
* Added a warning log that triggers during import time for improved visibility.
## v0.6.4
* Enhanced custom encryption by parallelizing metadata and config processes, added encryption for thread.config and some checkpoints, improved tests and schema consistency.
* Ensured the Go server starts as `core-api` in the queue entrypoint for consistent runtime behavior.
## v0.6.2
* Resolved an issue that caused duplicate calls to middleware when `mount_prefix` was specified.
## v0.6.0
This minor version updates the streaming APIs `/join-stream` and `/stream` behavior with respect to the `last-event-id` parameter to align with the SSE spec. Previously, passing a last-event-id would return that message in addition to any following messages. Going forward, these APIs will only return new messages following the provided last-event-id. For example, with the following stream, previously passing a last-event-id of `2` would return the messages with ids `2` and `3`, but will now only return the message with id `3`:
```json theme={null}
{
"id": 1,
"event": "message",
"data": {
"content": "Excluded"
}
},
{
"id": 2,
"event": "message",
"data": {
"content": "Passed last-event-id"
}
},
{
"id": 3,
"event": "message",
"data": {
"content": "Included"
}
}
```
This bump also includes some fixes, including a bug exposing unintended internal events in run streams.
## v0.5.42
* Modified the Go server to rely solely on the CLI `-service` flag for determining service mode, ignoring the globally set `FF_USE_CORE_API` for better deployment specificity.
## v0.5.41
Fixed an issue with cron jobs in hybrid mode by ensuring proper initialization of the ENTERPRISE\_SAAS global flag.
## v0.5.39
* Completed the implementation of custom encryptions for runs and crons, along with simplifying encryption processes.
* Introduced support for streaming subgraph events in both `values` and `updates` stream modes.
## v0.5.38
* Implemented complete custom encryption for threads, ensuring all thread data is properly secured and encrypted.
* Ensured Redis attempt flags are consistently expired to prevent stale data.
* Added core authentication and support for OR/AND filters, enhancing security and flexibility.
## v0.5.37
Added a `name` parameter to the assistants count API for improved search flexibility.
## v0.5.36
* Introduced configurable webhook support, allowing users to customize submitted webhooks and headers.
* Added an `/ok` endpoint at the root for easier health checks and simplified configuration.
## v0.5.34
Introduced custom encryption middleware, allowing users to define their own encryption methods for enhanced data protection.
## v0.5.33
Set Uvicorn's keep-alive timeout to 75 seconds to prevent occasional 502 errors and improve connection handling.
## v0.5.32
Introduced OpenTelemetry telemetry agent with support for New Relic integration.
## v0.5.31
Added Py-Spy profiling for improved analysis of deployment performance, with some limitations on coverage.
## v0.5.30
* Always configure loopback transport clients to enhance reliability.
* Ensured authentication headers are passed for remote non-stream methods in JS.
## v0.5.28
* Introduced a faster, Rust-based implementation of uuid7 to improve performance, now used in langsmith and langchain-core.
* Added support for `$or` and `$and` in PostgreSQL auth filters to enable complex logic in authentication checks.
* Capped psycopg and psycopg-pool versions to prevent infinite waiting on startup.
## v0.5.27
* Ensured `runs.list` with filters returns only run fields, preventing incorrect status data from being included.
* (JS) Updated `uuid` from version 10.0.0 to 13.0.0. and `exit-hook` from version 4.0.0 to 5.0.1.
## v0.5.26
Resolved issues with `store.put` when used without AsyncBatchedStore in the JavaScript environment.
## v0.5.25
* Introduced the ability to search assistants by their `name` using a new endpoint.
* Casted store\_get return types to tuple in JavaScript to ensure type consistency.
## v0.5.24
* Added executor metrics for Datadog and enhanced core stream API metrics for better performance tracking.
* Disabled Redis Go maintenance notifications to prevent startup errors with unsupported commands in Redis versions below 8.
## v0.5.20
Resolved an error in the executor service that occurred when handling large messages.
## v0.5.19
Upgraded built-in langchain-core to version 1.0.7 to address a prompt formatting vulnerability.
## v0.5.18
Introduced persistent cron threads with `on_run_completed: {keep,delete}` for enhanced cron management and retrieval options.
## v0.5.17
Enhanced task handling to support multiple interrupts, aligning with open-source functionality.
## v0.5.15
Added custom JSON unmarshalling for `Resume` and `Goto` commands to fix map-style null resume interpretation issues.
## v0.5.14
Ensured `pg make start` command functions correctly with core-api enabled.
## v0.5.13
Support `include` and `exclude` (plural form key for `includes` and `excludes`) since a doc incorrectly claimed support for that. Now the server accepts either.
## v0.5.11
* Ensured auth handlers are applied consistently when streaming threads, aligning with recent security practices.
* Bumped `undici` dependency from version 6.21.3 to 7.16.0, introducing various performance improvements and bug fixes.
* Updated `p-queue` from version 8.0.1 to 9.0.0, introducing new features and breaking changes, including the removal of the `throwOnTimeout` option.
## v0.5.10
Implemented healthcheck calls in the queue /ok handler to improve Kubernetes liveness and readiness probe compatibility.
## v0.5.9
* Resolved an issue causing an "unbound local error" for the `elapsed` variable during a SIGINT interruption.
* Mapped the "interrupted" status to A2A's "input-required" status for better task status alignment.
## v0.5.8
* Ensured environment variables are passed as a dictionary when starting langgraph-ui for compatibility with `uvloop`.
* Implemented CRUD operations for runs in Go, simplifying JSON merges and improving transaction readability, with PostgreSQL as a reference.
## v0.5.7
Replaced no-retry Redis client with a retry client to handle connection errors more effectively and reduced corresponding logging severity.
## v0.5.6
* Added pending time metrics to provide better insights into task waiting times.
* Replaced `pb.Value` with `ChannelValue` to streamline code structure.
## v0.5.5
Made the Redis `health_check_interval` more frequent and configurable for better handling of idle connections.
## v0.5.4
Implemented `ormsgpack` with `OPT_REPLACE_SURROGATES` and updated for compatibility with the latest FastAPI release affecting custom authentication dependencies.
## v0.5.2
Added retry logic for PostgreSQL connections during startup to enhance deployment reliability and improved error logging for easier debugging.
## v0.5.1
* Resolved an issue where persistence was not functioning correctly with LangChain.js's createAgent feature.
* Optimized assistants CRUD performance by improving database connection pooling and gRPC client reuse, reducing latency for large payloads.
## v0.5.0
This minor version now requires langgraph-checkpoint versions later than 3.0 to prevent a deserialization vulnerability in earlier versions of the langgraph-checkpoint library.
The `langgraph-checkpoint` library is compatible with `langgraph` minor versions 0.4, 0.5, 0.6, and 1.0.
This version removes default support for deserialization of payloads saved using the "json" type, which has never been the default.
By default, objects are serialized using msgpack. Under certain uncommon situations, payloads were serialized using an older "json" mode. If those payloads contained custom python objects, those will no longer be deserializable unless you provide a `serde` config:
```json theme={null}
{
"checkpointer": {
"serde": {
"allowed_json_modules": [
["my_agent", "my_file", "SomeType"],
]
}
}
}
```
## v0.4.47
* Validated and auto-corrected environment configuration types using TypeAdapter.
* Added support for LangChain.js and LangGraph.js version 1.x, ensuring compatibility.
* Updated hono library from version 4.9.7 to 4.10.3, addressing a CORS middleware security issue and enhancing JWT audience validation.
* Introduced a modular benchmark framework, adding support for assistants and streams, with improvements to the existing ramp benchmark methodology.
* Introduced a gRPC API for core threads CRUD operations, with updated Python and TypeScript clients.
* Updated `hono` package from version 4.9.7 to 4.10.2, including security improvements for JWT audience validation.
* Updated `hono` dependency from version 4.9.7 to 4.10.3 to fix a security issue and improve CORS middleware handling.
* Introduced basic CRUD operations for threads, including create, get, patch, delete, search, count, and copy, with support for Go, gRPC server, and Python and TypeScript clients.
## v0.4.46
Added an option to enable message streaming from subgraph events, giving users more control over event notifications.
## v0.4.45
* Implemented support for authorization on custom routes, controlled by the `enable_custom_route_auth` flag.
* Set default tracing to off for improved performance and simplified debugging.
## v0.4.44
Used Redis key prefix for license-related keys to prevent conflicts with existing setups.
## v0.4.43
Implemented a health check for Redis connections to prevent them from idling out.
## v0.4.40
* Prevented duplicate messages in resumable run and thread streams by addressing a race condition and adding tests to ensure consistent behavior.
* Ensured that runs don't start until the pubsub subscription is confirmed to prevent message drops on startup.
* Renamed platform from langgraph to improve clarity and branding.
* Reset PostgreSQL connections after use to prevent lock holding and improved error reporting for transaction issues.
## v0.4.39
* Upgraded `hono` from version 4.7.6 to 4.9.7, addressing a security issue related to the `bodyLimit` middleware.
* Allowed customization of the base authentication URL to enhance flexibility.
* Pinned the 'ty' dependency to a stable version using 'uv' to prevent unexpected linting failures.
## v0.4.38
* Replaced `LANGSMITH_API_KEY` with `LANGSMITH_CONTROL_PLANE_API_KEY` to support hybrid deployments requiring license verification.
* Introduced self-hosted log ingestion support, configurable via `SELF_HOSTED_LOGS_ENABLED` and `SELF_HOSTED_LOGS_ENDPOINT` environment variables.
## v0.4.37
Required create permissions for copying threads to ensure proper authorization.
## v0.4.36
* Improved error handling and added a delay to the sweep loop for smoother operation during Redis downtime or cancellation errors.
* Updated the queue entrypoint to start the core-api gRPC server when `FF_USE_CORE_API` is enabled.
* Introduced checks for invalid configurations in assistant endpoints to ensure consistency with other endpoints.
## v0.4.35
* Resolved a timezone issue in the core API, ensuring accurate time data retrieval.
* Introduced a new `middleware_order` setting to apply authentication middleware before custom middleware, allowing finer control over protected route configurations.
* Logged the Redis URL when errors occur during Redis client creation.
* Improved Go engine/runtime context propagation to ensure consistent execution flow.
* Removed the unnecessary `assistants.put` call from the executor entrypoint to streamline the process.
## v0.4.34
Blocked unauthorized users from updating thread TTL settings to enhance security.
## v0.4.33
* Improved error handling for Redis locks by logging `LockNotOwnedError` and extending initial pool migration lock timeout to 60 seconds.
* Updated the BaseMessage schema to align with the latest langchain-core version and synchronized build dependencies for consistent local development.
## v0.4.32
* Added a GO persistence layer to the API image, enabling GRPC server operation with PostgreSQL support and enhancing configurability.
* Set the status to error when a timeout occurs to improve error handling.
## v0.4.30
* Added support for context when using `stream_mode="events"` and included new tests for this functionality.
* Added support for overriding the server port using `$LANGGRAPH_SERVER_PORT` and removed an unnecessary Dockerfile `ARG` for cleaner configuration.
* Applied authorization filters to all table references in thread delete CTE to enhance security.
* Introduced self-hosted metrics ingestion capability, allowing metrics to be sent to an OTLP collector every minute when the corresponding environment variables are set.
* Ensured that the `set_latest` function properly updates the name and description of the version.
## v0.4.29
Ensured proper cleanup of redis pubsub connections in all scenarios.
## v0.4.28
* Added a format parameter to the queue metrics server for enhanced customization.
* Corrected `MOUNT_PREFIX` environment variable usage in CLI for consistency with documentation and to prevent confusion.
* Added a feature to log warnings when messages are dropped due to no subscribers, controllable via a feature flag.
* Added support for Bookworm and Bullseye distributions in Node images.
* Consolidated executor definitions by moving them from the `langgraph-go` repository, improving manageability and updating the checkpointer setup method for server migrations.
* Ensured correct response headers are sent for a2a, improving compatibility and communication.
* Consolidated PostgreSQL checkpoint implementation, added CI testing for the `/core` directory, fixed RemoteStore test errors, and enhanced the Store implementation with transactions.
* Added PostgreSQL migrations to the queue server to prevent errors from graphs being added before migrations are performed.
## v0.4.27
Replaced `coredis` with `redis-py` to improve connection handling and reliability under high traffic loads.
## v0.4.24
* Added functionality to return full message history for A2A calls in accordance with the A2A spec.
* Added a `LANGGRAPH_SERVER_HOST` environment variable to Dockerfiles to support custom host settings for dual stack mode.
## v0.4.23
Use a faster message codec for redis streaming.
## v0.4.22
Ported long-stream handling to the run stream, join, and cancel endpoints for improved stream management.
## v0.4.21
* Added A2A streaming functionality and enhanced testing with the A2A SDK.
* Added Prometheus metrics to track language usage in graphs, middleware, and authentication for improved insights.
* Fixed bugs in Open Source Software related to message conversion for chunks.
* Removed await from pubsub subscribes to reduce flakiness in cluster tests and added retries in the shutdown suite to enhance API stability.
## v0.4.20
Optimized Pubsub initialization to prevent overhead and address subscription timing issues, ensuring smoother run execution.
## v0.4.19
Removed warnings from psycopg by addressing function checks introduced in version 3.2.10.
## v0.4.17
Filtered out logs with mount prefix to reduce noise in logging output.
## v0.4.16
* Added support for implicit thread creation in a2a to streamline operations.
* Improved error serialization and emission in distributed runtime streams, enabling more comprehensive testing.
## v0.4.13
* Monitored queue status in the health endpoint to ensure correct behavior when PostgreSQL fails to initialize.
* Addressed an issue with unequal swept ID lengths to improve log clarity.
* Enhanced streaming outputs by avoiding re-serialization of DR payloads, using msgpack byte inspection for json-like parsing.
## v0.4.12
* Ensured metrics are returned even when experiencing database connection issues.
* Optimized update streams to prevent unnecessary data transmission.
* Upgraded `hono` from version 4.9.2 to 4.9.6 in the `storage_postgres/langgraph-api-server` for improved URL path parsing security.
* Added retries and an in-memory cache for LangSmith access calls to improve resilience against single failures.
## v0.4.11
Added support for TTL (time-to-live) in thread updates.
## v0.4.10
In distributed runtime, update serde logic for final checkpoint -> thread setting.
## v0.4.9
* Added support for filtering search results by IDs in the search endpoint for more precise queries.
* Included configurable headers for assistant endpoints to enhance request customization.
* Implemented a simple A2A endpoint with support for agent card retrieval, task creation, and task management.
## v0.4.7
Stopped the inclusion of x-api-key to enhance security.
## v0.4.6
Fixed a race condition when joining streams, preventing duplicate start events.
## v0.4.5
* Ensured the checkpointer starts and stops correctly before and after the queue to improve shutdown and startup efficiency.
* Resolved an issue where workers were being prematurely cancelled when the queue was cancelled.
* Prevented queue termination by adding a fallback for cases when Redis fails to wake a worker.
## v0.4.4
* Set the custom auth thread\_id to None for stateless runs to prevent conflicts.
* Improved Redis signaling in the Go runtime by adding a wakeup worker and Redis lock implementation, and updated sweep logic.
## v0.4.3
* Added stream mode to thread stream for improved data processing.
* Added a durability parameter to runs for improved data persistence.
## v0.4.2
Ensured pubsub is initialized before creating a run to prevent errors from missing messages.
## v0.4.0
Minor version 0.4 comes with a number of improvements as well as some breaking changes.
* Emitted attempt messages correctly within the thread stream.
* Reduced cluster conflicts by using only the thread ID for hashing in cluster mapping, prioritizing efficiency with stream\_thread\_cache.
* Introduced a stream endpoint for threads to track all outputs across sequentially executed runs.
* Made the filter query builder in PostgreSQL more robust against malformed expressions and improved validation to prevent potential security risks.
This minor version also includes a couple of breaking changes to improve the usability and security of the service:
* In this minor version, we stop the practice of automatically including headers as configurable values in your runs. You can opt-in to specific patterns by setting **configurable\_headers** in your agent server config.
* Run stream event IDs (for resumable streams) are now in the format of `ms-seq` instead of the previous format. We retain backwards compatibility for the old format, but we recommend using the new format for new code.
## v0.3.4
* Added custom Prometheus metrics for Redis/PG connection pools and switched the queue server to Uvicorn/Starlette for improved monitoring.
* Restored Wolfi image build by correcting shell command formatting and added a Makefile target for testing with nginx.
## v0.3.3
* Added timeouts to specific Redis calls to prevent workers from being left active.
* Updated the Golang runtime and added pytest skips for unsupported functionalities, including initial support for passing store to node and message streaming.
* Introduced a reverse proxy setup for serving combined Python and Node.js graphs, with nginx handling server routing, to facilitate a Postgres/Redis backend for the Node.js API server.
## v0.3.1
Added a statement timeout to the pool to prevent long-running queries.
## v0.3.0
* Set a default 15-minute statement timeout and implemented monitoring for long-running queries to ensure system efficiency.
* Stop propagating run configurable values to the thread configuration, because this can cause issues on subsequent runs if you are specifying a checkpoint\_id. This is a **slight breaking change** in behavior, since the thread value will no longer automatically reflect the unioned configuration of the most recent run. We believe this behavior is more intuitive, however.
* Enhanced compatibility with older worker versions by handling event data in channel names within ops.py.
## v0.2.137
Fixed an unbound local error and improved logging for thread interruptions or errors, along with type updates.
## v0.2.136
* Added enhanced logging to aid in debugging metaview issues.
* Upgraded executor and runtime to the latest version for improved performance and stability.
## v0.2.135
Ensured async coroutines are properly awaited to prevent potential runtime errors.
## v0.2.134
Enhanced search functionality to improve performance by allowing users to select specific columns for query results.
## v0.2.133
* Added count endpoints for crons, threads, and assistants to enhance data tracking (#1132).
* Improved SSH functionality for better reliability and stability.
* Updated @langchain/langgraph-api to version 0.0.59 to fix an invalid state schema issue.
## v0.2.132
* Added Go language images to enhance project compatibility and functionality.
* Printed internal PIDs for JS workers to facilitate process inspection via SIGUSR1 signal.
* Resolved a `run_pkey` error that occurred when attempting to insert duplicate runs.
* Added `ty run` command and switched to using uuid7 for generating run IDs.
* Implemented the initial Golang runtime to expand language support.
## v0.2.131
Added support for `object agent spec` with descriptions in JS.
## v0.2.130
* Added a feature flag (FF\_RICH\_THREADS=false) to disable thread updates on run creation, reducing lock contention and simplifying thread status handling.
* Utilized existing connections for `aput` and `apwrite` operations to improve performance.
* Improved error handling for decoding issues to enhance data processing reliability.
* Excluded headers from logs to improve security while maintaining runtime functionality.
* Fixed an error that prevented mapping slots to a single node.
* Added debug logs to track node execution in JS deployments for improved issue diagnosis.
* Changed the default multitask strategy to enqueue, improving throughput by eliminating the need to fetch inflight runs during new run insertions.
* Optimized database operations for `Runs.next` and `Runs.sweep` to reduce redundant queries and improve efficiency.
* Improved run creation speed by skipping unnecessary inflight runs queries.
## v0.2.129
* Stopped passing internal LGP fields to context to prevent breaking type checks.
* Exposed content-location headers to ensure correct resumability behavior in the API.
## v0.2.128
Ensured synchronized updates between `configurable` and `context` in assistants, preventing setup errors and supporting smoother version transitions.
## v0.2.127
Excluded unrequested stream modes from the resumable stream to optimize functionality.
## v0.2.126
* Made access logger headers configurable to enhance logging flexibility.
* Debounced the Runs.stats function to reduce the frequency of expensive calls and improve performance.
* Introduced debouncing for sweepers to enhance performance and efficiency (#1147).
* Acquired a lock for TTL sweeping to prevent database spamming during scale-out operations.
## v0.2.125
Updated tracing context replicas to use the new format, ensuring compatibility.
## v0.2.123
Added an entrypoint to the queue replica for improved deployment management.
## v0.2.122
Utilized persisted interrupt status in `join` to ensure correct handling of user's interrupt state after completion.
## v0.2.121
* Consolidated events to a single channel to prevent race conditions and optimize startup performance.
* Ensured custom lifespans are invoked on queue workers for proper setup, and added tests.
## v0.2.120
* Restored the original streaming behavior of runs, ensuring consistent inclusion of interrupt events based on `stream_mode` settings.
* Optimized `Runs.next` query to reduce average execution time from \~14.43ms to \~2.42ms, improving performance.
* Added support for stream mode "tasks" and "checkpoints", normalized the UI namespace, and upgraded `@langchain/langgraph-api` for enhanced functionality.
## v0.2.117
Added a composite index on threads for faster searches with owner-based authentication and updated the default sort order to `updated_at` for improved query performance.
## v0.2.116
Reduced the default number of history checkpoints from 10 to 1 to optimize performance.
## v0.2.115
Optimized cache re-use to enhance application performance and efficiency.
## v0.2.113
Improved thread search pagination by updating response headers with `X-Pagination-Total` and `X-Pagination-Next` for better navigation.
## v0.2.112
* Ensured sync logging methods are awaited and added a linter to prevent future occurrences.
* Fixed an issue where JavaScript tasks were not being populated correctly for JS graphs.
## v0.2.111
Fixed JS graph streaming failure by starting the heartbeat as soon as the connection opens.
## v0.2.110
Added interrupts as default values for join operations while preserving stream behavior.
## v0.2.109
Fixed an issue where config schema was missing when `config_type` was not set, ensuring more reliable configurations.
## v0.2.108
Prepared for LangGraph v0.6 compatibility with new context API support and bug fixes.
## v0.2.107
* Implemented caching for authentication processes to enhance performance and efficiency.
* Optimized database performance by merging count and select queries.
## v0.2.106
Made log streams resumable, enhancing reliability and improving user experience when reconnecting.
## v0.2.105
Added a heapdump endpoint to save memory heap information to a file.
## v0.2.103
Used the correct metadata endpoint to resolve issues with data retrieval.
## v0.2.102
* Captured interrupt events in the wait method to preserve previous behavior from langgraph 0.5.0.
* Added support for SDK structlog in the JavaScript environment for enhanced logging capabilities.
## v0.2.101
Corrected the metadata endpoint for self-hosted deployments.
## v0.2.99
* Improved license check by adding an in-memory cache and handling Redis connection errors more effectively.
* Reloaded assistants to preserve manually created ones while discarding those removed from the configuration file.
* Reverted changes to ensure the UI namespace for gen UI is a valid JavaScript property name.
* Ensured that the UI namespace for generated UI is a valid JavaScript property name, improving API compliance.
* Enhanced error handling to return a 422 status code for unprocessable entity requests.
## v0.2.98
Added context to langgraph nodes to improve log filtering and trace visibility.
## v0.2.97
* Improved interoperability with the ckpt ingestion worker on the main loop to prevent task scheduling issues.
* Delayed queue worker startup until after migrations are completed to prevent premature execution.
* Enhanced thread state error handling by adding specific metadata and improved response codes for better clarity when state updates fail during creation.
* Exposed the interrupt ID when retrieving the thread state to improve API transparency.
## v0.2.96
Added a fallback mechanism for configurable header patterns to handle exclude/include settings more effectively.
## v0.2.95
* Avoided setting the future if it is already done to prevent redundant operations.
* Resolved compatibility errors in CI by switching from `typing.TypedDict` to `typing_extensions.TypedDict` for Python versions below 3.12.
## v0.2.94
* Improved performance by omitting pending sends for langgraph versions 0.5 and above.
* Improved server startup logs to provide clearer warnings when the DD\_API\_KEY environment variable is set.
## v0.2.93
Removed the GIN index for run metadata to improve performance.
## v0.2.92
Enabled copying functionality for blobs and checkpoints, improving data management flexibility.
## v0.2.91
Reduced writes to the `checkpoint_blobs` table by inlining small values (null, numeric, str, etc.). This means we don't need to store extra values for channels that haven't been updated.
## v0.2.90
Improve checkpoint writes via node-local background queueing.
## v0.2.89
Decoupled checkpoint writing from thread/run state by removing foreign keys and updated logger to prevent timeout-related failures.
## v0.2.88
Removed the foreign key constraint for `thread` in the `run` table to simplify database schema.
## v0.2.87
Added more detailed logs for Redis worker signaling to improve debugging.
## v0.2.86
Honored tool descriptions in the `/mcp` endpoint to align with expected functionality.
## v0.2.85
Added support for the `on_disconnect` field to `runs/wait` and included disconnect logs for better debugging.
## v0.2.84
Removed unnecessary status updates to streamline thread handling and updated version to 0.2.84.
## v0.2.83
* Reduced the default time-to-live for resumable streams to 2 minutes.
* Enhanced data submission logic to send data to both Beacon and LangSmith instance based on license configuration.
* Enabled submission of self-hosted data to a LangSmith instance when the endpoint is configured.
## v0.2.82
Addressed a race condition in background runs by implementing a lock using join, ensuring reliable execution across CTEs.
## v0.2.81
Optimized run streams by reducing initial wait time to improve responsiveness for older or non-existent runs.
## v0.2.80
Corrected parameter passing in the `logger.ainfo()` API call to resolve a TypeError.
## v0.2.79
* Fixed a JsonDecodeError in checkpointing with remote graph by correcting JSON serialization to handle trailing slashes properly.
* Introduced a configuration flag to disable webhooks globally across all routes.
## v0.2.78
* Added timeout retries to webhook calls to improve reliability.
* Added HTTP request metrics, including a request count and latency histogram, for enhanced monitoring capabilities.
## v0.2.77
* Added HTTP metrics to improve performance monitoring.
* Changed the Redis cache delimiter to reduce conflicts with subgraph message names and updated caching behavior.
## v0.2.76
Updated Redis cache delimiter to prevent conflicts with subgraph messages.
## v0.2.74
Scheduled webhooks in an isolated loop to ensure thread-safe operations and prevent errors with PYTHONASYNCIODEBUG=1.
## v0.2.73
* Fixed an infinite frame loop issue and removed the dict\_parser due to structlog's unexpected behavior.
* Throw a 409 error on deadlock occurrence during run cancellations to handle lock conflicts gracefully.
## v0.2.72
* Ensured compatibility with future langgraph versions.
* Implemented a 409 response status to handle deadlock issues during cancellation.
## v0.2.71
Improved logging for better clarity and detail regarding log types.
## v0.2.70
Improved error handling to better distinguish and log TimeoutErrors caused by users from internal run timeouts.
## v0.2.69
Added sorting and pagination to the crons API and updated schema definitions for improved accuracy.
## v0.2.66
Fixed a 404 error when creating multiple runs with the same thread\_id using `on_not_exist="create"`.
## v0.2.65
* Ensured that only fields from `assistant_versions` are returned when necessary.
* Ensured consistent data types for in-memory and PostgreSQL users, improving internal authentication handling.
## v0.2.64
Added descriptions to version entries for better clarity.
## v0.2.62
* Improved user handling for custom authentication in the JS Studio.
* Added Prometheus-format run statistics to the metrics endpoint for better monitoring.
* Added run statistics in Prometheus format to the metrics endpoint.
## v0.2.61
Set a maximum idle time for Redis connections to prevent unnecessary open connections.
## v0.2.60
* Enhanced error logging to include traceback details for dictionary operations.
* Added a `/metrics` endpoint to expose queue worker metrics for monitoring.
## v0.2.57
* Removed CancelledError from retriable exceptions to allow local interrupts while maintaining retriability for workers.
* Introduced middleware to gracefully shut down the server after completing in-flight requests upon receiving a SIGINT.
* Reduced metadata stored in checkpoint to only include necessary information.
* Improved error handling in join runs to return error details when present.
## v0.2.56
Improved application stability by adding a handler for SIGTERM signals.
## v0.2.55
* Improved the handling of cancellations in the queue entrypoint.
* Improved cancellation handling in the queue entry point.
## v0.2.54
* Enhanced error message for LuaLock timeout during license validation.
* Fixed the \$contains filter in custom auth by requiring an explicit ::text cast and updated tests accordingly.
* Ensured project and tenant IDs are formatted as UUIDs for consistency.
## v0.2.53
* Resolved a timing issue to ensure the queue starts only after the graph is registered.
* Improved performance by setting thread and run status in a single query and enhanced error handling during checkpoint writes.
* Reduced the default background grace period to 3 minutes.
## v0.2.52
* Now logging expected graphs when one is omitted to improve traceability.
* Implemented a time-to-live (TTL) feature for resumable streams.
* Improved query efficiency and consistency by adding a unique index and optimizing row locking.
## v0.2.51
* Handled `CancelledError` by marking tasks as ready to retry, improving error management in worker processes.
* Added LG API version and request ID to metadata and logs for better tracking.
* Added LG API version and request ID to metadata and logs to improve traceability.
* Improved database performance by creating indexes concurrently.
* Ensured postgres write is committed only after the Redis running marker is set to prevent race conditions.
* Enhanced query efficiency and reliability by adding a unique index on thread\_id/running, optimizing row locks, and ensuring deterministic run selection.
* Resolved a race condition by ensuring Postgres updates only occur after the Redis running marker is set.
## v0.2.46
Introduced a new connection for each operation while preserving transaction characteristics in Threads state `update()` and `bulk()` commands.
## v0.2.45
* Enhanced streaming feature by incorporating tracing contexts.
* Removed an unnecessary query from the Crons.search function.
* Resolved connection reuse issue when scheduling next run for multiple cron jobs.
* Removed an unnecessary query in the Crons.search function to improve efficiency.
* Resolved an issue with scheduling the next cron run by improving connection reuse.
## v0.2.44
* Enhanced the worker logic to exit the pipeline before continuing when the Redis message limit is reached.
* Introduced a ceiling for Redis message size with an option to skip messages larger than 128 MB for improved performance.
* Ensured the pipeline always closes properly to prevent resource leaks.
## v0.2.43
* Improved performance by omitting logs in metadata calls and ensuring output schema compliance in value streaming.
* Ensured the connection is properly closed after use.
* Aligned output format to strictly adhere to the specified schema.
* Stopped sending internal logs in metadata requests to improve privacy.
## v0.2.42
* Added timestamps to track the start and end of a request's run.
* Added tracer information to the configuration settings.
* Added support for streaming with tracing contexts.
## v0.2.41
Added locking mechanism to prevent errors in pipelined executions.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-server-changelog.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Distributed tracing with Agent Server
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-distributed-tracing
Unify traces when calling your deployed Agent Server from another service using RemoteGraph or the SDK.
When you call a deployed [Agent Server](/langsmith/agent-server) from another service, you can propagate trace context so that the entire request appears as a single unified trace in LangSmith. This uses LangSmith's [distributed tracing](/langsmith/distributed-tracing) capabilities, which propagate context via HTTP headers.
## How it works
Distributed tracing links runs across services using context propagation headers:
1. The **client** infers the trace context from the current run and sends it as HTTP headers.
2. The **server** reads the headers and adds them to the run's config and metadata as `langsmith-trace` and `langsmith-project` configurable values. You can choose to use these to set the tracing context for a given run when your agent is used.
The headers used are:
* `langsmith-trace`: Contains the trace's dotted order.
* `baggage`: Specifies the LangSmith project and other optional tags and metadata.
To opt-in to distributed tracing, both client and server need to opt in.
## Configure the server
To accept distributed trace context, your graph must read the trace headers from the config and set the tracing context. The headers are passed through the `configurable` field as `langsmith-trace` and `langsmith-project`.
```python theme={null}
import contextlib
import langsmith as ls
from langgraph.graph import StateGraph, MessagesState
# Define your graph
builder = StateGraph(MessagesState)
# ... add nodes and edges ...
my_graph = builder.compile()
@contextlib.contextmanager
async def graph(config):
configurable = config.get("configurable", {})
parent_trace = configurable.get("langsmith-trace")
parent_project = configurable.get("langsmith-project")
# If you want to also include metadata and tags from the client
metadata = configurable.get("langsmith-metadata")
tags = configurable.get("langsmith-tags")
with ls.tracing_context(parent=parent_trace, project_name=parent_project, metadata=metadata, tags=tags):
yield my_graph
```
Export this `graph` function in your `langgraph.json`:
```json theme={null}
{
"graphs": {
"agent": "./src/agent.py:graph"
}
}
```
## Connect from the client
Set `distributed_tracing=True` when initializing [`RemoteGraph`](https://reference.langchain.com/python/langsmith/deployment/remote_graph/). This automatically propagates trace headers on all requests.
```python theme={null}
from langgraph.graph import StateGraph
from langgraph.pregel.remote import RemoteGraph
remote_graph = RemoteGraph(
"agent",
url="",
distributed_tracing=True, # Enable trace propagation
)
def subgraph_node(query: str):
# Trace context is automatically propagated
return remote_graph.invoke({
"messages": [{"role": "user", "content": query}]
})['messages'][-1]['content']
# The RemoteGraph is called in the context of some on going work.
# This could be a parent LangGraph agent, code traced with `@ls.traceable`,
# or any other instrumented code.
graph = (
StateGraph(str)
.add_node(subgraph_node)
.add_edge("__start__", "subgraph_node")
.compile()
)
# The remote graph's execution will appear as a child of this trace
result = graph.invoke("What's the weather in SF?")
```
If you're using the [LangGraph SDK](/langsmith/reference) directly, propagate trace headers manually using `run_tree.to_headers()`:
```python theme={null}
from langgraph_sdk import get_client
import langsmith as ls
client = get_client(url="")
with ls.trace("call_remote_agent", inputs={"query": query}) as rt:
headers = rt.to_headers()
async for chunk in client.runs.stream(
thread_id=None,
assistant_id="agent",
input={"messages": [{"role": "user", "content": query}]},
stream_mode="values",
headers=headers, # Pass trace headers
):
pass
return chunk
result = await call_remote_agent("What's the weather in SF?")
```
## Related
* [Distributed tracing](/langsmith/distributed-tracing): General distributed tracing concepts and patterns
* [RemoteGraph](/langsmith/use-remote-graph): Full guide to interacting with deployments using RemoteGraph
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-server-distributed-tracing.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Configure LangSmith Agent Server for scale
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/agent-server-scale
The default configuration for LangSmith Agent Server is designed to handle substantial read and write load across a variety of different workloads. By following the best practices outlined below, you can tune your Agent Server to perform optimally for your specific workload. This page describes scaling considerations for the Agent Server and provides examples to help configure your deployment.
If you're not yet familiar with how API servers and queue workers operate at the container level, read the [runtime architecture](/langsmith/agent-server#runtime-architecture) overview first.
For some example self-hosted configurations, refer to the [Example Agent Server configurations for scale](#example-self-hosted-agent-server-configurations) section.
## Scaling for write load
Write load is primarily driven by the following factors:
* Creation of new [runs](/langsmith/background-run)
* Creation of new checkpoints during run execution
* Writing to long term memory
* Creation of new [threads](/langsmith/use-threads)
* Creation of new [assistants](/langsmith/assistants)
* Deletion of runs, checkpoints, threads, assistants and cron jobs
The following components are primarily responsible for handling write load:
* API server: Handles initial request and persistence of data to the database.
* Queue worker: Handles the execution of runs.
* Redis: Handles the storage of ephemeral data about on-going runs.
* Postgres: Handles the storage of all data, including run, thread, assistant, cron job, checkpointing and long term memory.
### Best practices for scaling the write path
#### Change `N_JOBS_PER_WORKER` based on assistant characteristics
The default value of [`N_JOBS_PER_WORKER`](/langsmith/env-var#n-jobs-per-worker) is 10. You can change this value to scale the maximum number of runs that can be executed at a time by a single queue worker based on the characteristics of your assistant.
Some general guidelines for changing `N_JOBS_PER_WORKER`:
* If your assistant is CPU bounded, the default value of 10 is likely sufficient. You might lower `N_JOBS_PER_WORKER` if you notice excessive CPU usage on queue workers or delays in run execution.
* If your assistant is IO bounded, increase `N_JOBS_PER_WORKER` to handle more concurrent runs per worker.
There is no upper limit to `N_JOBS_PER_WORKER`. However, queue workers are greedy when fetching new runs, which means they will try to pick up as many runs as they have available jobs and begin executing them immediately. Setting `N_JOBS_PER_WORKER` too high in environments with bursty traffic can lead to uneven worker utilization and increased run execution times.
#### Avoid synchronous blocking operations
Avoid synchronous blocking operations in your code and prefer asynchronous operations. Long synchronous operations can block the main event loop, causing longer request and run execution times and potential timeouts.
For example, consider an application that needs to sleep for 1 second. Instead of using synchronous code like this:
```python theme={null}
import time
def my_function():
time.sleep(1)
```
Prefer asynchronous code like this:
```python theme={null}
import asyncio
async def my_function():
await asyncio.sleep(1)
```
If an assistant requires synchronous blocking operations, set [`BG_JOB_ISOLATED_LOOPS`](/langsmith/env-var#bg-job-isolated-loops) to `True` to execute each run in a separate event loop.
#### Minimize redundant checkpointing
Minimize redundant checkpointing by setting [`durability`](/oss/python/langgraph/durable-execution#durability-modes) to the minimum value necessary to ensure your data is durable.
The default durability mode is `"async"`, meaning checkpoints are written after each step asynchronously. If an assistant needs to persist only the final state of the run, `durability` can be set to `"exit"`, storing only the final state of the run. This can be set when creating the run:
```python theme={null}
from langgraph_sdk import get_client
client = get_client(url=)
thread = await client.threads.create()
run = await client.runs.create(
thread_id=thread["thread_id"],
assistant_id="agent",
durability="exit"
)
```
#### Self-hosted
These settings are only required for [self-hosted](/langsmith/self-hosted) deployments. By default, [cloud](/langsmith/cloud) deployments already have these best practices enabled.
##### Enable the use of queue workers
By default, the API server manages the queue and does not use queue workers. You can enable the use of queue workers by setting the `queue.enabled` configuration to `true`.
```yaml theme={null}
queue:
enabled: true
```
This will allow the API server to offload the queue management to the queue workers, significantly reducing the load on the API server and allowing it to focus on handling requests.
##### Support a number of jobs equal to expected throughput
The more runs you execute in parallel, the more jobs you will need to handle the load. There are two main parameters to scale the available jobs:
* `number_of_queue_workers`: The number of queue workers provisioned.
* `N_JOBS_PER_WORKER`: The number of runs that a single queue work can execute at a time. Defaults to 10.
You can calculate the available jobs with the following equation:
```
available_jobs = number_of_queue_workers * `N_JOBS_PER_WORKER`
```
Throughput is then the number of runs that can be executed per second by the available jobs:
```
throughput_per_second = available_jobs / average_run_execution_time_seconds
```
Therefore, the minimum number of queue workers you should provision to support your expected steady state throughput is:
```
number_of_queue_workers = throughput_per_second * average_run_execution_time_seconds / `N_JOBS_PER_WORKER`
```
##### Configure autoscaling for bursty workloads
Autoscaling is disabled by default, but should be configured for bursty workloads. Using the same calculations as the [previous section](#support-a-number-of-jobs-equal-to-expected-throughput), you can determine the maximum number of queue workers you should allow the autoscaler to scale to based on maximum expected throughput.
## Scaling for read load
Read load is primarily driven by the following factors:
* Getting the results of a [run](/langsmith/background-run)
* Getting the state of a [thread](/langsmith/use-threads)
* Searching for [runs](/langsmith/background-run), [threads](/langsmith/use-threads), [cron jobs](/langsmith/cron-jobs) and [assistants](/langsmith/assistants)
* Retrieving checkpoints and long term memory
The following components are primarily responsible for handling read load:
* API server: Handles the request and direct retrieval of data from the database.
* Postgres: Handles the storage of all data, including run, thread, assistant, cron job, checkpointing and long term memory.
* Redis: Handles the storage of ephemeral data about on-going runs, including streaming messages from queue workers to api servers.
### Best practices for scaling the read path
#### Use filtering to reduce the number of resources returned per request
[Agent Server](/langsmith/agent-server) provides a search API for each resource type. These APIs implement pagination by default and offer many filtering options. Use filtering to reduce the number of resources returned per request and improve performance.
#### Set a TTLs to automatically delete old data
Set a [TTL on threads](/langsmith/configure-ttl) to automatically clean up old data. Runs and checkpoints are automatically deleted when the associated thread is deleted.
#### Avoid polling and use /join to monitor the state of a run
Avoid polling the state of a run by using the `/join` API endpoint. This method returns the final state of the run once the run is complete.
If you need to monitor the output of a run in real-time, use the `/stream` API endpoint. This method streams the run output including the final state of the run.
#### Self-hosted
These settings are only required for [self-hosted](/langsmith/self-hosted) deployments. By default, [cloud](/langsmith/cloud) deployments already have these best practices enabled.
##### Configure autoscaling for bursty workloads
Autoscaling is disabled by default, but should be configured for bursty workloads. You can determine the maximum number of api servers you should allow the autoscaler to scale to based on maximum expected throughput. The default for [cloud](/langsmith/cloud) deployments is a maximum of 10 API servers.
## Example self-hosted Agent Server configurations
The exact optimal configuration depends on your application complexity, request patterns, and data requirements. Use the following examples in combination with the information in the previous sections and your specific usage to update your deployment configuration as needed. If you have any questions, contact support via [support.langchain.com](https://support.langchain.com).
The following table provides an overview comparing different LangSmith Agent Server configurations for various load patterns (read requests per second / write requests per second) and standard assistant characteristics (average run execution time of 1 second, moderate CPU and memory usage):
| | **[Low / low](#low-reads-low-writes)** | **[Low / high](#low-reads-high-writes)** | **[High / low](#high-reads-low-writes)** | [Medium / medium](#medium-reads-medium-writes) | [High / high](#high-reads-high-writes) |
| :--------------------------------------------- | :------------------------------------- | :--------------------------------------- | :--------------------------------------- | :--------------------------------------------- | :------------------------------------- |
| Write requests per second | 5 | 5 | 500 | 50 | 500 |
| Read requests per second | 5 | 500 | 5 | 50 | 500 |
| **API servers** (1 CPU, 2Gi per server) | 1 (default) | 6 | 10 | 3 | 15 |
| **Queue workers** (1 CPU, 2Gi per worker) | 1 (default) | 10 | 1 (default) | 5 | 10 |
| **`N_JOBS_PER_WORKER`** | 10 (default) | 50 | 10 | 10 | 50 |
| **Redis resources** | 2 Gi (default) | 2 Gi (default) | 2 Gi (default) | 2 Gi (default) | 2 Gi (default) |
| **Postgres resources** | 2 CPU 8 Gi (default) | 4 CPU 16 Gi memory | 4 CPU 16 Gi | 4 CPU 16 Gi memory | 8 CPU 32 Gi memory |
The following sample configurations enable each of these setups. Load levels are defined as:
* Low means approximately 5 requests per second
* Medium means approximately 50 requests per second
* High means approximately 500 requests per second
### Low reads, low writes
The default [LangSmith Deployment](/langsmith/deployments) configuration will handle this load. No custom resource configuration is needed here.
### Low reads, high writes
You have a high volume of write requests (500 per second) being processed by your deployment, but relatively few read requests (5 per second).
For this, we recommend a configuration like this:
```yaml theme={null}
# Example configuration for low reads, high writes (5 read/500 write requests per second)
api:
replicas: 6
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
queue:
replicas: 10
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
config:
numberOfJobsPerWorker: 50
redis:
resources:
requests:
memory: "2Gi"
limits:
memory: "2Gi"
postgres:
resources:
requests:
cpu: "4"
memory: "16Gi"
limits:
cpu: "8"
memory: "32Gi"
```
### High reads, low writes
You have a high volume of read requests (500 per second) but relatively few write requests (5 per second).
For this, we recommend a configuration like this:
```yaml theme={null}
# Example configuration for high reads, low writes (500 read/5 write requests per second)
api:
replicas: 10
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
queue:
replicas: 1 # Default, minimal write load
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
redis:
resources:
requests:
memory: "2Gi"
limits:
memory: "2Gi"
postgres:
resources:
requests:
cpu: "4"
memory: "16Gi"
limits:
cpu: "8"
memory: "32Gi"
# Consider read replicas for high read scenarios
readReplicas: 2
```
### Medium reads, medium writes
This is a balanced configuration that should handle moderate read and write loads (50 read/50 write requests per second).
For this, we recommend a configuration like this:
```yaml theme={null}
# Example configuration for medium reads, medium writes (50 read/50 write requests per second)
api:
replicas: 3
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
queue:
replicas: 5
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
redis:
resources:
requests:
memory: "2Gi"
limits:
memory: "2Gi"
postgres:
resources:
requests:
cpu: "4"
memory: "16Gi"
limits:
cpu: "8"
memory: "32Gi"
```
### High reads, high writes
You have high volumes of both read and write requests (500 read/500 write requests per second).
For this, we recommend a configuration like this:
```yaml theme={null}
# Example configuration for high reads, high writes (500 read/500 write requests per second)
api:
replicas: 15
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
queue:
replicas: 10
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
config:
numberOfJobsPerWorker: 50
redis:
resources:
requests:
memory: "2Gi"
limits:
memory: "2Gi"
postgres:
resources:
requests:
cpu: "8"
memory: "32Gi"
limits:
cpu: "16"
memory: "64Gi"
```
### Autoscaling
If your deployment experiences bursty traffic, you can enable autoscaling to scale the number of API servers and queue workers to handle the load.
Here is a sample configuration for autoscaling for high reads and high writes:
```yaml theme={null}
api:
autoscaling:
enabled: true
minReplicas: 15
maxReplicas: 25
queue:
autoscaling:
enabled: true
minReplicas: 10
maxReplicas: 20
```
Ensure that your deployment environment has sufficient resources to scale to the recommended size. Monitor your applications and infrastructure to ensure optimal performance. Consider implementing monitoring and alerting to track resource usage and application performance.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/agent-server-scale.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Alerts in LangSmith
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/alerts
**Self-hosted Version Requirement**
Access to alerts requires Helm chart version **0.10.3** or later.
## Overview
Effective observability in LLM applications requires proactive detection of failures, performance degradations, and regressions. LangSmith's alerts feature helps identify critical issues such as:
* API rate limit violations from model providers
* Latency increases for your application
* Application changes that affect feedback scores reflecting end-user experience
Alerts in LangSmith are project-scoped, requiring separate configuration for each monitored project.
## Configuring an alert
### Step 1: Navigate to create alert
First navigate to the Tracing project that you would like to configure alerts for. Click the Alerts icon on the top right hand corner of the page to view existing alerts for that project and set up a new alert.
### Step 2: Select metric type
LangSmith offers threshold-based alerting on three core metrics:
| Metric Type | Description | Use Case |
| ------------------ | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Errored Runs** | Track runs with an error status | Monitors for failures in an application. |
| **Feedback Score** | Measures the average feedback score | Track [feedback from end users](/langsmith/attach-user-feedback) or [online evaluation results](/langsmith/online-evaluations-llm-as-judge) to alert on regressions. |
| **Latency** | Measures average run execution time | Tracks the latency of your application to alert on spikes and performance bottlenecks. |
Additionally, for **Errored Runs** and **Run Latency**, you can define filters to narrow down the runs that trigger alerts. For example, you might create an error alert filter for all `llm` runs tagged with `support_agent` that encounter a `RateLimitExceeded` error.
### Step 2: Define alert conditions
Alert conditions consist of several components:
* **Aggregation Method**: Average, Percentage, or Count
* **Comparison Operator**: `>=`, `<=`, or exceeds threshold
* **Threshold Value**: Numerical value triggering the alert
* **Aggregation Window**: Time period for metric calculation (currently choose between 5 or 15 minutes)
* **Feedback Key** (Feedback Score alerts only): Specific feedback metric to monitor
**Example:** The configuration shown above would generate an alert when more than 5% of runs within the past 5 minutes result in errors.
You can preview alert behavior over a historical time window to understand how many datapoints—and which ones—would have triggered an alert at a chosen threshold (indicated in red). For example, setting an average latency threshold of 60 seconds for a project lets you visualize potential alerts, as shown in the image below.
### Step 3: Configure notification channel
LangSmith supports the following notification channels:
1. [PagerDuty integration](/langsmith/alerts-pagerduty)
2. [Webhook notifications](/langsmith/alerts-webhook)
Select the appropriate channel to ensure notifications reach the responsible team members.
## Best practices
* Adjust sensitivity based on application criticality
* Start with broader thresholds and refine based on observed patterns
* Ensure alert routing reaches appropriate on-call personnel
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/alerts.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Configure webhook notifications for LangSmith alerts
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/alerts-webhook
## Overview
This guide details the process for setting up webhook notifications for [LangSmith alerts](/langsmith/alerts). Before proceeding, make sure you have followed the steps leading up to the notification step of creating the alert by following [this guide](./alerts). Webhooks enable integration with custom services and third-party platforms by sending HTTP POST requests when alert conditions are triggered. Use webhooks to forward alert data to ticketing systems, chat applications, or custom monitoring solutions.
## Prerequisites
* An endpoint that can receive HTTP POST requests
* Appropriate authentication credentials for your receiving service (if required)
## Integration configuration
### Step 1: Prepare your receiving endpoint
Before configuring the webhook in LangSmith, ensure your receiving endpoint:
* Accepts HTTP POST requests
* Can process JSON payloads
* Is accessible from external services
* Has appropriate authentication mechanisms (if required)
Additionally, if on a custom deployment of LangSmith, make sure there are no firewall settings blocking egress traffic from LangSmith services.
### Step 2: Configure webhook parameters
In the notification section of your alert complete the webhook configuration with the following parameters:
**Required Fields**
* **URL**: The complete URL of your receiving endpoint
* Example: `https://api.example.com/incident-webhook`
**Optional Fields**
* **Headers**: JSON Key-value pairs sent with the webhook request
* Common headers include:
* `Authorization`: For authentication tokens
* `Content-Type`: Usually set to `application/json` (default)
* `X-Source`: To identify the source as LangSmith
* If no headers, then simply use `{}`
* **Request Body Template**: Customize the JSON payload sent to your endpoint
* Default: LangSmith sends the payload defined and the following additional key-value pairs appended to the payload:
* `project_name`: Name of the triggered alert
* `alert_rule_id`: A UUID to identify the LangSmith alert. This can be used as a de-duplication key in the webhook service.
* `alert_rule_name`: The name of the alert rule.
* `alert_rule_type`: The type of alert (as of 04/01/2025 all alerts are of type `threshold`).
* `alert_rule_attribute`: The attribute associated with the alert rule - `error_count`, `feedback_score` or `latency`.
* `triggered_metric_value`: The value of the metric at the time the threshold was triggered.
* `triggered_threshold`: The threshold that triggered the alert.
* `timestamp`: The timestamp that triggered the alert.
### Step 3: Test the webhook
Click **Send Test Alert** to send the webhook notification to ensure the notification works as intended.
## Troubleshooting
If webhook notifications aren't being delivered:
* Verify the webhook URL is correct and accessible
* Ensure any authentication headers are properly formatted
* Check that your receiving endpoint accepts POST requests
* Examine your endpoint's logs for received but rejected requests
* Verify your custom payload template is valid JSON format
## Security considerations
* Use HTTPS for your webhook endpoints
* Implement authentication for your webhook endpoint
* Consider adding a shared secret in your headers to verify webhook sources
* Validate incoming webhook requests before processing them
## Sending alerts to Slack using a webhook
Here is an example for configuring LangSmith alerts to send notifications to Slack channels using the [`chat.postMessage`](https://api.slack.com/methods/chat.postMessage) API.
### Prerequisites
* Access to a Slack workspace
* A LangSmith project to set up alerts
* Permissions to create Slack applications
### Step 1: Create a Slack app
1. Visit the [Slack API Applications page](https://api.slack.com/apps)
2. Click **Create New App**
3. Select **From scratch**
4. Provide an **App Name** (e.g., "LangSmith Alerts")
5. Select the workspace where you want to install the app
6. Click **Create App**
### Step 2: Configure bot permissions
1. In the left sidebar of your Slack app configuration, click **OAuth & Permissions**
2. Scroll down to **Bot Token Scopes** under **Scopes** and click **Add an OAuth Scope**
3. Add the following scopes:
* `chat:write` (Send messages as the app)
* `chat:write.public` (Send messages to channels the app isn't in)
* `channels:read` (View basic channel information)
### Step 3: Install the app to your workspace
1. Scroll up to the top of the **OAuth & Permissions** page
2. Click **Install to Workspace**
3. Review the permissions and click **Allow**
4. Copy the **Bot User OAuth Token** that appears (begins with `xoxb-`)
### Step 4: Add the bot to a Slack channel
Add the bot to the specific channel you want to receive alerts in. You can add a bot to a Slack channel by mentioning it in the message field (e.g., `@botname`).
You also need the channel ID to configure the webhook alert in LangSmith. You can find the channel ID by opening channel details > About
### Step 5: Configure the webhook alert in LangSmith
1. In LangSmith, navigate to your project
2. Select **Alerts → Create Alert**
3. Define your alert metrics and conditions
4. In the notification section, select **Webhook**
5. Configure the webhook with the following settings:
**Webhook URL**
```json theme={null}
https://slack.com/api/chat.postMessage
```
**Headers**
Replace `xoxb-your-token-here` with your Bot's User OAuth Token
```json theme={null}
{
"Content-Type": "application/json",
"Authorization": "Bearer xoxb-your-token-here"
}
```
**Request Body Template**
It is required to fill in the `{channel_id}` from the value found in Step 4.
The remaining fields: `alert_name`, `project_name` and `project_url` optionally add additional context to the alert message. You can find your `project_url` in the browser's URL bar. Copy the portion up to but not including any query parameters.
```json theme={null}
{
"channel": "{channel_id}",
"text": "{alert_name} triggered for {project_name}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "🚨{alert_name} has been triggered"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Please check the following link for more information:"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<{project-url}|View in LangSmith>"
}
}
]
}
```
6. Click **Save** to activate the webhook configuration
### Step 6: Test the integration
1. In the LangSmith alert configuration, click **Test Alert**
2. Check your specified Slack channel for the test notification
3. Verify that the message contains the expected alert information
### (Optional) Step 7: Link to the alert preview in the request body
After creating an alert, you can optionally link to its preview in the webhook's request body.
To configure this:
1. Save your alert
2. Find your saved alert in the alerts table and click it
3. Copy the displayed URL
4. Click "Edit Alert"
5. Replace the existing project URL with the copied alert preview URL
## Additional resources
* [LangSmith Alerts Documentation](/langsmith/alerts)
* [Slack chat.postMessage API Documentation](https://api.slack.com/methods/chat.postMessage)
* [Slack Block Kit Builder](https://app.slack.com/block-kit-builder/)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/alerts-webhook.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Analyze an experiment
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/analyze-an-experiment
This page describes some of the essential tasks for working with [*experiments*](/langsmith/evaluation-concepts#experiment) in LangSmith:
* **[Analyze a single experiment](#analyze-a-single-experiment)**: View and interpret experiment results, customize columns, filter data, and compare runs.
* [Set a baseline in the Experiments view](#set-a-baseline-in-the-experiments-view): Set a baseline for a dataset that you want to outperform.
* **[Download experiment results as a CSV](#download-experiment-results-as-a-csv)**: Export your experiment data for external analysis and sharing.
* **[Rename an experiment](#how-to-rename-an-experiment)**: Update experiment names in both the Playground and Experiments view.
## Analyze a single experiment
After running an experiment, you can use LangSmith's experiment view to analyze the results and draw insights about your experiment's performance.
### Open the experiment view
To open the experiment view, select the relevant [*dataset*](/langsmith/evaluation-concepts#datasets) from the **Dataset & Experiments** page and then select the experiment you want to view.
### View experiment results
#### Customize columns
By default, the experiment view shows the input, output, and reference output for each [example](/langsmith/evaluation-concepts#examples) in the dataset, feedback scores from evaluations and experiment metrics like cost, token counts, latency and status.
You can customize the columns using the **Display** button to make it easier to interpret experiment results:
* **Break out fields from inputs, outputs, and reference outputs** into their own columns. This is especially helpful if you have long inputs/outputs/reference outputs and want to surface important fields.
* **Hide and reorder columns** to create focused views for analysis.
* **Control decimal precision on feedback scores**. By default, LangSmith surfaces numerical feedback scores with a decimal precision of 2, but you can customize this setting to be up to 6 decimals.
* **Set the Heat Map threshold** to high, middle, and low for numeric feedback scores in your experiment, which affects the threshold at which score chips render as red or green:
You can set default configurations for an entire dataset or temporarily save settings just for yourself.
#### Sort and filter
To sort or filter feedback scores, you can use the actions in the column headers.
#### Table views
Depending on the view most useful for your analysis, you can change the formatting of the table by toggling between a compact view, a full, view, and a diff view.
* The **Compact** view shows each run as a one-line row, for ease of comparing scores at a glance.
* The **Full** view shows the full output for each run for digging into the details of individual runs.
* The **Diff** view shows the text difference between the reference output and the output for each run.
#### View the traces
Hover over any of the output cells, and click on the trace icon to view the trace for that run. This will open up a trace in the side panel.
To view the entire tracing project, click on the **View Project** button in the top right of the header.
#### View evaluator runs
For evaluator scores, you can view the source run by hovering over the evaluator score cell and clicking on the arrow icon. This will open up a trace in the side panel. If you're running a [LLM-as-a-judge evaluator](/langsmith/llm-as-judge), you can view the prompt used for the evaluator in this run. If your experiment has [repetitions](/langsmith/evaluation-concepts#repetitions), you can click on the aggregate average score to find links to all of the individual runs.
### Group results by metadata
You can add metadata to examples to categorize and organize them. For example, if you're evaluating factual accuracy on a question answering dataset, the metadata might include which subject area each question belongs to. Metadata can be added either [via the UI](/langsmith/manage-datasets-in-application#edit-example-metadata) or [via the SDK](/langsmith/manage-datasets-programmatically#update-single-example).
To analyze results by metadata, use the **Group by** dropdown in the top right corner of the experiment view and select your desired metadata key. This displays average feedback scores, latency, total tokens, and cost for each metadata group.
You will only be able to group by example metadata on experiments created after February 20th, 2025. Any experiments before that date can still be grouped by metadata, but only if the metadata is on the experiment traces themselves.
### Repetitions
If you've run your experiment with [*repetitions*](/langsmith/evaluation-concepts#repetitions), there will be arrows in the output results column so you can view outputs in the table. To view each run from the repetition, hover over the output cell and click the expanded view.
When you run an experiment with repetitions, LangSmith displays the average for each feedback score in the table. Click on the feedback score to view the feedback scores from individual runs, or to view the standard deviation across repetitions.
### Compare to another experiment
In the top right of the experiment view, you can select another experiment to compare to. This will open up a comparison view, where you can see how the two experiments compare. To learn more about the comparison view, see [how to compare experiment results](/langsmith/compare-experiment-results).
## Set a baseline in the Experiments view
While you may run dozens of tests, you typically have a specific benchmark you are trying to outperform. Setting a *baseline* anchors your results against this reference point, which allows you to identify improvements or regressions in a crowded experiment list.
By designating a baseline, you can:
* Highlight a reference: Explicitly mark your best-performing run so it remains visible at the top of the **Experiments** view as you iterate.
* See instant diffs: View performance deltas across all experiments automatically, which means you don't necessarily need to perform manual side-by-side selection.
* Accelerate assessment: Quickly determine if new iterations meet or exceed your current performance standards.
To set a baseline for a dataset:
1. In the [LangSmith UI](https://smith.langchain.com), navigate to the **Datasets & Experiments** option in the left menu.
2. Select the dataset that you want to work with from the table.
3. In the **Experiments** view, hover over experiments to find the **Set as baseline** button. Click to select your baseline experiment.
Your baseline experiment will pin to the top of the table and have the **Baseline** tag next to its name. Once an experiment is set as a baseline, the table will display scores against the baseline on each experiment for each column. When you are selecting multiple experiments for comparison, the baseline experiment will be the default source experiment to be compared to.
## Download experiment results as a CSV
LangSmith lets you download experiment results as a CSV file, which allows you to analyze and share your results.
To download as a CSV, click the download icon at the top of the experiment view. The icon is directly to the left of the [Compact toggle](/langsmith/compare-experiment-results#adjust-the-table-display).
There is a 5,000 row download limit for experiment results.
## Rename an experiment
Experiment names must be unique per workspace.
You can rename an experiment in the LangSmith UI in:
* The [Playground](#renaming-an-experiment-in-the-playground). When running experiments in the Playground, a default name with the format `pg::prompt-name::model::uuid` (eg. `pg::gpt-4.1-mini::897ee630`) is automatically assigned.
You can rename an experiment immediately after running it by editing its name in the Playground table header.
* The [Experiments view](#renaming-an-experiment-in-the-experiments-view). When viewing results in the experiments view, you can rename an experiment by using the pencil icon beside the experiment name.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/analyze-an-experiment.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Custom instrumentation
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/annotate-code
If you've decided you no longer want to trace your runs, you can remove the `LANGSMITH_TRACING` environment variable. Note that this does not affect the `RunTree` objects or API users, as these are meant to be low-level and not affected by the tracing toggle.
There are several ways to log traces to LangSmith.
If you are using LangChain (either Python or JS/TS), you can skip this section and go directly to the [LangChain-specific instructions](/langsmith/trace-with-langchain).
## Use `@traceable` / `traceable`
LangSmith makes it easy to log traces with minimal changes to your existing code with the `@traceable` decorator in Python and `traceable` function in TypeScript.
The `LANGSMITH_TRACING` environment variable must be set to `'true'` in order for traces to be logged to LangSmith, even when using `@traceable` or `traceable`. This allows you to toggle tracing on and off without changing your code.
Additionally, you will need to set the `LANGSMITH_API_KEY` environment variable to your API key (see [Setup](/) for more information).
By default, the traces will be logged to a project named `default`. To log traces to a different project, see [this section](/langsmith/log-traces-to-project).
The `@traceable` decorator is a simple way to log traces from the LangSmith Python SDK. Simply decorate any function with `@traceable`.
Note that when wrapping a sync function with `traceable`, (e.g. `formatPrompt` in the example below), you should use the `await` keyword when calling it to
ensure the trace is logged correctly.
```python Python theme={null}
from langsmith import traceable
from openai import Client
openai = Client()
@traceable
def format_prompt(subject):
return [
{
"role": "system",
"content": "You are a helpful assistant.",
},
{
"role": "user",
"content": f"What's a good name for a store that sells {subject}?"
}
]
@traceable(run_type="llm")
def invoke_llm(messages):
return openai.chat.completions.create(
messages=messages, model="gpt-4.1-mini", temperature=0
)
@traceable
def parse_output(response):
return response.choices[0].message.content
@traceable
def run_pipeline():
messages = format_prompt("colorful socks")
response = invoke_llm(messages)
return parse_output(response)
run_pipeline()
```
```typescript TypeScript theme={null}
import { traceable } from "langsmith/traceable";
import OpenAI from "openai";
const openai = new OpenAI();
const formatPrompt = traceable((subject: string) => {
return [
{
role: "system" as const,
content: "You are a helpful assistant.",
},
{
role: "user" as const,
content: `What's a good name for a store that sells ${subject}?`,
},
];
},{ name: "formatPrompt" });
const invokeLLM = traceable(
async ({ messages }: { messages: { role: string; content: string }[] }) => {
return openai.chat.completions.create({
model: "gpt-4.1-mini",
messages: messages,
temperature: 0,
});
},
{ run_type: "llm", name: "invokeLLM" }
);
const parseOutput = traceable(
(response: any) => {
return response.choices[0].message.content;
},
{ name: "parseOutput" }
);
const runPipeline = traceable(
async () => {
const messages = await formatPrompt("colorful socks");
const response = await invokeLLM({ messages });
return parseOutput(response);
},
{ name: "runPipeline" }
);
await runPipeline();
```
## Use the `trace` context manager (Python only)
In Python, you can use the `trace` context manager to log traces to LangSmith. This is useful in situations where:
1. You want to log traces for a specific block of code.
2. You want control over the inputs, outputs, and other attributes of the trace.
3. It is not feasible to use a decorator or wrapper.
4. Any or all of the above.
The context manager integrates seamlessly with the `traceable` decorator and `wrap_openai` wrapper, so you can use them together in the same application.
```python theme={null}
import openai
import langsmith as ls
from langsmith.wrappers import wrap_openai
client = wrap_openai(openai.Client())
@ls.traceable(run_type="tool", name="Retrieve Context")
def my_tool(question: str) -> str:
return "During this morning's meeting, we solved all world conflict."
def chat_pipeline(question: str):
context = my_tool(question)
messages = [
{ "role": "system", "content": "You are a helpful assistant. Please respond to the user's request only based on the given context." },
{ "role": "user", "content": f"Question: {question}\nContext: {context}"}
]
chat_completion = client.chat.completions.create(
model="gpt-4.1-mini", messages=messages
)
return chat_completion.choices[0].message.content
app_inputs = {"input": "Can you summarize this morning's meetings?"}
with ls.trace("Chat Pipeline", "chain", project_name="my_test", inputs=app_inputs) as rt:
output = chat_pipeline("Can you summarize this morning's meetings?")
rt.end(outputs={"output": output})
```
## Use the `RunTree` API
Another, more explicit way to log traces to LangSmith is via the `RunTree` API. This API allows you more control over your tracing - you can manually create runs and children runs to assemble your trace. You still need to set your `LANGSMITH_API_KEY`, but `LANGSMITH_TRACING` is not necessary for this method.
This method is not recommended, as it's easier to make mistakes in propagating trace context.
```python Python theme={null}
import openai
from langsmith.run_trees import RunTree
# This can be a user input to your app
question = "Can you summarize this morning's meetings?"
# Create a top-level run
pipeline = RunTree(
name="Chat Pipeline",
run_type="chain",
inputs={"question": question}
)
pipeline.post()
# This can be retrieved in a retrieval step
context = "During this morning's meeting, we solved all world conflict."
messages = [
{ "role": "system", "content": "You are a helpful assistant. Please respond to the user's request only based on the given context." },
{ "role": "user", "content": f"Question: {question}\nContext: {context}"}
]
# Create a child run
child_llm_run = pipeline.create_child(
name="OpenAI Call",
run_type="llm",
inputs={"messages": messages},
)
child_llm_run.post()
# Generate a completion
client = openai.Client()
chat_completion = client.chat.completions.create(
model="gpt-4.1-mini", messages=messages
)
# End the runs and log them
child_llm_run.end(outputs=chat_completion)
child_llm_run.patch()
pipeline.end(outputs={"answer": chat_completion.choices[0].message.content})
pipeline.patch()
```
```typescript TypeScript theme={null}
import OpenAI from "openai";
import { RunTree } from "langsmith";
// This can be a user input to your app
const question = "Can you summarize this morning's meetings?";
const pipeline = new RunTree({
name: "Chat Pipeline",
run_type: "chain",
inputs: { question }
});
await pipeline.postRun();
// This can be retrieved in a retrieval step
const context = "During this morning's meeting, we solved all world conflict.";
const messages = [
{ role: "system", content: "You are a helpful assistant. Please respond to the user's request only based on the given context." },
{ role: "user", content: `Question: ${question}Context: ${context}` }
];
// Create a child run
const childRun = await pipeline.createChild({
name: "OpenAI Call",
run_type: "llm",
inputs: { messages },
});
await childRun.postRun();
// Generate a completion
const client = new OpenAI();
const chatCompletion = await client.chat.completions.create({
model: "gpt-4.1-mini",
messages: messages,
});
// End the runs and log them
childRun.end(chatCompletion);
await childRun.patchRun();
pipeline.end({ outputs: { answer: chatCompletion.choices[0].message.content } });
await pipeline.patchRun();
```
## Example usage
You can extend the utilities above to conveniently trace any code. Below are some example extensions:
Trace any public method in a class:
```python theme={null}
from typing import Any, Callable, Type, TypeVar
T = TypeVar("T")
def traceable_cls(cls: Type[T]) -> Type[T]:
"""Instrument all public methods in a class."""
def wrap_method(name: str, method: Any) -> Any:
if callable(method) and not name.startswith("__"):
return traceable(name=f"{cls.__name__}.{name}")(method)
return method
# Handle __dict__ case
for name in dir(cls):
if not name.startswith("_"):
try:
method = getattr(cls, name)
setattr(cls, name, wrap_method(name, method))
except AttributeError:
# Skip attributes that can't be set (e.g., some descriptors)
pass
# Handle __slots__ case
if hasattr(cls, "__slots__"):
for slot in cls.__slots__: # type: ignore[attr-defined]
if not slot.startswith("__"):
try:
method = getattr(cls, slot)
setattr(cls, slot, wrap_method(slot, method))
except AttributeError:
# Skip slots that don't have a value yet
pass
return cls
@traceable_cls
class MyClass:
def __init__(self, some_val: int):
self.some_val = some_val
def combine(self, other_val: int):
return self.some_val + other_val
# See trace: https://smith.langchain.com/public/882f9ecf-5057-426a-ae98-0edf84fdcaf9/r
MyClass(13).combine(29)
```
## Ensure all traces are submitted before exiting
LangSmith's tracing is done in a background thread to avoid obstructing your production application. This means that your process may end before all traces are successfully posted to LangSmith. Here are some options for ensuring all traces are submitted before exiting your application.
### Using the LangSmith SDK
If you are using the LangSmith SDK standalone, you can use the `flush` method before exit:
```python Python theme={null}
from langsmith import Client
client = Client()
@traceable(client=client)
async def my_traced_func():
# Your code here...
pass
try:
await my_traced_func()
finally:
await client.flush()
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
const langsmithClient = new Client({});
const myTracedFunc = traceable(async () => {
// Your code here...
},{ client: langsmithClient });
try {
await myTracedFunc();
} finally {
await langsmithClient.flush();
}
```
### Using LangChain
If you are using LangChain, please refer to our [LangChain tracing guide](/langsmith/trace-with-langchain#ensure-all-traces-are-submitted-before-exiting).
If you prefer a video tutorial, check out the [Tracing Basics video](https://academy.langchain.com/pages/intro-to-langsmith-preview) from the Introduction to LangSmith Course.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/annotate-code.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Annotate traces and runs inline
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/annotate-traces-inline
LangSmith allows you to manually annotate traces with feedback within the application. This can be useful for adding context to a trace, such as a user's comment or a note about a specific issue.
You can annotate a trace either inline or by sending the trace to an annotation queue, which allows you to closely inspect and log feedbacks to runs one at a time.
Feedback tags are associated with your [workspace](/langsmith/administration-overview#workspaces).
**You can attach user feedback to ANY intermediate run (span) of the trace, not just the root span.**
This is useful for critiquing specific parts of the LLM application, such as the retrieval step or generation step of the RAG pipeline.
To annotate a trace inline, click on the `Annotate` in the upper right corner of trace view for any particular run that is part of the trace.
This will open up a pane that allows you to choose from feedback tags associated with your workspace and add a score for particular tags. You can also add a standalone comment. Follow [this guide](./set-up-feedback-criteria) to set up feedback tags for your workspace.
You can also set up new feedback criteria from within the pane itself.
You can use the labeled keyboard shortcuts to streamline the annotation process.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/annotate-traces-inline.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Use annotation queues
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/annotation-queues
*Annotation queues* provide a streamlined, directed view for human annotators to attach feedback to specific [runs](/langsmith/observability-concepts#runs). While you can always annotate [traces](/langsmith/observability-concepts#traces) inline, annotation queues provide a way to group runs together, prescribe rubrics, and track reviewer progress.
LangSmith supports two queue styles:
* [**Single-run annotation queues**](#single-run-annotation-queues) present one run at a time and let reviewers submit any rubric feedback you configure.
* [**Pairwise annotation queues (PAQs)**](#pairwise-annotation-queues) present two runs side-by-side so reviewers can quickly decide which output is better (or if they are equivalent) against the rubric items you define.
For a demonstration of using annotation queues, watch the [Getting started with annotation queues](#video-guide) video guide.
## Single-run annotation queues
Single-run queues present one run at a time and let reviewers submit any rubric feedback you configure. They can be created directly from the **Annotation queues** section in the [LangSmith UI](https://smith.langchain.com/).
### Create a single-run queue
1. Navigate to **Annotation queues** in the left navigation.
2. Click **+ New annotation queue** in the top-right corner.
#### Basic details
1. Fill in the **Name** and **Description** of the queue.
2. Optionally assign a **default dataset** to streamline exporting reviewed runs into a dataset in your LangSmith [workspace](/langsmith/administration-overview#workspaces).
#### Annotation rubric
1. Draft some high-level instructions for your annotators, which will be shown in the sidebar on every run.
2. Click **+ Desired Feedback** to add feedback keys to your annotation queue. Annotators will be presented with these feedback keys on each run.
3. Add a description for each, as well as a short description of each category, if the feedback is categorical.
For example, with the descriptions in the previous screenshot, reviewers will see the **Annotation Rubric** details in the right-hand pane of the UI.
#### Collaborator settings
When there are multiple annotators for a run:
* **Number of reviewers per run**: This determines the number of reviewers that must mark a run as **Done** for it to be removed from the queue. If you check **All workspace members review each run**, then a run will remain in the queue until all [workspace](/langsmith/administration-overview#workspaces) members have marked their review as **Done**.
* Reviewers cannot view the feedback left by other reviewers.
* Comments on runs are visible to all reviewers.
* **Enable reservations on runs**: When a reviewer views a run, the run is reserved for that reviewer for the specified **Reservation length**. If there are multiple reviewers per run as specified above, the run can be reserved by multiple reviewers (up to the number of reviewers per run) at the same time.
We recommend enabling reservations. This will prevent multiple annotators from reviewing the same run at the same time.
If a reviewer has viewed a run and then leaves the run without marking it **Done**, the reservation will expire after the specified **Reservation length**. The run is then released back into the queue and can be reserved by another reviewer.
Clicking **Requeue** for a run's annotation will only move the current run to the end of the current user's queue; it won't affect the queue order of any other user. It will also release the reservation that the current user has on that run.
Because of these settings, the number of runs visible to each reviewer can differ from the total queue size.
You can revisit the pencil icon in **Annotation queues** to update any settings later.
### Assign runs to a single-run queue
There are several ways to populate a single-run queue with work items:
* **From a trace view**: Click **Add to Annotation Queue** in the top-right corner of any [trace](/langsmith/observability-concepts#traces) view. You can add any intermediate [run](/langsmith/observability-concepts#runs), but not the root span.
* **From the runs table**: Select multiple runs, then click **Add to Annotation Queue** at the bottom of the page.
* **Automation rules**: [Set up a rule](/langsmith/rules) to automatically assign runs that match a filter (for example, errors or low user scores) into a queue.
* **Datasets & experiments**: Select one or more [experiments](/langsmith/evaluation-concepts#experiment) within a dataset and click ** Annotate**. Choose an existing queue or create a new one, then confirm the (single-run) queue option.
### Review a single-run queue
1. Navigate to the **Annotation Queues** section through the left-hand navigation bar.
2. Click on the queue you want to review. This will take you to a focused, cyclical view of the runs in the queue that require review.
3. You can attach a comment, attach a score for a particular [feedback](/langsmith/observability-concepts#feedback) criteria, add the run to a dataset or mark the run as reviewed. You can also remove the run from the queue for all users, despite any current reservations or settings for the queue, by clicking the **Trash** icon next to **View run**.
The keyboard shortcuts that are next to each option can help streamline the review process.
## Pairwise annotation queues
Pairwise annotation queues (PAQs) present two runs side-by-side so reviewers can quickly decide which output is better (or if they are equivalent) against the rubric items you define. They are designed for fast A/B comparisons between two experiments (often a baseline vs. a candidate model) and must be created from the **Datasets & Experiments** pages.
### Create a pairwise queue
1. Navigate to **Datasets & Experiments**, open a dataset, and select **exactly two experiments** you want to compare.
2. Click **Annotate**. In the popover, choose **Add to Pairwise Annotation Queue**. (The button is disabled until exactly two experiments are selected.)
3. Decide whether to send the experiments to an existing pairwise queue or create a new one.
4. Provide the queue details:
* **Basic details** (name and description)
* **Instructions & rubrics** tailored to pairwise scoring
* **Collaborator settings** (reviewer count, reservations, reservation length)
5. Submit the form to create the queue. LangSmith immediately pairs runs from the two experiments and populates the queue.
Key differences for PAQs:
* **Experiments**: You must provide two experiment sessions up front. LangSmith automatically pairs their runs in chronological order and populates the queue during creation.
* **Rubric**: Pairwise rubric items only require a feedback key and (optionally) a description. Annotators decide whether Run A, Run B, or both are better for each rubric item.
* **Dataset**: Pairwise queues do not use a default dataset, because comparisons span two experiments.
* **Reservations & reviewers**: The same collaborator controls apply. Reservations help prevent two people from judging the same comparison simultaneously.
### Add more comparisons to a pairwise queue
If you need to add more comparisons later, return to **Datasets & Experiments**, select the two experiments again, and choose **Add to Pairwise Annotation Queue** to append new pairs.
Selecting two experiments and creating a PAQ automatically pairs the runs. When augmenting an existing PAQ, LangSmith preserves historical comparisons and appends new pairs to the queue.
### Review a pairwise queue
1. From **Annotation queues**, select the pairwise queue you want to review.
2. Each queue item displays Run A on the left and Run B on the right, along with your rubric.
3. For every rubric item:
* Choose **A is better**, **B is better**, or **Equal**. The UI records binary feedback on both runs behind the scenes.
* Use hotkeys `A`, `B`, or `E` to lock in your choice.
4. Once you finish all rubric items, press **Done** (or `Enter` on the final rubric item) to advance to the next comparison.
5. Optional actions:
* Leave comments tied to either run.
* Requeue the comparison if you need to revisit it later.
* Open the full trace view for deeper debugging.
Reservations, reviewer thresholds, and comments behave identically to those in single-run queues, enabling teams to use different queue types without modifying their existing workflow.
Consider routing runs that already have user feedback (e.g., thumbs-down) into a single-run queue for triage and a pairwise queue for head-to-head comparisons against a stronger baseline. This helps you identify regressions quickly. To learn more about how to capture user feedback from your LLM application, follow the guide on [attaching user feedback](/langsmith/attach-user-feedback).
## Video guide
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/annotation-queues.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Control plane API reference for LangSmith Deployment
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/api-ref-control-plane
The control plane API is part of [LangSmith Deployment](/langsmith/deployments). With the control plane API, you can programmatically create, manage, and automate your [Agent Server](/langsmith/agent-server) deployments—for example, as part of a custom CI/CD workflow.
Browse the full API reference in the **Control Plane API** section in the sidebar, or refer to the endpoint groups:
* [Integrations (v1)](/api-reference/integrations-v1/list-github-integrations): GitHub integrations and repository listings
* [Deployments (v2)](/api-reference/deployments-v2): Create, manage, and update Agent Server deployments
* [Listeners (v2)](/api-reference/listeners-v2): Listener resources for self-hosted enterprise organizations
* [Auth Service (v2)](/api-reference/auth-service-v2): OAuth provider configuration and authentication flows
## Host
The control plane hosts for Cloud data regions:
| US | EU |
| -------------------------------- | ----------------------------------- |
| `https://api.host.langchain.com` | `https://eu.api.host.langchain.com` |
**Note**: Self-hosted deployments of LangSmith will have a custom host for the control plane. The control plane APIs can be accessed at the path `/api-host`. For example, `http(s):///api-host/v2/deployments`. See [here](../langsmith/self-host-usage#configuring-the-application-you-want-to-use-with-langsmith) for more details.
## Authentication
To authenticate with the control plane API, set the `X-Api-Key` header to a valid LangSmith API key and set the `X-Tenant-Id` header to a valid workspace ID to target.
Example `curl` command:
```shell theme={null}
curl --request GET \
--url http://localhost:8124/v2/deployments \
--header 'X-Api-Key: LANGSMITH_API_KEY'
--header 'X-Tenant-Id': WORKSPACE_ID'
```
## Versioning
Each endpoint path is prefixed with a version (e.g. `v1`, `v2`).
## Quick start
1. Call `POST /v2/deployments` to create a new Deployment. The response body contains the Deployment ID (`id`) and the ID of the latest (and first) revision (`latest_revision_id`).
2. Call `GET /v2/deployments/{deployment_id}` to retrieve the Deployment. Set `deployment_id` in the URL to the value of Deployment ID (`id`).
3. Poll for revision `status` until `status` is `DEPLOYED` by calling `GET /v2/deployments/{deployment_id}/revisions/{latest_revision_id}`.
4. Call `PATCH /v2/deployments/{deployment_id}` to update the deployment.
## Example Code
Below is example Python code that demonstrates how to orchestrate the control plane APIs to create a deployment, update the deployment, and delete the deployment.
```python theme={null}
import os
import time
import requests
from dotenv import load_dotenv
load_dotenv()
# required environment variables
CONTROL_PLANE_HOST = os.getenv("CONTROL_PLANE_HOST")
LANGSMITH_API_KEY = os.getenv("LANGSMITH_API_KEY")
WORKSPACE_ID = os.getenv("WORKSPACE_ID")
INTEGRATION_ID = os.getenv("INTEGRATION_ID")
MAX_WAIT_TIME = 1800 # 30 mins
def get_headers() -> dict:
"""Return common headers for requests to the control plane API."""
return {
"X-Api-Key": LANGSMITH_API_KEY,
"X-Tenant-Id": WORKSPACE_ID,
}
def create_deployment() -> str:
"""Create deployment. Return deployment ID."""
headers = get_headers()
headers["Content-Type"] = "application/json"
deployment_name = "my_deployment"
request_body = {
"name": deployment_name,
"source": "github",
"source_config": {
"integration_id": INTEGRATION_ID,
"repo_url": "https://github.com/langchain-ai/langgraph-example",
"deployment_type": "dev",
"build_on_push": False,
"custom_url": None,
"resource_spec": None,
},
"source_revision_config": {
"repo_ref": "main",
"langgraph_config_path": "langgraph.json",
"image_uri": None,
},
"secrets": [
{
"name": "OPENAI_API_KEY",
"value": "test_openai_api_key",
},
{
"name": "ANTHROPIC_API_KEY",
"value": "test_anthropic_api_key",
},
{
"name": "TAVILY_API_KEY",
"value": "test_tavily_api_key",
},
],
}
response = requests.post(
url=f"{CONTROL_PLANE_HOST}/v2/deployments",
headers=headers,
json=request_body,
)
if response.status_code != 201:
raise Exception(f"Failed to create deployment: {response.text}")
deployment_id = response.json()["id"]
print(f"Created deployment {deployment_name} ({deployment_id})")
return deployment_id
def get_deployment(deployment_id: str) -> dict:
"""Get deployment."""
response = requests.get(
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
headers=get_headers(),
)
if response.status_code != 200:
raise Exception(f"Failed to get deployment ID {deployment_id}: {response.text}")
return response.json()
def list_revisions(deployment_id: str) -> list[dict]:
"""List revisions.
Return list is sorted by created_at in descending order (latest first).
"""
response = requests.get(
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}/revisions",
headers=get_headers(),
)
if response.status_code != 200:
raise Exception(
f"Failed to list revisions for deployment ID {deployment_id}: {response.text}"
)
return response.json()
def get_revision(
deployment_id: str,
revision_id: str,
) -> dict:
"""Get revision."""
response = requests.get(
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}/revisions/{revision_id}",
headers=get_headers(),
)
if response.status_code != 200:
raise Exception(f"Failed to get revision ID {revision_id}: {response.text}")
return response.json()
def patch_deployment(deployment_id: str) -> None:
"""Patch deployment."""
headers = get_headers()
headers["Content-Type"] = "application/json"
# This creates a new revision because source_revision_config is included
response = requests.patch(
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
headers=headers,
json={
"source_config": {
"build_on_push": True,
},
"source_revision_config": {
"repo_ref": "main",
"langgraph_config_path": "langgraph.json",
},
},
)
if response.status_code != 200:
raise Exception(f"Failed to patch deployment: {response.text}")
print(f"Patched deployment ID {deployment_id}")
def wait_for_deployment(deployment_id: str, revision_id: str) -> None:
"""Wait for revision status to be DEPLOYED."""
start_time = time.time()
revision, status = None, None
while time.time() - start_time < MAX_WAIT_TIME:
revision = get_revision(deployment_id, revision_id)
status = revision["status"]
if status == "DEPLOYED":
break
elif "FAILED" in status:
raise Exception(f"Revision ID {revision_id} failed: {revision}")
print(f"Waiting for revision ID {revision_id} to be DEPLOYED...")
time.sleep(60)
if status != "DEPLOYED":
raise Exception(
f"Timeout waiting for revision ID {revision_id} to be DEPLOYED: {revision}"
)
def delete_deployment(deployment_id: str) -> None:
"""Delete deployment."""
response = requests.delete(
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
headers=get_headers(),
)
if response.status_code != 204:
raise Exception(
f"Failed to delete deployment ID {deployment_id}: {response.text}"
)
print(f"Deployment ID {deployment_id} deleted")
if __name__ == "__main__":
# create deployment and get the latest revision
deployment_id = create_deployment()
revisions = list_revisions(deployment_id)
latest_revision = revisions["resources"][0]
latest_revision_id = latest_revision["id"]
# wait for latest revision to be DEPLOYED
wait_for_deployment(deployment_id, latest_revision_id)
# patch the deployment and get the latest revision
patch_deployment(deployment_id)
revisions = list_revisions(deployment_id)
latest_revision = revisions["resources"][0]
latest_revision_id = latest_revision["id"]
# wait for latest revision to be DEPLOYED
wait_for_deployment(deployment_id, latest_revision_id)
# delete the deployment
delete_deployment(deployment_id)
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/api-ref-control-plane.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# App development in LangSmith Deployment
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/app-development
**LangSmith Deployment** builds on the open-source [LangGraph](/oss/python/langgraph/overview) framework for developing stateful, multi-agent applications.
LangGraph provides the core abstractions and execution model, while LangSmith adds managed infrastructure, observability, deployment options, assistants, and concurrency controls—supporting the full lifecycle from development to production.
LangSmith Deployment is framework-agnostic: you can deploy agents built with LangGraph or [other frameworks](/langsmith/deploy-other-frameworks). To get started with LangGraph itself, refer to the [LangGraph quickstart](/oss/python/langgraph/quickstart).
Manage agent configurations, connect to threads, and build interactive assistants.
Execute background jobs, stateless runs, cron jobs, and manage configurable headers.
Streaming, human-in-the-loop, webhooks, and concurrency controls like double-texting.
Step-by-step examples: AutoGen integration, streaming UI, and generative UI in React.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/app-development.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Application structure
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/application-structure
To deploy on LangSmith, an application must consist of one or more graphs, a configuration file (`langgraph.json`), a file that specifies dependencies, and an optional `.env` file that specifies environment variables.
This page explains how a LangSmith application is organized and how to provide the configuration details required for deployment.
## Key concepts
To deploy using LangSmith, provide the following information:
1. A [configuration file](#configuration-file-concepts) (`langgraph.json`) that specifies the dependencies, graphs, and environment variables to use for the application.
2. The [graphs](#graphs) that implement the logic of the application.
3. A file that specifies [dependencies](#dependencies) required to run the application.
4. [Environment variables](#environment-variables) that are required for the application to run.
**Framework agnostic**
LangSmith Deployment supports deploying a [LangGraph](/oss/python/langgraph/overview) *graph*. However, the implementation of a *node* of a graph can contain arbitrary code. This means any framework can be implemented within a node and deployed on LangSmith Deployment. This lets you implement your core application logic without using additional LangGraph OSS APIs while still using LangSmith for [deployment](/langsmith/deployments), scaling, and [observability](/langsmith/observability). For more details, refer to [Use any framework with LangSmith Deployment](/langsmith/application-structure#use-any-framework-with-langsmith-deployment).
## File structure
The following are examples of directory structures for Python and JavaScript applications:
```plaintext theme={null}
my-app/
├── my_agent # all project code lies within here
│ ├── utils # utilities for your graph
│ │ ├── __init__.py
│ │ ├── tools.py # tools for your graph
│ │ ├── nodes.py # node functions for your graph
│ │ └── state.py # state definition of your graph
│ ├── __init__.py
│ └── agent.py # code for constructing your graph
├── .env # environment variables
├── requirements.txt # package dependencies
└── langgraph.json # configuration file for LangGraph
```
```plaintext theme={null}
my-app/
├── my_agent # all project code lies within here
│ ├── utils # utilities for your graph
│ │ ├── __init__.py
│ │ ├── tools.py # tools for your graph
│ │ ├── nodes.py # node functions for your graph
│ │ └── state.py # state definition of your graph
│ ├── __init__.py
│ └── agent.py # code for constructing your graph
├── .env # environment variables
├── langgraph.json # configuration file for LangGraph
└── pyproject.toml # dependencies for your project
```
```plaintext theme={null}
my-app/
├── src # all project code lies within here
│ ├── utils # optional utilities for your graph
│ │ ├── tools.ts # tools for your graph
│ │ ├── nodes.ts # node functions for your graph
│ │ └── state.ts # state definition of your graph
│ └── agent.ts # code for constructing your graph
├── package.json # package dependencies
├── .env # environment variables
└── langgraph.json # configuration file for LangGraph
```
The directory structure of an application can vary depending on the programming language and the package manager used.
## Configuration file
The `langgraph.json` file is a JSON file that specifies the dependencies, graphs, environment variables, and other settings required to deploy an application.
For details on all supported keys in the JSON file, refer to the [LangGraph configuration file reference](/langsmith/cli#configuration-file).
The [LangGraph CLI](/langsmith/cli) defaults to using the configuration file `langgraph.json` in the current directory.
### Examples
* The dependencies involve a custom local package and the `langchain_openai` package.
* A single graph will be loaded from the file `./your_package/your_file.py` with the variable `agent`.
* The environment variables are loaded from the `.env` file.
```json theme={null}
{
"dependencies": [
"langchain_openai",
"./your_package"
],
"graphs": {
"my_agent": "./your_package/your_file.py:agent"
},
"env": "./.env"
}
```
* The dependencies will be loaded from a dependency file in the local directory (e.g., `package.json`).
* A single graph will be loaded from the file `./your_package/your_file.js` with the function `agent`.
* The environment variable `OPENAI_API_KEY` is set inline.
```json theme={null}
{
"dependencies": [
"."
],
"graphs": {
"my_agent": "./your_package/your_file.js:agent"
},
"env": {
"OPENAI_API_KEY": "secret-key"
}
}
```
## Dependencies
An application may depend on other Python packages or JavaScript libraries (depending on the programming language in which the application is written).
You will generally need to specify the following information for dependencies to be set up correctly:
1. A file in the directory that specifies the dependencies (e.g., `requirements.txt`, `pyproject.toml`, or `package.json`).
2. A `dependencies` key in the [configuration file](#configuration-file-concepts) that specifies the dependencies required to run the application.
3. Any additional binaries or system libraries can be specified using `dockerfile_lines` key in the [LangGraph configuration file](#configuration-file-concepts).
## Graphs
Use the `graphs` key in the [configuration file](#configuration-file-concepts) to specify which graphs will be available in the deployed application.
You can specify one or more graphs in the configuration file. Each graph is identified by a unique name and a path to either (1) a compiled graph or (2) a function that defines a graph.
### Use any framework with LangSmith Deployment
While LangSmith Deployment requires applications to be structured as a LangGraph graph, individual nodes within that graph can contain arbitrary code. This means you can use any framework or library within your nodes while still benefiting from LangSmith's deployment infrastructure.
The graph structure serves as a deployment interface, but your core application logic can use whichever tools and frameworks best suit your needs.
To deploy with LangSmith, you need:
1. **A LangGraph graph structure**: Define a graph using [`StateGraph`](https://reference.langchain.com/python/langgraph/graphs/#langgraph.graph.state.StateGraph) with [`add_node`](https://reference.langchain.com/python/langgraph/graphs/#langgraph.graph.state.StateGraph.add_node) and [`add_edge`](https://reference.langchain.com/python/langgraph/graphs/#langgraph.graph.state.StateGraph.add_edge).
2. **Node functions with arbitrary logic**: Your node functions can call any framework or library.
3. **A compiled graph**: [Compile](https://reference.langchain.com/python/langgraph/graphs/#langgraph.graph.state.StateGraph.compile) the graph to create a deployable application.
The following example shows how to wrap your existing application logic within a minimal LangGraph structure:
```python theme={null}
from langgraph.graph import StateGraph, START, END
from typing import TypedDict
# Your existing application logic using any framework
from app_logic import process_data
from app_logic import fetch_data
class State(TypedDict):
input: str
result: str
def my_app_node(state: State) -> State:
"""Node containing arbitrary framework code."""
# Use any framework or library here
raw_data = fetch_data(state["input"])
processed = process_data(raw_data)
return {"result": processed}
# Define the graph structure
graph = StateGraph(State)
graph.add_node("process", my_app_node) # Add node with your logic
graph.add_edge(START, "process") # Connect start to your node
graph.add_edge("process", END) # Connect your node to end
# Compile for deployment
app = graph.compile()
```
1. **A LangGraph graph structure**: Define a graph using [`StateGraph`](https://reference.langchain.com/javascript/classes/_langchain_langgraph.index.StateGraph.html) with [`addNode`](https://reference.langchain.com/javascript/classes/_langchain_langgraph.index.StateGraph.html#addnode) and [`addEdge`](https://reference.langchain.com/javascript/classes/_langchain_langgraph.index.StateGraph.html#addedge).
2. **Node functions with arbitrary logic**: Your node functions can call any framework or library.
3. **A compiled graph**: [Compile](https://reference.langchain.com/javascript/classes/_langchain_langgraph.index.StateGraph.html#compile) the graph to create a deployable application.
The following example shows how to wrap your existing application logic within a minimal LangGraph structure:
```typescript theme={null}
import { StateGraph, START, END } from "@langchain/langgraph";
import { Annotation } from "@langchain/langgraph";
// Your existing application logic using any framework
import { processData } from "./app-logic";
import { fetchData } from "./app-logic";
const State = Annotation.Root({
input: Annotation,
result: Annotation
});
async function myAppNode(state: typeof State.State) {
// Use any framework or library here
const rawData = await fetchData(state.input);
const processed = await processData(rawData);
return { result: processed };
}
// Define the graph structure
const graph = new StateGraph(State)
.addNode("process", myAppNode) // Add node with your logic
.addEdge(START, "process") // Connect start to your node
.addEdge("process", END); // Connect your node to end
// Compile for deployment
export const app = graph.compile();
```
In this example, the node functions (`my_app_node` for Python and `myAppNode` for JavaScript) can contain calls to any framework or library. The LangGraph structure simply provides the deployment interface and orchestration layer.
## Environment variables
If you're working with a deployed LangGraph application [locally](/langsmith/local-server), you can configure environment variables in the `env` key of the [configuration file](#configuration-file-concepts).
For a production deployment, you will typically want to configure the environment variables in the deployment environment.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/application-structure.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Assistants
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/assistants
*Assistants* allow you to manage configurations (e.g., prompts, LLM selection, tools) separately from your graph's core logic. This enables you to create multiple, specialized versions of the same graph architecture with different behavior at runtime. Through configuration variations (rather than structural graph changes), each assistant is optimized for a different [use case](#when-to-use-assistants).
For example, imagine a general-purpose writing agent built on a common graph architecture. While the structure remains the same, different writing styles—such as blog posts and tweets—require tailored configurations to optimize performance. To support these variations, you can create multiple assistants (e.g., one for blogs and another for tweets) that share the underlying graph but differ in model selection and system prompt.
The Agent Server API provides several endpoints for creating and managing assistants and their versions. See the [API reference](/langsmith/server-api-ref) for more details.
Assistants are a [LangSmith Deployment](/langsmith/deployments) concept. They are not available in the open source LangGraph library.
## When to use assistants
Assistants are ideal when you need to deploy the same graph architecture with different configurations. Common use cases include:
* **User-level personalization**
* Customize model selection, system prompts, or tool availability per user.
* Store user preferences and apply them automatically to each interaction.
* Enable users to choose between different AI personalities or expertise levels.
* **Customer or organization-specific configurations**
* Maintain separate configurations for different customers or organizations.
* Customize behavior for each client without deploying separate infrastructure.
* Isolate configuration changes to specific customers.
```mermaid theme={null}
graph TD
A["Graph: agent (deployed)"]
A --> B["Customer A Assistant ━━━━━━━━━━━━━ Model: GPT-4 Tone: Legal Tools: Custom"]
A --> C["Customer B Assistant ━━━━━━━━━━━━━ Model: Claude Tone: Casual Tools: Standard"]
A --> D["Customer C Assistant ━━━━━━━━━━━━━ Model: GPT-3.5 Tone: Formal Tools: Limited"]
style A fill:#4A90E2,stroke:#2E5C8A,stroke-width:3px,color:#fff
style B fill:#B3E0F2,stroke:#4A90E2,stroke-width:2px,color:#000
style C fill:#B3E0F2,stroke:#4A90E2,stroke-width:2px,color:#000
style D fill:#B3E0F2,stroke:#4A90E2,stroke-width:2px,color:#000
```
* **Environment-specific configurations**
* Use different models or settings for development, staging, and production.
* Test configuration changes in staging before promoting to production.
* Reduce costs in non-production environments with smaller models.
* **A/B testing and experimentation**
* Compare different prompts, models, or parameter settings.
* Roll out configuration changes gradually to a subset of users.
* Measure performance differences between configuration variants.
* **Specialized task variants**
* Create domain-specific versions of a general-purpose agent.
* Optimize configurations for different languages, regions, or industries.
* Maintain consistent graph logic while varying the execution details.
```mermaid theme={null}
graph TD
A["Graph: writing-agent (deployed)"]
A --> B["Blog Assistant ━━━━━━━━━━━━━ Model: GPT-4 Tone: Formal Style: Long-form Tools: SEO optimization"]
A --> C["Tweet Assistant ━━━━━━━━━━━━━ Model: GPT-4-mini Tone: Casual Style: 280-char limit Tools: Hashtag suggestions"]
A --> D["Email Assistant ━━━━━━━━━━━━━ Model: GPT-4 Tone: Professional Style: Medium length Tools: Templates"]
style A fill:#4A90E2,stroke:#2E5C8A,stroke-width:3px,color:#fff
style B fill:#B3E0F2,stroke:#4A90E2,stroke-width:2px,color:#000
style C fill:#B3E0F2,stroke:#4A90E2,stroke-width:2px,color:#000
style D fill:#B3E0F2,stroke:#4A90E2,stroke-width:2px,color:#000
```
## How assistants work with deployments
When you deploy a graph with LangSmith Deployment, [Agent Server](/langsmith/agent-server) automatically creates a **default assistant** tied to that graph's default configuration. You can then create additional assistants for the same graph, each with its own configuration.
If your deployment defines multiple graphs in [`langgraph.json`](/langsmith/application-structure#configuration-file), each graph gets its own default assistant:
```json theme={null}
{
"graphs": {
"graph_id_1": "path_to_graph_id_1", // default assistant created for graph_id_1
"graph_id_2": "path_to_graph_id_2" // default assistant created for graph_id_2
}
}
```
That is, there can be multiple default assistants—one for each graph defined in your deployment.
Assistants have several key features:
* **[Managed via API and UI](/langsmith/configuration-cloud)**: Create, list, update, version, and get assistants using the Agent Server/LangGraph SDKs or the [LangSmith UI](https://smith.langchain.com).
* **One graph, multiple assistants**: A single deployed graph can support multiple assistants, each with different configurations (e.g., prompts, models, tools).
* **[Versioned](#versioning) configurations**: Each assistant maintains its own configuration history through versioning. Editing an assistant creates a new version, and you can promote or roll back to any version.
* **[Configuration](#configuration) updates without graph changes**: Update prompts, model selection, and other settings through assistant configurations, enabling rapid iteration without modifying or redeploying your graph code.
When invoking an assistant, you can specify either in [`langgraph.json`](/langsmith/application-structure#configuration-file):
* A **graph ID** (e.g., `"agent"`): Uses the default assistant for that graph
* An **assistant ID** (UUID): Uses a specific assistant configuration
This flexibility allows you to quickly test with default settings or precisely control which configuration is used.
### Configuration
Assistants build on the LangGraph open source concept of [configuration](/oss/python/langgraph/graph-api#runtime-context).
While configuration is available in the open source LangGraph library, assistants are only present in [LangSmith Deployment](/langsmith/deployments) because they are tightly coupled to your deployed graph. Upon deployment, [Agent Server](/langsmith/agent-server) will automatically create a default assistant for each graph using the graph's default configuration settings.
In practice, an assistant is just an *instance* of a graph with a specific configuration. Therefore, multiple assistants can reference the same graph but can contain different configurations (e.g. prompts, models, tools). The LangSmith Deployment API provides several endpoints for creating and managing assistants. See the [API reference](/langsmith/server-api-ref) and [this how-to](/langsmith/configuration-cloud) for more details on how to create assistants.
### Versioning
Assistants support versioning to track changes over time. Once you've created an assistant, subsequent edits will automatically create new versions.
* Each update creates a new version of the assistant.
* You can promote any version to be the active version.
* Rolling back to a previous version is as simple as setting it as active.
* All versions remain available for reference and rollback.
When updating an assistant, you must provide the entire configuration payload. The update endpoint creates new versions from scratch and does not merge with previous versions. Make sure to include all configuration fields you want to retain.
For more details on how to manage assistant versions, refer to the [Manage assistants guide](/langsmith/configuration-cloud#create-a-new-version-for-your-assistant).
### Execution
A *run* is an invocation of an assistant. When you execute a run, you specify which assistant to use (either by graph ID for the default assistant or by assistant ID for a specific configuration).
```mermaid theme={null}
flowchart TB
subgraph deploy[Deployment]
G[Graph Code ━━━━━━━━━ Deployed logic]
end
subgraph config[Configuration]
A1[Assistant 1 GPT-4, Formal]
A2[Assistant 2 Claude, Casual]
end
subgraph state[State]
T1[Thread 1 User A]
T2[Thread 2 User B]
end
subgraph runs[Runs]
A1T1["Run: A1 + T1"]
A1T2["Run: A1 + T2"]
A2T1["Run: A2 + T1"]
end
A1 -.-> T1
A1 -.-> T2
A2 -.-> T1
A1T1 --> G
A1T2 --> G
A2T1 --> G
style G fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,color:#fff
style A1 fill:#B3E0F2,stroke:#4A90E2,stroke-width:2px,color:#000
style A2 fill:#B3E0F2,stroke:#4A90E2,stroke-width:2px,color:#000
style T1 fill:#FFE0B3,stroke:#F59E0B,stroke-width:2px,color:#000
style T2 fill:#FFE0B3,stroke:#F59E0B,stroke-width:2px,color:#000
style A1T1 fill:#B3F2C9,stroke:#10B981,stroke-width:2px,color:#000
style A1T2 fill:#B3F2C9,stroke:#10B981,stroke-width:2px,color:#000
style A2T1 fill:#B3F2C9,stroke:#10B981,stroke-width:2px,color:#000
```
This diagram shows how a **run** combines an assistant with a thread to execute the graph:
* **Graph** (blue): The deployed code containing your agent's logic
* **Assistants** (light blue): Configuration options (model, prompts, tools)
* **Threads** (orange): State containers for conversation history
* **Runs** (green): Executions that pair an assistant + thread
**Example combinations:**
* **Run: A1 + T1**: Assistant 1 configuration applied to User A's conversation
* **Run: A1 + T2**: Same assistant serving User B (different conversation)
* **Run: A2 + T1**: Different assistant applied to User A's conversation (configuration switch)
When executing a run:
* Each run may have its own input, configuration overrides, and metadata.
* Runs can be stateless (no thread) or stateful (executed on a [thread](/oss/python/langgraph/persistence#threads) for conversation persistence).
* Multiple runs can use the same assistant configuration.
* The assistant's configuration affects how the underlying graph executes.
The Agent Server API provides several endpoints for creating and managing runs. For more details, refer to the [API reference](/langsmith/server-api-ref)).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/assistants.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Log user feedback using the SDK
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/attach-user-feedback
**Key concepts**
* [Conceptual guide on tracing and feedback](/langsmith/observability-concepts)
* [Reference guide on feedback data format](/langsmith/feedback-data-format)
LangSmith makes it easy to attach feedback to traces.
This feedback can come from users, annotators, automated evaluators, etc., and is crucial for monitoring and evaluating applications.
## Use `create_feedback()` / `createFeedback`
Here we'll walk through how to log feedback using the SDK.
**Child runs**
You can attach user feedback to ANY child run of a trace, not just the trace (root run) itself.
This is useful for critiquing specific steps of the LLM application, such as the retrieval step or generation step of a RAG pipeline.
**Non-blocking creation (Python only)**
The Python client will automatically background feedback creation if you pass `trace_id=` to [`create_feedback()`](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client.create_feedback).
This is essential for low-latency environments, where you want to make sure your application isn't blocked on feedback creation.
```python Python theme={null}
from langsmith import trace, traceable, Client
@traceable
def foo(x):
return {"y": x * 2}
@traceable
def bar(y):
return {"z": y - 1}
client = Client()
inputs = {"x": 1}
with trace(name="foobar", inputs=inputs) as root_run:
result = foo(**inputs)
result = bar(**result)
root_run.outputs = result
trace_id = root_run.id
child_runs = root_run.child_runs
# Provide feedback for a trace (a.k.a. a root run)
client.create_feedback(
key="user_feedback",
score=1,
trace_id=trace_id,
comment="the user said that ..."
)
# Provide feedback for a child run
foo_run_id = [run for run in child_runs if run.name == "foo"][0].id
client.create_feedback(
key="correctness",
score=0,
run_id=foo_run_id,
# trace_id= is optional but recommended to enable batched and backgrounded
# feedback ingestion.
trace_id=trace_id,
)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
const client = new Client();
// ... Run your application and get the run_id...
// This information can be the result of a user-facing feedback form
await client.createFeedback(
runId,
"feedback-key",
{
score: 1.0,
comment: "comment",
}
);
```
You can even log feedback for in-progress runs using [`create_feedback()`](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client.create_feedback) / [`createFeedback`](https://reference.langchain.com/javascript/classes/langsmith.client.Client.html#createfeedback). See [this guide](/langsmith/access-current-span) for how to get the run ID of an in-progress run.
To learn more about how to filter traces based on various attributes, including user feedback, see [this guide](/langsmith/filter-traces-in-application).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/attach-user-feedback.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to audit evaluator scores
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/audit-evaluator-scores
LLM-as-a-judge evaluators don't always get it right. Because of this, it is often useful for a human to manually audit the scores left by an evaluator and correct them where necessary. LangSmith allows you to make corrections on evaluator scores in the UI or SDK.
## In the comparison view
In the comparison view, you may click on any feedback tag to bring up the feedback details. From there, click the "edit" icon on the right to bring up the corrections view. You may then type in your desired score in the text box under "Make correction". If you would like, you may also attach an explanation to your correction. This is useful if you are using a [few-shot evaluator](/langsmith/create-few-shot-evaluators) and will be automatically inserted into your few-shot examples in place of the `few_shot_explanation` prompt variable.
## In the runs table
In the runs table, find the "Feedback" column and click on the feedback tag to bring up the feedback details. Again, click the "edit" icon on the right to bring up the corrections view.
## In the SDK
Corrections can be made via the SDK's `update_feedback` function, with the `correction` dict. You must specify a `score` key which corresponds to a number for it to be rendered in the UI.
```python Python theme={null}
import langsmith
client = langsmith.Client()
client.update_feedback(
my_feedback_id,
correction={
"score": 1,
},
)
```
```typescript TypeScript theme={null}
import { Client } from 'langsmith';
const client = new Client();
await client.updateFeedback(
myFeedbackId,
{
correction: {
score: 1,
}
}
)
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/audit-evaluator-scores.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Authentication & access control
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/auth
LangSmith provides a flexible authentication and authorization system that can integrate with most authentication schemes.
## Core concepts
### Authentication vs authorization
While often used interchangeably, these terms represent distinct security concepts:
* [**Authentication**](#authentication) ("AuthN") verifies *who* you are. This runs as middleware for every request.
* [**Authorization**](#authorization) ("AuthZ") determines *what you can do*. This validates the user's privileges and roles on a per-resource basis.
In LangSmith, authentication is handled by your [`@auth.authenticate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth.authenticate) handler, and authorization is handled by your [`@auth.on`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth.on) handlers.
## Default security models
LangSmith provides different security defaults:
### LangSmith
* Uses LangSmith API keys by default
* Requires valid API key in `x-api-key` header
* Can be customized with your auth handler
**Custom auth**
Custom auth **is supported** for all plans in LangSmith.
### Self-hosted
* No default authentication
* Complete flexibility to implement your security model
* You control all aspects of authentication and authorization
## System architecture
A typical authentication setup involves three main components:
1. **Authentication Provider** (Identity Provider/IdP)
* A dedicated service that manages user identities and credentials
* Handles user registration, login, password resets, etc.
* Issues tokens (JWT, session tokens, etc.) after successful authentication
* Examples: Auth0, Supabase Auth, Okta, or your own auth server
2. **Agent Server** (Resource Server)
* Your agent or LangGraph application, which contains business logic and protected resources
* Validates tokens with the auth provider
* Enforces access control based on user identity and permissions
* Doesn't store user credentials directly
3. **Client Application** (Frontend)
* Web app, mobile app, or API client
* Collects time-sensitive user credentials and sends to auth provider
* Receives tokens from auth provider
* Includes these tokens in requests to the Agent Server
Here's how these components typically interact:
```mermaid theme={null}
sequenceDiagram
participant Client as Client App
participant Auth as Auth Provider
participant LG as Agent Server
Client->>Auth: 1. Login (username/password)
Auth-->>Client: 2. Return token
Client->>LG: 3. Request with token
Note over LG: 4. Validate token (@auth.authenticate)
LG-->>Auth: 5. Fetch user info
Auth-->>LG: 6. Confirm validity
Note over LG: 7. Apply access control (@auth.on.*)
LG-->>Client: 8. Return resources
```
Your [`@auth.authenticate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth.authenticate) handler in LangGraph handles steps 4-6, while your [`@auth.on`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth.on) handlers implement step 7.
## Authentication
Authentication in LangGraph runs as middleware on every request. Your [`@auth.authenticate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth.authenticate) handler receives request information and should:
1. Validate the credentials
2. Return [user info](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.MinimalUserDict) containing the user's identity and user information if valid
3. Raise an [HTTP exception](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.exceptions.HTTPException) or AssertionError if invalid
```python theme={null}
from langgraph_sdk import Auth
auth = Auth()
@auth.authenticate
async def authenticate(headers: dict) -> Auth.types.MinimalUserDict:
# Validate credentials (e.g., API key, JWT token)
api_key = headers.get(b"x-api-key")
if not api_key or not is_valid_key(api_key):
raise Auth.exceptions.HTTPException(
status_code=401,
detail="Invalid API key"
)
# Return user info - only identity and is_authenticated are required
# Add any additional fields you need for authorization
return {
"identity": "user-123", # Required: unique user identifier
"is_authenticated": True, # Optional: assumed True by default
"permissions": ["read", "write"], # Optional: for permission-based auth
# You can add more custom fields if you want to implement other auth patterns
"role": "admin",
"org_id": "org-456"
}
```
The returned user information is available:
* To your authorization handlers via [`ctx.user`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.AuthContext)
* In your application via `config["configuration"]["langgraph_auth_user"]`
The [`@auth.authenticate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth.authenticate) handler can accept any of the following parameters by name:
* request (Request): The raw ASGI request object
* path (str): The request path, e.g., `"/threads/abcd-1234-abcd-1234/runs/abcd-1234-abcd-1234/stream"`
* method (str): The HTTP method, e.g., `"GET"`
* path\_params (dict\[str, str]): URL path parameters, e.g., `{"thread_id": "abcd-1234-abcd-1234", "run_id": "abcd-1234-abcd-1234"}`
* query\_params (dict\[str, str]): URL query parameters, e.g., `{"stream": "true"}`
* headers (dict\[bytes, bytes]): Request headers
* authorization (str | None): The Authorization header value (e.g., `"Bearer "`)
In many of our tutorials, we will just show the "authorization" parameter to be concise, but you can opt to accept more information as needed
to implement your custom authentication scheme.
### Agent authentication
Custom authentication permits delegated access. The values you return in `@auth.authenticate` are added to the run context, giving agents user-scoped credentials lets them access resources on the user’s behalf.
```mermaid theme={null}
sequenceDiagram
%% Actors
participant ClientApp as Client
participant AuthProv as Auth Provider
participant LangGraph as Agent Server
participant SecretStore as Secret Store
participant ExternalService as External Service
%% Platform login / AuthN
ClientApp ->> AuthProv: 1. Login (username / password)
AuthProv -->> ClientApp: 2. Return token
ClientApp ->> LangGraph: 3. Request with token
Note over LangGraph: 4. Validate token (@auth.authenticate)
LangGraph -->> AuthProv: 5. Fetch user info
AuthProv -->> LangGraph: 6. Confirm validity
%% Fetch user tokens from secret store
LangGraph ->> SecretStore: 6a. Fetch user tokens
SecretStore -->> LangGraph: 6b. Return tokens
Note over LangGraph: 7. Apply access control (@auth.on.*)
%% External Service round-trip
LangGraph ->> ExternalService: 8. Call external service (with header)
Note over ExternalService: 9. External service validates header and executes action
ExternalService -->> LangGraph: 10. Service response
%% Return to caller
LangGraph -->> ClientApp: 11. Return resources
```
After authentication, the platform creates a special configuration object that is passed to your graph and all nodes via the configurable context.
This object contains information about the current user, including any custom fields you return from your [`@auth.authenticate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth.authenticate) handler.
To enable an agent to act on behalf of the user, use [custom authentication middleware](/langsmith/custom-auth). This will allow the agent to interact with external systems like MCP servers, external databases, and even other agents on behalf of the user.
For more information, see the [Use custom auth](/langsmith/custom-auth#enable-agent-authentication) guide.
### Agent authentication with MCP
For information on how to authenticate an agent to an MCP server, see the [MCP conceptual guide](/oss/python/langchain/mcp).
## Authorization
After authentication, LangGraph calls your [`@auth.on`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth) handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can:
1. Add metadata to be saved during resource creation by mutating the `value["metadata"]` dictionary directly. See the [supported actions table](#supported-actions) for the list of types the value can take for each action.
2. Filter resources by metadata during search/list or read operations by returning a [filter dictionary](#filter-operations).
3. Raise an HTTP exception if access is denied.
If you want to just implement simple user-scoped access control, you can use a single [`@auth.on`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth) handler for all resources and actions. If you want to have different control depending on the resource and action, you can use [resource-specific handlers](#resource-specific-handlers). See the [Supported Resources](#supported-resources) section for a full list of the resources that support access control.
```python theme={null}
@auth.on
async def add_owner(
ctx: Auth.types.AuthContext,
value: dict # The payload being sent to this access method
) -> dict: # Returns a filter dict that restricts access to resources
"""Authorize all access to threads, runs, crons, and assistants.
This handler does two things:
- Adds a value to resource metadata (to persist with the resource so it can be filtered later)
- Returns a filter (to restrict access to existing resources)
Args:
ctx: Authentication context containing user info, permissions, the path, and
value: The request payload sent to the endpoint. For creation
operations, this contains the resource parameters. For read
operations, this contains the resource being accessed.
Returns:
A filter dictionary that LangGraph uses to restrict access to resources.
See [Filter Operations](#filter-operations) for supported operators.
"""
# Create filter to restrict access to just this user's resources
filters = {"owner": ctx.user.identity}
# Get or create the metadata dictionary in the payload
# This is where we store persistent info about the resource
metadata = value.setdefault("metadata", {})
# Add owner to metadata - if this is a create or update operation,
# this information will be saved with the resource
# So we can filter by it later in read operations
metadata.update(filters)
# Return filters to restrict access
# These filters are applied to ALL operations (create, read, update, search, etc.)
# to ensure users can only access their own resources
return filters
```
### Resource-specific handlers
You can register handlers for specific resources and actions by chaining the resource and action names together with the [`@auth.on`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth) decorator.
When a request is made, the most specific handler that matches that resource and action is called. Below is an example of how to register handlers for specific resources and actions. For the following setup:
1. Authenticated users are able to create threads, read threads, and create runs on threads
2. Only users with the "assistants:create" permission are allowed to create new assistants
3. All other endpoints (e.g., e.g., delete assistant, crons, store) are disabled for all users.
**Supported Handlers**
For a full list of supported resources and actions, see the [Supported Resources](#supported-resources) section below.
```python theme={null}
# Generic / global handler catches calls that aren't handled by more specific handlers
@auth.on
async def reject_unhandled_requests(ctx: Auth.types.AuthContext, value: Any) -> False:
print(f"Request to {ctx.path} by {ctx.user.identity}")
raise Auth.exceptions.HTTPException(
status_code=403,
detail="Forbidden"
)
# Matches the "thread" resource and all actions - create, read, update, delete, search
# Since this is **more specific** than the generic @auth.on handler, it will take precedence
# over the generic handler for all actions on the "threads" resource
@auth.on.threads
async def on_thread(
ctx: Auth.types.AuthContext,
value: Auth.types.threads.create.value
):
# Setting metadata on the thread being created
# will ensure that the resource contains an "owner" field
# Then any time a user tries to access this thread or runs within the thread,
# we can filter by owner
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
return {"owner": ctx.user.identity}
# Thread creation. This will match only on thread create actions
# Since this is **more specific** than both the generic @auth.on handler and the @auth.on.threads handler,
# it will take precedence for any "create" actions on the "threads" resources
@auth.on.threads.create
async def on_thread_create(
ctx: Auth.types.AuthContext,
value: Auth.types.threads.create.value
):
# Reject if the user does not have write access
if "write" not in ctx.permissions:
raise Auth.exceptions.HTTPException(
status_code=403,
detail="User lacks the required permissions."
)
# Setting metadata on the thread being created
# will ensure that the resource contains an "owner" field
# Then any time a user tries to access this thread or runs within the thread,
# we can filter by owner
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
return {"owner": ctx.user.identity}
# Reading a thread. Since this is also more specific than the generic @auth.on handler, and the @auth.on.threads handler,
# it will take precedence for any "read" actions on the "threads" resource
@auth.on.threads.read
async def on_thread_read(
ctx: Auth.types.AuthContext,
value: Auth.types.threads.read.value
):
# Since we are reading (and not creating) a thread,
# we don't need to set metadata. We just need to
# return a filter to ensure users can only see their own threads
return {"owner": ctx.user.identity}
# Run creation, streaming, updates, etc.
# This takes precedenceover the generic @auth.on handler and the @auth.on.threads handler
@auth.on.threads.create_run
async def on_run_create(
ctx: Auth.types.AuthContext,
value: Auth.types.threads.create_run.value
):
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
# Inherit thread's access control
return {"owner": ctx.user.identity}
# Assistant creation
@auth.on.assistants.create
async def on_assistant_create(
ctx: Auth.types.AuthContext,
value: Auth.types.assistants.create.value
):
if "assistants:create" not in ctx.permissions:
raise Auth.exceptions.HTTPException(
status_code=403,
detail="User lacks the required permissions."
)
```
Notice that we are mixing global and resource-specific handlers in the above example. Since each request is handled by the most specific handler, a request to create a `thread` would match the `on_thread_create` handler but NOT the `reject_unhandled_requests` handler. A request to `update` a thread, however would be handled by the global handler, since we don't have a more specific handler for that resource and action.
### Filter operations
Authorization handlers can return `None`, a boolean, or a filter dictionary.
* `None` and `True` mean "authorize access to all underling resources"
* `False` means "deny access to all underling resources (raises a 403 exception)"
* A metadata filter dictionary will restrict access to resources
A filter dictionary is a dictionary with keys that match the resource metadata. It supports three operators:
* The default value is a shorthand for exact match, or "\$eq", below. For example, `{"owner": user_id}` will include only resources with metadata containing `{"owner": user_id}`
* `$eq`: Exact match (e.g., `{"owner": {"$eq": user_id}}`) - this is equivalent to the shorthand above, `{"owner": user_id}`
* `$contains`: List membership (e.g., `{"allowed_users": {"$contains": user_id}}`) or list containment (e.g., `{"allowed_users": {"$contains": [user_id_1, user_id_2]}}`). The value here must be an element of the list or a subset of the elements of the list, respectively. The metadata in the stored resource must be a list/container type.
A dictionary with multiple keys is treated using a logical `AND` filter. For example, `{"owner": org_id, "allowed_users": {"$contains": user_id}}` will only match resources with metadata whose "owner" is `org_id` and whose "allowed\_users" list contains `user_id`.
See the reference [`Auth`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth)(Auth) for more information.
## Common access patterns
Here are some typical authorization patterns:
### Single-owner resources
This common pattern lets you scope all threads, assistants, crons, and runs to a single user. It's useful for common single-user use cases like regular chatbot-style apps.
```python theme={null}
@auth.on
async def owner_only(ctx: Auth.types.AuthContext, value: dict):
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
return {"owner": ctx.user.identity}
```
### Permission-based access
This pattern lets you control access based on **permissions**. It's useful if you want certain roles to have broader or more restricted access to resources.
```python theme={null}
# In your auth handler:
@auth.authenticate
async def authenticate(headers: dict) -> Auth.types.MinimalUserDict:
...
return {
"identity": "user-123",
"is_authenticated": True,
"permissions": ["threads:write", "threads:read"] # Define permissions in auth
}
def _default(ctx: Auth.types.AuthContext, value: dict):
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
return {"owner": ctx.user.identity}
@auth.on.threads.create
async def create_thread(ctx: Auth.types.AuthContext, value: dict):
if "threads:write" not in ctx.permissions:
raise Auth.exceptions.HTTPException(
status_code=403,
detail="Unauthorized"
)
return _default(ctx, value)
@auth.on.threads.read
async def rbac_create(ctx: Auth.types.AuthContext, value: dict):
if "threads:read" not in ctx.permissions and "threads:write" not in ctx.permissions:
raise Auth.exceptions.HTTPException(
status_code=403,
detail="Unauthorized"
)
return _default(ctx, value)
```
## Supported resources
LangGraph provides three levels of authorization handlers, from most general to most specific:
1. **Global Handler** (`@auth.on`): Matches all resources and actions
2. **Resource Handler** (e.g., `@auth.on.threads`, `@auth.on.assistants`, `@auth.on.crons`): Matches all actions for a specific resource
3. **Action Handler** (e.g., `@auth.on.threads.create`, `@auth.on.threads.read`): Matches a specific action on a specific resource
The most specific matching handler will be used. For example, `@auth.on.threads.create` takes precedence over `@auth.on.threads` for thread creation.
If a more specific handler is registered, the more general handler will not be called for that resource and action.
"Type Safety"
Each handler has type hints available for its `value` parameter at `Auth.types.on...value`. For example:
```python theme={null}
@auth.on.threads.create
async def on_thread_create(
ctx: Auth.types.AuthContext,
value: Auth.types.on.threads.create.value # Specific type for thread creation
):
...
@auth.on.threads
async def on_threads(
ctx: Auth.types.AuthContext,
value: Auth.types.on.threads.value # Union type of all thread actions
):
...
@auth.on
async def on_all(
ctx: Auth.types.AuthContext,
value: dict # Union type of all possible actions
):
...
```
More specific handlers provide better type hints since they handle fewer action types.
#### Supported actions and types
Here are all the supported action handlers:
| Resource | Handler | Description | Value Type |
| -------------- | ----------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **Threads** | `@auth.on.threads.create` | Thread creation | [`ThreadsCreate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.ThreadsCreate) |
| | `@auth.on.threads.read` | Thread retrieval | [`ThreadsRead`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.ThreadsRead) |
| | `@auth.on.threads.update` | Thread updates | [`ThreadsUpdate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.ThreadsUpdate) |
| | `@auth.on.threads.delete` | Thread deletion | [`ThreadsDelete`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.ThreadsDelete) |
| | `@auth.on.threads.search` | Listing threads | [`ThreadsSearch`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.ThreadsSearch) |
| | `@auth.on.threads.create_run` | Creating or updating a run | [`RunsCreate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.RunsCreate) |
| **Assistants** | `@auth.on.assistants.create` | Assistant creation | [`AssistantsCreate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.AssistantsCreate) |
| | `@auth.on.assistants.read` | Assistant retrieval | [`AssistantsRead`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.AssistantsRead) |
| | `@auth.on.assistants.update` | Assistant updates | [`AssistantsUpdate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.AssistantsUpdate) |
| | `@auth.on.assistants.delete` | Assistant deletion | [`AssistantsDelete`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.AssistantsDelete) |
| | `@auth.on.assistants.search` | Listing assistants | [`AssistantsSearch`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.AssistantsSearch) |
| **Crons** | `@auth.on.crons.create` | Cron job creation | [`CronsCreate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.CronsCreate) |
| | `@auth.on.crons.read` | Cron job retrieval | [`CronsRead`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.CronsRead) |
| | `@auth.on.crons.update` | Cron job updates | [`CronsUpdate`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.CronsUpdate) |
| | `@auth.on.crons.delete` | Cron job deletion | [`CronsDelete`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.CronsDelete) |
| | `@auth.on.crons.search` | Listing cron jobs | [`CronsSearch`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.CronsSearch) |
"About Runs"
Runs are scoped to their parent thread for access control. This means permissions are typically inherited from the thread, reflecting the conversational nature of the data model. All run operations (reading, listing) except creation are controlled by the thread's handlers.
There is a specific `create_run` handler for creating new runs because it had more arguments that you can view in the handler.
## Next steps
For implementation details:
* Check out the introductory tutorial on [setting up authentication](/langsmith/set-up-custom-auth)
* See the how-to guide on implementing a [custom auth handlers](/langsmith/custom-auth)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/auth.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Authentication methods
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/authentication-methods
LangSmith supports multiple authentication methods for easy sign-up and login.
## Cloud
### Email/Password
Users can use an email address and password to sign up and login to LangSmith.
### Social providers
Users can alternatively use their credentials from GitHub or Google.
### SAML SSO
Enterprise customers can configure [SAML SSO](/langsmith/user-management) and [SCIM](/langsmith/user-management)
## Self-Hosted
Self-hosted customers have more control over how their users can login to LangSmith. For more in-depth coverage of configuration options, see [the self-hosting docs](/langsmith/self-hosted) and [Helm chart](https://github.com/langchain-ai/helm/tree/main/charts/langsmith).
### SSO with OAuth 2.0 and OIDC
Production installations should configure SSO in order to use an external identity provider. This enables users to login through an identity platform like Auth0/Okta. LangSmith supports almost any OIDC-compliant provider. Learn more about configuring SSO in the [SSO configuration guide](/langsmith/self-host-sso)
### Email/Password a.k.a. basic auth
This auth method requires very little configuration as it does not require an external identity provider. It is most appropriate to use for self-hosted trials. Learn more in the [basic auth configuration guide](/langsmith/self-host-basic-auth)
### None
This authentication mode will be removed after the launch of Basic Auth.
If zero authentication methods are enabled, a self-hosted installation does not require any login/sign-up. This configuration should only be used for verifying installation at the infrastructure level, as the feature set supported in this mode is restricted with only a single organization and workspace.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/authentication-methods.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Self-hosted LangSmith on AWS
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/aws-self-hosted
When running LangSmith on [Amazon Web Services (AWS)](https://aws.amazon.com/), you can set up in either [full self-hosted](/langsmith/self-hosted) or [hybrid](/langsmith/hybrid) mode. Full self-hosted mode deploys a complete LangSmith platform with observability functionality as well as the option to create [agent deployments](/langsmith/deployments). Hybrid mode includes just the infrastructure to run agents in a data plane within your cloud, while our SaaS provides the control plane and observability functionality.
This page provides:
* [Initial setup steps](#initial-setup) for deploying to EKS, configuring managed services, and setting up authentication.
* [AWS-specific architecture patterns](#reference-architecture) and reference diagrams.
* [Service recommendations](#compute-options) and best practices.
* [AWS Well-Architected best practices](#aws-well-architected-best-practices) for operational excellence, security, and reliability.
LangChain provides Terraform modules specifically for AWS to help provision infrastructure for LangSmith. These modules can quickly set up EKS clusters, RDS, ElastiCache, S3, and networking resources.
View the [AWS Terraform modules](https://github.com/langchain-ai/terraform/tree/main/modules/aws) for documentation and examples.
## Initial setup
Follow the [Kubernetes installation guide](/langsmith/kubernetes). LangSmith is tested on Amazon Elastic Kubernetes Service (EKS).
**EKS-specific notes:**
* Ensure the EBS CSI Driver is installed for persistent storage
* Use the `ebs.csi.aws.com` storage class provisioner
For production deployments, connect to AWS managed services:
Store trace data in S3
PostgreSQL database
Redis for caching
Analytics database
Use [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) to authenticate LangSmith pods to AWS services without static credentials.
**Key pages:**
* [S3 IRSA configuration](/langsmith/self-host-blob-storage#iam-role-for-service-account-recommended)
* [RDS IAM authentication](/langsmith/self-host-external-postgres#iam-authentication)
* [ElastiCache IAM authentication](/langsmith/self-host-external-redis#iam-authentication)
After completing these initial setup steps, you can review the complete AWS architecture and best practices below.
## Reference architecture
We recommend leveraging AWS's managed services to provide a scalable, secure, and resilient platform. The following architecture applies to both self-hosted and hybrid and aligns with the [AWS Well-Architected Framework](https://aws.amazon.com/architecture/well-architected/):
* **Ingress & networking**: Requests enter via [Amazon Application Load Balancer (ALB)](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/) within your [VPC](https://aws.amazon.com/vpc/), secured using [AWS WAF](https://aws.amazon.com/waf/) and [IAM](https://aws.amazon.com/iam/)-based authentication.
* **Frontend & backend services:** Containers run on [Amazon EKS](https://aws.amazon.com/eks/), orchestrated behind the ALB. routes requests to other services within the cluster as necessary.
* **Storage & databases:**
* [Amazon RDS for PostgreSQL](https://aws.amazon.com/rds/postgresql/) or [Aurora](https://aws.amazon.com/rds/aurora/): metadata, projects, users, and short-term and long-term memory for deployed agents. LangSmith supports PostgreSQL version 14 or higher.
* [Amazon ElastiCache (Redis)](https://aws.amazon.com/elasticache/redis/): caching and job queues. ElastiCache can be in single-instance or cluster mode, running Redis OSS version 5 or higher.
* ClickHouse + [Amazon EBS](https://aws.amazon.com/ebs/): analytics and trace storage.
* We recommend using an [externally managed ClickHouse solution](/langsmith/self-host-external-clickhouse) unless security or compliance reasons
prevent you from doing so.
* ClickHouse is not required for hybrid deployments.
* [Amazon S3](https://aws.amazon.com/s3/): object storage for trace artifacts and telemetry.
* **LLM integration:** Optionally proxy requests to [Amazon Bedrock](https://aws.amazon.com/bedrock/) or [Amazon SageMaker](https://aws.amazon.com/sagemaker/) for LLM inference.
* **Monitoring & observability:** Integrate with [Amazon CloudWatch](https://aws.amazon.com/cloudwatch/)
## Compute options
LangSmith supports multiple compute options depending on your requirements:
| Compute option | Description | Suitable for |
| ------------------------------------------ | ----------------------------------------- | ------------------------------------ |
| **Elastic Kubernetes Service (preferred)** | Advanced scaling and multi-tenant support | Large enterprises |
| **EC2-based** | Full control, BYO-infra | Regulated or air-gapped environments |
## AWS Well-Architected best practices
This reference is designed to align with the six pillars of the AWS Well-Architected Framework:
### Operational excellence
* Automate deployments with IaC ([CloudFormation](https://aws.amazon.com/cloudformation/) / [Terraform](https://www.terraform.io/)).
* Use [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) for configuration.
* Configure your LangSmith instance to [export telemetry data](/langsmith/export-backend) and continuously monitor via [CloudWatch Logs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html).
* The preferred method to manage [LangSmith deployments](/langsmith/deployments) is to create a CI process that builds [Agent Server](/langsmith/agent-server) images and pushes them to [ECR](https://aws.amazon.com/ecr/). Create a test deployment for pull requests before deploying a new revision to staging or production upon PR merge.
### Security
* Use [IAM](https://aws.amazon.com/iam/) roles with least-privilege policies.
* Enable encryption at rest ([RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.Encryption.html), [S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingEncryption.html), ClickHouse volumes) and in transit (TLS 1.2+).
* Integrate with [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) for credentials.
* Use [Amazon Cognito](https://aws.amazon.com/cognito/) as an IDP in conjunction with LangSmith's built-in authentication and authorization features to secure access to agents and their tools.
### Reliability
* Replicate the LangSmith [data plane](/langsmith/data-plane) across regions: Deploy identical data planes to Kubernetes clusters in different regions for LangSmith Deployment. Deploy [RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZSingleStandby.html) and [ECS](https://aws.amazon.com/ecs/) services across [Multi-AZ](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/).
* Implement [auto-scaling](https://aws.amazon.com/autoscaling/) for backend workers.
* Use [Amazon Route 53](https://aws.amazon.com/route53/) health checks and failover policies.
### Performance efficiency
* Leverage [EC2](https://aws.amazon.com/ec2/) instances for optimized compute.
* Use [S3 Intelligent-Tiering](https://aws.amazon.com/s3/storage-classes/intelligent-tiering/) for infrequently accessed trace data.
### Cost optimization
* Right-size [EKS](https://aws.amazon.com/eks/) clusters using [Compute Savings Plans](https://aws.amazon.com/savingsplans/compute-pricing/).
* Monitor cost KPIs using [AWS Cost Explorer](https://aws.amazon.com/aws-cost-management/aws-cost-explorer/) dashboards.
### Sustainability
* Minimize idle workloads with on-demand compute.
* Store telemetry in low-latency, low-cost tiers.
* Enable auto-shutdown for non-prod environments.
## Security and compliance
LangSmith can be configured for:
* [PrivateLink](https://aws.amazon.com/privatelink/)-only access (no public internet exposure, besides egress necessary for billing).
* [KMS](https://aws.amazon.com/kms/)-based encryption keys for S3, RDS, and EBS.
* Audit logging to [CloudWatch](https://aws.amazon.com/cloudwatch/) and [AWS CloudTrail](https://aws.amazon.com/cloudtrail/).
Customers can deploy in [GovCloud](https://aws.amazon.com/govcloud-us/), ISO, or HIPAA regions as needed.
## Monitoring and evals
Use LangSmith to:
* Capture traces from LLM apps running on [Bedrock](https://aws.amazon.com/bedrock/) or [SageMaker](https://aws.amazon.com/sagemaker/).
* Evaluate model outputs via [LangSmith datasets](/langsmith/manage-datasets).
* Track latency, token usage, and success rates.
Integrate with:
* [AWS CloudWatch](https://aws.amazon.com/cloudwatch/) dashboards.
* [OpenTelemetry](https://opentelemetry.io/) and [Prometheus](https://prometheus.io/) exporters.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/aws-self-hosted.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Self-hosted LangSmith on Azure
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/azure-self-hosted
When running LangSmith on [Microsoft Azure](https://azure.microsoft.com/), you can set up in either [full self-hosted](/langsmith/self-hosted) or [hybrid](/langsmith/hybrid) mode. Full self-hosted mode deploys a complete LangSmith platform with observability functionality as well as the option to create agent deployments. Hybrid mode entails just the infrastructure to run agents in a data plane within your cloud, while our SaaS provides the control plane and observability functionality.
This page provides:
* [Initial setup steps](#initial-setup) for deploying to AKS, configuring managed services, and setting up authentication.
* [Azure-specific architecture patterns](#reference-architecture) and reference diagrams.
* [Compute and networking guidance](#compute-and-networking-on-azure) and best practices.
* [Security and access control](#security-and-access-control) recommendations for Azure deployments.
LangChain provides Terraform modules specifically for Azure to help provision infrastructure for LangSmith. These modules can quickly set up AKS clusters, Azure Database for PostgreSQL, Azure Managed Redis, Blob Storage, and networking resources.
View the [Azure Terraform modules](https://github.com/langchain-ai/terraform/tree/main/modules/azure) for documentation and examples.
## Initial setup
Follow the [Kubernetes installation guide](/langsmith/kubernetes). LangSmith is tested on Azure Kubernetes Service (AKS).
**AKS-specific notes:**
* LangSmith works with standard AKS clusters
* Use Azure Disk storage class for persistent volumes
For production deployments, connect to Azure managed services:
Store trace data in Azure Blob
PostgreSQL database
Redis for caching
Analytics database
Use [Azure Workload Identity](https://azure.github.io/azure-workload-identity/docs/introduction.html) to authenticate LangSmith pods to Azure services.
**Key pages:**
* [Azure Blob managed identity](/langsmith/self-host-blob-storage#azure-blob-storage)
* [Azure Database Entra authentication](/langsmith/self-host-external-postgres#iam-authentication)
* [Azure Cache Entra authentication](/langsmith/self-host-external-redis#iam-authentication)
After completing these initial setup steps, you can review the complete Azure architecture and best practices below.
## Reference architecture
We recommend using Azure's managed services to provide a scalable, secure, and resilient platform. The following architecture applies to both self-hosted and hybrid deployments:
* **Client interfaces**: Users interact with LangSmith via a web browser or the LangChain SDK. All traffic terminates at an [Azure Load Balancer](https://azure.microsoft.com/en-us/products/load-balancer/) and is routed to the frontend (NGINX) within the [AKS](https://azure.microsoft.com/en-us/products/kubernetes-service/) cluster before being routed to another service within the cluster if necessary.
* **Storage services**: The platform requires persistent storage for traces, metadata and caching. On Azure the recommended services are:
* **[Azure Database for PostgreSQL (Flexible Server)](https://azure.microsoft.com/en-us/products/postgresql/)** for transactional data (e.g., runs, projects). Azure's high-availability options provision a standby replica in another zone; data is synchronously committed to both primary and standby servers. LangSmith requires PostgreSQL version 14 or higher.
* **[Azure Managed Redis](https://azure.microsoft.com/en-us/products/managed-redis/)** for queues and caching. Best practices include storing small values and breaking large objects into multiple keys, using pipelining to maximize throughput and ensuring the client and server reside in the same region. You can also use [Azure Cache for Redis](https://azure.microsoft.com/en-us/products/cache), running either in single-instance or cluster mode. LangSmith requires Redis OSS version 5 or higher.
* **ClickHouse** for high-volume analytics of traces. We recommend using an [externally managed ClickHouse solution](/langsmith/self-host-external-clickhouse). If, for security or compliance reasons, that is not an option, deploy a ClickHouse cluster on AKS using the open-source operator. Ensure replication across [availability zones](https://learn.microsoft.com/en-us/azure/reliability/availability-zones-overview) for durability. Clickhouse is not required for a hybrid deployment.
* **[Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs/)** for large artifacts. Use redundant storage configurations such as read-access geo-redundant (RA-GRS) or geo-zone-redundant (RA-GZRS) storage and design applications to read from the secondary region during an outage.
## Compute and networking on Azure
### Azure Kubernetes Service (AKS)
[AKS](https://azure.microsoft.com/en-us/products/kubernetes-service/) is the recommended compute platform for production deployments. This section outlines the key considerations for planning your setup.
#### Network model
Use [Azure CNI](https://learn.microsoft.com/en-us/azure/aks/configure-azure-cni) networking for production clusters. This model integrates the cluster into an existing virtual network, assigns IP addresses to each pod and node, and allows direct connectivity to on-premises or other Azure services. Ensure the subnet has enough IPs for nodes and pods, avoid overlapping address ranges and allocate additional IP space for scale-out events.
#### Ingress and load balancing
Use Kubernetes Ingress resources and controllers to distribute HTTP/HTTPS traffic. Ingress controllers operate at layer 7 and can route traffic based on URL paths and handle TLS termination. They reduce the number of public IP addresses compared to layer-4 load balancers. Use the [application routing add-on](https://learn.microsoft.com/en-us/azure/aks/app-routing) for managed NGINX ingress controllers integrated with [Azure DNS](https://azure.microsoft.com/en-us/products/dns/) and [Key Vault](https://azure.microsoft.com/en-us/products/key-vault/) for SSL certificates.
#### Web Application Firewall (WAF)
For additional protection against attacks, deploy a [WAF](https://learn.microsoft.com/en-us/azure/web-application-firewall/overview) such as [Azure Application Gateway](https://azure.microsoft.com/en-us/products/application-gateway/). A WAF filters traffic using OWASP rules and can terminate TLS before the traffic reaches your AKS cluster.
#### Network policies
Apply [Kubernetes network policies](https://learn.microsoft.com/en-us/azure/aks/use-network-policies) to restrict pod-to-pod traffic and reduce the impact of compromised workloads. Enable network policy support when creating the cluster and design rules based on application connectivity.
#### High availability
Configure node pools across [availability zones](https://learn.microsoft.com/en-us/azure/reliability/availability-zones-overview) and use Pod Disruption Budgets (PDB) and multiple replicas for all deployments. Set pod resource requests and limits; the [AKS resource management best practices](https://learn.microsoft.com/en-us/azure/aks/developer-best-practices-resource-management) recommend setting CPU and memory limits to prevent pods from consuming all resources. Use [Cluster Autoscaler](https://learn.microsoft.com/en-us/azure/aks/cluster-autoscaler) and [Vertical Pod Autoscaler](https://learn.microsoft.com/en-us/azure/aks/vertical-pod-autoscaler) to scale node pools and adjust pod resources automatically.
### Networking and identity
#### Virtual network integration
Deploy AKS into its own [virtual network](https://azure.microsoft.com/en-us/products/virtual-network/) and create separate subnets for the cluster, database, Redis, and storage endpoints. Use [Private Link](https://azure.microsoft.com/en-us/products/private-link/) and [service endpoints](https://learn.microsoft.com/en-us/azure/virtual-network/virtual-network-service-endpoints-overview) to keep traffic within your virtual network and avoid exposure to the public internet.
#### Authentication
Integrate LangSmith with [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id) (Azure AD) for single sign-on. Use Azure AD OAuth2 for bearer tokens and assign roles to control access to the UI and API.
## Storage and data services
### Azure Database for PostgreSQL
#### High availability
Use [Flexible Server](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/overview) with high-availability mode. Azure provisions a standby replica either within the same availability zone (zonal) or across zones (zone-redundant). Data is synchronously committed to both the primary and standby servers, ensuring that committed data is not lost. Zone-redundant configurations place the standby in a different zone to protect against zone outages but may add write latency.
#### Backups and disaster recovery
Enable [automatic backups](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-backup-restore) and configure geo-redundant backup storage to protect against region-wide outages. For critical applications, create read replicas in a secondary region.
#### Scaling
Choose an appropriate SKU that matches your workload; Flexible Server allows scaling compute and storage independently. Monitor metrics and configure alerts through [Azure Monitor](https://azure.microsoft.com/en-us/products/monitor/).
### Azure Managed Redis
#### Persistence and redundancy
Choose a tier that provides replication and persistence. Configure Redis persistence or data backup for durability. For high-availability, use [active geo-replication](https://learn.microsoft.com/en-us/azure/redis/how-to-active-geo-replication) or zone-redundant caches depending on the tier.
### ClickHouse on Azure
ClickHouse is used for analytical workloads (traces and feedback). If you cannot use an externally managed solution, deploy a ClickHouse cluster on AKS using Helm or the official operator. For resilience, replicate data across nodes and availability zones. Consider using [Azure Disks](https://azure.microsoft.com/en-us/products/storage/disks/) for local storage and mount them as StatefulSets.
### Azure Blob Storage
#### Redundancy
Choose a redundancy configuration based on your recovery objectives. Use [read-access geo-redundant (RA-GRS) or geo-zone-redundant (RA-GZRS) storage](https://learn.microsoft.com/en-us/azure/storage/common/storage-redundancy) and design applications to switch reads to the secondary region during a primary region outage.
#### Naming and partitioning
Use naming conventions that improve load balancing across partitions and plan for the maximum number of concurrent clients. Stay within Azure's scalability and capacity targets and partition data across multiple storage accounts if necessary.
#### Networking
Access blob storage through [private endpoints](https://learn.microsoft.com/en-us/azure/storage/common/storage-private-endpoints) or by using SAS tokens and CORS rules to enable direct client access.
## Security and access control
### Azure Key Vault
#### Separate vaults per application and environment
Store secrets such as database connection strings and API keys in [Azure Key Vault](https://azure.microsoft.com/en-us/products/key-vault/). Use a dedicated vault for each application and environment (dev, test, prod) to limit the impact of a security breach.
#### Access control
Use the [RBAC permission model](https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-guide) to assign roles at the vault scope and restrict access to required principals. Restrict network access using Private Link and firewalls.
#### Data protection and logging
Enable [soft delete and purge protection](https://learn.microsoft.com/en-us/azure/key-vault/general/soft-delete-overview) to prevent accidental deletion. Turn on logging and configure alerts for Key Vault access events.
### Network security
#### Ingress isolation
Expose only the frontend service through the ingress controller or WAF. Other services should be internal and communicate through cluster networking.
#### RBAC and pod security
Use [Kubernetes RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) to control who can deploy, modify, or read resources. Enable [pod security admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/) to enforce baseline, restricted, or privileged profiles.
#### Secrets management
Mount secrets from Key Vault into pods using [CSI Secret Store](https://learn.microsoft.com/en-us/azure/aks/csi-secrets-store-driver). Avoid storing secrets in environment variables or configuration files.
## Observability and monitoring
Configure your LangSmith instance to [export telemetry data](/langsmith/export-backend) so you can use Azure's services to monitor it.
### Azure Monitor
Use [Azure Monitor](https://azure.microsoft.com/en-us/products/monitor/) for metrics, logs, and alerting. Proactive monitoring involves configuring alerts on key signals like node CPU/memory utilization, pod status, and service latency. Azure Monitor alerts notify you when predefined thresholds are exceeded.
### Managed Prometheus and Grafana
Enable [Azure Monitor managed Prometheus](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/prometheus-metrics-overview) to collect Kubernetes metrics. Combine it with [Grafana dashboards](https://azure.microsoft.com/en-us/products/managed-grafana/) for visualization. Define service-level objectives (SLOs) and configure alerts accordingly.
### Container Insights
Install [Container Insights](https://learn.microsoft.com/en-us/azure/azure-monitor/containers/container-insights-overview) to capture logs and metrics from AKS nodes and pods. Use [Azure Log Analytics workspaces](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-overview) to query and analyze logs.
### Application logging
Ensure LangSmith services emit logs to stdout/stderr and forward them via [Fluent Bit](https://fluentbit.io/) or the Azure Monitor agent.
## Continuous integration
* The preferred method to manage [LangSmith deployments](/langsmith/deployments) is to create a CI process that builds [Agent Server](/langsmith/agent-server) images and pushes them to [Azure Container Registry](https://azure.microsoft.com/en-us/products/container-registry). Create a test deployment for pull requests before deploying a new revision to staging or production upon PR merge.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/azure-self-hosted.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to kick off background runs
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/background-run
This guide covers how to kick off background runs for your agent.
This can be useful for long running jobs.
## Setup
First let's set up our client and thread:
```python theme={null}
from langgraph_sdk import get_client
client = get_client(url=)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
# create thread
thread = await client.threads.create()
print(thread)
```
```js theme={null}
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: });
// Using the graph deployed with the name "agent"
const assistantID = "agent";
// create thread
const thread = await client.threads.create();
console.log(thread);
```
```bash theme={null}
curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Output:
```
{
'thread_id': '5cb1e8a1-34b3-4a61-a34e-71a9799bd00d',
'created_at': '2024-08-30T20:35:52.062934+00:00',
'updated_at': '2024-08-30T20:35:52.062934+00:00',
'metadata': {},
'status': 'idle',
'config': {},
'values': None
}
```
## Check runs on thread
If we list the current runs on this thread, we will see that it's empty:
```python theme={null}
runs = await client.runs.list(thread["thread_id"])
print(runs)
```
```js theme={null}
let runs = await client.runs.list(thread['thread_id']);
console.log(runs);
```
```bash theme={null}
curl --request GET \
--url /threads//runs
```
Output:
```
[]
```
## Start runs on thread
Now let's kick off a run:
```python theme={null}
input = {"messages": [{"role": "user", "content": "what's the weather in sf"}]}
run = await client.runs.create(thread["thread_id"], assistant_id, input=input)
```
```js theme={null}
let input = {"messages": [{"role": "user", "content": "what's the weather in sf"}]};
let run = await client.runs.create(thread["thread_id"], assistantID, { input });
```
```bash theme={null}
curl --request POST \
--url /threads//runs \
--header 'Content-Type: application/json' \
--data '{
"assistant_id":
}'
```
The first time we poll it, we can see `status=pending`:
```python theme={null}
print(await client.runs.get(thread["thread_id"], run["run_id"]))
```
```js theme={null}
console.log(await client.runs.get(thread["thread_id"], run["run_id"]));
```
```bash theme={null}
curl --request GET \
--url /threads//runs/
```
Output:
```
{
"run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b",
"thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a",
"assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca",
"created_at": "2024-09-04T01:46:47.244887+00:00",
"updated_at": "2024-09-04T01:46:47.244887+00:00",
"metadata": {},
"status": "pending",
"kwargs": {
"input": {
"messages": [
{
"role": "user",
"content": "what's the weather in sf"
}
]
},
"config": {
"metadata": {
"created_by": "system"
},
"configurable": {
"run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b",
"user_id": "",
"graph_id": "agent",
"thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a",
"assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca",
"checkpoint_id": null
}
},
"webhook": null,
"temporary": false,
"stream_mode": [
"values"
],
"feedback_keys": null,
"interrupt_after": null,
"interrupt_before": null
},
"multitask_strategy": "reject"
}
```
Now we can join the run, wait for it to finish and check that status again:
```python theme={null}
await client.runs.join(thread["thread_id"], run["run_id"])
print(await client.runs.get(thread["thread_id"], run["run_id"]))
```
```js theme={null}
await client.runs.join(thread["thread_id"], run["run_id"]);
console.log(await client.runs.get(thread["thread_id"], run["run_id"]));
```
```bash theme={null}
curl --request GET \
--url /threads//runs//join &&
curl --request GET \
--url /threads//runs/
```
Output:
```
{
"run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b",
"thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a",
"assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca",
"created_at": "2024-09-04T01:46:47.244887+00:00",
"updated_at": "2024-09-04T01:46:47.244887+00:00",
"metadata": {},
"status": "success",
"kwargs": {
"input": {
"messages": [
{
"role": "user",
"content": "what's the weather in sf"
}
]
},
"config": {
"metadata": {
"created_by": "system"
},
"configurable": {
"run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b",
"user_id": "",
"graph_id": "agent",
"thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a",
"assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca",
"checkpoint_id": null
}
},
"webhook": null,
"temporary": false,
"stream_mode": [
"values"
],
"feedback_keys": null,
"interrupt_after": null,
"interrupt_before": null
},
"multitask_strategy": "reject"
}
```
Perfect! The run succeeded as we would expect. We can double check that the run worked as expected by printing out the final state:
```python theme={null}
final_result = await client.threads.get_state(thread["thread_id"])
print(final_result)
```
```js theme={null}
let finalResult = await client.threads.getState(thread["thread_id"]);
console.log(finalResult);
```
```bash theme={null}
curl --request GET \
--url /threads//state
```
Output:
```
{
"values": {
"messages": [
{
"content": "what's the weather in sf",
"additional_kwargs": {},
"response_metadata": {},
"type": "human",
"name": null,
"id": "beba31bf-320d-4125-9c37-cadf526ac47a",
"example": false
},
{
"content": [
{
"id": "toolu_01AaNPSPzqia21v7aAKwbKYm",
"input": {},
"name": "tavily_search_results_json",
"type": "tool_use",
"index": 0,
"partial_json": "{\"query\": \"weather in san francisco\"}"
}
],
"additional_kwargs": {},
"response_metadata": {
"stop_reason": "tool_use",
"stop_sequence": null
},
"type": "ai",
"name": null,
"id": "run-f220faf8-1d27-4f73-ad91-6bb3f47e8639",
"example": false,
"tool_calls": [
{
"name": "tavily_search_results_json",
"args": {
"query": "weather in san francisco"
},
"id": "toolu_01AaNPSPzqia21v7aAKwbKYm",
"type": "tool_call"
}
],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 273,
"output_tokens": 61,
"total_tokens": 334
}
},
{
"content": "[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.78, 'lon': -122.42, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1725052131, 'localtime': '2024-08-30 14:08'}, 'current': {'last_updated_epoch': 1725051600, 'last_updated': '2024-08-30 14:00', 'temp_c': 21.1, 'temp_f': 70.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 11.9, 'wind_kph': 19.1, 'wind_degree': 290, 'wind_dir': 'WNW', 'pressure_mb': 1018.0, 'pressure_in': 30.07, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 59, 'cloud': 25, 'feelslike_c': 21.1, 'feelslike_f': 70.0, 'windchill_c': 18.6, 'windchill_f': 65.5, 'heatindex_c': 18.6, 'heatindex_f': 65.5, 'dewpoint_c': 12.2, 'dewpoint_f': 54.0, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 15.0, 'gust_kph': 24.2}}\"}]",
"additional_kwargs": {},
"response_metadata": {},
"type": "tool",
"name": "tavily_search_results_json",
"id": "686b2487-f332-4e58-9508-89b3a814cd81",
"tool_call_id": "toolu_01AaNPSPzqia21v7aAKwbKYm",
"artifact": {
"query": "weather in san francisco",
"follow_up_questions": null,
"answer": null,
"images": [],
"results": [
{
"title": "Weather in San Francisco",
"url": "https://www.weatherapi.com/",
"content": "{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.78, 'lon': -122.42, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1725052131, 'localtime': '2024-08-30 14:08'}, 'current': {'last_updated_epoch': 1725051600, 'last_updated': '2024-08-30 14:00', 'temp_c': 21.1, 'temp_f': 70.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 11.9, 'wind_kph': 19.1, 'wind_degree': 290, 'wind_dir': 'WNW', 'pressure_mb': 1018.0, 'pressure_in': 30.07, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 59, 'cloud': 25, 'feelslike_c': 21.1, 'feelslike_f': 70.0, 'windchill_c': 18.6, 'windchill_f': 65.5, 'heatindex_c': 18.6, 'heatindex_f': 65.5, 'dewpoint_c': 12.2, 'dewpoint_f': 54.0, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 15.0, 'gust_kph': 24.2}}",
"score": 0.976148,
"raw_content": null
}
],
"response_time": 3.07
},
"status": "success"
},
{
"content": [
{
"text": "\n\nThe search results provide the current weather conditions in San Francisco. According to the data, as of 2:00 PM on August 30, 2024, the temperature in San Francisco is 70\u00b0F (21.1\u00b0C) with partly cloudy skies. The wind is blowing from the west-northwest at around 12 mph (19 km/h). The humidity is 59% and visibility is 9 miles (16 km). Overall, it looks like a nice late summer day in San Francisco with comfortable temperatures and partly sunny conditions.",
"type": "text",
"index": 0
}
],
"additional_kwargs": {},
"response_metadata": {
"stop_reason": "end_turn",
"stop_sequence": null
},
"type": "ai",
"name": null,
"id": "run-8fecc61d-3d9f-4e16-8e8a-92f702be498a",
"example": false,
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 837,
"output_tokens": 124,
"total_tokens": 961
}
}
]
},
"next": [],
"tasks": [],
"metadata": {
"step": 3,
"run_id": "1ef67140-eb23-684b-8253-91d4c90bb05e",
"source": "loop",
"writes": {
"agent": {
"messages": [
{
"id": "run-8fecc61d-3d9f-4e16-8e8a-92f702be498a",
"name": null,
"type": "ai",
"content": [
{
"text": "\n\nThe search results provide the current weather conditions in San Francisco. According to the data, as of 2:00 PM on August 30, 2024, the temperature in San Francisco is 70\u00b0F (21.1\u00b0C) with partly cloudy skies. The wind is blowing from the west-northwest at around 12 mph (19 km/h). The humidity is 59% and visibility is 9 miles (16 km). Overall, it looks like a nice late summer day in San Francisco with comfortable temperatures and partly sunny conditions.",
"type": "text",
"index": 0
}
],
"example": false,
"tool_calls": [],
"usage_metadata": {
"input_tokens": 837,
"total_tokens": 961,
"output_tokens": 124
},
"additional_kwargs": {},
"response_metadata": {
"stop_reason": "end_turn",
"stop_sequence": null
},
"invalid_tool_calls": []
}
]
}
},
"user_id": "",
"graph_id": "agent",
"thread_id": "5cb1e8a1-34b3-4a61-a34e-71a9799bd00d",
"created_by": "system",
"assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca"
},
"created_at": "2024-08-30T21:09:00.079909+00:00",
"checkpoint_id": "1ef67141-3ca2-6fae-8003-fe96832e57d6",
"parent_checkpoint_id": "1ef67141-2129-6b37-8002-61fc3bf69cb5"
}
```
We can also just print the content of the last AIMessage:
```python theme={null}
print(final_result['values']['messages'][-1]['content'][0]['text'])
```
```js theme={null}
console.log(finalResult['values']['messages'][finalResult['values']['messages'].length-1]['content'][0]['text']);
```
```bash theme={null}
curl --request GET \
--url /threads//state | jq -r '.values.messages[-1].content.[0].text'
```
Output:
```
The search results provide the current weather conditions in San Francisco. According to the data, as of 2:00 PM on August 30, 2024, the temperature in San Francisco is 70°F (21.1°C) with partly cloudy skies. The wind is blowing from the west-northwest at around 12 mph (19 km/h). The humidity is 59% and visibility is 9 miles (16 km). Overall, it looks like a nice late summer day in San Francisco with comfortable temperatures and partly sunny conditions.
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/background-run.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Manage billing in your account
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/billing
This page describes how to manage billing for your LangSmith organization:
* [Set up billing for your account](#set-up-billing-for-your-account): Complete the billing setup process for Developer and Plus plans, including special instructions for legacy accounts.
* [Track contract usage (Enterprise)](#track-contract-usage-enterprise): Monitor your prepaid commitment consumption and usage patterns.
* [Update your information](#update-your-information): Modify invoice email addresses, business information, and tax IDs for your organization.
* [Enforce spend limits](#enforce-spend-limits): Learn how to manage your spend through usage limits and data retention.
## Set up billing for your account
Before using this guide, note the following:
* If you are interested in the [Enterprise](https://www.langchain.com/pricing) plan, please [contact sales](https://www.langchain.com/contact-sales). This guide is only for our self-serve billing plans.
To set up billing for your LangSmith organization, navigate to the [Billing and Usage](https://smith.langchain.com/settings/payments) page under **Settings**. Depending on your organization's settings, there are different setup guides:
* [Developer plan](#developer-plan%3A-set-up-billing-on-your-personal-organization)
* [Plus plan](#plus-plan%3A-set-up-billing-on-a-shared-organization)
### Developer plan: set up billing on your personal organization
Personal organizations are limited to 5,000 traces per month until a credit card is added. To add a card:
1. Click **Add card to remove trace limit**.
2. Add your credit card information.
3. Once complete, you will no longer be rate limited to 5,000 traces, and you will be charged for any excess traces at rates specified on the [pricing](https://www.langchain.com/pricing-langsmith) page.
### Plus plan: set up billing on a shared organization
Team organizations are given an initial 10,000 traces per month. Any excess traces will be charged at rates specified on the [pricing](https://www.langchain.com/pricing-langsmith) page.
New organizations that you manually create are required to be on the Plus Plan. If you see a message about needing to upgrade to Plus to use this organization, follow these steps.
1. Click **Upgrade to Plus**.
2. Invite members to your organization, as desired.
3. Enter your credit card information. Then, enter business information, invoice email, and tax ID. If this organization belongs to a business, check the **This is a business** checkbox and enter the information accordingly. For more information, refer to the [Update your information section](#update-your-information).
## Track contract usage (Enterprise)
Contract burndown tracking is available for **Enterprise plan** customers with prepaid commitments. You must have the `organization:manage` permission to access this feature.
For more details on the Enterprise plan, [contact the sales team](https://www.langchain.com/contact-sales).
The contract burndown feature provides enterprise customers with visibility into their prepaid contract usage. It displays how much of your committed credits have been consumed, tracks monthly usage patterns, and shows your product rates.
### View contract burndown
To view your contract usage:
1. Navigate to **Settings** > **Usage Configuration**.
2. Select the **Contract Usage** tab.
You'll see:
* **Usage summary**: Total usage, total credits, and any overages. Overages occur when usage exceeds your total prepaid commitment.
* **Commitment progress bars**: Visual indicators for each commitment showing percentage consumed and dollar amounts for used vs. remaining. Contracts can have multiple commitments that apply to different time periods (e.g., year 1 and year 2 of a multi-year contract) or different products.
* **Monthly usage chart**: Bar chart showing usage amounts for each month within your contract period.
* **Product rates**: Table of your entitled products and their pricing.
## Update your information (Paid plans only)
To update business information for your LangSmith organization, head to the [Billing and Usage](https://smith.langchain.com/settings/payments) page under **Settings**.
### Invoice email
To update the email address for invoices, follow these steps:
1. Navigate to the **Plans and Billing** tab.
2. Locate the section beneath the payment method, where the current invoice email is displayed.
3. Enter the new email address for invoices in the provided field.
4. The new email address will be automatically saved.
You will receive all future invoices to the updated email address.
### Business information and tax ID
In certain jurisdictions, LangSmith is required to collect sales tax. If you are a business, providing your tax ID may qualify you for a sales tax exemption.
To update your organization's business information, follow these steps:
1. Navigate to the **Plans and Billing** tab.
2. Below the invoice email section, you will find a checkbox labeled **Business**.
3. Check the **Business** checkbox if your organization belongs to a business.
4. A business information section will appear, allowing you to enter or update the following details:
* Business Name
* Address
* Tax ID for applicable jurisdictions
5. A Tax ID field will appear for applicable jurisdictions after you select a country.
6. After entering the necessary information, click the **Save** button to save your changes.
This ensures that your business information is up-to-date and accurate for billing and tax purposes.
## Enforce spend limits
You may find it helpful to read the following pages, before continuing with this section on optimizing your tracing spend:
* [Data Retention Conceptual Docs](/langsmith/administration-overview#data-retention)
* [Usage Limiting Conceptual Docs](/langsmith/administration-overview#usage-limits)
Some of the features mentioned in this guide are not currently available on Enterprise plan due to its custom nature of billing. If you are on the Enterprise plan and have questions about cost optimization, contact your sales rep or support via [support.langchain.com](https://support.langchain.com).
### Understand your current usage
The first step of any optimization process is to understand current usage. LangSmith provides two ways to do this: [Usage graph](#usage-graph) and [Invoices](#invoices).
LangSmith Usage is measured per workspace, because workspaces often represent development environments (as in the example), or teams within an organization.
#### Usage graph
The usage graph lets you examine how much of each usage-based pricing metric you have consumed. It does not directly show spend (which you will review later in the draft invoice).
Navigate to the usage graph under **Settings** -> **Billing and Usage** -> **Usage Graph**.
There are several usage metrics that LangSmith charges for:
* LangSmith Traces (Base Charge): tracks all traces that you send to LangSmith.
* LangSmith Traces (Extended Data Retention Upgrades): tracks all traces that also have our Extended 400 Day Data Retention.
* LangSmith Deployment Runs: tracks end-to-end invocations of deployed LangGraph agents.
For more details on traces, refer to the [data retention conceptual docs](/langsmith/administration-overview#data-retention). For more details on Deployment Runs, refer to [LangSmith Deployment billing](#langsmith-deployment-billing).
#### Invoices
To understand how your usage translates to spend, navigate to the **Invoices** tab. The first invoice that will appear on screen is a draft of your current month's invoice, which shows your running spend thus far this month.
LangSmith's Usage Graph and Invoice use the term `tenant_id` to refer to a workspace ID. They are interchangeable.
### Set limits on usage
#### Set spend limit for workspace
1. To set limits, navigate to **Settings** -> **Billing and Usage** -> **Usage limits**.
2. Input a spend limit for your selected workspace. LangSmith will determine an appropriate number of base and extended trace limits to match that spend. The trace limits include the free trace allocation that comes with your plan (see details on [pricing page](https://smith.langchain.com/settings/payments)).
For organizations with **multiple workspaces only**: For simplicity, LangSmith incorporates the free traces into the cost calculation of the **first workspace only**. In actuality, the free traces can be "consumed" by any workspace. Therefore, although workspace-level spend limits are approximate for multi-workspace organizations, the organization-level spend limit is absolute.
#### Configure trace tier distribution
LangSmith has two trace tiers: base traces and extended traces. Base traces have the base retention and are short-lived (14 days), while extended traces have extended retention and are long-lived (400 days). For more information, refer to the [data retention conceptual docs](/langsmith/administration-overview#data-retention).
Set the desired default trace tier by selecting an option below the **Default data retention** label. All traces will have this tier by default when they are registered. Note that because extended traces cost more than base traces, selecting **Extended** as your default data retention option will result in less overall traces allowed in the billing period. By default, updating this setting will only apply to future incoming traces. To apply to all existing traces in the workspace, select the checkbox.
If the default data retention is set to **Base** you can optionally use the slider to distribute trace limits across base and extended tracess. LangSmith automatically provides a suggestion for this distribution but you can tailor this to your needs. For example, if you are running lots of automations or other features that may upgrade a trace to extended, you may want to increase your extended trace limits. To see the complete list of features that may upgrade a trace, [see here](https://docs.langchain.com/langsmith/administration-overview#how-it-works:~:text=Data%20retention%20auto%2Dupgrades).
The extended data retention limit can cause features other than tracing to stop working once reached. If you plan to use this feature, read more about its [functionality and side effects](/langsmith/administration-overview#side-effects-of-extended-data-retention-traces-limit).
### Other methods of managing traces
#### Change project-level default retention
Data retention settings are adjustable per tracing project.
Navigate to **Projects** > ***Your project name*** > Select **Retention** and select the desired default retention. This will only affect retention (and pricing) for **traces going forward**.
#### Apply extended data retention to a percentage of traces
You may not want all traces to expire after 14 days. You can automatically extend the retention of traces that match some criteria by creating an [automation rule](/langsmith/rules). You might want to apply extended data retention to specific types of traces, such as:
* 10% of all traces: For general analysis or analyzing trends long term.
* Errored traces: To investigate and debug issues thoroughly.
* Traces with specific metadata: For long-term examination of particular features or user flows.
To configure this:
1. Navigate to **Projects** > ***Your project name*** > Select **+ New** > Select **New Automation**.
2. Name your rule and optionally apply filters or a sample rate. For more information on configuring filters, refer to [filtering techniques](/langsmith/filter-traces-in-application#filter-operators).
When an automation rule matches any [run](/langsmith/observability-concepts#runs) within a [trace](/langsmith/observability-concepts#traces), then all runs within the trace are upgraded to be retained for 400 days.
For example, this is the expected configuration to keep 10% of all traces for extended data retention:
If you want to keep a subset of traces for **longer than 400 days** for data collection purposes, you can create another run rule that sends some runs to a dataset of your choosing. A dataset allows you to store the trace inputs and outputs (e.g., as a key-value dataset), and will persist indefinitely, even after the trace gets deleted.
### LangSmith Deployment billing
In addition to traces, LangSmith charges for deployed agents via LangSmith Deployment (formerly LangGraph Platform).
* **Deployment Runs**: A one end-to-end invocation of a deployed LangGraph agent and is billed at \$0.005 each. Nodes and subgraphs within a single agent execution are not charged separately. Calls to other LangGraph agents are charged separately to the deployment hosting the called agent. When using human-in-the-loop with interrupts, resuming after an interrupt creates a separate Deployment Run.
* **Deployment Uptime**: You are also charged for the time your deployment's database is live and persisting state. See the [pricing page](https://www.langchain.com/pricing) for uptime costs by deployment type (Development vs Production).
For high-volume deployment usage, please [contact our sales team](https://www.langchain.com/contact-sales) to discuss custom pricing options.
### Summary
If you have questions about further managing your spend, please contact support via [support.langchain.com](https://support.langchain.com).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/billing.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Automatically run evaluators on experiments
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/bind-evaluator-to-dataset
LangSmith supports two ways to grade experiments created via the SDK:
* **Programmatically**, by specifying evaluators in your code (see [this guide](/langsmith/evaluate-llm-application) for details)
* By **binding evaluators to a dataset** in the UI. This will automatically run the evaluators on any new experiments created, in addition to any evaluators you've set up via the SDK. This is useful when you're iterating on your application (target function), and have a standard set of evaluators you want to run for all experiments.
## Configuring an evaluator on a dataset
1. Click on the **Datasets and Experiments** tab in the sidebar.
2. Select the dataset you want to configure the evaluator for.
3. Click on the **+ Evaluator** button to add an evaluator to the dataset. This will open a pane you can use to configure the evaluator.
When you configure an evaluator for a dataset, it will only affect the experiment runs that are created after the evaluator is configured. It will not affect the evaluation of experiment runs that were created before the evaluator was configured.
## LLM-as-a-judge evaluators
The process for binding evaluators to a dataset is very similar to the process for configuring a LLM-as-a-judge evaluator in the Playground. View instructions for [configuring an LLM-as-a-judge evaluator in the Playground.](/langsmith/llm-as-judge?mode=ui)
## Custom code evaluators
The process for binding a code evaluators to a dataset is very similar to the process for configuring a code evaluator in online evaluation. View instruction for [configuring code evaluators](/langsmith/online-evaluations-code).
The only difference between configuring a code evaluator in online evaluation and binding a code evaluator to a dataset is that the custom code evaluator can reference outputs that are part of the dataset's `Example`.
For custom code evaluators bound to a dataset, the evaluator function takes in two arguments:
* A `Run` ([reference](/langsmith/run-data-format)). This represents the new run in your experiment. For example, if you ran an experiment via SDK, this would contain the input/output from your chain or model you are testing.
* An `Example` ([reference](/langsmith/example-data-format)). This represents the reference example in your dataset that the chain or model you are testing uses. The `inputs` to the Run and Example should be the same. If your Example has a reference `outputs`, then you can use this to compare to the run's output for scoring.
The code below shows an example of a simple evaluator function that checks that the outputs exactly equal the reference outputs.
```python Python theme={null}
import numpy as np
def perform_eval(run, example):
# run is a Run object
# example is an Example object
output = run['outputs']['output']
ref_output = example['outputs']['outputs']
output_match = np.array_equal(output, ref_output)
return { "exact_match": output_match }
```
```javascript JavaScript theme={null}
function perform_eval(run, example) {
// run is a Run object
// example is an Example object
const output = run.outputs.output;
const refOutput = example.outputs.outputs;
// Deep equality check for arrays/objects
const outputMatch = JSON.stringify(output) === JSON.stringify(refOutput);
return { "exact_match": outputMatch };
}
```
## Next steps
* Analyze your experiment results in the [experiments tab](/langsmith/analyze-an-experiment)
* Compare your experiment results in the [comparison view](/langsmith/compare-experiment-results)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/bind-evaluator-to-dataset.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Implement a CI/CD pipeline using LangSmith Deployment and Evaluation
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/cicd-pipeline-example
This guide demonstrates how to implement a comprehensive CI/CD pipeline for AI agent applications deployed in LangSmith Deployment. In this example, you'll use the [LangGraph](/oss/python/langgraph/overview) open source framework for orchestrating and building the agent, [LangSmith](/langsmith/home) for observability and evaluations. This pipeline is based on the [cicd-pipeline-example repository](https://github.com/langchain-ai/cicd-pipeline-example).
## Overview
The CI/CD pipeline provides:
* **Automated testing**: Unit, integration, and end-to-end tests.
* **Offline evaluations**: Performance assessment using [AgentEvals](https://github.com/langchain-ai/agentevals), [OpenEvals](https://github.com/langchain-ai/openevals) and [LangSmith](https://docs.langchain.com/langsmith/home).
* **Preview and production deployments**: Automated staging and quality-gated production releases using the Control Plane API.
* **Monitoring**: Continuous evaluation and alerting.
## Pipeline architecture
The CI/CD pipeline consists of several key components that work together to ensure code quality and reliable deployments:
```mermaid theme={null}
graph TD
A1[Code or Graph Change] --> B1[Trigger CI Pipeline]
A2[Prompt Commit in PromptHub] --> B1
A3[Online Evaluation Alert] --> B1
A4[PR Opened] --> B1
subgraph "Testing"
B1 --> C1[Run Unit Tests]
B1 --> C2[Run Integration Tests]
B1 --> C3[Run End to End Tests]
B1 --> C4[Run Offline Evaluations]
C4 --> D1[Evaluate with OpenEvals or AgentEvals]
C4 --> D2[Assertions: Hard and Soft]
C1 --> E1[Run LangGraph Dev Server Test]
C2 --> E1
C3 --> E1
D1 --> E1
D2 --> E1
end
E1 --> F1[Push to Staging Deployment - Deploy to LangSmith as Development Type]
F1 --> G1[Run Online Evaluations on Live Data]
G1 --> H1[Attach Scores to Traces]
H1 --> I1[If Quality Below Threshold]
I1 --> J1[Send to Annotation Queue]
I1 --> J2[Trigger Alert via Webhook]
I1 --> J3[Push Trace to Golden Dataset]
F1 --> K1[Promote to Production if All Pass - Deploy to LangSmith Production]
J2 --> L1[Slack or PagerDuty Notification]
subgraph Manual Review
J1 --> M1[Human Labeling]
M1 --> J3
end
```
### Trigger sources
There are multiple ways you can trigger this pipeline, either during development or if your application is already live. The pipeline can be triggered by:
* **Code changes**: Pushes to main/development branches where you can modify the LangGraph architecture, try different models, update agent logic, or make any code improvements.
* **PromptHub updates**: Changes to prompt templates stored in LangSmith PromptHub—whenever there's a new prompt commit, the system triggers a webhook to run the pipeline.
* **Online evaluation alerts**: Performance degradation notifications from live deployments
* **LangSmith traces webhooks**: Automated triggers based on trace analysis and performance metrics.
* **Manual trigger**: Manual initiation of the pipeline for testing or emergency deployments.
### Testing layers
Compared to traditional software, testing AI agent applications also requires assessing response quality, so it is important to test each part of the workflow. The pipeline implements multiple testing layers:
1. **Unit tests**: Individual node and utility function testing.
2. **Integration tests**: Component interaction testing.
3. **End-to-end tests**: Full graph execution testing.
4. **Offline evaluations**: Performance assessment with real-world scenarios including end-to-end evaluations, single-step evaluations, agent trajectory analysis, and multi-turn simulations.
5. **LangGraph dev server tests**: Use the [langgraph-cli](/langsmith/cli) tool for spinning up (inside the GitHub Action) a local server to run the LangGraph agent. This polls the `/ok` server API endpoint until it is available and for 30 seconds, after that it throws an error.
## GitHub actions workflow
The CI/CD pipeline uses GitHub Actions with the [Control Plane API](/langsmith/api-ref-control-plane) and [LangSmith API](https://api.smith.langchain.com/redoc) to automate deployment. A helper script manages API interactions and deployments: [https://github.com/langchain-ai/cicd-pipeline-example/blob/main/.github/scripts/langgraph\_api.py](https://github.com/langchain-ai/cicd-pipeline-example/blob/main/.github/scripts/langgraph_api.py).
The workflow includes:
* **New agent deployment**: When a new PR is opened and tests pass, a new preview deployment is created in LangSmith Deployment using the [Control Plane API](/langsmith/api-ref-control-plane). This allows you to test the agent in a staging environment before promoting to production.
* **Agent deployment revision**: A revision happens when an existing deployment with the same ID is found, or when the PR is merged into main. In the case of merging to main, the preview deployment is deleted and a production deployment is created. This ensures that any updates to the agent are properly deployed and integrated into the production infrastructure.
* **Testing and evaluation workflow**: In addition to the more traditional testing phases (unit tests, integration tests, end-to-end tests, etc.), the pipeline includes [offline evaluations](/langsmith/evaluation-concepts#offline-evaluation) and [Agent dev server testing](/langsmith/local-server) because you want to test the quality of your agent. These evaluations provide comprehensive assessment of the agent's performance using real-world scenarios and data.
Evaluates the final output of your agent against expected results. This is the most common type of evaluation that checks if the agent's final response meets quality standards and answers the user's question correctly.
Tests individual steps or nodes within your LangGraph workflow. This allows you to validate specific components of your agent's logic in isolation, ensuring each step functions correctly before testing the full pipeline.
Analyzes the complete path your agent takes through the graph, including all intermediate steps and decision points. This helps identify bottlenecks, unnecessary steps, or suboptimal routing in your agent's workflow. It also evaluates whether your agent invoked the right tools in the right order or at the right time.
Tests conversational flows where the agent maintains context across multiple interactions. This is crucial for agents that handle follow-up questions, clarifications, or extended dialogues with users.
See the [LangGraph testing documentation](/oss/python/langgraph/test) for specific testing approaches and the [evaluation approaches guide](/langsmith/evaluation-approaches) for a comprehensive overview of offline evaluations.
### Prerequisites
Before setting up the CI/CD pipeline, ensure you have:
* An AI agent application (in this case built using [LangGraph](/oss/python/langgraph/overview))
* A [LangSmith account](https://smith.langchain.com/)
* A [LangSmith API key](/langsmith/create-account-api-key) needed to deploy agents and retrieve experiment results
* Project-specific environment variables configured in your repository secrets (e.g., LLM model API keys, vector store credentials, database connections)
While this example uses GitHub, the CI/CD pipeline works with other Git hosting platforms including GitLab, Bitbucket, and others.
## Deployment options
LangSmith supports multiple deployment methods, depending on how your [LangSmith instance is hosted](/langsmith/platform-setup):
* **Cloud LangSmith**: Direct GitHub integration.
* **Self-Hosted/Hybrid**: Container registry-based deployments.
The deployment flow starts by modifying your agent implementation. At minimum, you must have a [`langgraph.json`](/langsmith/application-structure) and dependency file in your project (`requirements.txt` or `pyproject.toml`). Use the `langgraph dev` CLI tool to check for errors—fix any errors; otherwise, the deployment will succeed when deployed to LangSmith Deployment.
```mermaid theme={null}
graph TD
A[Agent Implementation] --> B[langgraph.json + dependencies]
B --> C[Test Locally with langgraph dev]
C --> D{Errors?}
D -->|Yes| E[Fix Issues]
E --> C
D -->|No| F[Choose LangSmith Instance]
F --> G[Cloud LangSmith]
F --> H[Self-Hosted/Hybrid LangSmith]
subgraph "Cloud LangSmith"
G --> I[Method 1: Connect GitHub Repo in UI]
G --> J[Method 2: Control Plane API with GitHub Repo]
I --> K[Deploy via LangSmith UI]
J --> L[Deploy via Control Plane API]
end
subgraph "Self-Hosted/Hybrid LangSmith"
H --> S[Build Docker Image langgraph build]
S --> T[Push to Container Registry]
T --> U{Deploy via?}
U -->|UI| V[Specify Image URI in UI]
U -->|API| W[Use Control Plane API]
V --> X[Deploy via LangSmith UI]
W --> Y[Deploy via Control Plane API]
end
K --> AA[Agent Ready for Use]
L --> AA
X --> AA
Y --> AA
AA --> BB{Connect via?}
BB -->|LangGraph SDK| CC[Use LangGraph SDK]
BB -->|RemoteGraph| DD[Use RemoteGraph]
BB -->|REST API| EE[Use REST API]
BB -->|LangGraph Studio UI| FF[Use LangGraph Studio UI]
```
### Prerequisites for manual deployment
Before deploying your agent, ensure you have:
1. **LangGraph graph**: Your agent implementation (e.g., `./agents/simple_text2sql.py:agent`).
2. **Dependencies**: Either `requirements.txt` or `pyproject.toml` with all required packages.
3. **Configuration**: `langgraph.json` file specifying:
* Path to your agent graph
* Dependencies location
* Environment variables
* Python version
Example `langgraph.json`:
```json theme={null}
{
"graphs": {
"simple_text2sql": "./agents/simple_text2sql.py:agent"
},
"env": ".env",
"python_version": "3.11",
"dependencies": ["."],
"image_distro": "wolfi"
}
```
### Local development and testing
First, test your agent locally using [Studio](/langsmith/studio):
```bash theme={null}
# Start local development server with Studio
langgraph dev
```
This will:
* Spin up a local server with Studio.
* Allow you to visualize and interact with your graph.
* Validate that your agent works correctly before deployment.
If your agent runs locally without any errors, it means that deployment to LangSmith will likely succeed. This local testing helps catch configuration issues, dependency problems, and agent logic errors before attempting deployment.
See the [LangGraph CLI documentation](/langsmith/cli#dev) for more details.
### Method 1: LangSmith Deployment UI
Deploy your agent using the LangSmith deployment interface:
1. Go to your [LangSmith dashboard](https://smith.langchain.com).
2. Navigate to the **Deployments** section.
3. Click the **+ New Deployment** button in the top right.
4. Select your GitHub repository containing your LangGraph agent from the dropdown menu.
**Supported deployments:**
* **Cloud LangSmith**: Direct GitHub integration with dropdown menu
* **Self-Hosted/Hybrid LangSmith**: Specify your image URI in the Image Path field (e.g., `docker.io/username/my-agent:latest`)
**Benefits:**
* Simple UI-based deployment
* Direct integration with your GitHub repository (cloud)
* No manual Docker image management required (cloud)
### Method 2: Control plane API
Deploy using the Control Plane API with different approaches for each deployment type:
**For Cloud LangSmith:**
* Use the Control Plane API to create deployments by pointing to your GitHub repository
* No Docker image building required for cloud deployments
**For Self-Hosted/Hybrid LangSmith:**
```bash theme={null}
# Build Docker image
langgraph build -t my-agent:latest
# Push to your container registry
docker push my-agent:latest
```
You can push to any container registry (Docker Hub, AWS ECR, Azure ACR, Google GCR, etc.) that your deployment environment has access to.
**Supported deployments:**
* **Cloud LangSmith**: Use the Control Plane API to create deployments from your GitHub repository
* **Self-Hosted/Hybrid LangSmith**: Use the Control Plane API to create deployments from your container registry
See the [LangGraph CLI build documentation](/langsmith/cli#build) for more details.
### Connect to your deployed Agent
* **[LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph-sdk-python)**: Use the LangGraph SDK for programmatic integration.
* **[RemoteGraph](/langsmith/use-remote-graph)**: Connect using RemoteGraph for remote graph connections (to use your graph in other graphs).
* **[REST API](/langsmith/server-api-ref)**: Use HTTP-based interactions with your deployed agent.
* **[Studio](/langsmith/studio)**: Access the visual interface for testing and debugging.
### Environment configuration
#### Database & cache configuration
By default, LangSmith Deployment create PostgreSQL and Redis instances for you. To use external services, set the following environment variables in your new deployment or revision:
```bash theme={null}
# Set environment variables for external services
export POSTGRES_URI_CUSTOM="postgresql://user:pass@host:5432/db"
export REDIS_URI_CUSTOM="redis://host:6379/0"
```
See the [environment variables documentation](/langsmith/env-var#postgres-uri-custom) for more details.
## Troubleshooting
### Wrong API endpoints
If you're experiencing connection issues, verify you're using the correct endpoint format for your LangSmith instance. There are two different APIs with different endpoints:
#### LangSmith API (Traces, ingestion, etc.)
For LangSmith API operations (traces, evaluations, datasets):
| Region | Endpoint |
| ------ | ------------------------------------ |
| US | `https://api.smith.langchain.com` |
| EU | `https://eu.api.smith.langchain.com` |
For self-hosted LangSmith instances, use `http(s):///api` where `` is your self-hosted instance URL.
If you're setting the endpoint in the `LANGSMITH_ENDPOINT` environment variable, you need to add `/v1` at the end (e.g., `https://api.smith.langchain.com/v1` or `http(s):///api/v1` if self-hosted).
#### LangSmith Deployment API (Deployments)
For LangSmith Deployment operations (deployments, revisions):
| Region | Endpoint |
| ------ | ----------------------------------- |
| US | `https://api.host.langchain.com` |
| EU | `https://eu.api.host.langchain.com` |
For self-hosted LangSmith instances, use `http(s):///api-host` where `` is your self-hosted instance URL.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/cicd-pipeline-example.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangGraph CLI
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/cli
**LangGraph CLI** is a command-line tool for building and running the [Agent Server](/langsmith/agent-server) locally. The resulting server exposes all API endpoints for runs, threads, assistants, etc., and includes supporting services such as a managed database for checkpointing and storage.
## Installation
1. Ensure Docker is installed (e.g., `docker --version`).
2. Install the CLI:
```bash [Python (pip)] theme={null}
pip install langgraph-cli
```
```bash JavaScript theme={null}
# Use latest on demand
npx @langchain/langgraph-cli
# Or install globally (available as `langgraphjs`)
npm install -g @langchain/langgraph-cli
```
3. Verify the install
```bash [Python (pip)] theme={null}
langgraph --help
```
```bash JavaScript theme={null}
npx @langchain/langgraph-cli --help
```
### Quick commands
| Command | What it does |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| [`langgraph dev`](#dev) | Starts a lightweight local dev server (no Docker required), ideal for rapid testing. |
| [`langgraph build`](#build) | Builds a Docker image of your LangGraph API server for deployment. |
| [`langgraph dockerfile`](#dockerfile) | Emits a Dockerfile derived from your config for custom builds. |
| [`langgraph up`](#up) | Starts the LangGraph API server locally in Docker. Requires Docker running; LangSmith API key for local dev; license for production. |
For JS, use `npx @langchain/langgraph-cli ` (or `langgraphjs` if installed globally).
## Configuration file
To build and run a valid application, the LangGraph CLI requires a JSON configuration file that follows this [schema](https://raw.githubusercontent.com/langchain-ai/langgraph/refs/heads/main/libs/cli/schemas/schema.json). It contains the following properties:
The LangGraph CLI defaults to using the configuration file named langgraph.json in the current directory.
| Key | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `dependencies` | **Required**. Array of dependencies for LangSmith API server. Dependencies can be one of the following:
A single period (`"."`), which will look for local Python packages.
The directory path where `pyproject.toml`, `setup.py` or `requirements.txt` is located. For example, if `requirements.txt` is located in the root of the project directory, specify `"./"`. If it's located in a subdirectory called `local_package`, specify `"./local_package"`. Do not specify the string `"requirements.txt"` itself.
A Python package name.
|
| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example:
`./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`
`./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and returns an instance of `langgraph.graph.state.StateGraph` or `langgraph.graph.state.CompiledStateGraph`. See [how to rebuild a graph at runtime](/langsmith/graph-rebuild) for more details.
|
| `auth` | *(Added in v0.0.11)* Auth configuration containing the path to your authentication handler. Example: `./your_package/auth.py:auth`, where `auth` is an instance of `langgraph_sdk.Auth`. See [authentication guide](/langsmith/auth) for details. |
| `base_image` | Optional. Base image to use for the LangGraph API server. Defaults to `langchain/langgraph-api` or `langchain/langgraphjs-api`. Use this to pin your builds to a particular version of the langgraph API, such as `"langchain/langgraph-server:0.2"`. See [https://hub.docker.com/r/langchain/langgraph-server/tags](https://hub.docker.com/r/langchain/langgraph-server/tags) for more details. (added in `langgraph-cli==0.2.8`) |
| `image_distro` | Optional. Linux distribution for the base image. Must be one of `"debian"`, `"wolfi"`, `"bookworm"`, or `"bullseye"`. If omitted, defaults to `"debian"`. Available in `langgraph-cli>=0.2.11`. |
| `env` | Path to `.env` file or a mapping from environment variable to its value. |
| `store` | Configuration for adding semantic search and/or time-to-live (TTL) to the BaseStore. Contains the following fields:
`index` (optional): Configuration for semantic search indexing with fields `embed`, `dims`, and optional `fields`.
`ttl` (optional): Configuration for item expiration. An object with optional fields: `refresh_on_read` (boolean, defaults to `true`), `default_ttl` (float, lifespan in **minutes**; applied to newly created items only; existing items are unchanged; defaults to no expiration), and `sweep_interval_minutes` (integer, how often to check for expired items, defaults to no sweeping).
|
| `ui` | Optional. Named definitions of UI components emitted by the agent, each pointing to a JS/TS file. (added in `langgraph-cli==0.1.84`) |
| `python_version` | `3.11`, `3.12`, or `3.13`. Defaults to `3.11`. |
| `node_version` | Specify `node_version: 20` to use LangGraph.js. |
| `pip_config_file` | Path to `pip` config file. |
| `pip_installer` | *(Added in v0.3)* Optional. Python package installer selector. It can be set to `"auto"`, `"pip"`, or `"uv"`. From version 0.3 onward the default strategy is to run `uv pip`, which typically delivers faster builds while remaining a drop-in replacement. In the uncommon situation where `uv` cannot handle your dependency graph or the structure of your `pyproject.toml`, specify `"pip"` here to revert to the earlier behaviour. |
| `keep_pkg_tools` | *(Added in v0.3.4)* Optional. Control whether to retain Python packaging tools (`pip`, `setuptools`, `wheel`) in the final image. Accepted values:
true : Keep all three tools (skip uninstall).
false / omitted : Uninstall all three tools (default behaviour).
list\[str] : Names of tools to retain. Each value must be one of "pip", "setuptools", "wheel".
. By default, all three tools are uninstalled. |
| `dockerfile_lines` | Array of additional lines to add to Dockerfile following the import from parent image. |
| `checkpointer` | Configuration for the checkpointer. Supports:
`ttl` (optional): Object with `strategy`, `sweep_interval_minutes`, `default_ttl` controlling checkpoint expiry.
`serde` (optional, 0.5+): Object with `allowed_json_modules` and `pickle_fallback` to tune deserialization behavior.
|
| `http` | HTTP server configuration with the following fields:
`app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](/langsmith/custom-routes).
`cors`: CORS configuration with fields such as `allow_origins`, `allow_methods`, `allow_headers`, `allow_credentials`, `allow_origin_regex`, `expose_headers`, and `max_age`.
`configurable_headers`: Define which request headers to expose as configurable values via `includes` / `excludes` patterns.
`logging_headers`: Mirror of `configurable_headers` for excluding sensitive headers from logs.
`middleware_order`: Choose how custom middleware and auth interact. `auth_first` runs authentication hooks before custom middleware, while `middleware_first` (default) runs your middleware first.
`enable_custom_route_auth`: Apply auth checks to routes added through `app`.
`mount_prefix`: Prefix for mounted routes (e.g., "/my-deployment/api").
|
| `webhooks` | *(Added in v0.5.36)* Configuration for outbound webhook delivery. Contains:
`env_prefix`: Required prefix for environment variables referenced in header templates (defaults to `LG_WEBHOOK_`).
`headers`: Static headers to include with webhook requests. Values may contain templates like `${{ env.VAR }}`.
`url`: URL validation policy with `allowed_domains`, `allowed_ports`, `require_https`, `disable_loopback`, and `max_url_length`.
|
| `api_version` | *(Added in v0.3.7)* Which semantic version of the LangGraph API server to use (e.g., `"0.3"`). Defaults to latest. Check the server [changelog](/langsmith/agent-server-changelog) for details on each release. |
| Key | Description |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example:
`./src/graph.ts:variable`, where `variable` is an instance of [`CompiledStateGraph`](https://reference.langchain.com/python/langgraph/graphs/#langgraph.graph.state.CompiledStateGraph)
`./src/graph.ts:makeGraph`, where `makeGraph` is a function that takes a config dictionary (`LangGraphRunnableConfig`) and returns an instance of [`StateGraph`](https://reference.langchain.com/python/langgraph/graphs/#langgraph.graph.state.StateGraph) or [`CompiledStateGraph`](https://reference.langchain.com/python/langgraph/graphs/#langgraph.graph.state.CompiledStateGraph). See [how to rebuild a graph at runtime](/langsmith/graph-rebuild) for more details.
|
| `env` | Path to `.env` file or a mapping from environment variable to its value. |
| `store` | Configuration for adding semantic search and/or time-to-live (TTL) to the BaseStore. Contains the following fields:
`index` (optional): Configuration for semantic search indexing with fields `embed`, `dims`, and optional `fields`.
`ttl` (optional): Configuration for item expiration. An object with optional fields: `refresh_on_read` (boolean, defaults to `true`), `default_ttl` (float, lifespan in **minutes**; applied to newly created items only; existing items are unchanged; defaults to no expiration), and `sweep_interval_minutes` (integer, how often to check for expired items, defaults to no sweeping).
|
| `node_version` | Specify `node_version: 20` to use LangGraph.js. |
| `dockerfile_lines` | Array of additional lines to add to Dockerfile following the import from parent image. |
| `checkpointer` | Configuration for the checkpointer. Supports:
`ttl` (optional): Object with `strategy`, `sweep_interval_minutes`, `default_ttl` controlling checkpoint expiry.
`serde` (optional, 0.5+): Object with `allowed_json_modules` and `pickle_fallback` to tune deserialization behavior.
|
| `http` | HTTP server configuration mirroring the Python options:
`cors` with `allow_origins`, `allow_methods`, `allow_headers`, `allow_credentials`, `allow_origin_regex`, `expose_headers`, `max_age`.
`configurable_headers` and `logging_headers` pattern lists.
`middleware_order` (`auth_first` or `middleware_first`).
`enable_custom_route_auth` plus the same boolean route toggles as above.
|
| `webhooks` | *(Added in v0.5.36)* Configuration for outbound webhook delivery. Contains:
`env_prefix`: Required prefix for environment variables referenced in header templates (defaults to `LG_WEBHOOK_`).
`headers`: Static headers to include with webhook requests. Values may contain templates like `${{ env.VAR }}`.
`url`: URL validation policy with `allowed_domains`, `allowed_ports`, `require_https`, `disable_loopback`, and `max_url_length`.
|
| `api_version` | *(Added in v0.3.7)* Which semantic version of the LangGraph API server to use (e.g., `"0.3"`). Defaults to latest. Check the server [changelog](/langsmith/agent-server-changelog) for details on each release. |
### Examples
#### Basic configuration
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"dependencies": ["."],
"graphs": {
"chat": "chat.graph:graph"
}
}
```
#### Using Wolfi base images
You can specify the Linux distribution for your base image using the `image_distro` field. Valid options are `debian`, `wolfi`, `bookworm`, or `bullseye`. Wolfi is the recommended option as it provides smaller and more secure images. This is available in `langgraph-cli>=0.2.11`.
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"dependencies": ["."],
"graphs": {
"chat": "chat.graph:graph"
},
"image_distro": "wolfi"
}
```
#### Adding semantic search to the store
All deployments come with a DB-backed BaseStore. Adding an "index" configuration to your `langgraph.json` will enable [semantic search](/langsmith/semantic-search) within the BaseStore of your deployment.
The `index.fields` configuration determines which parts of your documents to embed:
* If omitted or set to `["$"]`, the entire document will be embedded
* To embed specific fields, use JSON path notation: `["metadata.title", "content.text"]`
* Documents missing specified fields will still be stored but won't have embeddings for those fields
* You can still override which fields to embed on a specific item at `put` time using the `index` parameter
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"memory_agent": "./agent/graph.py:graph"
},
"store": {
"index": {
"embed": "openai:text-embedding-3-small",
"dims": 1536,
"fields": ["$"]
}
}
}
```
**Common model dimensions**
* `openai:text-embedding-3-large`: 3072
* `openai:text-embedding-3-small`: 1536
* `openai:text-embedding-ada-002`: 1536
* `cohere:embed-english-v3.0`: 1024
* `cohere:embed-english-light-v3.0`: 384
* `cohere:embed-multilingual-v3.0`: 1024
* `cohere:embed-multilingual-light-v3.0`: 384
#### Semantic search with a custom embedding function
If you want to use semantic search with a custom embedding function, you can pass a path to a custom embedding function:
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"memory_agent": "./agent/graph.py:graph"
},
"store": {
"index": {
"embed": "./embeddings.py:embed_texts",
"dims": 768,
"fields": ["text", "summary"]
}
}
}
```
The `embed` field in store configuration can reference a custom function that takes a list of strings and returns a list of embeddings. Example implementation:
```python theme={null}
# embeddings.py
def embed_texts(texts: list[str]) -> list[list[float]]:
"""Custom embedding function for semantic search."""
# Implementation using your preferred embedding model
return [[0.1, 0.2, ...] for _ in texts] # dims-dimensional vectors
```
#### Adding custom authentication
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"dependencies": ["."],
"graphs": {
"chat": "chat.graph:graph"
},
"auth": {
"path": "./auth.py:auth",
"openapi": {
"securitySchemes": {
"apiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key"
}
},
"security": [{ "apiKeyAuth": [] }]
},
"disable_studio_auth": false
}
}
```
See the [authentication conceptual guide](/langsmith/auth) for details, and the [setting up custom authentication](/langsmith/set-up-custom-auth) guide for a practical walk through of the process.
#### Configuring store item Time-to-Live
You can configure default data expiration for items/memories in the BaseStore using the `store.ttl` key. This determines how long items are retained after they are last accessed (with reads potentially refreshing the timer based on `refresh_on_read`). Note that these defaults can be overwritten on a per-call basis by modifying the corresponding arguments in `get`, `search`, etc.
The `ttl` configuration is an object containing optional fields:
* `refresh_on_read`: If `true` (the default), accessing an item via `get` or `search` resets its expiration timer. Set to `false` to only refresh TTL on writes (`put`).
* `default_ttl`: The default lifespan of an item in **minutes**. Applies only to newly created items; existing items are not modified. If not set, items do not expire by default.
* `sweep_interval_minutes`: How frequently (in minutes) the system should run a background process to delete expired items. If not set, sweeping does not occur automatically.
Here is an example enabling a 7-day TTL (10080 minutes), refreshing on reads, and sweeping every hour:
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"dependencies": ["."],
"graphs": {
"memory_agent": "./agent/graph.py:graph"
},
"store": {
"ttl": {
"refresh_on_read": true,
"sweep_interval_minutes": 60,
"default_ttl": 10080
}
}
}
```
#### Configuring checkpoint Time-to-Live
You can configure the time-to-live (TTL) for checkpoints using the `checkpointer` key. This determines how long checkpoint data is retained before being automatically handled according to the specified strategy (e.g., deletion). Two optional sub-objects are supported:
* `ttl`: Includes `strategy`, `sweep_interval_minutes`, and `default_ttl`, which collectively set how checkpoints expire.
* `serde` *(Agent server 0.5+)* : Lets you control deserialization behavior for checkpoint payloads.
Here's an example setting a default TTL of 30 days (43200 minutes):
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"dependencies": ["."],
"graphs": {
"chat": "chat.graph:graph"
},
"checkpointer": {
"ttl": {
"strategy": "delete",
"sweep_interval_minutes": 10,
"default_ttl": 43200
}
}
}
```
In this example, checkpoints older than 30 days will be deleted, and the check runs every 10 minutes.
#### Configuring checkpointer serde
The `checkpointer.serde` object shapes deserialization:
* `allowed_json_modules` defines an allow list for custom Python objects you want the server to be able to deserialize from payloads saved in "json" mode. This is a list of `[path, to, module, file, symbol]` sequences. If omitted, only LangChain-safe defaults are allowed. You can unsafely set to `true` to allow any module to be deserialized.
* `pickle_fallback`: Whether to fall back to pickle deserialization when JSON decoding fails.
```json theme={null}
{
"checkpointer": {
"serde": {
"allowed_json_modules": [
["my_agent", "auth", "SessionState"]
]
}
}
}
```
#### Customizing HTTP middleware and headers
The `http` block lets you fine-tune request handling:
* `middleware_order`: Choose `"auth_first"` to run authentication before your middleware, or `"middleware_first"` (default) to invert that order.
* `enable_custom_route_auth`: Extend authentication to routes you mount through `http.app`.
* `configurable_headers` / `logging_headers`: Each accepts an object with optional `includes` and `excludes` arrays; wildcards are supported and exclusions run before inclusions.
* `cors`: Customize your server's CORS (Cross-Origin Resource Sharing) configuration. Example `langgraph.json` file for configuring CORS:
```json theme={null}
{
...
"http": {
"cors": {
"allow_origins": ["https://example.com", "https://app.example.com"],
"allow_methods": ["GET", "POST"],
"allow_headers": ["Authorization", "Content-Type"],
"allow_credentials": true,
"allow_origin_regex": "^https://.*\\.example\\.com$",
"expose_headers": ["x-pagination-total", "x-pagination-next", "x-request-id"],
"max_age": 600
}
},
...
}
```
Customizing your server's CORS configuration will override the functionality of setting the [`CORS_ALLOW_ORIGINS` environment variable](/langsmith/env-var#cors_allow_origins).
#### Configuring webhooks
You can configure custom headers and URL restrictions for outbound webhook requests:
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"dependencies": ["."],
"graphs": {
"chat": "chat.graph:graph"
},
"webhooks": {
"headers": {
"Authorization": "Bearer ${{ env.LG_WEBHOOK_TOKEN }}"
},
"url": {
"allowed_domains": ["*.mycompany.com"],
"require_https": true
}
}
}
```
See [Use webhooks](/langsmith/use-webhooks#add-headers-to-webhook-requests) for details on header configuration, environment variable templating, and URL restrictions.
#### Pinning API version
*(Added in v0.3.7)*
You can pin the API version of the Agent Server by using the `api_version` key. This is useful if you want to ensure that your server uses a specific version of the API.
By default, builds in Cloud deployments use the latest stable version of the server. This can be pinned by setting the `api_version` key to a specific version.
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"dependencies": ["."],
"graphs": {
"chat": "chat.graph:graph"
},
"api_version": "0.2"
}
```
#### Basic configuration
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"graphs": {
"chat": "./src/graph.ts:graph"
}
}
```
#### Pinning API version
*(Added in v0.3.7)*
You can pin the API version of the Agent Server by using the `api_version` key. This is useful if you want to ensure that your server uses a specific version of the API.
By default, builds in Cloud deployments use the latest stable version of the server. This can be pinned by setting the `api_version` key to a specific version.
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"dependencies": ["."],
"graphs": {
"chat": "./src/chat/graph.ts:graph"
},
"api_version": "0.2"
}
```
## Commands
**Usage**
The base command for the LangGraph CLI is `langgraph`.
```
langgraph [OPTIONS] COMMAND [ARGS]
```
The base command for the LangGraph.js CLI is `langgraphjs`.
```
npx @langchain/langgraph-cli [OPTIONS] COMMAND [ARGS]
```
We recommend using `npx` to always use the latest version of the CLI.
### `dev`
Run LangGraph API server in development mode with hot reloading and debugging capabilities. This lightweight server requires no Docker installation and is suitable for development and testing. State is persisted to a local directory.
Currently, the CLI only supports Python >= 3.11.
If you need more information on when to use `langgraph dev` vs `langgraph up`, refer to the [Local development & testing guide](/langsmith/local-dev-testing) for a detailed comparison.
**Installation**
This command requires the "inmem" extra to be installed:
```bash theme={null}
pip install -U "langgraph-cli[inmem]"
```
**Usage**
```
langgraph dev [OPTIONS]
```
**Options**
| Option | Default | Description |
| ----------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables |
| `--host TEXT` | `127.0.0.1` | Host to bind the server to |
| `--port INTEGER` | `2024` | Port to bind the server to |
| `--no-reload` | | Disable auto-reload |
| `--n-jobs-per-worker INTEGER` | | Number of jobs per worker. Default is 10 |
| `--debug-port INTEGER` | | Port for debugger to listen on |
| `--wait-for-client` | `False` | Wait for a debugger client to connect to the debug port before starting the server |
| `--no-browser` | | Skip automatically opening the browser when the server starts |
| `--studio-url TEXT` | | URL of the Studio instance to connect to. Defaults to [https://smith.langchain.com](https://smith.langchain.com) |
| `--allow-blocking` | `False` | Do not raise errors for synchronous I/O blocking operations in your code (added in `0.2.6`) |
| `--tunnel` | `False` | Expose the local server via a public tunnel (Cloudflare) for remote frontend access. This avoids issues with browsers like Safari or networks blocking localhost connections |
| `--help` | | Display command documentation |
Run LangGraph API server in development mode with hot reloading capabilities. This lightweight server requires no Docker installation and is suitable for development and testing. State is persisted to a local directory.
**Usage**
```
npx @langchain/langgraph-cli dev [OPTIONS]
```
**Options**
| Option | Default | Description |
| ----------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables |
| `--host TEXT` | `127.0.0.1` | Host to bind the server to |
| `--port INTEGER` | `2024` | Port to bind the server to |
| `--no-reload` | | Disable auto-reload |
| `--n-jobs-per-worker INTEGER` | | Number of jobs per worker. Default is 10 |
| `--debug-port INTEGER` | | Port for debugger to listen on |
| `--wait-for-client` | `False` | Wait for a debugger client to connect to the debug port before starting the server |
| `--no-browser` | | Skip automatically opening the browser when the server starts |
| `--studio-url TEXT` | | URL of the Studio instance to connect to. Defaults to [https://smith.langchain.com](https://smith.langchain.com) |
| `--allow-blocking` | `False` | Do not raise errors for synchronous I/O blocking operations in your code |
| `--tunnel` | `False` | Expose the local server via a public tunnel (Cloudflare) for remote frontend access. This avoids issues with browsers or networks blocking localhost connections |
| `--help` | | Display command documentation |
### `build`
Build LangSmith API server Docker image.
**Usage**
```
langgraph build [OPTIONS]
```
**Options**
| Option | Default | Description |
| ------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--platform TEXT` | | Target platform(s) to build the Docker image for. Example: `langgraph build --platform linux/amd64,linux/arm64` |
| `-t, --tag TEXT` | | **Required**. Tag for the Docker image. Example: `langgraph build -t my-image` |
| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangSmith API server with locally built images. |
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
| `--build-command TEXT`\* | | Build command to run. Runs from the directory where your `langgraph.json` file lives. Example: `langgraph build --build-command "yarn run turbo build"` |
| `--install-command TEXT`\* | | Install command to run. Runs from the directory where you call `langgraph build` from. Example: `langgraph build --install-command "yarn install"` |
| `--help` | | Display command documentation. |
\*Only supported for JS deployments, will have no impact on Python deployments.
Build LangSmith API server Docker image.
**Usage**
```
npx @langchain/langgraph-cli build [OPTIONS]
```
**Options**
| Option | Default | Description |
| ------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `--platform TEXT` | | Target platform(s) to build the Docker image for. Example: `langgraph build --platform linux/amd64,linux/arm64` |
| `-t, --tag TEXT` | | **Required**. Tag for the Docker image. Example: `langgraph build -t my-image` |
| `--no-pull` | | Use locally built images. Defaults to `false` to build with latest remote Docker image. |
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
| `--help` | | Display command documentation. |
### `up`
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangSmith. Requires a license key for production use.
If you need more information on when to use `langgraph dev` vs `langgraph up`, refer to the [Local development & testing guide](/langsmith/local-dev-testing) for a detailed comparison.
**Usage**
```
langgraph up [OPTIONS]
```
**Options**
| Option | Default | Description |
| ---------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `--wait` | | Wait for services to start before returning. Implies --detach |
| `--base-image TEXT` | `langchain/langgraph-api` | Base image to use for the LangGraph API server. Pin to specific versions using version tags. |
| `--image TEXT` | | Docker image to use for the langgraph-api service. If specified, skips building and uses this image directly. |
| `--postgres-uri TEXT` | Local database | Postgres URI to use for the database. |
| `--watch` | | Restart on file changes |
| `--debugger-base-url TEXT` | `http://127.0.0.1:[PORT]` | URL used by the debugger to access LangGraph API. |
| `--debugger-port INTEGER` | | Pull the debugger image locally and serve the UI on specified port |
| `--verbose` | | Show more output from the server logs. |
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
| `-d, --docker-compose FILE` | | Path to docker-compose.yml file with additional services to launch. |
| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph up --port 8000` |
| `--pull / --no-pull` | `pull` | Pull latest images. Use `--no-pull` for running the server with locally-built images. Example: `langgraph up --no-pull` |
| `--recreate / --no-recreate` | `no-recreate` | Recreate containers even if their configuration and image haven't changed |
| `--help` | | Display command documentation. |
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangSmith. Requires a license key for production use.
**Usage**
```
npx @langchain/langgraph-cli up [OPTIONS]
```
**Options**
| Option | Default | Description |
| ---------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `--wait` | | Wait for services to start before returning. Implies --detach |
| `--base-image TEXT` | `langchain/langgraph-api` | Base image to use for the LangGraph API server. Pin to specific versions using version tags. |
| `--image TEXT` | | Docker image to use for the langgraph-api service. If specified, skips building and uses this image directly. |
| `--postgres-uri TEXT` | Local database | Postgres URI to use for the database. |
| `--watch` | | Restart on file changes |
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
| `-d, --docker-compose FILE` | | Path to docker-compose.yml file with additional services to launch. |
| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph up --port 8000` |
| `--no-pull` | | Use locally built images. Defaults to `false` to build with latest remote Docker image. |
| `--recreate` | | Recreate containers even if their configuration and image haven't changed |
| `--help` | | Display command documentation. |
### `dockerfile`
Generate a Dockerfile for building a LangSmith API server Docker image.
**Usage**
```
langgraph dockerfile [OPTIONS] SAVE_PATH
```
**Options**
| Option | Default | Description |
| ------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `-c, --config FILE` | `langgraph.json` | Path to the [configuration file](#configuration-file) declaring dependencies, graphs and environment variables. |
| `--help` | | Show this message and exit. |
Example:
```bash theme={null}
langgraph dockerfile -c langgraph.json Dockerfile
```
This generates a Dockerfile that looks similar to:
```dockerfile theme={null}
FROM langchain/langgraph-api:3.11
ADD ./pipconf.txt /pipconfig.txt
RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt langchain_community langchain_anthropic langchain_openai wikipedia scikit-learn
ADD ./graphs /deps/__outer_graphs/src
RUN set -ex && \
for line in '[project]' \
'name = "graphs"' \
'version = "0.1"' \
'[tool.setuptools.package-data]' \
'"*" = ["**/*"]'; do \
echo "$line" >> /deps/__outer_graphs/pyproject.toml; \
done
RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph", "storm": "/deps/__outer_graphs/src/storm.py:graph"}'
```
The `langgraph dockerfile` command translates all the configuration in your `langgraph.json` file into Dockerfile commands. When using this command, you will have to re-run it whenever you update your `langgraph.json` file. Otherwise, your changes will not be reflected when you build or run the dockerfile.
Generate a Dockerfile for building a LangSmith API server Docker image.
**Usage**
```
npx @langchain/langgraph-cli dockerfile [OPTIONS] SAVE_PATH
```
**Options**
| Option | Default | Description |
| ------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `-c, --config FILE` | `langgraph.json` | Path to the [configuration file](#configuration-file) declaring dependencies, graphs and environment variables. |
| `--help` | | Show this message and exit. |
Example:
```bash theme={null}
npx @langchain/langgraph-cli dockerfile -c langgraph.json Dockerfile
```
This generates a Dockerfile that looks similar to:
```dockerfile theme={null}
FROM langchain/langgraphjs-api:20
ADD . /deps/agent
RUN cd /deps/agent && yarn install
ENV LANGSERVE_GRAPHS='{"agent":"./src/react_agent/graph.ts:graph"}'
WORKDIR /deps/agent
RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts
```
The `npx @langchain/langgraph-cli dockerfile` command translates all the configuration in your `langgraph.json` file into Dockerfile commands. When using this command, you will have to re-run it whenever you update your `langgraph.json` file. Otherwise, your changes will not be reflected when you build or run the dockerfile.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/cli.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Cloud
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/cloud
If you're ready to deploy your app to Cloud, follow the [Cloud deployment quickstart](/langsmith/deployment-quickstart) or the [full setup guide](/langsmith/deploy-to-cloud). This page explains the Cloud managed architecture for reference.
The **Cloud** option is a fully managed model where LangChain hosts and operates all LangSmith infrastructure and services:
* **Fully managed infrastructure**: LangChain handles all infrastructure, updates, scaling, and maintenance.
* **Deploy from GitHub**: Connect your repositories and deploy with a few clicks.
* **Automated CI/CD**: Build process is handled automatically by the platform.
* **LangSmith UI**: Full access to [observability](/langsmith/observability), [evaluation](/langsmith/evaluation), [deployment management](/langsmith/deployments), and [Studio](/langsmith/studio).
| | **Who manages it** | **Where it runs** |
| --------------------------------------------- | ------------------ | ----------------- |
| **LangSmith platform (UI, APIs, datastores)** | LangChain | LangChain's cloud |
| **Your Agent Servers** | LangChain | LangChain's cloud |
| **CI/CD for your apps** | LangChain | LangChain's cloud |
## Get started
To deploy your first application to Cloud, follow the [Cloud deployment quickstart](/langsmith/deployment-quickstart) or refer to the [comprehensive setup guide](/langsmith/deploy-to-cloud).
## Cloud architecture and scalability
This section is only relevant for the cloud-managed LangSmith services available at [https://smith.langchain.com](https://smith.langchain.com) and [https://eu.smith.langchain.com](https://eu.smith.langchain.com).
For information on the self-hosted LangSmith solution, please refer to the [self-hosted documentation](/langsmith/self-hosted).
LangSmith is deployed on Google Cloud Platform (GCP) and is designed to be highly scalable. Many customers run production workloads on LangSmith for LLM application observability, evaluation, and agent deployment
### Architecture
The US-based LangSmith service is deployed in the `us-central1` (Iowa) region of GCP.
The [EU-based LangSmith service](https://eu.smith.langchain.com) is now available (as of mid-July 2024) and is deployed in the `europe-west4` (Netherlands) region of GCP. If you are interested in an enterprise plan in this region, [contact our sales team](https://www.langchain.com/contact-sales).
#### Regional storage
The resources and services in this table are stored in the location corresponding to the URL where sign-up occurred (either the US or EU). Cloud-managed LangSmith uses [Supabase](https://supabase.com) for authentication/authorization and [ClickHouse Cloud](https://clickhouse.com/cloud) for data warehouse.
| | US | EU |
| ---------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| URL | [https://smith.langchain.com](https://smith.langchain.com) | [https://eu.smith.langchain.com](https://eu.smith.langchain.com) |
| API URL | [https://api.smith.langchain.com](https://api.smith.langchain.com) | [https://eu.api.smith.langchain.com](https://eu.api.smith.langchain.com) |
| GCP | us-central1 (Iowa) | europe-west4 (Netherlands) |
| Supabase | AWS us-east-1 (N. Virginia) | AWS eu-central-1 (Germany) |
| ClickHouse Cloud | us-central1 (Iowa) | europe-west4 (Netherlands) |
| [LangSmith deployment](/langsmith/deployments) | us-central1 (Iowa) | europe-west4 (Netherlands) |
See the [Regions FAQ](/langsmith/regions-faq) for more information.
#### Region-independent storage
Data listed here is stored exclusively in the US:
* Payment and billing information with Stripe and Metronome
#### GCP services
LangSmith is composed of the following services, all deployed on Google Kubernetes Engine (GKE):
* LangSmith Frontend: serves the LangSmith UI.
* LangSmith Backend: serves the LangSmith API.
* LangSmith Platform Backend: handles authentication and other high-volume tasks. (Internal service)
* LangSmith Playground: handles forwarding requests to various LLM providers for the Playground feature.
* LangSmith Queue: handles processing of asynchronous tasks. (Internal service)
LangSmith uses the following GCP storage services:
* Google Cloud Storage (GCS) for runs inputs and outputs.
* Google Cloud SQL PostgreSQL for transactional workloads.
* Google Cloud Memorystore for Redis for queuing and caching.
* Clickhouse Cloud on GCP for trace ingestion and analytics. Our services connect to Clickhouse Cloud, which is hosted in the same GCP region, via a private endpoint.
Some additional GCP services we use include:
* Google Cloud Load Balancer for routing traffic to the LangSmith services.
* Google Cloud CDN for caching static assets.
* Google Cloud Armor for security and rate limits. For more information on rate limits we enforce, please refer to [this guide](/langsmith/administration-overview#rate-limits).
### Allowlisting IP addresses
#### Egress from LangChain SaaS
All traffic leaving LangSmith services will be routed through a NAT gateway. All traffic will appear to originate from the following IP addresses:
| US | EU |
| -------------- | -------------- |
| 34.59.65.97 | 34.13.192.67 |
| 34.67.51.221 | 34.147.105.64 |
| 34.46.212.37 | 34.90.22.166 |
| 34.132.150.88 | 34.147.36.213 |
| 35.188.222.201 | 34.32.137.113 |
| 34.58.194.127 | 34.91.238.184 |
| 34.59.97.173 | 35.204.101.241 |
| 104.198.162.55 | 35.204.48.32 |
It may be helpful to allowlist these IP addresses if connecting to your own AzureOpenAI service or other endpoints that may be required by the Playground or Online Evaluation.
#### Ingress into LangChain SaaS
The langchain endpoints map to the following static IP addresses:
| US | EU |
| -------------- | ------------ |
| 34.8.121.39 | 34.95.92.214 |
| 34.107.251.234 | 34.13.73.122 |
You may need to allowlist these to enable traffic from your private network to LangSmith SaaS endpoints (`api.smith.langchain.com`, `smith.langchain.com`, `beacon.langchain.com`, `eu.api.smith.langchain.com`, `eu.smith.langchain.com`, `eu.beacon.langchain.com`).
### API rate limits
LangSmith enforces rate limits on API endpoints to ensure service stability and fair usage. The following table shows the rate limits for different endpoints in both US and EU regions. Note that:
* Rate limits are expressed as `count / interval` where count is the number of requests allowed within the interval (in seconds). For example, `2000 / 10` means 2000 requests per 10 seconds.
* When no HTTP method is specified in the endpoint column, the rate limit applies to all HTTP methods for that endpoint.
* When a specific method is listed (e.g., `POST`, `GET`), the rate limit applies only to that method.
| Match / Endpoint (method) | Identity key | US prod limit | EU prod limit | Category |
| ------------------------------------------- | ---------------- | ------------- | ------------- | -------------------------------------------- |
| OPTIONS, `/info`, `*/v1/metadata/submit` | IP | 2000 / 10 | 2000 / 10 | [High throughput](#rate-limit-categories) |
| `/auth` | `x-api-key` | 2000 / 10 | 2000 / 10 | [High throughput](#rate-limit-categories) |
| `/auth` | `x-user-id` + IP | 2000 / 10 | 2000 / 10 | [High throughput](#rate-limit-categories) |
| `/v1/beacon` | IP | 2000 / 10 | 2000 / 10 | [High throughput](#rate-limit-categories) |
| `/repos` | `x-api-key` | 100 / 60 | 100 / 60 | [Repository](#rate-limit-categories) |
| `/repos` | `x-user-id` + IP | 100 / 60 | 100 / 60 | [Repository](#rate-limit-categories) |
| `POST /runs/batch` | `x-api-key` | 2000 / 10 | 2000 / 10 | [High throughput](#rate-limit-categories) |
| `POST /otel/v1/traces` | `x-api-key` | 2000 / 10 | 2000 / 10 | [Run ingest](#rate-limit-categories) |
| `POST` containing `/charts` | `x-api-key` | 750 / 600 | 750 / 600 | [Charts](#rate-limit-categories) |
| `POST` containing `/charts` | `x-user-id` + IP | 750 / 600 | 750 / 600 | [Charts](#rate-limit-categories) |
| `POST /runs/multipart` | `x-api-key` | 6000 / 10 | 6000 / 10 | [Multipart ingest](#rate-limit-categories) |
| `POST /runs/query` | `x-api-key` | 15 / 10 | 15 / 10 | [Run query (API)](#rate-limit-categories) |
| `POST /runs/query` | `x-user-id` + IP | 300 / 10 | 300 / 10 | [Run query (User)](#rate-limit-categories) |
| `/generate` | `x-api-key` | 30 / 3600 | 30 / 3600 | [Generation](#rate-limit-categories) |
| `/generate` | `x-user-id` + IP | 30 / 3600 | 30 / 3600 | [Generation](#rate-limit-categories) |
| `/commits` | `x-api-key` | 10000 / 60 | 2000 / 60 | [Commits](#rate-limit-categories) |
| `/commits` | `x-user-id` + IP | 10000 / 60 | 2000 / 60 | [Commits](#rate-limit-categories) |
| `DELETE /sessions` or `*/trigger` | `x-api-key` | 10 / 60 | 10 / 60 | [Deletion](#rate-limit-categories) |
| `DELETE /sessions` or `*/trigger` | `x-user-id` + IP | 30 / 60 | 30 / 60 | [Deletion](#rate-limit-categories) |
| `POST /runs` (single run ingest) | `x-api-key` | 2000 / 10 | 2000 / 10 | [Run ingest](#rate-limit-categories) |
| `PATCH` containing `/runs` | `x-api-key` | 2000 / 10 | 2000 / 10 | [Run ingest](#rate-limit-categories) |
| `POST /feedback` | `x-api-key` | 2000 / 10 | 2000 / 10 | [High throughput](#rate-limit-categories) |
| `GET /runs/{uuid}` or `/api/v1/runs/{uuid}` | `x-api-key` | 30 / 60 | 30 / 60 | [Run lookup](#rate-limit-categories) |
| `GET` containing `/examples` | `x-api-key` | 5000 / 60 | 5000 / 60 | [Examples](#rate-limit-categories) |
| Any request with `x-api-key` | `x-api-key` | 1000 / 10 | 1000 / 10 | [Default (API key)](#rate-limit-categories) |
| Any request with `x-user-id` | `x-user-id` + IP | 1000 / 10 | 1000 / 10 | [Default (User)](#rate-limit-categories) |
| `/public/download` | IP | 5000 / 60 | 5000 / 60 | [Public download](#rate-limit-categories) |
| `/runs/stats` | `x-api-key` | 1 / 10 | 20 / 10 | [Stats](#rate-limit-categories) |
| All other IPs (catch-all) | IP | 100 / 60 | 100 / 60 | [Public (catch-all)](#rate-limit-categories) |
#### Rate limit categories
* **High throughput**: General high-volume endpoints for core operations like authentication, metadata, and feedback.
* **Repository**: Repository and prompt management operations.
* **Run ingest**: Individual trace/run ingestion endpoints for observability.
* **Charts**: Chart generation and visualization endpoints.
* **Multipart ingest**: Bulk run ingestion via multipart upload for high-volume tracing.
* **Run query (API)**: API key-based run query operations with stricter limits for complex queries.
* **Run query (User)**: User-based run query operations with higher limits for interactive use.
* **Generation**: AI-powered code and content generation endpoints (limited to prevent abuse).
* **Commits**: Prompt versioning and commit operations.
* **Deletion**: Session deletion and workflow trigger operations.
* **Run lookup**: Retrieving specific runs by UUID.
* **Examples**: Fetching dataset examples for few-shot prompting.
* **Default (API key)**: Fallback rate limit for authenticated API requests not matching specific patterns.
* **Default (User)**: Fallback rate limit for authenticated user requests not matching specific patterns.
* **Public download**: High-volume public download endpoints for shared resources.
* **Stats**: Run statistics and analytics endpoints (region-specific limits apply).
* **Public (catch-all)**: Default rate limit for unauthenticated public access.
For more information on rate limits and other service limits, refer to the [Administration overview](/langsmith/administration-overview#rate-limits).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/cloud.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to define a code evaluator
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/code-evaluator-sdk
Code evaluators are functions that take a dataset example and the resulting application output, and return one or more metrics. These functions can be passed directly into the [`evaluate()`](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client.evaluate) or [`aevaluate()`](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client.aevaluate) functions.
To define code evaluators in the LangSmith UI, refer to [How to define a code evaluator (UI)](/langsmith/code-evaluator-ui).
## Basic example
```python Python theme={null}
from langsmith import evaluate
def correct(outputs: dict, reference_outputs: dict) -> bool:
"""Check if the answer exactly matches the expected answer."""
return outputs["answer"] == reference_outputs["answer"]
def dummy_app(inputs: dict) -> dict:
return {"answer": "hmm i'm not sure", "reasoning": "i didn't understand the question"}
results = evaluate(
dummy_app,
data="dataset_name",
evaluators=[correct]
)
```
```typescript TypeScript theme={null}
import type { EvaluationResult } from "langsmith/evaluation";
const correct = async ({ outputs, referenceOutputs }: {
outputs: Record;
referenceOutputs?: Record;
}): Promise => {
const score = outputs?.answer === referenceOutputs?.answer;
return { key: "correct", score };
}
```
## Evaluator args
code evaluator functions must have specific argument names. They can take any subset of the following arguments:
* `run: Run`: The full [Run](/langsmith/run-data-format) object generated by the application on the given example.
* `example: Example`: The full dataset [Example](/langsmith/example-data-format), including the example inputs, outputs (if available), and metadata (if available).
* `inputs: dict`: A dictionary of the inputs corresponding to a single example in a dataset.
* `outputs: dict`: A dictionary of the outputs generated by the application on the given `inputs`.
* `reference_outputs/referenceOutputs: dict`: A dictionary of the reference outputs associated with the example, if available.
For most use cases you'll only need `inputs`, `outputs`, and `reference_outputs`. `run` and `example` are useful only if you need some extra trace or example metadata outside of the actual inputs and outputs of the application.
When using JS/TS these should all be passed in as part of a single object argument.
## Evaluator output
Code evaluators are expected to return one of the following types:
Python and JS/TS
* `dict`: dicts of the form `{"score" | "value": ..., "key": ...}` allow you to customize the metric type ("score" for numerical and "value" for categorical) and metric name. This if useful if, for example, you want to log an integer as a categorical metric.
Python only
* `int | float | bool`: this is interpreted as a continuous metric that can be averaged, sorted, etc. The function name is used as the name of the metric.
* `str`: this is interpreted as a categorical metric. The function name is used as the name of the metric.
* `list[dict]`: return multiple metrics using a single function.
## Additional examples
Requires `langsmith>=0.2.0`
```python Python theme={null}
from langsmith import evaluate, wrappers
from langsmith.schemas import Run, Example
from openai import AsyncOpenAI
# Assumes you've installed pydantic.
from pydantic import BaseModel
# We can still pass in Run and Example objects if we'd like
def correct_old_signature(run: Run, example: Example) -> dict:
"""Check if the answer exactly matches the expected answer."""
return {"key": "correct", "score": run.outputs["answer"] == example.outputs["answer"]}
# Just evaluate actual outputs
def concision(outputs: dict) -> int:
"""Score how concise the answer is. 1 is the most concise, 5 is the least concise."""
return min(len(outputs["answer"]) // 1000, 4) + 1
# Use an LLM-as-a-judge
oai_client = wrappers.wrap_openai(AsyncOpenAI())
async def valid_reasoning(inputs: dict, outputs: dict) -> bool:
"""Use an LLM to judge if the reasoning and the answer are consistent."""
instructions = """
Given the following question, answer, and reasoning, determine if the reasoning for the
answer is logically valid and consistent with question and the answer."""
class Response(BaseModel):
reasoning_is_valid: bool
msg = f"Question: {inputs['question']}\nAnswer: {outputs['answer']}\nReasoning: {outputs['reasoning']}"
response = await oai_client.beta.chat.completions.parse(
model="gpt-4.1-mini",
messages=[{"role": "system", "content": instructions,}, {"role": "user", "content": msg}],
response_format=Response
)
return response.choices[0].message.parsed.reasoning_is_valid
def dummy_app(inputs: dict) -> dict:
return {"answer": "hmm i'm not sure", "reasoning": "i didn't understand the question"}
results = evaluate(
dummy_app,
data="dataset_name",
evaluators=[correct_old_signature, concision, valid_reasoning]
)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
import { evaluate } from "langsmith/evaluation";
import { Run, Example } from "langsmith/schemas";
import OpenAI from "openai";
// Type definitions
interface AppInputs {
question: string;
}
interface AppOutputs {
answer: string;
reasoning: string;
}
interface Response {
reasoning_is_valid: boolean;
}
// Old signature evaluator
function correctOldSignature(run: Run, example: Example) {
return {
key: "correct",
score: run.outputs?.["answer"] === example.outputs?.["answer"],
};
}
// Output-only evaluator
function concision({ outputs }: { outputs: AppOutputs }) {
return {
key: "concision",
score: Math.min(Math.floor(outputs.answer.length / 1000), 4) + 1,
};
}
// LLM-as-judge evaluator
const openai = new OpenAI();
async function validReasoning({
inputs,
outputs
}: {
inputs: AppInputs;
outputs: AppOutputs;
}) {
const instructions = `\
Given the following question, answer, and reasoning, determine if the reasoning for the \
answer is logically valid and consistent with question and the answer.`;
const msg = `Question: ${inputs.question}
Answer: ${outputs.answer}
Reasoning: ${outputs.reasoning}`;
const response = await openai.chat.completions.create({
model: "gpt-4",
messages: [
{ role: "system", content: instructions },
{ role: "user", content: msg }
],
response_format: { type: "json_object" },
functions: [{
name: "parse_response",
parameters: {
type: "object",
properties: {
reasoning_is_valid: {
type: "boolean",
description: "Whether the reasoning is valid"
}
},
required: ["reasoning_is_valid"]
}
}]
});
const parsed = JSON.parse(response.choices[0].message.content ?? "{}") as Response;
return {
key: "valid_reasoning",
score: parsed.reasoning_is_valid ? 1 : 0
};
}
// Example application
function dummyApp(inputs: AppInputs): AppOutputs {
return {
answer: "hmm i'm not sure",
reasoning: "i didn't understand the question"
};
}
const results = await evaluate(dummyApp, {
data: "dataset_name",
evaluators: [correctOldSignature, concision, validReasoning],
client: new Client()
});
```
## Related
* [Evaluate aggregate experiment results](/langsmith/summary): Define summary evaluators, which compute metrics for an entire experiment.
* [Run an evaluation comparing two experiments](/langsmith/evaluate-pairwise): Define pairwise evaluators, which compute metrics by comparing two (or more) experiments against each other.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/code-evaluator-sdk.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to define a code evaluator
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/code-evaluator-ui
Code evaluators in the [LangSmith UI](https://smith.langchain.com) allow you to write custom evaluation logic using Python or TypeScript code directly in the interface. Unlike [LLM-as-a-judge](/langsmith/llm-as-judge) evaluators that use a model to evaluate outputs, code evaluators use deterministic logic you define.
To define code evaluators programmatically using the SDK, refer to [How to define a code evaluator (SDK)](/langsmith/code-evaluator-sdk).
## Step 1. Create the evaluator
1. Create an evaluator from one of the following pages in the [LangSmith UI](https://smith.langchain.com):
* In the playground or from a dataset: Select the **+ Evaluator** button.
* Select **Add rules**, configure your rule and select **Apply evaluator**.
2. Give your evaluator a clear name that describes what it measures (e.g., "Exact Match").
3. Select **Create code evaluator** from the evaluator type options.
## Step 2. Write your evaluator code
**Custom code evaluators restrictions.**
**Allowed Libraries**: You can import all standard library functions, as well as the following public packages:
```
numpy (v2.2.2): "numpy"
pandas (v1.5.2): "pandas"
jsonschema (v4.21.1): "jsonschema"
scipy (v1.14.1): "scipy"
sklearn (v1.26.4): "scikit-learn"
```
**Network Access**: You cannot access the internet from a custom code evaluator.
In the **Add Custom Code Evaluator** page, define your evaluation logic using Python or TypeScript.
Your evaluator function must be named `perform_eval` and should:
1. Accept `run` and `example` parameters.
2. Access data via `run['inputs']`, `run['outputs']`, and `example['outputs']`.
3. Return a dictionary where each key is a metric name and each value is the score for that metric. Each key represents a piece of feedback you want to return. For example, `{"correctness": 1, "silliness": 0}` would create two pieces of feedback on the run.
### Function signature
```python theme={null}
def perform_eval(run, example):
# Access the data
inputs = run['inputs']
outputs = run['outputs']
reference_outputs = example['outputs'] # Optional: reference/expected outputs
# Your evaluation logic here
score = ...
# Return a dict with your metric name
return {"metric_name": score}
```
### Example: Exact match evaluator
```python theme={null}
def perform_eval(run, example):
"""Check if the answer exactly matches the expected answer."""
actual = run['outputs']['answer']
expected = example['outputs']['answer']
is_correct = actual == expected
return {"exact_match": is_correct}
```
### Example: Input-based evaluator
```python theme={null}
def perform_eval(run, example):
"""Check if the input text contains toxic language."""
text = run['inputs'].get('text', '').lower()
toxic_words = ["idiot", "stupid", "hate", "awful"]
is_toxic = any(word in text for word in toxic_words)
return {"is_toxic": is_toxic}
```
## Step 3. Test and save
1. Test your evaluator on example data to ensure it works as expected
2. Click **Save** to make the evaluator available for use
## Use your code evaluator
Once created, you can use your code evaluator:
* When running evaluations from the [playground](/langsmith/observability-concepts#prompt-playground)
* As part of a dataset to [automatically run evaluations on experiments](/langsmith/bind-evaluator-to-dataset)
## Related
* [LLM-as-a-judge evaluator (UI)](/langsmith/llm-as-judge): Use an LLM to evaluate outputs
* [Composite evaluators](/langsmith/composite-evaluators-ui): Combine multiple evaluator scores
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/code-evaluator-ui.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Beta LangSmith Collector-Proxy
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/collector-proxy
This is a beta feature. The API may change in future releases.
The LangSmith Collector-Proxy is a lightweight, high-performance proxy server that sits between your application and the LangSmith backend. It batches and compresses trace data before sending it to LangSmith, reducing network overhead and improving performance.
## When to use the Collector-Proxy
The Collector-Proxy is particularly valuable when:
* You're running multiple instances of your application in parallel and need to efficiently aggregate traces
* You want more efficient tracing than direct OTEL API calls to LangSmith (the collector optimizes batching and compression)
* You're using a language that doesn't have a native LangSmith SDK
## Key features
* **Efficient Data Transfer** Batches multiple spans into fewer, larger uploads.
* **Compression** Uses zstd to minimize payload size.
* **OTLP Support** Accepts OTLP JSON and Protobuf over HTTP POST.
* **Semantic Translation** Maps GenAI semantic conventions to the LangSmith Run model.
* **Flexible Batching** Flush by span count or time interval.
## Configuration
Configure via environment variables:
| Variable | Description | Default |
| -------------------- | --------------------------------- | --------------------------------- |
| `HTTP_PORT` | Port to run the proxy server | `4318` |
| `LANGSMITH_ENDPOINT` | LangSmith backend URL | `https://api.smith.langchain.com` |
| `LANGSMITH_API_KEY` | API key for LangSmith | **Required** (env var or header) |
| `LANGSMITH_PROJECT` | Default tracing project | Default project if not specified |
| `BATCH_SIZE` | Spans per upload batch | `100` |
| `FLUSH_INTERVAL_MS` | Flush interval in milliseconds | `1000` |
| `MAX_BUFFER_BYTES` | Max uncompressed buffer size | `10485760` (10 MB) |
| `MAX_BODY_BYTES` | Max incoming request body size | `209715200` (200 MB) |
| `MAX_RETRIES` | Retry attempts for failed uploads | `3` |
| `RETRY_BACKOFF_MS` | Initial backoff in milliseconds | `100` |
### Project configuration
The Collector-Proxy supports LangSmith project configuration with the following priority:
1. If a project is specified in the request headers (`Langsmith-Project`), that project will be used
2. If no project is specified in headers, it will use the project set in the `LANGSMITH_PROJECT` environment variable
3. If neither is set, it will trace to the `default` project.
### Authentication
The API key can be provided either:
* As an environment variable (`LANGSMITH_API_KEY`)
* In the request headers (`X-API-Key`)
## Deployment (Docker)
You can deploy the Collector-Proxy with Docker:
1. **Build the image**
```bash theme={null}
docker build \
-t langsmith-collector-proxy:beta .
```
2. **Run the container**
```bash theme={null}
docker run -d \
-p 4318:4318 \
-e LANGSMITH_API_KEY= \
-e LANGSMITH_PROJECT= \
langsmith-collector-proxy:beta
```
## Usage
Point any OTLP-compatible client or the OpenTelemetry Collector exporter at:
```bash theme={null}
export OTEL_EXPORTER_OTLP_ENDPOINT=http://:4318/v1/traces
export OTEL_EXPORTER_OTLP_HEADERS="X-API-Key=,Langsmith-Project="
```
Send a test trace:
```bash theme={null}
curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
--data '{
"resourceSpans": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": { "stringValue": "test-service" }
}
]
},
"scopeSpans": [
{
"scope": {
"name": "example/instrumentation",
"version": "1.0.0"
},
"spans": [
{
"traceId": "T6nh/mMkIONaoHewS9UWIw==",
"spanId": "0tEqJwCpvU0=",
"name": "parent-span",
"kind": "SPAN_KIND_INTERNAL",
"startTimeUnixNano": 1747675155185223936,
"endTimeUnixNano": 1747675156185223936,
"attributes": [
{
"key": "gen_ai.prompt",
"value": {
"stringValue": "{\"text\":\"Hello, world!\"}"
}
},
{
"key": "gen_ai.usage.input_tokens",
"value": {
"intValue": "5"
}
},
{
"key": "gen_ai.completion",
"value": {
"stringValue": "{\"text\":\"Hi there!\"}"
}
},
{
"key": "gen_ai.usage.output_tokens",
"value": {
"intValue": "3"
}
}
],
"droppedAttributesCount": 0,
"events": [],
"links": [],
"status": {}
}
]
}
]
}
]
}'
```
## Health & scaling
* **Liveness**: `GET /live` → 200
* **Readiness**: `GET /ready` → 200
## Horizontal scaling
To ensure full traces are batched correctly, route spans with the same trace ID to the same instance (e.g., via consistent hashing).
## Fork & extend
Fork the [Collector-Proxy repo on GitHub](https://github.com/langchain-ai/langsmith-collector-proxy) and implement your own converter:
* Create a custom `GenAiConverter` or modify the existing one in `internal/translator/otel_converter.go`
* Register the custom converter in `internal/translator/translator.go`
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/collector-proxy.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to compare experiment results
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/compare-experiment-results
When you are iterating on your LLM application (such as changing the model or the prompt), you may want to compare the results of different [*experiments*](/langsmith/evaluation-concepts#experiment).
LangSmith supports a comparison view that lets you identify key differences, regressions, and improvements between different experiments.
## Open the comparison view
1. To access the experiment comparison view, navigate to the **Datasets & Experiments** page.
2. Select a dataset, which will open the **Experiments** tab.
3. Select two or more experiments and then click **Compare**.
## Adjust the table display
You can toggle between different display options on the right-hand-side bar of the **Comparing Experiments** page.
### Filters
You can apply filters to the experiment comparison view to narrow down specific examples. Common examples for filters include:
* Examples that contain specific `input` / `output`.
* Runs with status `success` or `error`.
* Runs that take more than x seconds in `latency`.
* Specific `metadata`, `tag`, or `feedback`.
In addition to applying filters on the overall experiment view, you can apply filters on individual columns as well. Select the icon at the top of any column to view the available filters for that column's data.
### Columns
You can select and hide individual feedback keys or individual metrics in the **Columns** settings to isolate the information you need in the comparison view.
### **Full** vs. **Compact** view
* **Full**: Toggling **Full** will show the full text of the input, output, and reference output for each run. If the output is too long to display in the table, you can click on **Expand** to view the full content.
* **Compact**: **Compact** view displays a preview of the experiment results for each example.
### Display types
There are three built-in experiment views that cover several display types: **Default**, **YAML**, **JSON**.
## View regressions and improvements
In the comparison view, red highlights runs that *regressed* on any feedback key against your source experiment, while green highlights runs that *improved*. At the top of each feedback column, you can see how many runs did better or worse than your source experiment.
Click the regression or improvement buttons at the top of each column to show only runs that regressed or improved in that experiment.
## View side-by-side diffs
When comparing two experiments, for JSON and YAML display styles, you can toggle on the experiment diff mode to compare experiment outputs. The diff mode highlights modifications between outputs, and can be particularly useful for structured output comparisons.
## Update source experiment and metric
To track regressions across experiments, you can:
1. At the top of the **Comparison** view, hover over an experiment icon and select **Set as source experiment** from the dropdown. You can also add or remove experiments from this dropdown. By default, the first selected experiment is set as the source.
2. Within the **Feedback** columns, you can configure whether a higher score is better for each feedback key. This preference will be stored. By default, a higher score is assumed to be better.
## Open a trace
If the example you're evaluating is from an ingested [run](/langsmith/observability-concepts#runs), you can hover over the output cell and click on the trace icon to open the trace view for that run. This will open up a trace in the side panel.
## Expand detailed view
You can click on any cell to open up a detailed view of the experiment result on that particular example input, along with feedback keys and scores.
## Use experiment metadata as chart labels
You can configure the x-axis labels for the charts based on [experiment metadata](/langsmith/filter-experiments-ui#background-add-metadata-to-your-experiments).
Select a metadata key from the **Charts** dropdown at the top-right of the **Comparison** view to change the x-axis labels.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/compare-experiment-results.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Compare traces
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/compare-traces
To compare traces, click on the **Compare** button in the upper right hand side of any trace view.
This will show the trace run table. Select the trace you want to compare against the original trace.
The pane will open with both traces selected in a side by side comparison view.
To stop comparing, close the pane or click on **Stop comparing** in the upper right hand side of the pane.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/compare-traces.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith Deployment components
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/components
When running self-hosted [LangSmith Deployment](/langsmith/deploy-self-hosted-full-platform), your installation includes several key components. Together these tools and services provide a complete solution for building, deploying, and managing graphs (including agentic applications) in your own infrastructure:
```mermaid theme={null}
flowchart
subgraph LangSmith Deployment
A[LangGraph CLI] -->|creates| B(Agent Server deployment)
B <--> D[Studio]
B <--> E[SDKs]
B <--> F[RemoteGraph]
end
```
* [Agent Server](/langsmith/agent-server): Defines an opinionated API and runtime for deploying graphs and agents. Handles execution, state management, and persistence so you can focus on building logic rather than server infrastructure.
* [LangGraph CLI](/langsmith/cli): A command-line interface to build, package, and interact with graphs locally and prepare them for deployment.
* [Studio](/langsmith/studio): A specialized IDE for visualization, interaction, and debugging. Connects to a local Agent Server for developing and testing your graph.
* [Python/JS SDK](/langsmith/reference): The Python/JS SDK provides a programmatic way to interact with deployed graphs and agents from your applications.
* [RemoteGraph](/langsmith/use-remote-graph): Allows you to interact with a deployed graph as though it were running locally.
* [Control Plane](/langsmith/control-plane): The UI and APIs for creating, updating, and managing Agent Server deployments.
* [Data plane](/langsmith/data-plane): The runtime layer that executes your graphs, including Agent Servers, their backing services (PostgreSQL, Redis, etc.), and the listener that reconciles state from the control plane.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/components.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to create a composite evaluator
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/composite-evaluators-sdk
*Composite evaluators* are a way to combine multiple evaluator scores into a single [score](/langsmith/evaluation-concepts#evaluator-outputs). This is useful when you want to evaluate multiple aspects of your application and combine the results into a single result.
This guide describes setting up an evaluation that uses multiple evaluators and combines their scores with a custom aggregation function using the [LangSmith SDK](https://reference.langchain.com/python/langsmith/observability/sdk).
Requires langsmith>=0.4.29
To create composite evaluators in the LangSmith UI, refer to [How to create a composite evaluator (UI)](/langsmith/composite-evaluators-ui).
## 1. Configure evaluators on a dataset
Start by configuring your evaluators. In this example, the application generates a tweet from a blog introduction and uses three evaluators — summary, tone, and formatting — to assess the output.
If you already have your own dataset with evaluators configured, you can skip this step.
```python theme={null}
import os
from dotenv import load_dotenv
from openai import OpenAI
from langsmith import Client
from pydantic import BaseModel
import json
# Load environment variables from .env file
load_dotenv()
# Access environment variables
openai_api_key = os.getenv('OPENAI_API_KEY')
langsmith_api_key = os.getenv('LANGSMITH_API_KEY')
langsmith_project = os.getenv('LANGSMITH_PROJECT', 'default')
# Create a dataset. Only need to do this once.
client = Client()
oai_client = OpenAI()
examples = [
{
"inputs": {"blog_intro": "Today we're excited to announce the general availability of LangSmith — our purpose-built infrastructure and management layer for deploying and scaling long-running, stateful agents. Since our beta last June, nearly 400 companies have used LangSmith to deploy their agents into production. Agent deployment is the next hard hurdle for shipping reliable agents, and LangSmith dramatically lowers this barrier with: 1-click deployment to go live in minutes, 30 API endpoints for designing custom user experiences that fit any interaction pattern, Horizontal scaling to handle bursty, long-running traffic, A persistence layer to support memory, conversational history, and async collaboration with human-in-the-loop or multi-agent workflows, Native Studio, the agent IDE, for easy debugging, visibility, and iteration "},
},
{
"inputs": {"blog_intro": "Klarna has reshaped global commerce with its consumer-centric, AI-powered payment and shopping solutions. With over 85 million active users and 2.5 million daily transactions on its platform, Klarna is a fintech leader that simplifies shopping while empowering consumers with smarter, more flexible financial solutions. Klarna's flagship AI Assistant is revolutionizing the shopping and payments experience. Built on LangGraph and powered by LangSmith, the AI Assistant handles tasks ranging from customer payments, to refunds, to other payment escalations. With 2.5 million conversations to date, the AI Assistant is more than just a chatbot; it's a transformative agent that performs the work equivalent of 700 full-time staff, delivering results quickly and improving company efficiency."},
},
]
dataset = client.create_dataset(dataset_name="Blog Intros")
client.create_examples(
dataset_id=dataset.id,
examples=examples,
)
# Define a target function. In this case, we're using a simple function that generates a tweet from a blog intro.
def generate_tweet(inputs: dict) -> dict:
instructions = (
"Given the blog introduction, please generate a catchy yet professional tweet that can be used to promote the blog post on social media. Summarize the key point of the blog post in the tweet. Use emojis in a tasteful manner."
)
messages = [
{"role": "system", "content": instructions},
{"role": "user", "content": inputs["blog_intro"]},
]
result = oai_client.responses.create(
input=messages, model="gpt-5-nano"
)
return {"tweet": result.output_text}
# Define evaluators. In this case, we're using three evaluators: summary, formatting, and tone.
def summary(inputs: dict, outputs: dict) -> bool:
"""Judge whether the tweet is a good summary of the blog intro."""
instructions = "Given the following text and summary, determine if the summary is a good summary of the text."
class Response(BaseModel):
summary: bool
msg = f"Question: {inputs['blog_intro']}\nAnswer: {outputs['tweet']}"
response = oai_client.responses.parse(
model="gpt-5-nano",
input=[{"role": "system", "content": instructions,}, {"role": "user", "content": msg}],
text_format=Response
)
parsed_response = json.loads(response.output_text)
return parsed_response["summary"]
def formatting(inputs: dict, outputs: dict) -> bool:
"""Judge whether the tweet is formatted for easy human readability."""
instructions = "Given the following text, determine if it is formatted well so that a human can easily read it. Pay particular attention to spacing and punctuation."
class Response(BaseModel):
formatting: bool
msg = f"{outputs['tweet']}"
response = oai_client.responses.parse(
model="gpt-5-nano",
input=[{"role": "system", "content": instructions,}, {"role": "user", "content": msg}],
text_format=Response
)
parsed_response = json.loads(response.output_text)
return parsed_response["formatting"]
def tone(inputs: dict, outputs: dict) -> bool:
"""Judge whether the tweet's tone is informative, friendly, and engaging."""
instructions = "Given the following text, determine if the tweet is informative, yet friendly and engaging."
class Response(BaseModel):
tone: bool
msg = f"{outputs['tweet']}"
response = oai_client.responses.parse(
model="gpt-5-nano",
input=[{"role": "system", "content": instructions,}, {"role": "user", "content": msg}],
text_format=Response
)
parsed_response = json.loads(response.output_text)
return parsed_response["tone"]
# Calling evaluate() with the dataset, target function, and evaluators.
results = client.evaluate(
generate_tweet,
data=dataset.name,
evaluators=[summary, tone, formatting],
experiment_prefix="gpt-5-nano",
)
# Get the experiment name to be used in client.get_experiment_results() in the next section
experiment_name = results.experiment_name
```
## 2. Create composite feedback
Create composite feedback that aggregates the individual evaluator scores using your custom function. This example uses a weighted average of the individual evaluator scores.
```python theme={null}
from typing import Dict
import math
from langsmith import Client
from dotenv import load_dotenv
load_dotenv()
# TODO: Replace with your experiment name. Can be found in UI or from the above client.evaluate() result
YOUR_EXPERIMENT_NAME = "placeholder_experiment_name"
# Set weights for the individual evaluator scores
DEFAULT_WEIGHTS: Dict[str, float] = {
"summary": 0.7,
"tone": 0.2,
"formatting": 0.1,
}
WEIGHTED_FEEDBACK_NAME = "weighted_summary"
# Pull experiment results
client = Client()
results = client.get_experiment_results(
name=YOUR_EXPERIMENT_NAME,
)
# Calculate weighted score for each run
def calculate_weighted_score(feedback_stats: dict) -> float:
if not feedback_stats:
return float("nan")
# Check if all required metrics are present and have data
required_metrics = set(DEFAULT_WEIGHTS.keys())
available_metrics = set(feedback_stats.keys())
if not required_metrics.issubset(available_metrics):
return float("nan")
# Calculate weighted score
total_score = 0.0
for metric, weight in DEFAULT_WEIGHTS.items():
metric_data = feedback_stats[metric]
if metric_data.get("n", 0) > 0 and "avg" in metric_data:
total_score += metric_data["avg"] * weight
else:
return float("nan")
return total_score
# Process each run and write feedback
# Note that experiment results need to finish processing before this should be called.
for example_with_runs in results["examples_with_runs"]:
for run in example_with_runs.runs:
if run.feedback_stats:
score = calculate_weighted_score(run.feedback_stats)
if not math.isnan(score):
client.create_feedback(
run_id=run.id,
key=WEIGHTED_FEEDBACK_NAME,
score=float(score)
)
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/composite-evaluators-sdk.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to create a composite evaluator
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/composite-evaluators-ui
*Composite evaluators* are a way to combine multiple evaluator scores into a single [score](/langsmith/evaluation-concepts#evaluator-outputs). This is useful when you want to evaluate multiple aspects of your application and combine the results into a single result.
This guide shows you how to define a [composite evaluator](/langsmith/evaluation-concepts#llm-as-judge) using the [LangSmith UI](https://smith.langchain.com).
To create composite evaluators programmatically using the SDK, refer to [How to create a composite evaluator (SDK)](/langsmith/composite-evaluators-sdk).
## Create a composite evaluator
You can create composite evaluators on a [tracing project](/langsmith/observability-concepts#projects) (for [online evaluations](/langsmith/evaluation-concepts#online-evaluation)) or a [dataset](/langsmith/evaluation-concepts#datasets) (for [offline evaluations](/langsmith/evaluation-concepts#offline-evaluation)). With composite evaluators in the UI, you can compute a weighted average or weighted sum of multiple evaluator scores, with configurable weights.
### 1. Navigate to the tracing project or dataset
To start configuring a composite evaluator, navigate to the **Tracing Projects** or **Dataset & Experiments** tab and select a project or dataset.
* From within a tracing project: **+ New** > **Evaluator** > **Composite score**
* From within a dataset: **+ Evaluator** > **Composite score**
### 2. Configure the composite evaluator
1. Name your evaluator.
2. Select an aggregation method, either **Average** or **Sum**.
* **Average**: ∑(weight\*score) / ∑(weight).
* **Sum**: ∑(weight\*score).
3. Add the feedback keys you want to include in the composite score.
4. Add the weights for the feedback keys. By default, the weights are equal for each feedback key. Adjust the weights to increase or decrease the importance of specific feedback keys in the final score.
5. Click **Create** to save the evaluator.
If you need to adjust the weights for the composite scores, they can be updated after the evaluator is created. The resulting scores will be updated for all runs that have the evaluator configured.
### 3. View composite evaluator results
Composite scores are attached to a run as **feedback**, similarly to feedback from a single evaluator. How you can view them depends on where the evaluation was run:
**On a tracing project**:
* Composite scores appear as feedback on runs.
* [Filter for runs](/langsmith/filter-traces-in-application) with a composite score, or where the composite score meets a certain threshold.
* [Create a chart](/langsmith/dashboards#custom-dashboards) to visualize trends in the composite score over time.
**On a dataset**:
* View the composite scores in the experiments tab. You can also filter and sort experiments based on the average composite score of their runs.
* Click into an experiment to view the composite score for each run.
If any of the constituent evaluators are not configured on the run, the composite score will not be calculated for that run.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/composite-evaluators-ui.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Conditional tracing
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/conditional-tracing
When you have the environment variable `LANGSMITH_TRACING=true` set globally, traces are automatically sent to LangSmith. This guide shows you how to disable or customize tracing selectively for specific requests.
The [`tracing_context`](https://reference.langchain.com/python/langsmith/observability/sdk/run_helpers/#langsmith.run_helpers.tracing_context) context manager (Python) and [`tracingEnabled`](https://reference.langchain.com/javascript/classes/langsmith.run_trees.RunTree.html#tracingenabled) option (TypeScript) allow you to override global tracing settings at runtime, without restructuring your code or changing environment variables.
Use conditional tracing when you need to:
* **Comply with data retention policies**: Some clients may require zero data retention for compliance or privacy reasons.
* **Handle sensitive operations**: Disable tracing for operations involving PII, credentials, or confidential data.
* **Implement per-tenant configurations**: Route traces to different projects or apply different settings based on the customer.
* **Control costs**: Disable tracing for low-value requests while maintaining visibility into critical operations.
* **Support feature flags**: Enable tracing only when specific features or experimental code paths are active.
The following sections provide language-specific examples that you can adapt to your application logic and business requirements.
## How tracing context works
When you use the [`tracing_context`](https://reference.langchain.com/python/langsmith/observability/sdk/run_helpers/#langsmith.run_helpers.tracing_context) context manager, it overrides the global tracing configuration for code executed within its scope. This means you can keep automatic tracing enabled globally while selectively controlling tracing behavior for specific function calls.
There are three priority levels of control:
1. **`tracing_context(enabled=...)`**: highest priority (context manager for scoped tracing control).
2. **`ls.configure(enabled=...)`**: global configuration (sets global tracing behavior).
3. **Environment variables**: lowest priority (`LANGSMITH_TRACING`).
## Disable tracing for specific invocations
To disable tracing for a specific operation, wrap it in a `tracing_context` with `enabled=False`:
```python theme={null}
import langsmith as ls
from langsmith import traceable
# LANGSMITH_TRACING=true is set globally
@traceable
def my_function(input_text: str):
return process(input_text)
# Default invocation - is traced
result = my_function("regular data")
# Disable tracing for sensitive data
with ls.tracing_context(enabled=False):
result = my_function("sensitive data") # not traced
```
This pattern is useful for one-off cases where you know specific data should not be logged.
## Enable conditional tracing based on business logic
You can dynamically enable or disable tracing based on runtime conditions, such as client settings or request properties.
```python theme={null}
import langsmith as ls
from langsmith import traceable
@traceable
def my_function(input_text: str):
return process(input_text)
def client_requires_zero_retention(client_id: str) -> bool:
"""
Check if a client has a zero-retention policy.
In production, this would query a database, configuration service,
or feature flag system. Consider caching results for performance.
"""
# Example: Query from database or config
zero_retention_clients = get_zero_retention_clients() # Your implementation
return client_id in zero_retention_clients
def handle_request(client_id: str, user_input: str):
"""
Process a request with conditional tracing based on client requirements.
"""
should_disable = client_requires_zero_retention(client_id)
with ls.tracing_context(enabled=not should_disable):
return my_function(user_input)
# Example usage
handle_request("client-a", "some input") # Traced or not based on client settings
```
## Customize tracing configuration per request
You can also customize tracing settings dynamically, such as routing traces to different projects or adding request-specific metadata.
```python theme={null}
import langsmith as ls
from langsmith import traceable
@traceable
def my_function(input_text: str):
return process(input_text)
def handle_request(client_id: str, user_input: str, region: str):
"""
Route traces to client-specific projects with custom metadata.
"""
client_tier = get_client_tier(client_id) # e.g., "enterprise", "standard"
with ls.tracing_context(
enabled=True,
project_name=f"client-{client_id}",
tags=["production", f"tier-{client_tier}", f"region-{region}"],
metadata={
"client_id": client_id,
"region": region,
"tier": client_tier
}
):
return my_function(user_input)
# Traces go to "client-abc" project with custom tags and metadata
handle_request("abc", "some input", "us-west")
```
This pattern is useful for:
* **Multi-tenant applications**: Isolate traces by customer in separate projects
* **Regional deployments**: Track performance and behavior by geographic region
* **Feature branches**: Route experimental feature traces to dedicated projects
* **User segmentation**: Analyze behavior by user tier, cohort, or A/B test group
## Work with automatic tracing
The [`tracing_context`](https://reference.langchain.com/python/langsmith/observability/sdk/run_helpers/#langsmith.run_helpers.tracing_context) context manager works with automatic tracing. You can keep `LANGSMITH_TRACING=true` set globally and use `tracing_context` to override settings for specific requests:
```python theme={null}
import os
import langsmith as ls
from langsmith import traceable
# Global environment variable set
os.environ["LANGSMITH_TRACING"] = "true"
@traceable
def process_data(data: str):
return data.upper()
# Automatically traced (respects LANGSMITH_TRACING)
process_data("hello")
# Override global setting - disable for this call
with ls.tracing_context(enabled=False):
process_data("sensitive") # not traced
# Override global setting - enable with custom config
with ls.tracing_context(
enabled=True,
project_name="special-project"
):
process_data("important") # Traced to "special-project"
```
## Nest tracing contexts
When you nest `tracing_context` blocks, the innermost context takes precedence.
```python Python theme={null}
import langsmith as ls
from langsmith import traceable
@traceable
def inner_function(data: str):
return data
@traceable
def outer_function(data: str):
# This call respects the inner context
return inner_function(data)
# Outer context disables tracing
with ls.tracing_context(enabled=False):
# But inner context re-enables it
with ls.tracing_context(enabled=True):
outer_function("data") # is traced
```
This can be useful when you want to temporarily enable tracing for debugging within a normally non-traced section.
## Reusable tracing wrapper
Create a decorator to automatically apply conditional tracing logic.
```python Python theme={null}
import functools
import langsmith as ls
from langsmith import traceable
def conditional_trace(check_function):
"""
Decorator that conditionally traces based on a check function.
Args:
check_function: Function that returns True if tracing should be enabled
"""
def decorator(func):
traced_func = traceable(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
should_trace = check_function(*args, **kwargs)
with ls.tracing_context(enabled=should_trace):
return traced_func(*args, **kwargs)
return wrapper
return decorator
# Usage
def should_trace_client(client_id: str, *args, **kwargs) -> bool:
return not client_requires_zero_retention(client_id)
@conditional_trace(should_trace_client)
def process_request(client_id: str, data: str):
return data.upper()
# Automatically applies conditional tracing based on client_id
process_request("client-a", "some data")
```
## How tracing enabled works
In TypeScript, you control tracing per-function using the [`tracingEnabled`](https://reference.langchain.com/javascript/classes/langsmith.run_trees.RunTree.html#tracingenabled) parameter when calling [`traceable()`](https://reference.langchain.com/python/langsmith/observability/sdk/run_helpers/#langsmith.run_helpers.traceable). This allows you to selectively enable or disable tracing at the function level.
A two-level system where tracing is controlled per-function:
1. **`tracingEnabled` parameter**: highest priority (pass to [`traceable()`](https://reference.langchain.com/python/langsmith/observability/sdk/run_helpers/#langsmith.run_helpers.traceable) config).
2. **Environment variables**: lowest priority (`LANGSMITH_TRACING`).
## Disable tracing for specific invocations
To disable tracing for a specific operation, create a version of your traceable function with `tracingEnabled: false`:
```typescript theme={null}
import { traceable } from "langsmith/traceable";
const myFunction = traceable(
(inputText: string) => {
return process(inputText);
},
{ name: "my_function" }
);
// Default invocation - is traced
await myFunction("regular data");
// Disable tracing for sensitive data
const myFunctionNoTrace = traceable(
(inputText: string) => {
return process(inputText);
},
{ name: "my_function", tracingEnabled: false }
);
await myFunctionNoTrace("sensitive data"); // not traced
```
This pattern is useful for one-off cases where you know specific data should not be logged.
## Enable conditional tracing based on business logic
In many applications, you need to dynamically control tracing based on runtime conditions—such as client privacy requirements, regulatory compliance, or feature flags.
In TypeScript, the most efficient approach is to create both traced and non-traced variants of your function upfront, then select between them at runtime based on your business logic. This avoids the performance overhead of creating new traced wrappers on every request while still providing fine-grained control over when tracing occurs. For example:
```typescript theme={null}
import { traceable } from "langsmith/traceable";
// Define the core logic once
function processText(inputText: string): string {
// Your actual processing logic
return inputText.toUpperCase();
}
// Create traced and non-traced variants upfront
const myFunction = traceable(processText, { name: "my_function" });
const myFunctionNoTrace = traceable(processText, {
name: "my_function",
tracingEnabled: false
});
function clientRequiresZeroRetention(clientId: string): boolean {
/**
* Check if a client has a zero-retention policy.
*
* In production, this would query a database, configuration service,
* or feature flag system. Consider caching results for performance.
*/
const zeroRetentionClients = getZeroRetentionClients(); // Your implementation
return zeroRetentionClients.includes(clientId);
}
async function handleRequest(clientId: string, userInput: string) {
/**
* Process a request with conditional tracing based on client requirements.
* Efficiently selects pre-created traced or non-traced variant.
*/
const shouldDisable = clientRequiresZeroRetention(clientId);
// Select the appropriate pre-created variant
const fn = shouldDisable ? myFunctionNoTrace : myFunction;
return await fn(userInput);
}
// Example usage
await handleRequest("client-a", "some input"); // Traced or not based on client settings
```
## Work with automatic tracing
The [`tracingEnabled`](https://reference.langchain.com/javascript/classes/langsmith.run_trees.RunTree.html#tracingenabled) option works seamlessly with automatic tracing. You can keep `LANGSMITH_TRACING=true` set globally and use `tracingEnabled` to override settings for specific functions.
```typescript theme={null}
import { traceable } from "langsmith/traceable";
// Global tracing enabled via environment
process.env.LANGSMITH_TRACING = "true";
const processData = traceable(
(data: string) => {
return data.toUpperCase();
},
{ name: "process_data" }
);
// Automatically traced (respects LANGSMITH_TRACING)
await processData("hello");
// Override global setting - disable for this call
const processDataNoTrace = traceable(
(data: string) => {
return data.toUpperCase();
},
{ name: "process_data", tracingEnabled: false }
);
await processDataNoTrace("sensitive"); // not traced
// Override global setting - enable with custom config
const processDataCustom = traceable(
(data: string) => {
return data.toUpperCase();
},
{
name: "process_data",
project_name: "special-project",
tracingEnabled: true
}
);
await processDataCustom("important"); // Traced to "special-project"
```
## Comparison with sampling
Conditional tracing and [sampling](/langsmith/sample-traces) serve different purposes:
| Feature | Conditional tracing | Sampling |
| ------------------ | ------------------------------------------------- | -------------------------------------------- |
| **Control** | Deterministic (explicit enable/disable) | Probabilistic (random sampling) |
| **Use case** | Business logic, compliance, per-request decisions | Cost optimization, high-volume observability |
| **Predictability** | Guaranteed behavior for specific requests | Statistical representation of traffic |
| **Configuration** | Runtime code logic | Environment variable or client config |
You can combine both approaches for fine-grained control.
## Related
* [Trace without environment variables](/langsmith/trace-without-env-vars): Configure tracing programmatically instead of using environment variables.
* [Set a sampling rate for traces](/langsmith/sample-traces): Probabilistically sample traces to reduce volume
* [Mask inputs and outputs](/langsmith/mask-inputs-outputs): Hide sensitive data in traces instead of disabling tracing entirely.
* [Add metadata and tags to traces](/langsmith/add-metadata-tags): Categorize and filter traces with custom attributes.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/conditional-tracing.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Use HTTP headers for runtime configuration
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/configurable-headers
LangGraph allows runtime configuration to modify agent behavior and permissions dynamically. When using [LangSmith Deployment](/langsmith/deployment-quickstart), you can pass this configuration in the request body (`config`) or specific request headers. This enables adjustments based on user identity or other requests.
For privacy, control which headers are passed to the runtime configuration via the `http.configurable_headers` section in your [`langgraph.json`](/langsmith/application-structure#configuration-file) file.
Here's how to customize the included and excluded headers:
```json theme={null}
{
"http": {
"configurable_headers": {
"includes": ["x-user-id", "x-organization-id", "my-prefix-*"],
"excludes": ["authorization", "x-api-key"]
}
}
}
```
The `includes` and `excludes` lists accept exact header names or patterns using `*` to match any number of characters. For your security, no other regex patterns are supported.
## Using within your graph
You can access the included headers in your graph using the `config` argument of any node.
```python theme={null}
def my_node(state, config):
organization_id = config["configurable"].get("x-organization-id")
...
```
Or by fetching from context (useful in tools and or within other nested functions).
```python theme={null}
from langgraph.config import get_config
def search_everything(query: str):
organization_id = get_config()["configurable"].get("x-organization-id")
...
```
You can even use this to dynamically compile the graph.
```python theme={null}
# my_graph.py.
import contextlib
@contextlib.asynccontextmanager
async def generate_agent(config):
organization_id = config["configurable"].get("x-organization-id")
if organization_id == "org1":
graph = ...
yield graph
else:
graph = ...
yield graph
```
```json theme={null}
{
"graphs": {"agent": "my_grph.py:generate_agent"}
}
```
### Opt-out of configurable headers
If you'd like to opt-out of configurable headers, you can simply set a wildcard pattern in the `s` list:
```json theme={null}
{
"http": {
"configurable_headers": {
"excludes": ["*"]
}
}
}
```
This will exclude all headers from being added to your run's configuration.
Note that exclusions take precedence over inclusions.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/configurable-headers.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Include HTTP headers in server logs
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/configurable-logs
By default, the [Agent Server](/langsmith/agent-server) omits HTTP headers from server logs for privacy reasons. However, logging request and correlation IDs can help you debug issues and trace requests across distributed systems. You can opt-in to logging headers for all API calls by modifying the `logging_headers` section in your [`langgraph.json`](/langsmith/application-structure#configuration-file) file.
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"http": {
"logging_headers": {
"includes": ["request-id", "x-purchase-id", "*-trace-*"],
"excludes": ["authorization", "x-api-key", "x-organization-id", "x-user-id"]
}
}
}
```
The `includes` and `excludes` lists accept exact header names or glob patterns using `*` as a wildcard to match any number of characters (case-insensitive). For your security, no other pattern types are supported.
Note that exclusions take precedence over inclusions. For example, if you include `*-id` but exclude `x-user-id`, the `x-user-id` header will not be logged.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/configurable-logs.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Manage assistants
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/configuration-cloud
This page describes how to create, configure, and manage [assistants](/langsmith/assistants). Assistants allow you to customize your [deployed](/langsmith/deployments) graph's behavior through configuration—such as model selection, prompts, and tool availability—without changing the underlying graph code.
You can work with the [SDK](https://reference.langchain.com/python/langsmith/deployment/sdk/) or in the [LangSmith UI](https://smith.langchain.com).
## Understand assistant configuration
Assistants store *context* values that customize graph behavior at runtime. You define a context schema in your graph code, then provide specific context values when creating an assistant via the [`context` parameter](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.client.AssistantsClient.create).
Consider this example of a `call_model` node that reads the `model_name` from the context:
```python Python theme={null}
class ContextSchema(TypedDict):
model_name: str
builder = StateGraph(AgentState, context_schema=ContextSchema)
def call_model(state, runtime: Runtime[ContextSchema]):
messages = state["messages"]
model = _get_model(runtime.context.get("model_name", "anthropic"))
response = model.invoke(messages)
return {"messages": [response]}
```
```javascript JavaScript theme={null}
import { Annotation } from "@langchain/langgraph";
const ContextSchema = Annotation.Root({
model_name: Annotation,
system_prompt: Annotation,
});
const builder = new StateGraph(AgentState, ContextSchema)
function callModel(state: State, runtime: Runtime[ContextSchema]) {
const messages = state.messages;
const model = _getModel(runtime.context.model_name ?? "anthropic");
const response = model.invoke(messages);
return { messages: [response] };
}
```
When you create an assistant, you provide specific values for these configuration fields. The assistant stores this configuration and applies it whenever the graph runs.
For more information on configuration in [LangGraph](/oss/python/langgraph/overview), refer to the [runtime context documentation](/oss/python/langgraph/graph-api#runtime-context).
**Select SDK or UI for your workflow:**
## Create an assistant
Use the [AssistantsClient.create](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.client.AssistantsClient.create) method to create a new assistant. This method requires:
* **Graph ID**: The name of the deployed graph this assistant will use (e.g., `"agent"`).
* **Context**: Configuration values matching your graph's context schema.
* **Name**: A descriptive name for the assistant.
The following example creates an assistant with `model_name` set to `openai`:
```python Python theme={null}
from langgraph_sdk import get_client
# Initialize the client with your deployment URL
client = get_client(url=)
# Create an assistant for the "agent" graph
# The first parameter is the graph ID (also called graph name)
openai_assistant = await client.assistants.create(
"agent", # Graph ID of the deployed graph
context={"model_name": "openai"},
name="Open AI Assistant"
)
print(openai_assistant)
# Output includes the assistant_id (UUID) that uniquely identifies this assistant
```
```javascript JavaScript theme={null}
import { Client } from "@langchain/langgraph-sdk";
// Initialize the client with your deployment URL
const client = new Client({ apiUrl: });
// Create an assistant for the "agent" graph
const openAIAssistant = await client.assistants.create({
graphId: 'agent', // Graph ID of the deployed graph
name: "Open AI Assistant",
context: { "model_name": "openai" },
});
console.log(openAIAssistant);
// Output includes the assistant_id (UUID) that uniquely identifies this assistant
```
```bash cURL theme={null}
curl --request POST \
--url /assistants \
--header 'Content-Type: application/json' \
--data '{"graph_id":"agent", "context":{"model_name":"openai"}, "name": "Open AI Assistant"}'
```
**Response:**
The API returns an assistant object containing:
* `assistant_id`: A UUID that uniquely identifies this assistant
* `graph_id`: The graph this assistant is configured for
* `context`: The configuration values you provided
* `name`, `metadata`, timestamps, and other fields
```json theme={null}
{
"assistant_id": "62e209ca-9154-432a-b9e9-2d75c7a9219b",
"graph_id": "agent",
"name": "Open AI Assistant",
"context": {
"model_name": "openai"
},
"metadata": {},
"created_at": "2024-08-31T03:09:10.230718+00:00",
"updated_at": "2024-08-31T03:09:10.230718+00:00"
}
```
The `assistant_id` (a UUID like `"62e209ca-9154-432a-b9e9-2d75c7a9219b"`) uniquely identifies this assistant configuration. You'll use this ID when running your graph to specify which configuration to apply.
**Graph ID vs Assistant ID**
When creating an assistant, you specify a **graph ID** (graph name like `"agent"`). This returns an **assistant ID** (UUID like `"62e209ca..."`). You can use either when running your graph:
* **Graph ID** (e.g., `"agent"`): Uses the default assistant for that graph
* **Assistant ID** (UUID): Uses the specific assistant configuration
See [Use an assistant](#use-an-assistant) for examples.
## Use an assistant
To use an assistant, pass its `assistant_id` when creating a run. The example below uses the assistant we created above:
```python Python theme={null}
# Create a thread for the conversation
thread = await client.threads.create()
# Prepare the input
input = {"messages": [{"role": "user", "content": "who made you?"}]}
# Run the graph using the assistant's configuration
# Pass the assistant_id (UUID) as the second parameter
async for event in client.runs.stream(
thread["thread_id"],
openai_assistant["assistant_id"], # Assistant ID (UUID)
input=input,
stream_mode="updates",
):
print(f"Receiving event of type: {event.event}")
print(event.data)
print("\n\n")
```
```javascript JavaScript theme={null}
// Create a thread for the conversation
const thread = await client.threads.create();
// Prepare the input
const input = { "messages": [{ "role": "user", "content": "who made you?" }] };
// Run the graph using the assistant's configuration
// Pass the assistant_id (UUID) as the second parameter
const streamResponse = client.runs.stream(
thread["thread_id"],
openAIAssistant["assistant_id"], // Assistant ID (UUID)
{
input,
streamMode: "updates"
}
);
for await (const event of streamResponse) {
console.log(`Receiving event of type: ${event.event}`);
console.log(event.data);
console.log("\n\n");
}
```
```bash cURL theme={null}
# First, create a thread
thread_id=$(curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}' | jq -r '.thread_id')
# Run the graph with the assistant ID (UUID)
curl --request POST \
--url "/threads/${thread_id}/runs/stream" \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": "",
"input": {
"messages": [
{
"role": "user",
"content": "who made you?"
}
]
},
"stream_mode": ["updates"]
}' | \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "") {
print data_content "\n"
}
sub(/^event: /, "Receiving event of type: ", $0)
printf "%s...\n", $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "") {
print data_content "\n\n"
}
}
'
```
**Response:**
The stream returns events as the graph executes with your assistant's configuration:
```
Receiving event of type: metadata
{'run_id': '1ef6746e-5893-67b1-978a-0f1cd4060e16'}
Receiving event of type: updates
{'agent': {'messages': [{'content': 'I was created by OpenAI...', ...}]}}
```
**Using graph ID vs assistant ID**
You can pass either a **graph ID** or **assistant ID** when running your graph:
```python theme={null}
# Option 1: Use graph ID to get the default assistant
client.runs.stream(thread_id, "agent", input=input)
# Option 2: Use assistant ID (UUID) for a specific configuration
client.runs.stream(thread_id, "62e209ca-9154-432a-b9e9-2d75c7a9219b", input=input)
```
## Create a new version for your assistant
Use the [AssistantsClient.update](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.client.AssistantsClient.update) method to create a new version of an assistant.
**Updates require full configuration**
You must provide the **entire** configuration when updating. The update endpoint creates new versions from scratch and does not merge with previous versions. Include all configuration fields you want to retain.
For example, to add a system prompt to the assistant:
```python Python theme={null}
# Update the assistant with a new configuration
# IMPORTANT: Include ALL configuration fields, not just the ones you're changing
openai_assistant_v2 = await client.assistants.update(
openai_assistant["assistant_id"], # Assistant ID (UUID)
context={
"model_name": "openai", # Must include existing fields
"system_prompt": "You are a mindful assistant!", # New field
},
)
# This creates version 2 and sets it as the active version
# Future runs using this assistant_id will use version 2
```
```javascript JavaScript theme={null}
// Update the assistant with a new configuration
// IMPORTANT: Include ALL configuration fields, not just the ones you're changing
const openaiAssistantV2 = await client.assistants.update(
openAIAssistant["assistant_id"], // Assistant ID (UUID)
{
context: {
model_name: 'openai', // Must include existing fields
system_prompt: 'You are a mindful assistant!', // New field
},
},
);
// This creates version 2 and sets it as the active version
// Future runs using this assistant_id will use version 2
```
```bash cURL theme={null}
curl --request PATCH \
--url /assistants/ \
--header 'Content-Type: application/json' \
--data '{
"context": {"model_name": "openai", "system_prompt": "You are a mindful assistant!"}
}'
```
The update creates a new version and automatically sets it as active. All future runs using this assistant ID will use the new configuration.
## Use a previous assistant version
Use the `setLatest` method to change which version is active:
```python Python theme={null}
# Roll back to version 1 of the assistant
await client.assistants.set_latest(
openai_assistant['assistant_id'], # Assistant ID (UUID)
1 # Version number
)
# All future runs using this assistant_id will now use version 1
```
```javascript JavaScript theme={null}
// Roll back to version 1 of the assistant
await client.assistants.setLatest(
openaiAssistant['assistant_id'], // Assistant ID (UUID)
1 // Version number
);
// All future runs using this assistant_id will now use version 1
```
```bash cURL theme={null}
curl --request POST \
--url /assistants//latest \
--header 'Content-Type: application/json' \
--data '{
"version": 1
}'
```
After changing the active version, all runs using this assistant ID will use the specified version's configuration.
## Create an assistant
You can create assistants from the [LangSmith UI](https://smith.langchain.com):
1. Navigate to your deployment and select the **Assistants** tab.
2. Click **+ New assistant**.
3. In the form that opens:
* Select the graph this assistant is for.
* Provide a name and description.
* Configure the assistant using the configuration schema for that graph.
4. Click **Create assistant**.
This will take you to [Studio](/langsmith/studio) where you can test the assistant. Return to the **Assistants** tab to see your newly created assistant in the table.
## Use an assistant
To use an assistant in the LangSmith UI:
1. Navigate to your deployment and select the **Assistants** tab.
2. Find the assistant you want to use.
3. Click **Studio** for that assistant.
This opens [Studio](/langsmith/studio) with the selected assistant. When you submit an input (in **Graph** or **Chat** mode), the assistant's configuration will be applied to the run.
## Create a new version for your assistant
To update an assistant and create a new version from the UI, you can use either the Assistants tab or Studio. Either method creates a new version and sets it as the active version:
1. Navigate to your deployment and select the **Assistants** tab.
2. Find the assistant you want to edit.
3. Click **Edit**.
4. Modify the assistant's name, description, or configuration.
5. Save your changes.
1. Open Studio for the assistant.
2. Click **Manage Assistants**.
3. Edit the assistant's configuration.
4. Save your changes.
## Use a previous assistant version
To set a previous version as active from Studio:
1. Open Studio for the assistant.
2. Click **Manage Assistants**.
3. Locate the assistant and select the version you want to use.
4. Toggle the **Active** switch for that version.
This updates the assistant to use the selected version for all future runs.
Deleting an assistant will delete **all** of its versions. There is currently no way to delete a single version. To skip a version, simply set a different version as active.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/configuration-cloud.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Configure run input and output preview
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/configure-input-output-preview
By default, LangSmith uses a heuristic to determine what to display in the **Input** and **Output** columns of your **Runs** table. However, you can customize exactly what appears in these columns by configuring custom preview paths for specific trace types.
This is particularly useful when:
* Your traces have deeply nested structures.
* You want to focus on specific fields in your data.
* The default heuristic doesn't show the most relevant information for your use case.
## Configure preview format in the UI
### Access preview settings
1. Navigate to a trace in your project.
2. Select the **Runs** tab.
3. Locate **Format** at the top of the runs table.
4. In the **Configure Input and Output previews** side window, select a trace name from the dropdown.
When you select a trace name, LangSmith loads a successful trace example and renders its structure as an expandable tree. Each node in the tree represents a field in your data, showing:
* Field names (e.g., messages, output, metadata).
* Array indices (e.g., \[0], \[1], \[-1] for last item).
* Item counts for arrays (e.g., (3) indicating 3 items).
* Preview values for strings and numbers displayed inline.
### Set the path
1. Select the **Input** or **Output** tab. Then, either the:
* Dropdown to specify the path directly from your input data that should be shown in the preview.
* Interactive tree view of a sample trace's data structure, which you can explore and select the exact field you want to display.
To select a field:
1. Navigate the tree by clicking the arrow icons (▶) to expand or collapse nested objects and arrays.
2. Click the checkbox next to the field you want to display in the preview. The selected path appears in the text input preceding the tree.
When you select a checkbox, the path is automatically constructed using the correct syntax (e.g., messages\[-1].content).
| Method | Best For | Example |
| -------------- | ---------------------------------------------------------- | ----------------------------------------- |
| Tree selection | Exploring unfamiliar data structures, seeing sample values | Click through: messages → \[-1] → content |
| Manual typing | When you know exactly what you want, faster for deep paths | Type: output.data.results\[0].answer |
Arrays with more than 3 items are automatically condensed to prevent overwhelming views:
```
☐ messages (15)
☐ [0]
☐ [1]
... (click to expand all 15 items)
```
Click the **...** button to expand and view all array items.
## Example
For example, your trace input is this:
```
{
"messages": [
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "What is the weather today?"}
],
"metadata": {
"user_id": "user123",
"session_id": "sess456"
}
}
```
To display the user's question:
1. Expand the **messages** node (shows array items).
2. Expand `[1]` (the second message, which is the user message).
3. Click the checkbox next to **content**.
4. The input field shows: `messages[1].content`.
Or, use negative indexing for the last message:
1. Expand **messages**.
2. Expand `[-1]`.
3. Click **content**.
4. Result: `messages[-1].content` (always shows the last message).
If you see `"No paths available"` in the tree:
* Ensure you have at least one successful trace with the selected trace name in the last 7 days.
* The trace must have data in the input/output field you're configuring.
* Try sending a test trace if needed.
## Next steps
* Learn more about [viewing and filtering traces](/langsmith/filter-traces-in-application).
* Explore [custom output rendering](/langsmith/custom-output-rendering) for advanced visualization.
* Set up [metadata and tags](/langsmith/add-metadata-tags) to organize your traces.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/configure-input-output-preview.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to add TTLs to your application
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/configure-ttl
**Prerequisites**
This guide assumes familiarity with [LangSmith](/langsmith/home), [Persistence](/oss/python/langgraph/persistence), and [Cross-thread persistence](/oss/python/langgraph/persistence#memory-store) concepts.
LangSmith persists both [checkpoints](/oss/python/langgraph/persistence#checkpoints) (thread state) and [cross-thread memories](/oss/python/langgraph/persistence#memory-store) (store items). You can configure Time-to-Live (TTL) policies in [`langgraph.json`](/langsmith/application-structure#configuration-file) to manage the lifecycle of this data automatically, preventing indefinite accumulation.
## Configuring thread and checkpoint TTL
Checkpoints capture the state of conversation threads. Setting a TTL ensures old checkpoints and thread metadata are automatically deleted.
Add a `checkpointer.ttl` configuration to your `langgraph.json` file:
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"checkpointer": {
"ttl": {
"strategy": "delete",
"sweep_interval_minutes": 60,
"default_ttl": 43200
}
}
}
```
* `strategy`: Specifies the action taken on expiration.
* `"delete"`: Removes the entire thread including all associated run and checkpoint data when the TTL expires.
* `"keep_latest"`: Retains the thread and latest checkpoint, but deletes older checkpoint data that subsequent runs won't need.
* `sweep_interval_minutes`: Defines how often, in minutes, the system checks for expired checkpoints.
* `default_ttl`: Sets the default lifespan of threads (and corresponding checkpoints) in minutes (e.g., 43200 minutes = 30 days). Applies only to checkpoints created after this configuration is deployed; existing checkpoints/threads are not changed. To clear older data, delete it explicitly.
## Configuring store item TTL
Store items allow cross-thread data persistence. Configuring TTL for store items helps manage memory by removing stale data.
Add a `store.ttl` configuration to your `langgraph.json` file:
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"store": {
"ttl": {
"refresh_on_read": true,
"sweep_interval_minutes": 120,
"default_ttl": 10080
}
}
}
```
* `refresh_on_read`: (Optional, default `true`) If `true`, accessing an item via `get` or `search` resets its expiration timer. If `false`, TTL only refreshes on `put`.
* `sweep_interval_minutes`: (Optional) Defines how often, in minutes, the system checks for expired items. If omitted, no sweeping occurs.
* `default_ttl`: (Optional) Sets the default lifespan of store items in minutes (e.g., 10080 minutes = 7 days). Applies only to items created after this configuration is deployed; existing items are not changed. If you need to clear older items, delete them manually. If omitted, items do not expire by default.
## Combining TTL configurations
You can configure TTLs for both checkpoints and store items in the same `langgraph.json` file to set different policies for each data type. Here is an example:
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"checkpointer": {
"ttl": {
"strategy": "delete",
"sweep_interval_minutes": 60,
"default_ttl": 43200
}
},
"store": {
"ttl": {
"refresh_on_read": true,
"sweep_interval_minutes": 120,
"default_ttl": 10080
}
}
}
```
## Configure per-thread TTL
You can apply [TTL configurations per-thread](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.client.ThreadsClient.create).
```python theme={null}
thread = await client.threads.create(
ttl={
"strategy": "delete",
"ttl": 43200 # 30 days in minutes
}
)
```
Thread-level TTLs will also delete all associated checkpoints. As a result, you can set a thread-level TTL and avoid setting a separate TTL for checkpoints.
## Runtime overrides
The default `store.ttl` settings from `langgraph.json` can be overridden at runtime by providing specific TTL values in SDK method calls like `get`, `put`, and `search`.
## Deployment process
After configuring TTLs in `langgraph.json`, deploy or restart your LangGraph application for the changes to take effect. Use [`langgraph dev`](/langsmith/local-dev-testing#langgraph-dev) for local development or [`langgraph up`](/langsmith/local-dev-testing#langgraph-up) for Docker deployment.
For details on other configurable options, refer to the [LangGraph CLI reference page](/langsmith/cli#configuration-file).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/configure-ttl.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith control plane
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/control-plane
The *control plane* is the part of LangSmith that manages deployments. It includes the control plane UI, where users create and update [Agent Servers](/langsmith/agent-server), and the control plane APIs, which support the UI and provide programmatic access.
When you make an update through the control plane, the update is stored in control plane state. The [data plane](/langsmith/data-plane) “listener” polls for these updates by calling the control plane APIs.
## Control plane UI
From the control plane UI, you can:
* View a list of outstanding deployments.
* View details of an individual deployment.
* Create a new deployment.
* Update a deployment.
* Update environment variables for a deployment.
* View build and server logs of a deployment.
* View deployment metrics such as CPU and memory usage.
* Delete a deployment.
The Control plane UI is embedded in [LangSmith](https://docs.smith.langchain.com).
## Control plane API
This section describes the data model of the control plane API. The API is used to create, update, and delete deployments. See the [control plane API reference](/langsmith/api-ref-control-plane) for more details.
### Integrations
An integration is an abstraction for a `git` repository provider (e.g. GitHub). It contains all of the required metadata needed to connect with and deploy from a `git` repository.
### Deployments
A deployment is an instance of an Agent Server. A single deployment can have many revisions.
### Revisions
A revision is an iteration of a deployment. When a new deployment is created, an initial revision is automatically created. To deploy code changes or update secrets for a deployment, a new revision must be created.
### Listeners
A listener is an instance of a ["listener" application](/langsmith/data-plane#”listener”-application). A listener contains metadata about the application (e.g. version) and metadata about the compute infrastructure where it can deploy to (e.g. Kubernetes namespaces).
The listener data model only applies for [Hybrid](/langsmith/hybrid) and [Self-Hosted](/langsmith/self-hosted) deployments.
## Control plane features
This section describes various features of the control plane.
### Deployment types
For simplicity, the control plane offers two deployment types with different resource allocations: `Development` and `Production`.
| **Deployment Type** | **CPU/Memory** | **Scaling** | **Database** |
| ------------------- | --------------- | ----------------- | -------------------------------------------------------------------------------- |
| Development | 1 CPU, 1 GB RAM | Up to 1 replica | 10 GB disk, no backups |
| Production | 2 CPU, 2 GB RAM | Up to 10 replicas | Autoscaling disk, automatic backups, highly available (multi-zone configuration) |
CPU and memory resources are per replica.
**Immutable Deployment Type**
Once a deployment is created, the deployment type cannot be changed.
**Self-Hosted Deployment**
Resources for [Hybrid](/langsmith/hybrid) and [Self-Hosted](/langsmith/self-hosted) deployments can be fully customized. Deployment types are only applicable for [Cloud](/langsmith/cloud) deployments.
#### Production
`Production` type deployments are suitable for "production" workloads. For example, select `Production` for customer-facing applications in the critical path.
Resources for `Production` type deployments can be manually increased on a case-by-case basis depending on use case and capacity constraints. Contact support via [support.langchain.com](https://support.langchain.com) to request an increase in resources.
#### Development
`Development` type deployments are suitable development and testing. For example, select `Development` for internal testing environments. `Development` type deployments are not suitable for "production" workloads.
**Preemptible Compute Infrastructure**
`Development` type deployments (API server, queue server, and database) are provisioned on preemptible compute infrastructure. This means the compute infrastructure **may be terminated at any time without notice**. This may result in intermittent...
* Redis connection timeouts/errors
* Postgres connection timeouts/errors
* Failed or retrying background runs
This behavior is expected. Preemptible compute infrastructure **significantly reduces the cost to provision a `Development` type deployment**. By design, Agent Server is fault-tolerant. The implementation will automatically attempt to recover from Redis/Postgres connection errors and retry failed background runs.
`Production` type deployments are provisioned on durable compute infrastructure, not preemptible compute infrastructure.
Database disk size for `Development` type deployments can be manually increased on a case-by-case basis depending on use case and capacity constraints. For most use cases, [TTLs](/langsmith/configure-ttl) should be configured to manage disk usage. Contact support via [support.langchain.com](https://support.langchain.com) to request an increase in resources.
### Database provisioning
The control plane and [data plane](/langsmith/data-plane) "listener" application coordinate to automatically create a Postgres database for each deployment. The database serves as the [persistence layer](/oss/python/langgraph/persistence#memory-store) for the deployment.
When implementing a LangGraph application, a [checkpointer](/oss/python/langgraph/persistence#checkpointer-libraries) does not need to be configured by the developer. Instead, a checkpointer is automatically configured for the graph. Any checkpointer configured for a graph will be replaced by the one that is automatically configured.
There is no direct access to the database. All access to the database occurs through the [Agent Server](/langsmith/agent-server).
The database is never deleted until the deployment itself is deleted.
A custom Postgres instance can be configured for [Hybrid](/langsmith/hybrid) and [Self-Hosted](/langsmith/self-hosted) deployments.
### Asynchronous deployment
Infrastructure for deployments and revisions are provisioned and deployed asynchronously. They are not deployed immediately after submission. Currently, deployment can take up to several minutes.
* When a new deployment is created, a new database is created for the deployment. Database creation is a one-time step. This step contributes to a longer deployment time for the initial revision of the deployment.
* When a subsequent revision is created for a deployment, there is no database creation step. The deployment time for a subsequent revision is significantly faster compared to the deployment time of the initial revision.
* The deployment process for each revision contains a build step, which can take up to a few minutes.
The control plane and [data plane](/langsmith/data-plane) "listener" application coordinate to achieve asynchronous deployments.
### Monitoring
After a deployment is ready, the control plane monitors the deployment and records various metrics, such as:
* CPU and memory usage of the deployment.
* Number of container restarts.
* Number of replicas (this will increase with [autoscaling](/langsmith/data-plane#autoscaling)).
* [PostgreSQL](/langsmith/data-plane#postgres) CPU, memory usage, and disk usage.
* [Agent Server queue](/langsmith/agent-server#persistence-and-task-queue) pending/active run count.
* [Agent Server API](/langsmith/agent-server) success response count, error response count, and latency.
These metrics are displayed as charts in the Control Plane UI.
### LangSmith integration
A [LangSmith](/langsmith/home) tracing project is automatically created for each deployment. The tracing project has the same name as the deployment. When creating a deployment, the `LANGCHAIN_TRACING` and `LANGSMITH_API_KEY`/`LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set automatically by the control plane.
When a deployment is deleted, the traces and the tracing project are not deleted.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/control-plane.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Cost tracking
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/cost-tracking
Building agents at scale introduces non-trivial, usage-based costs that can be difficult to track. LangSmith automatically records LLM token usage and costs for major providers, and also allows you to submit custom cost data for any additional components.
This gives you a single, unified view of costs across your entire application, which makes it easy to monitor, understand, and debug your spend.
This guide covers:
* [Viewing costs in the LangSmith UI](#viewing-costs-in-the-langsmith-ui)
* [How cost tracking works](#cost-tracking)
* [How to send custom cost data](#send-custom-cost-data)
## Viewing costs in the LangSmith UI
In the [LangSmith UI](https://smith.langchain.com), you can explore usage and spend in three main ways: first by understanding how tokens and costs are broken down, then by viewing those details within individual traces, and finally by inspecting aggregated metrics in project stats and dashboards.
### Token and cost breakdowns
Token usage and costs are broken down into three categories:
* **Input**: Tokens in the prompt sent to the model. Subtypes include: cache reads, text tokens, image tokens, etc
* **Output**: Tokens generated in the response from the model. Subtypes include: reasoning tokens, text tokens, image tokens, etc
* **Other**: Costs from tool calls, retrieval steps or any custom runs.
You can view detailed breakdowns by hovering over cost sections in the UI. When available, each section is further categorized by subtype.
You can inspect these breakdowns throughout the LangSmith UI, described in the following section.
### Where to view token and cost breakdowns
The trace tree shows the most detailed view of token usage and cost (for a single trace). It displays the total usage for the entire trace, aggregated values for each parent run and token and cost breakdowns for each child run.
Open any run inside a tracing project to view its trace tree.
When tracking costs across threads, ensure that all child runs include the thread metadata (`session_id`, `thread_id`, or `conversation_id`). Without thread metadata on child runs, token counts and costs from those runs won't be included in thread-level aggregations. Refer to [configuring threads](/langsmith/threads) for details on setting thread metadata.
The project stats panel shows the total token usage and cost for all traces in a project.
Dashboards help you explore cost and token usage trends over time. The [prebuilt dashboard](/langsmith/dashboards/#prebuilt-dashboards) for a tracing project shows total costs and a cost breakdown by input and output tokens.
You may also configure custom cost tracking charts in [custom dashboards](https://docs.langchain.com/langsmith/dashboards#custom-dashboards).
## Cost tracking
You can track costs in two ways:
1. Costs for LLM calls can be **automatically derived from token counts and model prices**
2. Cost for LLM calls or any other run type can be **manually specified as part of the run data**
The approach you use will depend on on what you're tracking and how your model pricing is structured:
| Method | Run type: LLM | Run type: Other |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| **Automatically** |
Calling LLMs with [LangChain](/oss/python/langchain/overview)
Tracing LLM calls to OpenAI, Anthropic or models that follow an OpenAI-compliant format with `@traceable`
Using LangSmith wrappers for [OpenAI](/langsmith/trace-openai) or [Anthropic](/langsmith/trace-anthropic)
For other model providers, read the [token and cost information guide](/langsmith/log-llm-trace#provide-token-and-cost-information)
| Not applicable. |
| **Manually** | If LLM call costs are non-linear (eg. follow a custom cost function) | Send costs for any run types, e.g. tool calls, retrieval steps |
### LLM calls: Automatically track costs based on token counts
To compute cost automatically from token usage, you need to provide **token counts**, the **model and provider** and the **model price**.
Follow the instructions below if you’re using model providers whose responses don’t follow the same patterns as one of OpenAI or Anthropic.
These steps are **only required** if you are *not*:
* Calling LLMs with [LangChain](/oss/python/langchain/overview)
* Using `@traceable` to trace LLM calls to OpenAI, Anthropic or models that follow an OpenAI-compliant format
* Using LangSmith wrappers for [OpenAI](/langsmith/trace-openai) or [Anthropic](/langsmith/trace-anthropic).
**1. Send token counts**
Many models include token counts as part of the response. You must extract this information and include it in your run using one of the following methods:
Set a `usage_metadata` field on the run's metadata. The advantage of this approach is that you do not need to change your traced function’s runtime outputs
```python Python theme={null}
from langsmith import traceable, get_current_run_tree
inputs = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "I'd like to book a table for two."},
]
@traceable(
run_type="llm",
metadata={"ls_provider": "my_provider", "ls_model_name": "my_model"}
)
def chat_model(messages: list):
# Imagine this is the real model output format your application expects
assistant_message = {
"role": "assistant",
"content": "Sure, what time would you like to book the table for?"
}
# Token usage you compute or receive from the provider
token_usage = {
"input_tokens": 27,
"output_tokens": 13,
"total_tokens": 40,
"input_token_details": {"cache_read": 10}
}
# Attach token usage to the LangSmith run
run = get_current_run_tree()
run.set(usage_metadata=token_usage)
return assistant_message
chat_model(inputs)
```
```typescript TypeScript theme={null}
import { traceable, getCurrentRunTree } from "langsmith/traceable";
const inputs = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "I'd like to book a table for two." },
];
const chatModel = traceable(
async ({ messages }) => {
// The output your application expects
const assistantMessage = {
role: "assistant",
content: "Sure, what time would you like to book the table for?",
};
// Token usage you compute or receive from the provider
const tokenUsage = {
input_tokens: 27,
output_tokens: 13,
total_tokens: 40,
input_token_details: { cache_read: 10 },
};
// Attach usage to the LangSmith run
const runTree = getCurrentRunTree();
runTree.metadata.usage_metadata = tokenUsage;
return assistantMessage;
},
{
run_type: "llm",
name: "chat_model",
metadata: {
ls_provider: "my_provider",
ls_model_name: "my_model",
},
}
);
await chatModel({ messages: inputs });
```
Include the `usage_metadata` key directly within the object returned by your traced function. LangSmith will extract it from the output.
```python Python theme={null}
from langsmith import traceable
inputs = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "I'd like to book a table for two."},
]
output = {
"choices": [
{
"message": {
"role": "assistant",
"content": "Sure, what time would you like to book the table for?"
}
}
],
"usage_metadata": {
"input_tokens": 27,
"output_tokens": 13,
"total_tokens": 40,
"input_token_details": {"cache_read": 10}
},
}
@traceable(
run_type="llm",
metadata={"ls_provider": "my_provider", "ls_model_name": "my_model"}
)
def chat_model(messages: list):
return output
chat_model(inputs)
```
```typescript TypeScript theme={null}
import { traceable } from "langsmith/traceable";
const messages = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "I'd like to book a table for two." }
];
const output = {
choices: [
{
message: {
role: "assistant",
content: "Sure, what time would you like to book the table for?",
},
},
],
usage_metadata: {
input_tokens: 27,
output_tokens: 13,
total_tokens: 40,
},
};
const chatModel = traceable(
async ({
messages,
}: {
messages: { role: string; content: string }[];
model: string;
}) => {
return output;
},
{
run_type: "llm",
name: "chat_model",
metadata: {
ls_provider: "my_provider",
ls_model_name: "my_model"
}
}
);
await chatModel({ messages });
```
In either case, the usage metadata should contain a subset of the following LangSmith-recognized fields:
The following fields in the `usage_metadata` dict are recognized by LangSmith. You can view the full [Python types](https://github.com/langchain-ai/langsmith-sdk/blob/e705fbd362be69dd70229f94bc09651ef8056a61/python/langsmith/schemas.py#L1196-L1227) or [TypeScript interfaces](https://github.com/langchain-ai/langsmith-sdk/blob/e705fbd362be69dd70229f94bc09651ef8056a61/js/src/schemas.ts#L637-L689) directly.
Number of tokens used in the model input. Sum of all input token types.
Number of tokens used in the model response. Sum of all output token types.
Number of tokens used in the input and output. Optional, can be inferred. Sum of input\_tokens + output\_tokens.
Breakdown of input token types. Keys are token-type strings, values are counts. Example `{"cache_read": 5}`.
Known fields include: `audio`, `text`, `image`, `cache_read`, `cache_creation`. Additional fields are possible depending on the model or provider.
Breakdown of output token types. Keys are token-type strings, values are counts. Example `{"reasoning": 5}`.
Known fields include: `audio`, `text`, `image`, `reasoning`. Additional fields are possible depending on the model or provider.
Cost of the input tokens.
Cost of the output tokens.
Cost of the tokens. Optional, can be inferred. Sum of input\_cost + output\_cost.
Details of the input cost. Keys are token-type strings, values are cost amounts.
Details of the output cost. Keys are token-type strings, values are cost amounts.
**Cost Calculations**
The cost for a run is computed greedily from most-to-least specific token type. Suppose you set a price of \$2 per 1M input tokens with a detailed price of \$1 per 1M `cache_read` input tokens, and \$3 per 1M output tokens. If you uploaded the following usage metadata:
```python theme={null}
{
"input_tokens": 20,
"input_token_details": {"cache_read": 5},
"output_tokens": 10,
"total_tokens": 30,
}
```
Then, the token costs would be computed as follows:
```python theme={null}
# Notice that LangSmith computes the cache_read cost and then for any
# remaining input_tokens, the default input price is applied.
input_cost = 5 * 1e-6 + (20 - 5) * 2e-6 # 3.5e-5
output_cost = 10 * 3e-6 # 3e-5
total_cost = input_cost + output_cost # 6.5e-5
```
**2. Specify model name**
When using a custom model, the following fields need to be specified in a [run's metadata](/langsmith/add-metadata-tags) in order to associate token counts with costs. It's also helpful to provide these metadata fields to identify the model when viewing traces and when filtering.
* `ls_provider`: The provider of the model, e.g., “openai”, “anthropic”
* `ls_model_name`: The name of the model, e.g., “gpt-4.1-mini”, “claude-3-opus-20240229”
**3. Set model prices**
A model pricing map is used to map model names to their per-token prices to compute costs from token counts. LangSmith's [model pricing table](https://smith.langchain.com/settings/workspaces/models) is used for this.
The table comes with pricing information for most OpenAI, Anthropic, and Gemini models. You can [add prices for other models](/langsmith/cost-tracking#create-a-new-model-price-entry), or [overwrite pricing for default models](/langsmith/cost-tracking#update-an-existing-model-price-entry) if you have custom pricing.
For models that have different pricing for different token types (e.g., multimodal or cached tokens), you can specify a breakdown of prices for each token type. Hovering over the `...` next to the input/output prices shows you the price breakdown by token type.
Updates to the model pricing map are not reflected in the costs for traces already logged. We do not currently support backfilling model pricing changes.
To modify the default model prices, create a new entry with the same model, provider and match pattern as the default entry.
To create a *new entry* in the model pricing map, click on the `+ Model` button in the top right corner.
Here, you can specify the following fields:
* **Model Name**: The human-readable name of the model.
* **Input Price**: The cost per 1M input tokens for the model. This number is multiplied by the number of tokens in the prompt to calculate the prompt cost.
* **Input Price Breakdown** (Optional): The breakdown of price for each different type of input token, e.g. `cache_read`, `video`, `audio`
* **Output Price**: The cost per 1M output tokens for the model. This number is multiplied by the number of tokens in the completion to calculate the completion cost.
* **Output Price Breakdown** (Optional): The breakdown of price for each different type of output token, e.g. `reasoning`, `image`, etc.
* **Model Activation Date** (Optional): The date from which the pricing is applicable. Only runs after this date will apply this model price.
* **Match Pattern**: A regex pattern to match the model name. This is used to match the value for `ls_model_name` in the run metadata.
* **Provider** (Optional): The provider of the model. If specified, this is matched against `ls_provider` in the run metadata.
Once you have set up the model pricing map, LangSmith will automatically calculate and aggregate the token-based costs for traces based on the token counts provided in the LLM invocations.
### LLM calls: Sending costs directly
If your model follows a non-linear pricing scheme, we recommend calculating costs client-side and sending them to LangSmith as `usage_metadata`.
Gemini 3 Pro Preview and Gemini 2.5 Pro follow a pricing scheme with a stepwise cost function. We support this pricing scheme for Gemini by default. For any other models with non-linear pricing, you will need to follow these instructions to calculate costs.
```python Python theme={null}
from langsmith import traceable, get_current_run_tree
inputs = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "I'd like to book a table for two."},
]
@traceable(
run_type="llm",
metadata={"ls_provider": "my_provider", "ls_model_name": "my_model"}
)
def chat_model(messages: list):
llm_output = {
"choices": [
{
"message": {
"role": "assistant",
"content": "Sure, what time would you like to book the table for?"
}
}
],
"usage_metadata": {
# Specify cost (in dollars) for the inputs and outputs
"input_cost": 1.1e-6,
"input_cost_details": {"cache_read": 2.3e-7},
"output_cost": 5.0e-6,
},
}
run = get_current_run_tree()
run.set(usage_metadata=llm_output["usage_metadata"])
return llm_output["choices"][0]["message"]
chat_model(inputs)
```
```typescript TypeScript theme={null}
import { traceable, getCurrentRunTree } from "langsmith/traceable";
const messages = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "I'd like to book a table for two." }
];
const chatModel = traceable(
async (messages: { role: string; content: string }[]) => {
const llmOutput = {
choices: [
{
message: {
role: "assistant",
content: "Sure, what time would you like to book the table for?",
},
},
],
// Specify cost (in dollars) for the inputs and outputs
usage_metadata: {
input_cost: 1.1e-6,
input_cost_details: { cache_read: 2.3e-7 },
output_cost: 5.0e-6,
},
};
// Attach usage metadata to the run
const runTree = getCurrentRunTree();
runTree.metadata.usage_metadata = llmOutput.usage_metadata;
// Return only the assistant message
return llmOutput.choices[0].message;
},
{
run_type: "llm",
name: "chat_model",
metadata: {
ls_provider: "my_provider",
ls_model_name: "my_model",
},
}
);
await chatModel(messages);
```
### Other runs: Sending costs
You can also send cost information for any non-LLM runs, such as tool calls.The cost must be specified in the `total_cost` field under the runs `usage_metadata`.
Set a `total_cost` field on the run’s `usage_metadata`. The advantage of this approach is that you do not need to change your traced function’s runtime outputs
```python Python theme={null}
from langsmith import traceable, get_current_run_tree
# Example tool: get_weather
@traceable(run_type="tool", name="get_weather")
def get_weather(city: str):
# Your tool logic goes here
result = {
"temperature_f": 68,
"condition": "sunny",
"city": city,
}
# Cost for this tool call (computed however you like)
tool_cost = 0.0015
# Attach usage metadata to the LangSmith run
run = get_current_run_tree()
run.set(usage_metadata={"total_cost": tool_cost})
# Return only the actual tool result (no usage info)
return result
tool_response = get_weather("San Francisco")
```
```typescript TypeScript theme={null}
import { traceable, getCurrentRunTree } from "langsmith/traceable";
// Example tool: get_weather
const getWeather = traceable(
async ({ city }) => {
// Your tool logic goes here
const result = {
temperature_f: 68,
condition: "sunny",
city,
};
// Cost for this tool call (computed however you like)
const toolCost = 0.0015;
// Attach usage metadata to the LangSmith run
const runTree = getCurrentRunTree();
runTree.metadata.usage_metadata = {
total_cost: toolCost,
};
// Return only the actual tool result (no usage info)
return result;
},
{
run_type: "tool",
name: "get_weather",
}
);
const toolResponse = await getWeather({ city: "San Francisco" });
```
Include the `usage_metadata` key directly within the object returned by your traced function. LangSmith will extract it from the output.
```python Python theme={null}
from langsmith import traceable
# Example tool: get_weather
@traceable(run_type="tool", name="get_weather")
def get_weather(city: str):
# Your tool logic goes here
result = {
"temperature_f": 68,
"condition": "sunny",
"city": city,
}
# Attach tool call costs here
return {
**result,
"usage_metadata": {
"total_cost": 0.0015, # <-- cost for this tool call
},
}
tool_response = get_weather("San Francisco")
```
```typescript TypeScript theme={null}
import { traceable } from "langsmith/traceable";
// Example tool: get_weather
const getWeather = traceable(
async ({ city }) => {
// Your tool logic goes here
const result = {
temperature_f: 68,
condition: "sunny",
city,
};
// Attach tool call costs here
return {
...result,
usage_metadata: {
total_cost: 0.0015, // <-- cost for this tool call
},
};
},
{
run_type: "tool",
name: "get_weather",
}
);
const toolResponse = await getWeather({ city: "San Francisco" });
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/cost-tracking.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Create a prompt
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/create-a-prompt
In the [LangSmith UI](https://smith.langchain.com), navigate to the **Playground** in the left-hand sidebar or from the application homepage.
## Compose your prompt
The left panel of the playground is an editable view of the prompt.
The prompt is made up of messages, each of which has a *role*, including:
* **System**: The "instruction manual". Use this to define the AI's persona, tone, and ground rules (e.g., "You are a helpful assistant that explains things like the weather").
* **Human**: The "user". This represents the person asking questions or providing instructions to the AI.
* **AI**: The "assistant". This is the model’s response. In the playground, you can use this to provide "few-shot" examples—showing the AI exactly how you want it to respond.
* **Tool / Function**: These roles represent the output from external tools (like a calculator or a search engine). They help you test how the AI should behave after receiving specific data.
* **Chat**: A general-purpose role, often used when importing logs or conversation history where specific labels haven't been assigned.
* **Messages List**: A dynamic placeholder. This allows you to add a variable that contains an entire list of previous messages, making it easy to manage long conversation histories.
### Template format
The default [template format](/langsmith/prompt-template-format) is f-string, but you can change the prompt template format to mustache by clicking on the dropbox below the prompt boxes.
### Add a template variable
Prompts become particularly useful when you add variables in your prompt. You can use variables to add dynamic content to your prompt. Add a template variable in one of two ways:
* Add `{variable_name}` to your prompt (with one curly brace on each side for f-string or two for mustache).
* Highlight text you want to templatize and click **Convert to variable** tooltip button that displays. Enter a name for your variable, and convert.
Once you've added a variable, the right panel of the playground will have an **Input** box for a sample input for the prompt variable. Fill these in with values to test the prompt.
For more details on the prompt template formats generally and examples in both syntax, refer to the [Prompt template format](/langsmith/prompt-template-format) guide.
### Structured output
Adding an output schema to your prompt will get output in a structured format. Learn more about structured output [here](/langsmith/prompt-engineering-concepts#structured-output).
### Tools
You can also add a tool by clicking the **+ Tool** button at the bottom of the prompt editor. For more information on how to use tools, refer to [Use tools](/langsmith/use-tools).
Use the **[Polly](/langsmith/polly)** AI assistant in the Playground to generate tools, create output schemas, and optimize your prompts with AI assistance.
## Run the prompt
To run a prompt, use **Start** at the top of the right panel in the playground.
## Save your prompt
To save your prompt, click the **Save** button and name your prompt.
The model and configuration you select in the playground settings will be saved with the prompt. When you reopen the prompt, the model and configuration will automatically load from the saved version.
The first time you create a public prompt, you'll be asked to set a LangChain Hub handle. All your public prompts will be linked to this handle. In a shared workspace, this handle will be set for the whole workspace.
## View your prompts
After you've created a prompt, you can view a table of your prompts under **Prompts** in the left-hand side bar.
## Add metadata
To add metadata to your prompt, click the **More** icon on the top right-hand side of the page and then click the **Update metadata** from the dropdown. This brings you to a page where you can add additional information about the prompt, including a description and README.
# Next steps
Now that you've created a prompt, you can use it in your application code. See [how to pull a prompt programmatically](/langsmith/manage-prompts-programmatically#pull-a-prompt).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/create-a-prompt.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Create an account and API key
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/create-account-api-key
To get started with LangSmith, you need to create an account. You can sign up for a free account in the [LangSmith UI](https://smith.langchain.com). LangSmith supports sign in with Google, GitHub, and email.
## API keys
LangSmith supports two types of API keys: Service Keys and Personal Access Tokens. Both types of tokens can be used to authenticate requests to the LangSmith API, but they have different use cases.
For more details on Service Keys and Personal Access Tokens, refer to the [Administration overview page](/langsmith/administration-overview).
## Create an API key
To log traces and run evaluations with LangSmith, you will need to create an API key to authenticate your requests. API keys can be scoped to a set of [workspaces](/langsmith/administration-overview#workspaces), or the entire [organization](/langsmith/administration-overview#organizations).
To create either type of API key:
1. Navigate to the [Settings page](https://smith.langchain.com/settings) and scroll to the **API Keys** section.
2. For service keys, choose between an organization-scoped and workspace-scoped key. If the key is workspace-scoped, the workspaces must then be specified.
Enterprise users are also able to [assign specific roles](/langsmith/administration-overview#workspace-roles-rbac) to the key, which adjusts its permissions.
3. Set the key's expiration; the key will become unusable after the number of days chosen, or never, if that is selected.
4. Click **Create API Key.**
The API key will be shown only once, so make sure to copy it and store it in a safe place.
## Delete an API key
To delete an API key:
1. Navigate to the [Settings page](https://smith.langchain.com/settings) and scroll to the **API Keys** section.
2. Find the API key you need to delete from the table. Toggle **Personal** or **Service** as needed.
3. Select the trash icon in the **Actions** column and confirm deletion.
## Configure the SDK
You may set the following environment variables in addition to `LANGSMITH_API_KEY`.
This is only required if using the EU instance.
`LANGSMITH_ENDPOINT=https://eu.api.smith.langchain.com`
This is only required for keys scoped to more than one workspace.
`LANGSMITH_WORKSPACE_ID=`
## Using API keys outside of the SDK
See [instructions for managing your organization via API](/langsmith/manage-organization-by-api).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/create-account-api-key.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to improve your evaluator with few-shot examples
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/create-few-shot-evaluators
Using LLM-as-a-judge evaluators can be very helpful when you can't evaluate your system programmatically. However, their effectiveness depends on their quality and how well they align with human reviewer feedback. LangSmith provides the ability to improve the alignment of LLM-as-a-judge evaluator to human preferences using few-shot examples.
Human corrections are automatically inserted into your evaluator prompt using few-shot examples. Few-shot examples is a technique inspired by [few-shot prompting](https://www.promptingguide.ai/techniques/fewshot) that guides the models output with a few high-quality examples.
This guide covers how to set up few-shot examples as part of your LLM-as-a-judge evaluator and apply corrections to feedback scores.
## How few-shot examples work
* Few-shot examples are added to your evaluator prompt using the `{{Few-shot examples}}` variable
* Creating an evaluator with few-shot examples, will automatically create a dataset for you, which will be auto-populated with few-shot examples once you start making corrections
* At runtime, these examples will inserted into the evaluator to serve as a guide for its outputs - this will help the evaluator to better align with human preferences
## Configure your evaluator
Few-shot examples are not currently supported in LLM-as-a-judge evaluators that use the prompt hub and are only compatible with prompts that use mustache formatting.
Before enabling few-shot examples, set up your LLM-as-a-judge evaluator. If you haven't done this yet, follow the steps in the [LLM-as-a-judge evaluator guide](/langsmith/llm-as-judge).
### 1. Configure variable mapping
Each few-shot example is formatted according to the variable mapping specified in the configuration. The variable mapping for few-shot examples, should contain the same variables as your main prompt, plus a `few_shot_explanation` and a `score` variable which should have the same name as your feedback key.
For example, if your main prompt has variables `question` and `response`, and your evaluator outputs a `correctness` score, then your few-shot prompt should have the vartiables `question`, `response`, `few_shot_explanation`, and `correctness`.
### 2. Specify the number of few-shot examples to use
You may also specify the number of few-shot examples to use. The default is 5. If your examples are very long, you may want to set this number lower to save tokens - whereas if your examples tend to be short, you can set a higher number in order to give your evaluator more examples to learn from. If you have more examples in your dataset than this number, we will randomly choose them for you.
## Make corrections
[Audit evaluator scores](/langsmith/audit-evaluator-scores)
As you start logging traces or running experiments, you will likely disagree with some of the scores that your evaluator has given. When you [make corrections to these scores](/langsmith/audit-evaluator-scores), you will begin seeing examples populated inside your corrections dataset. As you make corrections, make sure to attach explanations - these will get populated into your evaluator prompt in place of the `few_shot_explanation` variable.
The inputs to the few-shot examples will be the relevant fields from the inputs, outputs, and reference (if this an offline evaluator) of your chain/dataset. The outputs will be the corrected evaluator score and the explanations that you created when you left the corrections. Feel free to edit these to your liking. Here is an example of a few-shot example in a corrections dataset:
Note that the corrections may take a minute or two to be populated into your few-shot dataset. Once they are there, future runs of your evaluator will include them in the prompt!
## View your corrections dataset
In order to view your corrections dataset:
* **Online evaluators**: Select your run rule and click **Edit Rule**
* **Offline evaluators**: Select your evaluator and click **Edit Evaluator**
Head to your dataset of corrections linked in the **Improve evaluator accuracy using few-shot examples** section. You can view and update your few-shot examples in the dataset.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/create-few-shot-evaluators.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Use cron jobs
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/cron-jobs
There are many situations in which it is useful to run an assistant on a schedule.
For example, say that you're building an assistant that runs daily and sends an email summary
of the day's news. You could use a cron job to run the assistant every day at 8:00 PM.
LangSmith Deployment supports cron jobs, which run on a user-defined schedule. The user specifies a schedule, an assistant, and some input. After that, on the specified schedule, the server will:
* Create a new thread with the specified assistant
* Send the specified input to that thread
Note that this sends the same input to the thread every time.
The LangSmith Deployment API provides several endpoints for creating and managing cron jobs. See the [API reference](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref/) for more details.
Sometimes you don't want to run your graph based on user interaction, but rather you would like to schedule your graph to run on a schedule - for example if you wish for your graph to compose and send out a weekly email of to-dos for your team. LangSmith Deployment allows you to do this without having to write your own script by using the `Crons` client. To schedule a graph job, you need to pass a [cron expression](https://crontab.cronhub.io/) to inform the client when you want to run the graph. `Cron` jobs are run in the background and do not interfere with normal invocations of the graph.
All cron schedules are interpreted in **UTC**. Make sure to convert your desired execution time to UTC when specifying the schedule.
## Setup
First, let's set up our SDK client, assistant, and thread:
```python theme={null}
from langgraph_sdk import get_client
client = get_client(url=)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
# create thread
thread = await client.threads.create()
print(thread)
```
```js theme={null}
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
// create thread
const thread = await client.threads.create();
console.log(thread);
```
```bash theme={null}
curl --request POST \
--url /assistants/search \
--header 'Content-Type: application/json' \
--data '{
"limit": 10,
"offset": 0
}' | jq -c 'map(select(.config == null or .config == {})) | .[0].graph_id' && \
curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Output:
```
{
'thread_id': '9dde5490-2b67-47c8-aa14-4bfec88af217',
'created_at': '2024-08-30T23:07:38.242730+00:00',
'updated_at': '2024-08-30T23:07:38.242730+00:00',
'metadata': {},
'status': 'idle',
'config': {},
'values': None
}
```
## Cron job on a thread
To create a cron job associated with a specific thread, you can write:
```python theme={null}
# This schedules a job to run at 15:27 (3:27PM) UTC every day
cron_job = await client.crons.create_for_thread(
thread["thread_id"],
assistant_id,
schedule="27 15 * * *",
input={"messages": [{"role": "user", "content": "What time is it?"}]},
)
```
```js theme={null}
// This schedules a job to run at 15:27 (3:27PM) UTC every day
const cronJob = await client.crons.create_for_thread(
thread["thread_id"],
assistantId,
{
schedule: "27 15 * * *",
input: { messages: [{ role: "user", content: "What time is it?" }] }
}
);
```
```bash theme={null}
curl --request POST \
--url /threads//runs/crons \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": ,
}'
```
Note that it is **very** important to delete `Cron` jobs that are no longer useful. Otherwise you could rack up unwanted API charges to the LLM! You can delete a `Cron` job using the following code:
```python theme={null}
await client.crons.delete(cron_job["cron_id"])
```
```js theme={null}
await client.crons.delete(cronJob["cron_id"]);
```
```bash theme={null}
curl --request DELETE \
--url /runs/crons/
```
## Cron job stateless
You can also create stateless cron jobs by using the following code. Stateless cron jobs create a new thread for each execution:
```python theme={null}
# This schedules a job to run at 15:27 (3:27PM) UTC every day
cron_job_stateless = await client.crons.create(
assistant_id,
schedule="27 15 * * *",
input={"messages": [{"role": "user", "content": "What time is it?"}]},
)
```
```js theme={null}
// This schedules a job to run at 15:27 (3:27PM) UTC every day
const cronJobStateless = await client.crons.create(
assistantId,
{
schedule: "27 15 * * *",
input: { messages: [{ role: "user", content: "What time is it?" }] }
}
);
```
```bash theme={null}
curl --request POST \
--url /runs/crons \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": ,
}'
```
Again, remember to delete your job once you are done with it!
```python theme={null}
await client.crons.delete(cron_job_stateless["cron_id"])
```
```js theme={null}
await client.crons.delete(cronJobStateless["cron_id"]);
```
```bash theme={null}
curl --request DELETE \
--url /runs/crons/
```
## Thread cleanup for stateless crons
This feature requires LangGraph API version **0.5.18** or later and Python SDK **0.3.2** or later, or JavaScript SDK **1.4.0** or later.
Every time a stateless cron is triggered, a new thread is created. Control what happens to that thread after the run completes using the `on_run_completed` parameter:
* **`"delete"`** (default): Automatically deletes the thread after the run completes.
* **`"keep"`**: Preserves the thread for later retrieval. You are responsible for cleaning up these threads. See [how to add TTLs to your application](/langsmith/configure-ttl) for the recommended approach.
### Example: Keeping threads for later retrieval
```python theme={null}
# Create a stateless cron that keeps threads after execution.
# Configure checkpointer.ttl in langgraph.json to auto-delete old threads.
# See: https://docs.langchain.com/langsmith/configure-ttl
cron_job = await client.crons.create(
assistant_id,
schedule="27 15 * * *",
input={"messages": [{"role": "user", "content": "Daily report"}]},
on_run_completed="keep"
)
# You can later retrieve the runs and their results
runs = await client.runs.search(
metadata={"cron_id": cron_job["cron_id"]}
)
```
```js theme={null}
// Create a stateless cron that keeps threads after execution.
// Configure checkpointer.ttl in langgraph.json to auto-delete old threads.
// See: https://docs.langchain.com/langsmith/configure-ttl
const cronJob = await client.crons.create(
assistantId,
{
schedule: "27 15 * * *",
input: { messages: [{ role: "user", content: "Daily report" }] },
onRunCompleted: "keep"
}
);
// You can later retrieve the runs and their results
const runs = await client.runs.search({
metadata: { cron_id: cronJob["cron_id"] }
});
```
```bash theme={null}
# Create a stateless cron that keeps threads after execution.
# Configure checkpointer.ttl in langgraph.json to auto-delete old threads.
# See: https://docs.langchain.com/langsmith/configure-ttl
curl --request POST \
--url /runs/crons \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": "",
"schedule": "27 15 * * *",
"input": {"messages": [{"role": "user", "content": "Daily report"}]},
"on_run_completed": "keep"
}'
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/cron-jobs.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Add custom authentication
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/custom-auth
This guide shows you how to add custom authentication to your LangSmith application. The steps on this page apply to both [cloud](/langsmith/cloud) and [self-hosted](/langsmith/self-hosted) deployments. It does not apply to isolated usage of the [LangGraph open source library](/oss/python/langgraph/overview) in your own custom server.
## Add custom authentication to your deployment
To leverage custom authentication and access user-level metadata in your deployments, set up custom authentication to automatically populate the `config["configurable"]["langgraph_auth_user"]` object through a custom authentication handler. You can then access this object in your graph with the `langgraph_auth_user` key to [allow an agent to perform authenticated actions on behalf of the user](#enable-agent-authentication).
1. Implement authentication:
Without a custom `@auth.authenticate` handler, LangGraph sees only the API-key owner (usually the developer), so requests aren’t scoped to individual end-users. To propagate custom tokens, you must implement your own handler.
```python theme={null}
from langgraph_sdk import Auth
import requests
auth = Auth()
def is_valid_key(api_key: str) -> bool:
is_valid = # your API key validation logic
return is_valid
@auth.authenticate # (1)!
async def authenticate(headers: dict) -> Auth.types.MinimalUserDict:
api_key = headers.get(b"x-api-key")
if not api_key or not is_valid_key(api_key):
raise Auth.exceptions.HTTPException(status_code=401, detail="Invalid API key")
# Fetch user-specific tokens from your secret store
user_tokens = await fetch_user_tokens(api_key)
return { # (2)!
"identity": api_key, # fetch user ID from LangSmith
"github_token" : user_tokens.github_token
"jira_token" : user_tokens.jira_token
# ... custom fields/secrets here
}
```
* This handler receives the request (headers, etc.), validates the user, and returns a dictionary with at least an identity field.
* You can add any custom fields you want (e.g., OAuth tokens, roles, org IDs, etc.).
2. In your [`langgraph.json`](/langsmith/application-structure#configuration-file), add the path to your auth file:
```json highlight={7-9} theme={null}
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"env": ".env",
"auth": {
"path": "./auth.py:my_auth"
}
}
```
3. Once you've set up authentication in your server, requests must include the required authorization information based on your chosen scheme. Assuming you are using JWT token authentication, you could access your deployments using any of the following methods:
```python theme={null}
from langgraph_sdk import get_client
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
client = get_client(
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await client.threads.search()
```
```python theme={null}
from langgraph.pregel.remote import RemoteGraph
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
remote-graph = RemoteGraph(
"agent",
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await remote-graph.ainvoke(...)
```
```javascript theme={null}
import { Client } from "@langchain/langgraph-sdk";
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
const client = new Client({
apiUrl: "http://localhost:2024",
defaultHeaders: { Authorization: `Bearer ${my_token}` },
});
const threads = await client.threads.search();
```
```javascript theme={null}
import { RemoteGraph } from "@langchain/langgraph/remote";
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
const remoteGraph = new RemoteGraph({
graphId: "agent",
url: "http://localhost:2024",
headers: { Authorization: `Bearer ${my_token}` },
});
const threads = await remoteGraph.invoke(...);
```
```bash theme={null}
curl -H "Authorization: Bearer ${your-token}" http://localhost:2024/threads
```
For more details on RemoteGraph, refer to the [Use RemoteGraph](/langsmith/use-remote-graph) guide.
## Enable agent authentication
After [authentication](#add-custom-authentication-to-your-deployment), the platform creates a special configuration object (`config`) that is passed to LangSmith deployment. This object contains information about the current user, including any custom fields you return from your `@auth.authenticate` handler.
To allow an agent to perform authenticated actions on behalf of the user, access this object in your graph with the `langgraph_auth_user` key:
```python theme={null}
def my_node(state, config):
user_config = config["configurable"].get("langgraph_auth_user")
# token was resolved during the @auth.authenticate function
token = user_config.get("github_token","")
...
```
Fetch user credentials from a secure secret store. Storing secrets in graph state is not recommended.
### Authorizing a user for Studio
By default, if you add custom authorization on your resources, this will also apply to interactions made from [Studio](/langsmith/studio). If you want, you can handle logged-in Studio users differently by checking [is\_studio\_user()](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.auth.types.StudioUser).
`is_studio_user` was added in version 0.1.73 of the langgraph-sdk. If you're on an older version, you can still check whether `isinstance(ctx.user, StudioUser)`.
```python theme={null}
from langgraph_sdk.auth import is_studio_user, Auth
auth = Auth()
# ... Setup authenticate, etc.
@auth.on
async def add_owner(
ctx: Auth.types.AuthContext,
value: dict # The payload being sent to this access method
) -> dict: # Returns a filter dict that restricts access to resources
if is_studio_user(ctx.user):
return {}
filters = {"owner": ctx.user.identity}
metadata = value.setdefault("metadata", {})
metadata.update(filters)
return filters
```
Only use this if you want to permit developer access to a graph deployed on the managed LangSmith SaaS.
## Learn more
* [Authentication & Access Control](/langsmith/auth)
* [Setting up custom authentication tutorial](/langsmith/set-up-custom-auth)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/custom-auth.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to customize the Dockerfile
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/custom-docker
Users can add an array of additional lines to add to the Dockerfile following the import from the parent LangGraph image. In order to do this, you simply need to modify your `langgraph.json` file by passing in the commands you want run to the `dockerfile_lines` key. For example, if we wanted to use `Pillow` in our graph you would need to add the following dependencies:
```
{
"dependencies": ["."],
"graphs": {
"openai_agent": "./openai_agent.py:agent",
},
"env": "./.env",
"dockerfile_lines": [
"RUN apt-get update && apt-get install -y libjpeg-dev zlib1g-dev libpng-dev",
"RUN pip install Pillow"
]
}
```
This would install the system packages required to use Pillow if we were working with `jpeg` or `png` image formats.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/custom-docker.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Connect to a custom model
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/custom-endpoint
The LangSmith playground allows you to use your own custom models. You can deploy a model server that exposes your model's API via [LangServe](https://github.com/langchain-ai/langserve), an open source library for serving LangChain applications. Behind the scenes, the playground will interact with your model server to generate responses.
## Deploy a custom model server
For your convenience, we have provided a sample model server that you can use as a reference. You can find the sample model server [here](https://github.com/langchain-ai/langsmith-model-server) We highly recommend using the sample model server as a starting point.
Depending on your model is an instruct-style or chat-style model, you will need to implement either `custom_model.py` or `custom_chat_model.py` respectively.
## Adding configurable fields
It is often useful to configure your model with different parameters. These might include temperature, model\_name, max\_tokens, etc.
To make your model configurable in the LangSmith playground, you need to add configurable fields to your model server. These fields can be used to change model parameters from the playground.
You can add configurable fields by implementing the `with_configurable_fields` function in the `config.py` file. You can
```python theme={null}
def with_configurable_fields(self) -> Runnable:
"""Expose fields you want to be configurable in the playground. We will automatically expose these to the
playground. If you don't want to expose any fields, you can remove this method."""
return self.configurable_fields(n=ConfigurableField(
id="n",
name="Num Characters",
description="Number of characters to return from the input prompt.",
))
```
## Use the model in the LangSmith Playground
Once you have deployed a model server, you can use it in the LangSmith Playground. Enter the playground and select either the `ChatCustomModel` or the `CustomModel` provider for chat-style model or instruct-style models.
Enter the `URL`. The playground will automatically detect the available endpoints and configurable fields. You can then invoke the model with the desired parameters.
If everything is set up correctly, you should see the model's response in the playground as well as the configurable fields specified in the `with_configurable_fields`.
See how to store your model configuration for later use [here](/langsmith/managing-model-configurations).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/custom-endpoint.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to add custom lifespan events
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/custom-lifespan
When deploying agents to LangSmith, you often need to initialize resources like database connections when your server starts up, and ensure they're properly closed when it shuts down. Lifespan events let you hook into your server's startup and shutdown sequence to handle these critical setup and teardown tasks.
This works the same way as [adding custom routes](/langsmith/custom-routes). You just need to provide your own [`Starlette`](https://www.starlette.io/applications/) app (including [`FastAPI`](https://fastapi.tiangolo.com/), [`FastHTML`](https://fastht.ml/) and other compatible apps).
Below is an example using FastAPI.
"Python only"
We currently only support custom lifespan events in Python deployments with `langgraph-api>=0.0.26`.
## Create app
Starting from an **existing** LangSmith application, add the following lifespan code to your `webapp.py` file. If you are starting from scratch, you can create a new app from a template using the CLI.
```bash theme={null}
langgraph new --template=new-langgraph-project-python my_new_project
```
Once you have a LangGraph project, add the following app code:
```python {highlight={19}} theme={null}
# ./src/agent/webapp.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
@asynccontextmanager
async def lifespan(app: FastAPI):
# for example...
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
# Create reusable session factory
async_session = sessionmaker(engine, class_=AsyncSession)
# Store in app state
app.state.db_session = async_session
yield
# Clean up connections
await engine.dispose()
app = FastAPI(lifespan=lifespan)
# ... can add custom routes if needed.
```
## Configure `langgraph.json`
Add the following to your `langgraph.json` configuration file. Make sure the path points to the `webapp.py` file you created above.
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"agent": "./src/agent/graph.py:graph"
},
"env": ".env",
"http": {
"app": "./src/agent/webapp.py:app"
}
// Other configuration options like auth, store, etc.
}
```
## Start server
Test the server out locally:
```bash theme={null}
langgraph dev --no-browser
```
You should see your startup message printed when the server starts, and your cleanup message when you stop it with `Ctrl+C`.
## Deploying
You can deploy your app as-is to cloud or to your self-hosted platform.
## Next steps
Now that you've added lifespan events to your deployment, you can use similar techniques to add [custom routes](/langsmith/custom-routes) or [custom middleware](/langsmith/custom-middleware) to further customize your server's behavior.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/custom-lifespan.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to add custom middleware
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/custom-middleware
When deploying agents to LangSmith, you can add custom middleware to your server to handle concerns like logging request metrics, injecting or checking headers, and enforcing security policies without modifying core server logic. This works the same way as [adding custom routes](/langsmith/custom-routes). You just need to provide your own [`Starlette`](https://www.starlette.io/applications/) app (including [`FastAPI`](https://fastapi.tiangolo.com/), [`FastHTML`](https://fastht.ml/) and other compatible apps).
Adding middleware lets you intercept and modify requests and responses globally across your deployment, whether they're hitting your custom endpoints or the built-in LangSmith APIs.
Below is an example using FastAPI.
"Python only"
We currently only support custom middleware in Python deployments with `langgraph-api>=0.0.26`.
## Create app
Starting from an **existing** LangSmith application, add the following middleware code to your `webapp.py` file. If you are starting from scratch, you can create a new app from a template using the CLI.
```bash theme={null}
langgraph new --template=new-langgraph-project-python my_new_project
```
Once you have a LangGraph project, add the following app code:
```python {highlight={5}} theme={null}
# ./src/agent/webapp.py
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
app = FastAPI()
class CustomHeaderMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
response.headers['X-Custom-Header'] = 'Hello from middleware!'
return response
# Add the middleware to the app
app.add_middleware(CustomHeaderMiddleware)
```
## Configure `langgraph.json`
Add the following to your `langgraph.json` configuration file. Make sure the path points to the `webapp.py` file you created above.
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"agent": "./src/agent/graph.py:graph"
},
"env": ".env",
"http": {
"app": "./src/agent/webapp.py:app"
}
// Other configuration options like auth, store, etc.
}
```
### Customize middleware ordering
By default, custom middleware runs before authentication logic. To run custom middleware *after* authentication, set `middleware_order` to `auth_first` in your `http` configuration. (This customization is supported starting with API server v0.4.35 and later.)
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"agent": "./src/agent/graph.py:graph"
},
"env": ".env",
"http": {
"app": "./src/agent/webapp.py:app",
"middleware_order": "auth_first"
},
"auth": {
"path": "./auth.py:my_auth"
}
}
```
## Start server
Test the server out locally:
```bash theme={null}
langgraph dev --no-browser
```
Now any request to your server will include the custom header `X-Custom-Header` in its response.
## Deploying
You can deploy this app as-is to cloud or to your self-hosted platform.
## Next steps
Now that you've added custom middleware to your deployment, you can use similar techniques to add [custom routes](/langsmith/custom-routes) or define [custom lifespan events](/langsmith/custom-lifespan) to further customize your server's behavior.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/custom-middleware.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Connect to an OpenAI compliant model provider/proxy
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/custom-openai-compliant-model
The LangSmith playground allows you to use any model that is compliant with the OpenAI API. You can utilize your model by setting the Proxy Provider for in the playground.
## Deploy an OpenAI compliant model
Many providers offer OpenAI compliant models or proxy services. Some examples of this include:
* [LiteLLM Proxy](https://github.com/BerriAI/litellm?tab=readme-ov-file#quick-start-proxy---cli)
* [Ollama](https://ollama.com/)
You can use these providers to deploy your model and get an API endpoint that is compliant with the OpenAI API.
Take a look at the full [specification](https://platform.openai.com/docs/api-reference/chat) for more information.
## Use the model in the LangSmith Playground
Once you have deployed a model server, you can use it in the LangSmith [Playground](/langsmith/prompt-engineering-concepts#prompt-playground).
To access the **Prompt Settings** menu:
1. Under the **Prompts** heading select the gear icon next to the model name.
2. In the **Model Configuration** tab, select the model to edit in the dropdown.
3. For the **Provider** dropdown, select **OpenAI Compatible Endpoint**.
4. Add your OpenAI Compatible Endpoint to the **Base URL** input.
If everything is set up correctly, you should see the model's response in the playground. You can also use this functionality to invoke downstream pipelines.
For information on how to store your model configuration , refer to [Configure prompt settings](/langsmith/managing-model-configurations).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/custom-openai-compliant-model.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Custom output rendering
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/custom-output-rendering
Custom output rendering allows you to visualize run outputs and dataset reference outputs using your own custom HTML pages. This is particularly useful for:
* **Domain-specific formatting**: Display medical records, legal documents, or other specialized data types in their native format.
* **Custom visualizations**: Create charts, graphs, or diagrams from numeric or structured output data.
In this page you'll learn how to:
* **[Configure custom rendering](#configure-custom-output-rendering)** in the LangSmith UI.
* **[Build a custom renderer](#build-a-custom-renderer)** to display output data.
* **[Understand where custom rendering appears](#where-custom-rendering-appears)** in LangSmith.
## Configure custom output rendering
Configure custom rendering at two levels:
* **For datasets**: Apply custom rendering to all runs associated with that dataset, wherever they appear—in experiments, run detail panes, or annotation queues.
* **For annotation queues**: Apply custom rendering to all runs within a specific annotation queue, regardless of which dataset they come from. This takes precedence over dataset-level configuration.
### For tracing projects
To configure custom output rendering for a tracing project:
1. Navigate to the **Tracing Projects** page.
2. Click on an existing tracing project or create a new one.
3. In the edit tracing project pane, scroll to the **Custom Output Rendering** section.
4. Toggle **Enable custom output rendering**.
5. Enter the webpage URL in the **URL** field.
6. Click **Save**.
### For datasets
To configure custom output rendering for a dataset:
1. Navigate to your dataset in the **Datasets & Experiments** page.
2. Click **⋮** (three-dot menu) in the top right corner.
3. Select **Custom Output Rendering**.
4. Toggle **Enable custom output rendering**.
5. Enter the webpage URL in the **URL** field.
6. Click **Save**.
### For annotation queues
To configure custom output rendering for an annotation queue:
1. Navigate to the **Annotation Queues** page.
2. Click on an existing annotation queue or create a new one.
3. In the annotation queue settings pane, scroll to the **Custom Output Rendering** section.
4. Toggle **Enable custom output rendering**.
5. Enter the webpage URL in the **URL** field.
6. Click **Save** or **Create**.
When custom rendering settings are applied at multiple levels, the precedence is as follows: annotation queue > dataset > tracing project.
## Build a custom renderer
### Understand the message format
Your HTML page will receive output data via the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). LangSmith sends messages with the following structure:
```typescript theme={null}
{
type: "output" | "reference",
data: {
// The outputs (actual output or reference output)
// Structure varies based on your application
},
metadata: {
inputs: {
// The inputs that generated this output
// Structure varies based on your application
}
}
}
```
* `type`: Indicates whether this is an actual output (`"output"`) or a reference output (`"reference"`).
* `data`: The output data itself.
* `metadata.inputs`: The input data that generated this output, provided for context.
**Message delivery timing**: LangSmith uses an exponential backoff retry mechanism to ensure your page receives the data even if it loads slowly. Messages are sent up to 6 times with increasing delays (100ms, 200ms, 400ms, 800ms, 1600ms, 3200ms).
### Example implementation
This example listens for incoming postMessage events and displays them on the page. Each message is numbered and formatted as JSON, making it easy to inspect the data structure LangSmith sends to your renderer.
```html theme={null}
PostMessage Echo
PostMessage Messages
```
## Where custom rendering appears
When enabled, your custom rendering will replace the default output view in:
* **Experiment comparison view**: When comparing outputs across multiple experiments:
* **Run detail panes**: When viewing runs that are associated with a dataset:
* **Annotation queues**: When reviewing runs in annotation queues:
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/custom-output-rendering.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to add custom routes
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/custom-routes
When deploying agents to LangSmith Deployment, your server automatically exposes routes for creating runs and threads, interacting with the long-term memory store, managing configurable assistants, and other core functionality ([see all default API endpoints](/langsmith/server-api-ref)).
You can add custom routes by providing your own [`Starlette`](https://www.starlette.io/applications/) app (including [`FastAPI`](https://fastapi.tiangolo.com/), [`FastHTML`](https://fastht.ml/) and other compatible apps). You make LangSmith aware of this by providing a path to the app in your `langgraph.json` configuration file.
Defining a custom app object lets you add any routes you'd like, so you can do anything from adding a `/login` endpoint to writing an entire full-stack web-app, all deployed in a single Agent Server.
Below is an example using FastAPI.
## Create app
Starting from an **existing** LangSmith application, add the following custom route code to your `webapp.py` file. If you are starting from scratch, you can create a new app from a template using the CLI.
```bash theme={null}
langgraph new --template=new-langgraph-project-python my_new_project
```
Once you have a LangGraph project, add the following app code:
```python {highlight={4}} theme={null}
# ./src/agent/webapp.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
def read_root():
return {"Hello": "World"}
```
## Configure `langgraph.json`
Add the following to your `langgraph.json` configuration file. Make sure the path points to the FastAPI application instance `app` in the `webapp.py` file you created above.
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"agent": "./src/agent/graph.py:graph"
},
"env": ".env",
"http": {
"app": "./src/agent/webapp.py:app"
}
// Other configuration options like auth, store, etc.
}
```
## Start server
Test the server out locally:
```bash theme={null}
langgraph dev --no-browser
```
If you navigate to `localhost:2024/hello` in your browser (`2024` is the default development port), you should see the `/hello` endpoint returning `{"Hello": "World"}`.
**Shadowing default endpoints**
The routes you create in the app are given priority over the system defaults, meaning you can shadow and redefine the behavior of any default endpoint.
## Deploying
You can deploy this app as-is to LangSmith or to your self-hosted platform.
## Next steps
Now that you've added a custom route to your deployment, you can use this same technique to further customize how your server behaves, such as defining [custom middleware](/langsmith/custom-middleware) and [custom lifespan events](/langsmith/custom-lifespan).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/custom-routes.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Monitor projects with dashboards
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/dashboards
Dashboards give you high-level insights into your trace data, helping you spot trends and monitor the health of your applications. Dashboards are available in the **Monitoring** tab in the left sidebar.
LangSmith offers two dashboard types:
* **Prebuilt dashboards**: Automatically generated for every tracing project.
* **Custom dashboards**: Fully configurable collections of charts tailored to your needs.
## Prebuilt dashboards
Prebuilt dashboards are created automatically for each project and cover essential metrics, such as trace count, error rates, token usage, and more. By default, the prebuilt dashboard for your tracing project can be accessed using the **Dashboard** button on the top right of the tracing project page.
**You cannot modify a prebuilt dashboard. In the future, we plan to allow you to clone a default dashboard in order to have a starting point to customize it.**
### Dashboard sections
Prebuilt dashboards are broken down into the following sections:
| Section | What it shows |
| :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Traces | Trace count, latency and error rates. A [trace](/langsmith/observability-concepts#traces) is a collection of [runs](/langsmith/observability-concepts#runs) related to a single operation. For example, if a user request triggers an agent, all runs for that agent invocation would be part of the same trace. |
| LLM Calls | LLM call count and latency. Includes all runs where run type is "llm". |
| Cost & Tokens | Total and per-trace token counts and costs, broken down by token type. Costs are measured using [LangSmith's cost tracking](/langsmith/log-llm-trace#manually-provide-token-counts). |
| Tools | Run counts, error rates, and latency stats for tool runs broken down by tool name. Includes runs where run type is "tool". Limits to top 5 most frequently occurring tools. |
| Run Types | Run counts, error rates, and latency stats for runs that are immediate children of the root run. This helps in understanding the high-level execution path of agents. Limits to top 5 most frequently occurring run names. Refer to the image following this table. |
| Feedback Scores | Aggregate stats for the top 5 most frequently occurring types of feedback. Charts show average score for numerical feedback and category counts for categorical feedback. |
For example, for the following trace, the following runs have a depth of 1:
### Group by
Group by [run tag or metadata](/langsmith/add-metadata-tags) can be used to split data over attributes that are important to your application. The global group by setting appears on the top right hand side of the dashboard. Note that the Tool and Run Type charts already have a group by applied, so the global group by won't take effect; the global group by will apply to all other charts.
When adding metadata to runs, we recommend having the same metadata on the trace, as well as the specific run (e.g. LLM call). Metadata and tags are not propagated from parent to child runs, or vice versa. So, if you want to see e.g. both your trace charts and your LLM call charts grouped on some metadata key then both your traces (root runs) and your LLM runs need to have that metadata attached.
## Custom dashboards
Create tailored collections of charts for tracking metrics that matter most for your application.
### Creating a new dashboard
1. Navigate to the **Monitor** tab in the left sidebar.
2. Click on the **+ New Dashboard** button.
3. Give your dashboard a name and a description.
4. Click on **Create**.
### Adding charts to your dashboard
1. Within a dashboard, click on the **+ New Chart** button to open up the chart creation pane.
2. Give your chart a name and a description.
3. Configure the chart.
### Chart configuration
#### Select tracing projects and filter runs
* Select one or more tracing projects to track metrics for.
* Use the **Chart filters** section to refine the matching runs. This filter applies to all data series in the chart. For more information on filtering traces, view our guide on [filtering traces in application](./filter-traces-in-application).
#### Pick a metric
* Choose a metric from the dropdown menu to set the y-axis of your chart. With a project and a metric selected, you'll see a preview of your chart and the matching runs.
* For certain metrics (such as latency, token usage, cost), we support comparing multiple metrics with the same unit. For example, you may want one chart where you can see prompt tokens and completion tokens. Each metric appears as a separate line.
#### Split the data
There are two ways to create multiple series in a chart (i.e. create multiple lines in a chart):
1. **Group by**: Group runs by [run tag or metadata](/langsmith/add-metadata-tags), run name, or run type. Group by automatically splits the data into multiple series based on the field selected. Note that group by is limited to the top 5 elements by frequency.
2. **Data series**: Manually define multiple series with individual filters. This is useful for comparing granular data within a single metric.
#### Pick a chart type
* Choose between a line chart and a bar chart for visualizing
### Save and manage charts
* Click `Save` to save your chart to the dashboard.
* Edit or delete a chart by clicking the triple dot button in the top right of the chart.
* Clone a chart by clicking the triple line button in the top right of the chart and selecting **+ Clone**. This will open a new chart creation pane with the same configurations as the original.
## Linking to a dashboard from a tracing project
You can link to any dashboard directly from a tracing project. By default, the prebuilt dashboard for your tracing project is selected. If you have a custom dashboard that you would like to link instead:
1. In your tracing project, click the three dots next to the **Dashboard** button.
2. Choose a dashboard to set as the new default.
## Example: user-journey monitoring
Use monitoring charts for mapping the decisions made by an agent at a particular node.
Consider an email assistant agent. At a particular node it makes a decision about an email to:
* send an email back
* notify the user
* no response needed
We can create a chart to track and visualize the breakdown of these decisions.
**Creating the chart**
1. **Metric Selection**: Select the metric `Run count`.
2. **Chart Filters**: Add a tree filter to include all of the traces with name `triage_input`. This means we only include traces that hit the `triage_input` node. Also add a chart filter for `Is Root` is `true`, so our count is not inflated by the number of nodes in the trace.
3. **Data Series**: Create a data series for each decision made at the `triage_input` node. The output of the decision is stored in the `triage.response` field of the output object, and the value of the decision is either `no`, `email`, or `notify`. Each of these decisions generates a separate data series in the chart.
Now we can visualize the decisions made at the `triage_input` node over time.
## Video guide
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/dashboards.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Bulk exporting trace data
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/data-export
**Plan restrictions apply**
Please note that the Data Export functionality is only supported for [LangSmith Plus or Enterprise tiers](https://www.langchain.com/pricing-langsmith).
LangSmith's bulk data export functionality allows you to export your traces into an external destination. This can be useful if you want to analyze the data offline in a tool such as BigQuery, Snowflake, RedShift, Jupyter Notebooks, etc.
An export can be launched to target a specific LangSmith project and date range. Once a batch export is launched, the system will handle the orchestration and [resilience of the export process](#failure-modes-and-retry-policy).
Exporting your data may take some time depending on the size of your data. LangSmith also has a limit on how many of your exports can run at the same time. Bulk exports have a runtime timeout of 72 hours, refer to [Automatic retry behavior](#automatic-retry-behavior) for more details.
## Destinations
Currently we support exporting to an S3 bucket or S3 API compatible bucket that you provide. The data will be exported in
[Parquet](https://parquet.apache.org/docs/overview/) columnar format. This format will allow you to easily import the data into
other systems. The data export will contain equivalent data fields as the [Run data format](/langsmith/run-data-format).
## Exporting data
### Destinations - providing a S3 bucket
To export LangSmith data, you will need to provide an S3 bucket where the data will be exported to.
The following information is needed for the export:
* **Bucket Name**: The name of the S3 bucket where the data will be exported to.
* **Prefix**: The root prefix within the bucket where the data will be exported to.
* **S3 Region**: The region of the bucket - this is needed for AWS S3 buckets.
* **Endpoint URL**: The endpoint URL for the S3 bucket - this is needed for S3 API compatible buckets.
* **Access Key**: The access key for the S3 bucket.
* **Secret Key**: The secret key for the S3 bucket.
* **Include Bucket in Prefix** (optional): Whether to include the bucket name as part of the path prefix. Defaults to `false` for new destinations or when the bucket name is already present in the path. Set to `true` for legacy compatibility or when using storage systems that require the bucket name in the path.
We support any S3 compatible bucket, for non AWS buckets such as GCS or MinIO, you will need to provide the endpoint URL.
### Preparing the destination
**For self-hosted and EU region deployments**
Update the LangSmith URL appropriately for self-hosted installations or organizations in the EU region in the requests below.
For the EU region, use `eu.api.smith.langchain.com`.
#### Permissions required
Both the `backend` and `queue` services require write access to the destination bucket:
* The `backend` service attempts to write a test file to the destination bucket when the export destination is created. It will delete the test file if it has permission to do so (delete access is optional).
* The `queue` service is responsible for bulk export execution and uploading the files to the bucket.
**AWS S3 permissions**
The minimal AWS S3 permission policy relies on the following permissions:
* `s3:PutObject` (required): Allows writing Parquet files to the bucket.
* `s3:DeleteObject` (optional): Cleans up test files during destination creation. If this permission isn't present, the file is left under the `/tmp` directory after destination creation.
* `s3:GetObject` (optional but recommended): Verifies file size after writing.
* `s3:AbortMultipartUpload` (optional but recommended): Avoids dangling multipart uploads.
Minimal IAM policy example:
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::YOUR_BUCKET_NAME/*"
]
}
]
}
```
Recommended IAM policy example with additional permissions:
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:DeleteObject",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::YOUR_BUCKET_NAME/*"
]
}
]
}
```
**Google Cloud Storage (GCS) permissions**
When using GCS with the S3-compatible XML API, the following IAM permissions are required:
* `storage.objects.create` (required): Allows writing files to the bucket.
* `storage.objects.delete` (optional): Cleans up test files during destination creation. If this permission isn't present, the file is left under the `/tmp` directory after destination creation.
* `storage.objects.get` (optional but recommended): Verifies file size after writing.
These permissions can be granted through the "Storage Object Admin" predefined role or a custom role.
#### Create a destination
The following example demonstrates how to create a destination using cURL. Replace the placeholder values with your actual configuration details.
Note that credentials will be stored securely in an encrypted form in our system.
```bash theme={null}
curl --request POST \
--url 'https://api.smith.langchain.com/api/v1/bulk-exports/destinations' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-Tenant-Id: YOUR_WORKSPACE_ID' \
--data '{
"destination_type": "s3",
"display_name": "My S3 Destination",
"config": {
"bucket_name": "your-s3-bucket-name",
"prefix": "root_folder_prefix",
"region": "your aws s3 region",
"endpoint_url": "your endpoint url for s3 compatible buckets",
"include_bucket_in_prefix": true
},
"credentials": {
"access_key_id": "YOUR_S3_ACCESS_KEY_ID",
"secret_access_key": "YOUR_S3_SECRET_ACCESS_KEY"
}
}'
```
Use the returned `id` to reference this destination in subsequent bulk export operations.
**If you receive an error while creating a destination, see [debug destination errors](#debugging-destination-errors) for details on how to debug this.**
#### Credentials configuration
**Requires LangSmith Helm version >= `0.10.34` (application version >= `0.10.91`)**
We support the following additional credentials formats besides static `access_key_id` and `secret_access_key`:
* To use [temporary credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html) that include an AWS session token,
additionally provide the `credentials.session_token` key when creating the bulk export destination.
* (Self-hosted only): To use environment-based credentials such as with [AWS IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) (IRSA),
omit the `credentials` key from the request when creating the bulk export destination.
In this case, the [standard Boto3 credentials locations](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#credentials) will be checked in the order defined by the library.
#### AWS S3 bucket
For AWS S3, you can leave off the `endpoint_url` and supply the region that matches the region of your bucket.
```bash theme={null}
curl --request POST \
--url 'https://api.smith.langchain.com/api/v1/bulk-exports/destinations' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-Tenant-Id: YOUR_WORKSPACE_ID' \
--data '{
"destination_type": "s3",
"display_name": "My AWS S3 Destination",
"config": {
"bucket_name": "my_bucket",
"prefix": "data_exports",
"region": "us-east-1"
},
"credentials": {
"access_key_id": "YOUR_S3_ACCESS_KEY_ID",
"secret_access_key": "YOUR_S3_SECRET_ACCESS_KEY"
}
}'
```
#### Google GCS XML S3 compatible bucket
When using Google's GCS bucket, you need to use the XML S3 compatible API, and supply the `endpoint_url`
which is typically `https://storage.googleapis.com`.
Here is an example of the API request when using the GCS XML API which is compatible with S3:
```bash theme={null}
curl --request POST \
--url 'https://api.smith.langchain.com/api/v1/bulk-exports/destinations' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-Tenant-Id: YOUR_WORKSPACE_ID' \
--data '{
"destination_type": "s3",
"display_name": "My GCS Destination",
"config": {
"bucket_name": "my_bucket",
"prefix": "data_exports",
"endpoint_url": "https://storage.googleapis.com"
"include_bucket_in_prefix": true
},
"credentials": {
"access_key_id": "YOUR_S3_ACCESS_KEY_ID",
"secret_access_key": "YOUR_S3_SECRET_ACCESS_KEY"
}
}'
```
See [Google documentation](https://cloud.google.com/storage/docs/interoperability#xml_api) for more info
### Create an export job
To export data, you will need to create an export job. This job will specify the destination, the project, the date range, and filter expression of the data to export. The filter expression is used to narrow down the set of runs exported and is optional. Not setting the filter field will export all runs. Refer to our [filter query language](/langsmith/trace-query-syntax#filter-query-language) and [examples](/langsmith/export-traces#use-filter-query-language) to determine the correct filter expression for your export.
You can use the following cURL command to create the job:
```bash theme={null}
curl --request POST \
--url 'https://api.smith.langchain.com/api/v1/bulk-exports' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-Tenant-Id: YOUR_WORKSPACE_ID' \
--data '{
"bulk_export_destination_id": "your_destination_id",
"session_id": "project_uuid",
"start_time": "2024-01-01T00:00:00Z",
"end_time": "2024-01-02T23:59:59Z",
"filter": "and(eq(run_type, \"llm\"), eq(name, \"ChatOpenAI\"), eq(input_key, \"messages.content\"), like(input_value, \"%messages.content%\"))",
"format_version": "v2_beta"
}'
```
The `session_id` is also known as the Tracing Project ID, which can be copied from the individual project view by clicking into the project in the Tracing Projects list.
Use the returned `id` to reference this export in subsequent bulk export operations.
#### Exportable fields
By default, bulk exports include the following fields for each run. You can optionally specify a subset of these fields when creating an export to reduce file size and export time. For more details, refer to [Limiting exported fields](#limiting-exported-fields).
The following fields are available for export:
**Identifiers & hierarchy:**
| Field | Description |
| ---------------------- | ----------------------------------------- |
| `id` | Run ID |
| `tenant_id` | Workspace/tenant ID |
| `session_id` | Project/session ID |
| `trace_id` | Trace ID |
| `parent_run_id` | Parent run ID |
| `parent_run_ids` | List of all parent run IDs |
| `reference_example_id` | Reference to example if part of a dataset |
**Basic metadata:**
| Field | Description |
| -------------- | ------------------------------------------ |
| `name` | Run name |
| `run_type` | Type of run (e.g., "chain", "llm", "tool") |
| `start_time` | Start timestamp (UTC) |
| `end_time` | End timestamp (UTC) |
| `status` | Run status (e.g., "success", "error") |
| `is_root` | Whether this is a root-level run |
| `dotted_order` | Hierarchical ordering string |
| `trace_tier` | Trace tier/retention level |
**Run data:**
| Field | Description |
| --------- | ----------------------- |
| `inputs` | Run inputs (JSON) |
| `outputs` | Run outputs (JSON) |
| `error` | Error message if failed |
| `extra` | Extra metadata (JSON) |
| `events` | Run events (JSON) |
**Tags & feedback:**
| Field | Description |
| ---------------- | -------------------------- |
| `tags` | List of tags |
| `feedback_stats` | Feedback statistics (JSON) |
**Token usage & costs:**
| Field | Description |
| ------------------- | ---------------------- |
| `total_tokens` | Total token count |
| `prompt_tokens` | Prompt token count |
| `completion_tokens` | Completion token count |
| `total_cost` | Total cost |
| `prompt_cost` | Prompt cost |
| `completion_cost` | Completion cost |
| `first_token_time` | Time to first token |
#### Limiting exported fields
Requires LangSmith Helm version >= `0.12.11` (application version >= `0.12.42`). This feature **is supported** in [scheduled bulk exports](#scheduled-exports) and [standard bulk exports](#create-an-export-job).
You can improve bulk export speed and reduce row size by limiting which fields are included in the exported Parquet files using the `export_fields` parameter. When `export_fields` is provided, only the specified fields are exported as columns in the Parquet files. When `export_fields` is not provided, all exportable fields are included.
This is particularly useful when you want to exclude larger fields like `inputs` and `outputs`.
The following example creates an export job that only includes specific fields:
```bash theme={null}
curl --request POST \
--url 'https://api.smith.langchain.com/api/v1/bulk-exports' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-Tenant-Id: YOUR_WORKSPACE_ID' \
--data '{
"bulk_export_destination_id": "your_destination_id",
"session_id": "project_uuid",
"start_time": "2024-01-01T00:00:00Z",
"end_time": "2024-01-02T23:59:59Z",
"export_fields": ["id", "name", "run_type", "start_time", "end_time", "status", "total_tokens", "total_cost"],
"format_version": "v2_beta"
}'
```
The `export_fields` parameter accepts an array of field names. Available fields include the [Run data format](/langsmith/run-data-format) fields as well as additional export-only fields:
* `tenant_id`
* `is_root`
**Performance tip**: Excluding `inputs` and `outputs` from your export can significantly improve export performance and reduce file sizes, especially for large runs. Only include these fields if you need them for your analysis.
### Scheduled exports
Requires LangSmith Helm version >= `0.10.42` (application version >= `0.10.109`)
Scheduled exports collect runs periodically and export to the configured destination.
To create a scheduled export, include `interval_hours` and remove `end_time`:
```bash theme={null}
curl --request POST \
--url 'https://api.smith.langchain.com/api/v1/bulk-exports' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-Tenant-Id: YOUR_WORKSPACE_ID' \
--data '{
"bulk_export_destination_id": "your_destination_id",
"session_id": "project_uuid",
"start_time": "2024-01-01T00:00:00Z",
"filter": "and(eq(run_type, \"llm\"), eq(name, \"ChatOpenAI\"), eq(input_key, \"messages.content\"), like(input_value, \"%messages.content%\"))",
"interval_hours": 1,
"format_version": "v2_beta"
}'
```
You can also use `export_fields` with scheduled exports to limit which fields are exported:
```bash theme={null}
curl --request POST \
--url 'https://api.smith.langchain.com/api/v1/bulk-exports' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-Tenant-Id: YOUR_WORKSPACE_ID' \
--data '{
"bulk_export_destination_id": "your_destination_id",
"session_id": "project_uuid",
"start_time": "2024-01-01T00:00:00Z",
"interval_hours": 1,
"export_fields": ["id", "name", "run_type", "start_time", "end_time", "status", "total_tokens", "total_cost"],
"format_version": "v2_beta"
}'
```
**Details**
* `interval_hours` must be between 1 hour and 168 hours (1 week) inclusive.
* For spawned exports, the first time range exported is `start_time=(scheduled_export_start_time), end_time=(start_time + interval_hours)`.
Then `start_time=(previous_export_end_time), end_time=(this_export_start_time + interval_hours)`, and so on.
* `end_time` must be omitted for scheduled exports. `end_time` is still required for non-scheduled exports.
* Scheduled exports can be stopped by [cancelling the export](#stop-an-export).
* Exports that have been spawned by a scheduled export have the `source_bulk_export_id` attribute filled.
* If desired, these spawned bulk exports must be canceled separately from the source scheduled bulk export -
canceling the source bulk export **does not** cancel the spawned bulk exports.
* Spawned exports run at `end_time + 10 minutes` to account for any runs that are submitted with `end_time` in the recent past.
* `format_version` (optional): The format version to use for the parquet files. `"v2_beta"` has (1) enhanced datatypes for the columns and (2) a Hive-compliant folder structure.
**Example**
If a scheduled bulk export is created with `start_time=2025-07-16T00:00:00Z` and `interval_hours=6`:
| Export | Start Time | End Time | Runs At |
| ------ | -------------------- | -------------------- | -------------------- |
| 1 | 2025-07-16T00:00:00Z | 2025-07-16T06:00:00Z | 2025-07-16T06:10:00Z |
| 2 | 2025-07-16T06:00:00Z | 2025-07-16T12:00:00Z | 2025-07-16T12:10:00Z |
| 3 | 2025-07-16T12:00:00Z | 2025-07-16T18:00:00Z | 2025-07-16T18:10:00Z |
## Monitoring the export job
### Monitor export status
To monitor the status of an export job, use the following cURL command:
```bash theme={null}
curl --request GET \
--url 'https://api.smith.langchain.com/api/v1/bulk-exports/{export_id}' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-Tenant-Id: YOUR_WORKSPACE_ID'
```
Replace `{export_id}` with the ID of the export you want to monitor. This command retrieves the current status of the specified export job.
### List runs for an export
An export is typically broken up into multiple runs which correspond to a specific date partition to export.
To list all runs associated with a specific export, use the following cURL command:
```bash theme={null}
curl --request GET \
--url 'https://api.smith.langchain.com/api/v1/bulk-exports/{export_id}/runs' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-Tenant-Id: YOUR_WORKSPACE_ID'
```
This command fetches all runs related to the specified export, providing details such as run ID, status, creation time, rows exported, etc.
### List all exports
To retrieve a list of all export jobs, use the following cURL command:
```bash theme={null}
curl --request GET \
--url 'https://api.smith.langchain.com/api/v1/bulk-exports' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-Tenant-Id: YOUR_WORKSPACE_ID'
```
This command returns a list of all export jobs along with their current statuses and creation timestamps.
### Stop an export
To stop an existing export, use the following cURL command:
```bash theme={null}
curl --request PATCH \
--url 'https://api.smith.langchain.com/api/v1/bulk-exports/{export_id}' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-Tenant-Id: YOUR_WORKSPACE_ID' \
--data '{
"status": "Cancelled"
}'
```
Replace `{export_id}` with the ID of the export you wish to cancel. Note that a job cannot be restarted once it has been cancelled,
you will need to create a new export job instead.
## Partitioning scheme
Data will be exported into your bucket into the follow Hive partitioned format:
```
//export_id=/tenant_id=/session_id=/runs/year=/month=/day=
```
## Importing data into other systems
Importing data from S3 and Parquet format is commonly supported by the majority of analytical systems. See below for documentation links:
### BigQuery
To import your data into BigQuery, see [Loading Data from Parquet](https://cloud.google.com/bigquery/docs/loading-data-cloud-storage-parquet) and also
[Hive Partitioned loads](https://cloud.google.com/bigquery/docs/hive-partitioned-loads-gcs).
### Snowflake
You can load data into Snowflake from S3 by following the [Load from Cloud Document](https://docs.snowflake.com/en/user-guide/tutorials/load-from-cloud-tutorial).
### RedShift
You can COPY data from S3 or Parquet into Amazon Redshift by following the [AWS COPY command documentation](https://docs.aws.amazon.com/redshift/latest/dg/r_COPY.html).
### Clickhouse
You can directly query data in S3 / Parquet format in Clickhouse. As an example, if using GCS, you can query the data as follows:
```sql theme={null}
SELECT count(distinct id) FROM s3('https://storage.googleapis.com///export_id=/**',
'access_key_id', 'access_secret', 'Parquet')
```
See [Clickhouse S3 Integration Documentation](https://clickhouse.com/docs/en/engines/table-engines/integrations/s3) for more information.
### DuckDB
You can query the data from S3 in-memory with SQL using DuckDB. See [S3 import Documentation](https://duckdb.org/docs/guides/network_cloud_storage/s3_import.html).
## Error handling
### Debugging destination errors
The destinations API endpoint will validate that the destination and credentials are valid and that write access is
is present for the bucket.
If you receive an error, and would like to debug this error, you can use the [AWS CLI](https://aws.amazon.com/cli/)
to test the connectivity to the bucket. You should be able to write a file with the CLI using the same
data that you supplied to the destinations API above.
**AWS S3:**
```bash theme={null}
aws configure
# set the same access key credentials and region as you used for the destination
> AWS Access Key ID:
> AWS Secret Access Key:
> Default region name [us-east-1]:
# List buckets
aws s3 ls /
# test write permissions
touch ./test.txt
aws s3 cp ./test.txt s3:///tmp/test.txt
```
**GCS Compatible Buckets:**
You will need to supply the endpoint\_url with `--endpoint-url` option.
For GCS, the `endpoint_url` is typically `https://storage.googleapis.com`:
```bash theme={null}
aws configure
# set the same access key credentials and region as you used for the destination
> AWS Access Key ID:
> AWS Secret Access Key:
> Default region name [us-east-1]:
# List buckets
aws s3 --endpoint-url= ls /
# test write permissions
touch ./test.txt
aws s3 --endpoint-url= cp ./test.txt s3:///tmp/test.txt
```
### Monitoring runs
You can monitor your runs using the [List Runs API](#list-runs-for-an-export). If this is a known error, this will be added to the `errors` field of the run.
### Common errors
Here are some common errors:
| Error | Description |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Access denied | The blob store credentials or bucket are not valid. This error occurs when the provided access key and secret key combination doesn't have the necessary permissions to access the specified bucket or perform the required operations. |
| Bucket is not valid | The specified blob store bucket is not valid. This error is thrown when the bucket doesn't exist or there is not enough access to perform writes on the bucket. |
| Key ID you provided does not exist | The blob store credentials provided are not valid. This error occurs when the access key ID used for authentication is not a valid key. |
| Invalid endpoint | The endpoint\_url provided is invalid. This error is raised when the specified endpoint is an invalid endpoint. Only S3 compatible endpoints are supported, for example `https://storage.googleapis.com` for GCS, `https://play.min.io` for minio, etc. If using AWS, you should omit the endpoint\_url. |
## Failure modes and retry policy
LangSmith bulk exports handle transient failures and infrastructure issues automatically to ensure resilience.
Each bulk export is divided into multiple *runs*, where each run processes data for a [specific date partition](#partitioning-scheme) (typically organized by day). Runs are processed independently, which enables:
* Parallel processing of different time periods.
* Independent retry logic for each run.
* Resumption from specific checkpoints if interrupted.
Each run (date range) in your export has its own [failure handling](#failure-scenarios) and [retry budget](#automation-retry-behavior). If a run fails after exhausting all retries, the entire export is marked as `FAILED`.
### Automatic retry behavior
Export jobs automatically retry transient failures with the following behavior:
* **Maximum retry attempts**: 20 retries per run (subject to change).
* **Retry delay**: 30 seconds between attempts (fixed, no exponential backoff).
* **Run timeout**: 4 hours maximum per run.
* **Overall workflow timeout**: 72 hours for the entire export.
### Failure scenarios
| Failure type | Cause | Automatic retry? | Action required |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| **Infrastructure interruption** | [Deployments](/langsmith/deployments), server restarts, worker crashes | Yes, automatically requeued with remaining retries. | None, jobs resume automatically. |
| **Run timeout** | Single run exceeds 4-hour limit | Yes, retried up to 20 times (subject to change). | If persistent, narrow date range, add filters, or [limit the exported fields](#limiting-exported-fields). |
| **Workflow timeout** | Entire export exceeds 72 hours | No | Reduce export scope (date range, filters) or break into smaller exports. |
| **Storage/destination errors** | [Invalid credentials](#credentials-configuration), [missing bucket](#destinations-providing-an-s3-bucket), [permission issues](#permissions-required) | No | Fix destination configuration and create new export. |
| **Destination deleted** | Bucket removed during export | No | Recreate destination and restart export. |
| **Terminal processing errors** | Data serialization issues, resource exhaustion | Yes, retried up to 20 times (subject to change). | Check run error details; may require investigation. |
Any single run failure (after all retries are exhausted) causes the entire export to fail.
### Export status lifecycle
Exports can have the following statuses:
| Status | Description |
| ----------- | ------------------------------------------------------- |
| `CREATED` | Export has been created but not yet started processing. |
| `RUNNING` | Export is actively processing runs. |
| `COMPLETED` | All runs successfully exported. |
| `FAILED` | One or more runs failed after exhausting retries. |
| `CANCELLED` | Export was manually cancelled by user. |
| `TIMEDOUT` | Export exceeded the 48-hour workflow timeout. |
Individual runs can have the same possible statuses: `CREATED`, `RUNNING`, `COMPLETED`, `FAILED`, `CANCELLED`, or `TIMEDOUT`.
### Concurrency and rate limits
To ensure system stability, exports are subject to the following limits:
* **Maximum concurrent runs per export**: 45
* **Maximum concurrent exports per workspace**: 15
If you have multiple exports running, new run jobs will queue until capacity becomes available.
### Progress tracking and resumability
The export system maintains detailed progress metadata for each run:
* Latest cursor position in the data stream.
* Number of rows exported.
* List of Parquet files written.
This progress tracking enables:
* **Graceful resumption**: If a run is interrupted (e.g., by a deployment), it resumes from the last checkpoint rather than starting over.
* **Progress monitoring**: Track how much data has been exported through the API.
* **Efficient retries**: Failed runs don't re-export data that was already successfully written.
### Troubleshooting failed exports
If your export fails, follow these steps:
1. **Check the export status**: Use the [`GET /api/v1/bulk-exports/{export_id}` endpoint](https://api.smith.langchain.com/redoc#tag/bulk-exports) to retrieve the export details and status.
2. **Review run errors**: Each run includes an [`errors` field](#error-handling) with detailed error messages keyed by retry attempt (e.g., `retry_0`, `retry_1`).
3. **Verify destination access**: Ensure your [destination bucket](#preparing-the-destination) still exists and [credentials](#credentials-configuration) are valid.
4. **Check run size**: If you see timeout errors, your date partitions may contain too much data. It may be helpful to [limit the exported fields](#limiting-exported-fields).
5. **Review system limits**: Ensure you're not hitting [concurrency limits](#concurrency-and-rate-limits) (5 runs per export, 3 exports per workspace).
To monitor your export job, refer to [Monitoring the export job](#monitoring-the-export-job).
For storage-related errors, you can test your destination configuration using the AWS CLI or gsutil before retrying the export.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/data-export.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith data plane
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/data-plane
The *data plane* consists of your [Agent Servers](/langsmith/agent-server) (deployments), their supporting infrastructure, and the "listener" application that continuously polls for updates from the [LangSmith control plane](/langsmith/control-plane).
## Server infrastructure
In addition to the [Agent Server](/langsmith/agent-server) itself, the following infrastructure components for each server are also included in the broad definition of "data plane":
* **PostgreSQL**: persistence layer for user, run, and memory data.
* **Redis**: communication and ephemeral metadata for workers.
* **Secrets store**: secure management of environment secrets.
* **Autoscalers**: scale server containers based on load.
## "Listener" application
The data plane "listener" application periodically calls [control plane APIs](/langsmith/control-plane#control-plane-api) to:
* Determine if new deployments should be created.
* Determine if existing deployments should be updated (i.e. new revisions).
* Determine if existing deployments should be deleted.
In other words, the data plane "listener" reads the latest state of the control plane (desired state) and takes action to reconcile outstanding deployments (current state) to match the latest state.
## PostgreSQL
PostgreSQL is the persistence layer for all user, run, and long-term memory data in a Agent Server. This stores both checkpoints (see more info [here](/oss/python/langgraph/persistence)), server resources (threads, runs, assistants and crons), as well as items saved in the long-term memory store (see more info [here](/oss/python/langgraph/persistence#memory-store)).
## Redis
Redis is used in each Agent Server as a way for server and queue workers to communicate, and to store ephemeral metadata. No user or run data is stored in Redis.
### Communication
All runs in an Agent Server are executed by a pool of background workers that are part of each deployment. In order to enable some features for those runs (such as cancellation and output streaming) we need a channel for two-way communication between the server and the worker handling a particular run. We use Redis to organize that communication.
1. A Redis list is used as a mechanism to wake up a worker as soon as a new run is created. Only a sentinel value is stored in this list, no actual run information. The run information is then retrieved from PostgreSQL by the worker.
2. A combination of a Redis string and Redis PubSub channel is used for the server to communicate a run cancellation request to the appropriate worker.
3. A Redis PubSub channel is used by the worker to broadcast streaming output from an agent while the run is being handled. Any open `/stream` request in the server will subscribe to that channel and forward any events to the response as they arrive. No events are stored in Redis at any time.
### Ephemeral metadata
Runs in an Agent Server may be retried for specific failures (currently only for transient PostgreSQL errors encountered during the run). In order to limit the number of retries (currently limited to 3 attempts per run) we record the attempt number in a Redis string when it is picked up. This contains no run-specific info other than its ID, and expires after a short delay.
## Data plane features
This section describes various features of the data plane.
### Data region
**Only for Cloud**
Data regions are only applicable for [Cloud](/langsmith/cloud) deployments.
Deployments can be created in 2 data regions: US and EU
The data region for a deployment is implied by the data region of the LangSmith organization where the deployment is created. Deployments and the underlying database for the deployments cannot be migrated between data regions.
### Autoscaling
[`Production` type](/langsmith/control-plane#deployment-types) deployments automatically scale up to 10 containers. Scaling is based on 3 metrics:
1. CPU utilization
2. Memory utilization
3. Number of pending (in progress) [runs](/langsmith/assistants#execution)
For CPU utilization, the autoscaler targets 75% utilization. This means the autoscaler will scale the number of containers up or down to ensure that CPU utilization is at or near 75%. For memory utilization, the autoscaler targets 75% utilization as well.
For number of pending runs, the autoscaler targets 10 pending runs. For example, if the current number of containers is 1, but the number of pending runs is 20, the autoscaler will scale up the deployment to 2 containers (20 pending runs / 2 containers = 10 pending runs per container).
Each metric is computed independently and the autoscaler will determine the scaling action based on the metric that results in the largest number of containers.
These metrics don't all apply to every container type. [Queue workers](/langsmith/agent-server#runtime-architecture) scale on pending run count — when the backlog grows, more workers spin up to drain it. [API servers](/langsmith/agent-server#runtime-architecture) scale on CPU and memory, responding to client request volume. This means a spike in run submissions won't slow down read operations like fetching thread state. For self-hosted configuration details, see [Configure Agent Server for scale](/langsmith/agent-server-scale).
Scale down actions are delayed for 30 minutes before any action is taken. In other words, if the autoscaler decides to scale down a deployment, it will first wait for 30 minutes before scaling down. After 30 minutes, the metrics are recomputed and the deployment will scale down if the recomputed metrics result in a lower number of containers than the current number. Otherwise, the deployment remains scaled up. This "cool down" period ensures that deployments do not scale up and down too frequently.
### Static IP addresses
**Only for Cloud**
Static IP addresses are only available for [Cloud](/langsmith/cloud) deployments.
All traffic from deployments created after January 6th 2025 will come through a NAT gateway. This NAT gateway will have several static IP addresses depending on the data region. For the list of static IP addresses, refer to the [Allowlist IP addresses table](/langsmith/deploy-to-cloud#allowlist-ip-addresses).
### Payload size
**Only for Cloud**
Payload size restrictions are only applicable to [Cloud](/langsmith/cloud) deployments.
The maximum payload size for all requests sent to [Cloud](/langsmith/cloud) deployments is 25 MB. Attempting to send a request with a payload larger than 25 MB will result in a `413 Payload Too Large` error.
### Custom PostgreSQL
Custom PostgreSQL instances are only available for [hybrid](/langsmith/hybrid) and [self-hosted](/langsmith/self-hosted) deployments.
A custom PostgreSQL instance can be used instead of the [one automatically created by the control plane](/langsmith/control-plane#database-provisioning). Specify the [`POSTGRES_URI_CUSTOM`](/langsmith/env-var#postgres-uri-custom) environment variable to use a custom PostgreSQL instance.
Multiple deployments can share the same PostgreSQL instance. For example, for `Deployment A`, `POSTGRES_URI_CUSTOM` can be set to `postgres://:@/?host=` and for `Deployment B`, `POSTGRES_URI_CUSTOM` can be set to `postgres://:@/?host=`. `` and `database_name_2` are different databases within the same instance, but `` is shared. **The same database cannot be used for separate deployments**.
### Custom Redis
Custom Redis instances are only available for [Hybrid](/langsmith/hybrid) and [Self-Hosted](/langsmith/self-hosted) deployments.
A custom Redis instance can be used instead of the one automatically created by the control plane. Specify the [REDIS\_URI\_CUSTOM](/langsmith/env-var#redis-uri-custom) environment variable to use a custom Redis instance.
Multiple deployments can share the same Redis instance. For example, for `Deployment A`, `REDIS_URI_CUSTOM` can be set to `redis://:/1` and for `Deployment B`, `REDIS_URI_CUSTOM` can be set to `redis://:/2`. `1` and `2` are different database numbers within the same instance, but `` is shared. **The same database number cannot be used for separate deployments**.
### LangSmith tracing
Agent Server is automatically configured to send traces to LangSmith. See the table below for details with respect to each deployment option.
| Cloud | Hybrid | Self-Hosted |
| -------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Required Trace to LangSmith SaaS. | Optional Disable tracing or trace to LangSmith SaaS. | Optional Disable tracing, trace to LangSmith SaaS, or trace to Self-Hosted LangSmith. |
### Telemetry
Agent Server is automatically configured to report telemetry metadata for billing purposes. See the table below for details with respect to each deployment option.
| Cloud | Hybrid | Self-Hosted |
| --------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Telemetry sent to LangSmith SaaS. | Telemetry sent to LangSmith SaaS. | Self-reported usage (audit) for air-gapped license key. Telemetry sent to LangSmith SaaS for LangSmith License Key. |
### Licensing
Agent Server is automatically configured to perform license key validation. See the table below for details with respect to each deployment option.
| Cloud | Hybrid | Self-Hosted |
| --------------------------------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------- |
| LangSmith API Key validated against LangSmith SaaS. | LangSmith API Key validated against LangSmith SaaS. | Air-gapped license key or Platform License Key validated against LangSmith SaaS. |
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/data-plane.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Data purging for compliance
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/data-purging-compliance
This guide covers the various features available after data reaches LangSmith Cloud servers to help you achieve your privacy goals.
## Data retention
LangSmith provides automatic data retention capabilities to help with compliance and storage management. Data retention policies can be configured at the organization and project levels.
For detailed information about data retention configuration and management, please refer to the [Data Retention concepts](/langsmith/administration-overview#data-retention) documentation.
## Trace deletes
You can use the API to complete trace deletes. The API supports two methods for deleting traces:
1. **By trace IDs and session ID**: Delete specific traces by providing a list of trace IDs and their corresponding session ID (up to 1000 traces per request)
2. **By metadata**: Delete traces across a workspace that match any of the specified metadata key-value pairs
For more details, refer to the [API spec](https://api.smith.langchain.com/redoc#tag/run/operation/delete_runs_api_v1_runs_delete_post).
All trace deletions will delete related entities like feedbacks, aggregations, and stats across all data storages.
### Deletion timeline
Trace deletions are processed during non-peak usage times and are not instant. LangChain runs the delete job on the weekend. There is no confirmation of deletion - you'll need to query the data again to verify it has been removed.
### Delete specific traces
To delete specific traces by their trace IDs from a single session:
The `session_id` is the project ID for the trace you are trying to delete. You can find it on the tracing project page in the LangSmith UI.
```bash theme={null}
curl -X POST "https://api.smith.langchain.com/api/v1/runs/delete" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"trace_ids": ["trace-id-1", "trace-id-2", "trace-id-3"],
"session_id": "session-id-1"
}'
```
### Delete by metadata
When deleting by metadata:
* Accepts a `metadata` object of key/value pairs. KV pair matching uses an **or** condition. A trace will match if it has **any** of the key-value pairs specified in metadata (not all)
* You don't need to specify a session id when deleting by metadata. Deletes will apply across the workspace.
To delete traces based on metadata across a workspace (matches **any** of the metadata key-value pairs):
```bash theme={null}
curl -X POST "https://api.smith.langchain.com/api/v1/runs/delete" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"user_id": "user123",
"environment": "staging"
}
}'
```
This will delete traces that have either `user_id: "user123"` **or** `environment: "staging"` in their metadata.
Remember that you can only schedule up to 1000 traces per session per request. For larger deletions, you'll need to make multiple requests.
## Example deletes
You can delete dataset examples self-serve via our API, which supports both soft and hard deletion methods depending on your data retention needs.
Hard deletes will permanently remove inputs, outputs, and metadata from ALL versions of the specified examples across the entire dataset history.
### Deleting examples is a two-step process
For bulk operations, example deletion follows a two-step process:
#### 1. Search for examples by metadata
Find all examples with matching metadata across all datasets in a workspace.
[GET /examples](https://api.smith.langchain.com/redoc#tag/examples/operation/read_examples_api_v1_examples_get)
* `as_of` must be explicitly specified as a timestamp. Only examples created before the `as_of` date will be returned
```bash theme={null}
curl -X GET "https://api.smith.langchain.com/api/v1/examples?as_of=2024-01-01T00:00:00Z" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"user_id": "user123",
"environment": "staging"
}
}'
```
This will return examples that have either `user_id: "user123"` **or** `environment: "staging"` in their metadata across all datasets in your workspace.
#### 2. Hard delete examples
Once you have the example IDs, send a delete request. This will zero-out the inputs, outputs, and metadata from all versions of the dataset for that example.
[POST /v1/platform/datasets/examples/delete/](https://api.smith.langchain.com/redoc?#tag/examples/paths/~1v1~1platform~1datasets~1examples~1delete/post)
* Specify `example_ids` (list of example IDs) and `hard_delete` (boolean) in the request body
```bash theme={null}
curl -X POST "https://api.smith.langchain.com/v1/platform/datasets/examples/delete/" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"example_ids": ["example-id-1", "example-id-2", "example-id-3"],
"hard_delete": true
}'
```
### Deletion types
#### Soft delete (default)
* Creates tombstoned entries with NULL inputs/outputs in the dataset
* Preserves historical data and maintains dataset versioning
* Only affects the current version of the dataset
#### Hard delete
* Permanently removes inputs, outputs, and metadata from ALL dataset versions
* Complete data removal when compliance requires zero-out across all versions
* Set `"hard_delete": true` in the request body
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/data-purging-compliance.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Data storage and privacy
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/data-storage-and-privacy
This document describes how data is processed in the LangGraph CLI and the Agent Server for both the in-memory server (`langgraph dev`) and the local Docker server (`langgraph up`). It also describes what data is tracked when interacting with the hosted Studio frontend.
## CLI
LangGraph **CLI** is the command-line interface for building and running LangGraph applications; see the [CLI guide](/langsmith/cli) to learn more.
By default, calls to most CLI commands log a single analytics event upon invocation. This helps us better prioritize improvements to the CLI experience. Each telemetry event contains the calling process's OS, OS version, Python version, the CLI version, the command name (`dev`, `up`, `run`, etc.), and booleans representing whether a flag was passed to the command. You can see the full analytics logic [here](https://github.com/langchain-ai/langgraph/blob/main/libs/cli/langgraph-cli/analytics.py).
You can disable all CLI telemetry by setting `LANGGRAPH_CLI_NO_ANALYTICS=1`.
## Agent Server
The [Agent Server](/langsmith/agent-server) provides a durable execution runtime that relies on persisting checkpoints of your application state, long-term memories, thread metadata, assistants, and similar resources to the local file system or a database. Unless you have deliberately customized the storage location, this information is either written to local disk (for `langgraph dev`) or a PostgreSQL database (for `langgraph up` and in all deployments).
### LangSmith tracing
When running the Agent server (either in-memory or in Docker), LangSmith tracing may be enabled to facilitate faster debugging and offer observability of graph state and LLM prompts in production. You can always disable tracing by setting `LANGSMITH_TRACING=false` in your server's runtime environment.
For more granular control, you can use [conditional tracing](/langsmith/conditional-tracing) to selectively enable or disable tracing based on runtime conditions, such as client requirements or data sensitivity.
### In-memory development server
`langgraph dev` runs an [in-memory development server](/langsmith/local-server) as a single Python process, designed for quick development and testing. It saves all checkpointing and memory data to disk within a `.langgraph_api` directory in the current working directory. Apart from the telemetry data described in the [CLI](#cli) section, no data leaves the machine unless you have enabled tracing or your graph code explicitly contacts an external service.
### Standalone Server
`langgraph up` builds your local package into a Docker image and runs the server as the [data plane](/langsmith/self-hosted) consisting of three containers: the API server, a PostgreSQL container, and a Redis container. All persistent data (checkpoints, assistants, etc.) are stored in the PostgreSQL database. Redis is used as a pubsub connection for real-time streaming of events. You can encrypt all checkpoints before saving to the database by setting a valid `LANGGRAPH_AES_KEY` environment variable. You can also specify [TTLs](/langsmith/configure-ttl) for checkpoints and cross-thread memories in `langgraph.json` to control how long data is stored. All persisted threads, memories, and other data can be deleted via the relevant API endpoints.
Additional API calls are made to confirm that the server has a valid license and to track the number of executed runs and tasks. Periodically, the API server validates the provided license key (or API key).
If you've disabled [tracing](#langsmith-tracing), no user data is persisted externally unless your graph code explicitly contacts an external service.
## Studio
[Studio](/langsmith/studio) is a graphical interface for interacting with your Agent Server. It does not persist any private data (the data you send to your server is not sent to LangSmith). Though the Studio interface is served at [smith.langchain.com](https://smith.langchain.com), it is run in your browser and connects directly to your local Agent Server so that no data needs to be sent to LangSmith.
If you are logged in, LangSmith does collect some usage analytics to help improve the debugging user experience. This includes:
* Page visits and navigation patterns
* User actions (button clicks)
* Browser type and version
* Screen resolution and viewport size
Importantly, no application data or code (or other sensitive configuration details) are collected. All of that is stored in the persistence layer of your Agent Server. When using Studio anonymously, no account creation is required and usage analytics are not collected.
## Quick reference
In summary, you can opt-out of server-side telemetry by turning off CLI analytics and disabling tracing.
| Variable | Purpose | Default |
| ------------------------------ | ------------------------- | ---------------------- |
| `LANGGRAPH_CLI_NO_ANALYTICS=1` | Disable CLI analytics | Analytics enabled |
| `LANGSMITH_API_KEY` | Enable LangSmith tracing | Tracing disabled |
| `LANGSMITH_TRACING=false` | Disable LangSmith tracing | Depends on environment |
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/data-storage-and-privacy.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Dataset prebuilt JSON schema types
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/dataset-json-types
LangSmith recommends that you set a schema on the inputs and outputs of your dataset schemas to ensure data consistency and that your examples are in the right format for downstream processing, like running evals.
In order to better support LLM workflows, LangSmith has support for a few different predefined prebuilt types. These schemas are hosted publicly by the LangSmith API, and can be defined in your dataset schemas using [JSON Schema references](https://json-schema.org/understanding-json-schema/structuring#dollarref). The table of available schemas can be seen below
| Type | JSON Schema Reference Link | Usage |
| ------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Message | [https://api.smith.langchain.com/public/schemas/v1/message.json](https://api.smith.langchain.com/public/schemas/v1/message.json) | Represents messages sent to a chat model, following the OpenAI standard format. |
| Tool | [https://api.smith.langchain.com/public/schemas/v1/tooldef.json](https://api.smith.langchain.com/public/schemas/v1/tooldef.json) | Tool definitions available to chat models for function calling, defined in OpenAI's JSON Schema inspired function format. |
LangSmith lets you define a series of transformations that collect the above prebuilt types from your traces and add them to your dataset. For more info on available transformations, see our [reference](/langsmith/dataset-transformations)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/dataset-json-types.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Dataset transformations
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/dataset-transformations
LangSmith allows you to attach transformations to fields in your dataset's schema that apply to your data before it is added to your dataset, whether that be from UI, API, or run rules.
Coupled with [LangSmith's prebuilt JSON schema types](/langsmith/dataset-json-types), these allow you to do easy preprocessing of your data before saving it into your datasets.
## Transformation types
| Transformation Type | Target Types | Functionality |
| --------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `remove_system_messages` | `Array[Message]` | Filters a list of messages to remove any system messages. |
| `convert_to_openai_message` | Message `Array[Message]` | Converts any incoming data from LangChain's internal serialization format to OpenAI's standard message format using langchain's [`convert_to_openai_messages`](https://reference.langchain.com/python/langchain_core/utils/#langchain_core.utils.function_calling.convert_to_openai_messages). If the target field is marked as required, and no matching message is found upon entry, it will attempt to extract a message (or list of messages) from several well-known LangSmith tracing formats (e.g., any traced LangChain [`BaseChatModel`](https://reference.langchain.com/python/langchain_core/language_models/#langchain_core.language_models.chat_models.BaseChatModel) run or traced run from the [LangSmith OpenAI wrapper](/langsmith/annotate-code#wrap-the-openai-client)), and remove the original key containing the message. |
| `convert_to_openai_tool` | `Array[Tool]` Only available on top level fields in the inputs dictionary. | Converts any incoming data into OpenAI standard tool formats here using langchain's [`convert_to_openai_tool`](https://reference.langchain.com/python/langchain_core/utils/#langchain_core.utils.function_calling.convert_to_openai_tool) Will extract tool definitions from a run's invocation parameters if present / no tools are found at the specified key. This is useful because LangChain chat models trace tool definitions to the `extra.invocation_params` field of the run rather than inputs. |
| `remove_extra_fields` | `Object` | Removes any field not defined in the schema for this target object. |
## Chat model prebuilt schema
The main use case for transformations is to simplify collecting production traces into datasets in a format that can be standardized across model providers for usage in evaluations / few shot prompting / etc downstream.
To simplify setup of transformations for our end users, LangSmith offers a pre-defined schema that will do the following:
* Extract messages from your collected runs and transform them into the openai standard format, which makes them compatible all LangChain ChatModels and most model providers' SDK for downstream evaluation and experimentation
* Extract any tools used by your LLM and add them to your example's input to be used for reproducability in downstream evaluation
Users who want to iterate on their system prompts often also add the Remove System Messages transformation on their input messages when using our Chat Model schema, which will prevent you from saving the system prompt to your dataset.
### Compatibility
The LLM run collection schema is built to collect data from LangChain [`BaseChatModel`](https://reference.langchain.com/python/langchain_core/language_models/#langchain_core.language_models.chat_models.BaseChatModel) runs or traced runs from the [LangSmith OpenAI wrapper](/langsmith/annotate-code#wrap-the-openai-client).
Please contact support via [support.langchain.com](https://support.langchain.com) if you have an LLM run you are tracing that is not compatible and we can extend support.
If you want to apply transformations to other sorts of runs (for example, representing LangGraph state with message history), please define your schema directly and manually add the relevant transformations.
### Enablement
When adding a run from a tracing project or annotation queue to a dataset, if it has the LLM run type, we will apply the Chat Model schema by default.
For enablement on new datasets, see our [dataset management how-to guide](/langsmith/manage-datasets-in-application).
### Specs
For the full API specs of the prebuilt schema, see the below sections:
#### Input schema
```json theme={null}
{
"type": "object",
"properties": {
"messages": {
"type": "array",
"items": {
"$ref": "https://api.smith.langchain.com/public/schemas/v1/message.json"
}
},
"tools": {
"type": "array",
"items": {
"$ref": "https://api.smith.langchain.com/public/schemas/v1/tooldef.json"
}
}
},
"required": ["messages"]
}
```
#### Output schema
```json theme={null}
{
"type": "object",
"properties": {
"message": {
"$ref": "https://api.smith.langchain.com/public/schemas/v1/message.json"
}
},
"required": ["message"]
}
```
#### Transformations
And the transformations look as follows:
```json theme={null}
[
{
"path": ["inputs"],
"transformation_type": "remove_extra_fields"
},
{
"path": ["inputs", "messages"],
"transformation_type": "convert_to_openai_message"
},
{
"path": ["inputs", "tools"],
"transformation_type": "convert_to_openai_tool"
},
{
"path": ["outputs"],
"transformation_type": "remove_extra_fields"
},
{
"path": ["outputs", "message"],
"transformation_type": "convert_to_openai_message"
}
]
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/dataset-transformations.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to define a target function to evaluate
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/define-target-function
There are three main pieces need to run an evaluation:
1. A [dataset](/langsmith/evaluation-concepts#datasets) of test inputs and expected outputs.
2. A target function which is what you're evaluating.
3. [Evaluators](/langsmith/evaluation-concepts#evaluators) that score your target function's outputs.
This guide shows you how to define the target function depending on the part of your application you are evaluating. See here for [how to create a dataset](/langsmith/manage-datasets-programmatically) and [how to define evaluators](/langsmith/code-evaluator-ui), and here for an [end-to-end example of running an evaluation](/langsmith/evaluate-llm-application).
## Target function signature
In order to evaluate an application in code, we need a way to run the application. When using `evaluate()` ([Python](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client.evaluate) / [JavaScript](https://reference.langchain.com/javascript/functions/langsmith.evaluation.evaluate.html)) we'll do this by passing in a *target function* argument. This is a function that takes in a dataset [Example's](/langsmith/evaluation-concepts#examples) inputs and returns the application output as a dict. Within this function we can call our application however we'd like. We can also format the output however we'd like. The key is that any evaluator functions we define should work with the output format we return in our target function.
```python theme={null}
from langsmith import Client
# 'inputs' will come from your dataset.
def dummy_target(inputs: dict) -> dict:
return {"foo": 1, "bar": "two"}
# 'inputs' will come from your dataset.
# 'outputs' will come from your target function.
def evaluator_one(inputs: dict, outputs: dict) -> bool:
return outputs["foo"] == 2
def evaluator_two(inputs: dict, outputs: dict) -> bool:
return len(outputs["bar"]) < 3
client = Client()
results = client.evaluate(
dummy_target, # <-- target function
data="your-dataset-name",
evaluators=[evaluator_one, evaluator_two],
...
)
```
`evaluate()` will automatically trace your target function. This means that if you run any traceable code within your target function, this will also be traced as child runs of the target trace.
## Example: Single LLM call
```python Python theme={null}
from langsmith import wrappers
from openai import OpenAI
# Optionally wrap the OpenAI client to automatically
# trace all model calls.
oai_client = wrappers.wrap_openai(OpenAI())
def target(inputs: dict) -> dict:
# This assumes your dataset has inputs with a 'messages' key.
# You can update to match your dataset schema.
messages = inputs["messages"]
response = oai_client.chat.completions.create(
messages=messages,
model="gpt-4.1-mini",
)
return {"answer": response.choices[0].message.content}
```
```typescript TypeScript theme={null}
import OpenAI from 'openai';
import { wrapOpenAI } from "langsmith/wrappers";
const client = wrapOpenAI(new OpenAI());
// This is the function you will evaluate.
const target = async(inputs) => {
// This assumes your dataset has inputs with a `messages` key
const messages = inputs.messages;
const response = await client.chat.completions.create({
messages: messages,
model: 'gpt-4.1-mini',
});
return { answer: response.choices[0].message.content };
}
```
```python Python (LangChain) theme={null}
from langchain.chat_models import init_chat_model
model = init_chat_model("gpt-4.1-mini")
def target(inputs: dict) -> dict:
# This assumes your dataset has inputs with a `messages` key
messages = inputs["messages"]
response = model.invoke(messages)
return {"answer": response.content}
```
```typescript TypeScript (LangChain) theme={null}
import { ChatOpenAI } from '@langchain/openai';
// This is the function you will evaluate.
const target = async(inputs) => {
// This assumes your dataset has inputs with a `messages` key
const messages = inputs.messages;
const model = new ChatOpenAI({ model: "gpt-4.1-mini" });
const response = await model.invoke(messages);
return {"answer": response.content};
}
```
## Example: Non-LLM component
```python Python theme={null}
from langsmith import traceable
# Optionally decorate with '@traceable' to trace all invocations of this function.
@traceable
def calculator_tool(operation: str, number1: float, number2: float) -> str:
if operation == "add":
return str(number1 + number2)
elif operation == "subtract":
return str(number1 - number2)
elif operation == "multiply":
return str(number1 * number2)
elif operation == "divide":
return str(number1 / number2)
else:
raise ValueError(f"Unrecognized operation: {operation}.")
# This is the function you will evaluate.
def target(inputs: dict) -> dict:
# This assumes your dataset has inputs with `operation`, `num1`, and `num2` keys.
operation = inputs["operation"]
number1 = inputs["num1"]
number2 = inputs["num2"]
result = calculator_tool(operation, number1, number2)
return {"result": result}
```
```typescript TypeScript theme={null}
import { traceable } from "langsmith/traceable";
// Optionally wrap in 'traceable' to trace all invocations of this function.
const calculatorTool = traceable(async ({ operation, number1, number2 }) => {
// Functions must return strings
if (operation === "add") {
return (number1 + number2).toString();
} else if (operation === "subtract") {
return (number1 - number2).toString();
} else if (operation === "multiply") {
return (number1 * number2).toString();
} else if (operation === "divide") {
return (number1 / number2).toString();
} else {
throw new Error("Invalid operation.");
}
});
// This is the function you will evaluate.
const target = async (inputs) => {
// This assumes your dataset has inputs with `operation`, `num1`, and `num2` keys
const result = await calculatorTool.invoke({
operation: inputs.operation,
number1: inputs.num1,
number2: inputs.num2,
});
return { result };
}
```
## Example: Application or agent
```python Python theme={null}
from my_agent import agent
# This is the function you will evaluate.
def target(inputs: dict) -> dict:
# This assumes your dataset has inputs with a `messages` key
messages = inputs["messages"]
# Replace `invoke` with whatever you use to call your agent
response = agent.invoke({"messages": messages})
# This assumes your agent output is in the right format
return response
```
```typescript TypeScript theme={null}
import { agent } from 'my_agent';
// This is the function you will evaluate.
const target = async(inputs) => {
// This assumes your dataset has inputs with a `messages` key
const messages = inputs.messages;
// Replace `invoke` with whatever you use to call your agent
const response = await agent.invoke({ messages });
// This assumes your agent output is in the right format
return response;
}
```
If you have a LangGraph/LangChain agent that accepts the inputs defined in your dataset and that returns the output format you want to use in your evaluators, you can pass that object in as the target directly:
```python theme={null}
from my_agent import agent
from langsmith import Client
client = Client()
client.evaluate(agent, ...)
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/define-target-function.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Set up hybrid LangSmith
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/deploy-hybrid
**Important**
The Hybrid deployment option requires an [Enterprise](https://langchain.com/pricing) plan.
The [**hybrid**](/langsmith/hybrid) model lets you run the [data plane](/langsmith/data-plane)—your Agent Server deployments and agent workloads—in your own cloud, while LangChain hosts and manages the [control plane](/langsmith/control-plane) (the LangSmith UI and orchestration). This setup gives you the flexibility of self-hosting your runtime environments with the convenience of a managed LangSmith instance.
The following steps describe how to connect your self-hosted data plane to the managed LangSmith control plane.
## Kubernetes
### Prerequisites
1. `KEDA` is installed on your cluster.
```bash theme={null}
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --create-namespace
```
`KEDA` is used to automatically scale the deployment system based on queue size.
2. A valid `Ingress` controller is installed on your cluster. For more information about configuring ingress for your deployment, refer to [Create an ingress for installations](/langsmith/self-host-ingress). We highly recommend using the modern [Gateway API](/langsmith/self-host-ingress#option-2%3A-gateway-api) in a production setup.
3. If you plan to have the listener watch multiple namespaces, you **MUST** use the [Gateway API](/langsmith/self-host-ingress#option-2%3A-gateway-api) or an [Istio Gateway](/langsmith/self-host-ingress#option-3%3A-istio-gateway) instead of the [standard ingress](/langsmith/self-host-ingress#option-1%3A-standard-ingress) resource. A standard ingress resource can only route traffic to services in the same namespace, whereas a Gateway or Istio Gateway can route traffic to services across multiple namespaces.
4. You have slack space in your cluster for multiple deployments. `Cluster-Autoscaler` is recommended to automatically provision new nodes.
5. You will need to enable egress to two control plane URLs. The listener polls these endpoints for deployments:
* [https://api.host.langchain.com](https://api.host.langchain.com)
* [https://api.smith.langchain.com](https://api.smith.langchain.com)
### Setup
1. Provide your LangSmith organization ID to us. Your LangSmith organization will be configured to deploy the data plane in your cloud.
2. Create a listener from the LangSmith UI. The `Listener` data model is configured for the actual ["listener" application](/langsmith/data-plane#”listener”-application).
1. In the left-hand navigation, select `Deployments` > `Listeners`.
2. In the top-right of the page, select `+ Create Listener`.
3. Enter a unique `Compute ID` for the listener. The `Compute ID` is a user-defined identifier that should be unique across all listeners in the current LangSmith workspace. The `Compute ID` is displayed to end users when they are creating a new deployment. Ensure that the `Compute ID` provides context to the end user about where their Agent Server deployments will be deployed to. For example, a `Compute ID` can be set to `k8s-cluster-name-dev-01`. In this example, the name of the Kubernetes cluster is `k8s-cluster-name`, `dev` denotes that the cluster is reserved for "development" workloads, and `01` is a numerical suffix to reduce naming collisions.
4. Enter one or more Kubernetes namespaces. Later, the "listener" application will be configured to deploy to each of these namespaces.
5. In the top-right on the page, select `Submit`.
6. After the listener is created, copy the listener ID. You will use it later when installing the actual "listener" application in the Kubernetes cluster (step 5).
**Important**
Creating a listener from the LangSmith UI does not install the "listener" application in the Kubernetes cluster.
3. A [Helm chart](https://github.com/langchain-ai/helm/tree/main/charts/langgraph-dataplane) is provided to install the necessary components in your Kubernetes cluster.
* `langgraph-dataplane-listener`: This is a service that listens to LangChain's [control plane](/langsmith/control-plane) for changes to your deployments and creates/updates downstream CRDs. This is the ["listener" application](/langsmith/data-plane#”listener”-application).
* `LangGraphPlatform CRD`: A CRD for LangSmith Deployment. This contains the spec for managing an instance of a LangSmith Deployment.
* `langgraph-dataplane-operator`: This operator handles changes to your LangSmith CRDs.
* `langgraph-dataplane-redis`: A Redis instance is used by the `langgraph-dataplane-listener` to manage various tasks (mainly creating and deleting deployments).
4. Configure your `langgraph-dataplane-values.yaml` file.
```bash theme={null}
config:
langsmithApiKey: "" # API Key of your Workspace
langsmithWorkspaceId: "" # Workspace ID
hostBackendUrl: "https://api.host.langchain.com" # Only override this if on EU
smithBackendUrl: "https://api.smith.langchain.com" # Only override this if on EU
langgraphListenerId: "" # Listener ID from Step 2f
watchNamespaces: "" # comma-separated list of Kubernetes namespaces that the listener and operator will deploy to
enableLGPDeploymentHealthCheck: true # enable/disable health check step for deployments
ingress:
hostname: "" # specify a hostname that will be configured for all deployments
operator:
enabled: true
createCRDs: true # set this to `false` if the CRD has been previously installed in the current Kubernetes cluster
```
* `config.langsmithApiKey`: The `langgraph-listener` deployment authenticates with LangChain's LangGraph control plane API with the `langsmithApiKey`.
* `config.langsmithWorkspaceId`: The `langgraph-listener` deployment is coupled to Agent Server deployments in the LangSmith workspace. In other words, the `langgraph-listener` deployment can only manage Agent Server deployments in the specified LangSmith workspace ID.
* `config.langgraphListenerId`: In addition to being coupled with a LangSmith workspace, the `langgraph-listener` deployment is also coupled to a listener. When a new Agent Server deployment is created, it is automatically coupled to a `langgraphListenerId`. Specifying `langgraphListenerId` ensures that the `langgraph-listener` deployment can only manage Agent Server deployments that are coupled to `langgraphListenerId`.
* `config.watchNamespaces`: A comma-separated list of Kubernetes namespaces that the `langgraph-listener` deployment will deploy to. This list should match the list of namespaces specified in step 2d.
* `config.enableLGPDeploymentHealthCheck`: To disable the Agent Server health check, set this to `false`.
* `ingress.hostname`: As part of the deployment workflow, the `langgraph-listener` deployment attempts to call the Agent Server health check endpoint (`GET /ok`) to verify that the application has started up correctly. A typical setup involves creating a shared DNS record or domain for Agent Server deployments. This is not managed by LangSmith. Once created, set `ingress.hostname` to the domain, which will be used to complete the health check.
* `operator.createCRDs`: Set this value to `false` if the Kubernetes cluster already has the `LangGraphPlatform CRD` installed. During installation, an error will occur if the CRD is already installed. This situation may occur if multiple listeners are deployed on the same Kubernetes cluster.
5. Deploy `langgraph-dataplane` Helm chart.
```bash theme={null}
helm repo add langchain https://langchain-ai.github.io/helm/
helm repo update
helm upgrade -i langgraph-dataplane langchain/langgraph-dataplane --values langgraph-dataplane-values.yaml --wait --debug
```
6. If successful, you will see three services start up in your namespace.
```bash theme={null}
NAME READY STATUS RESTARTS AGE
langgraph-dataplane-listener-6dd4749445-zjmr4 0/1 ContainerCreating 0 26s
langgraph-dataplane-operator-6b88879f9b-t76gk 1/1 Running 0 26s
langgraph-dataplane-redis-0 1/1 Running 0 25s
```
Your hybrid infrastructure is now ready to create deployments.
### Configuring additional data planes in the same cluster
To create a data plane in a different namespace in the same cluster, repeat the above steps and pass a `-n` option to `helm upgrade` to specify a different namespace.
**When installing multiple data planes in the same cluster, it is very important to follow the rules below:**
1. The `config.watchNamespaces` list should never intersect with other installations `config.watchNamespaces`. For example, if installation A is watching namespaces `foo,bar`, installation B cannot watch either `foo` or `bar`. Multiple operators or listeners watching the same namespace will lead to unexpected behavior. This means that multiple LangSmith workspaces cannot deploy to the same namespace! Please review the [cluster organization](/langsmith/hybrid#kubernetes-cluster-organization) section to understand this better.
2. It is required to use the [Gateway API](/langsmith/self-host-ingress#option-2%3A-gateway-api) or an [Istio Gateway](/langsmith/self-host-ingress#option-3%3A-istio-gateway). Relying on the [standard ingress](/langsmith/self-host-ingress#option-1%3A-standard-ingress) resource can cause conflicts with Ingress objects created by other data planes in the same cluster. Because behavior in these cases depends on the specific ingress controller, this may result in unpredictable or undesired outcomes.
## Next steps
Once your infrastructure is set up, you're ready to deploy applications. See the deployment guides in the [Deployment tab](/langsmith/deployments) for instructions on building and deploying your applications.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/deploy-hybrid.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Deploy other frameworks
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/deploy-other-frameworks
This guide shows you how to use [Functional API](/oss/python/langgraph/functional-api) to deploy a [Strands Agent](https://strandsagents.com/latest/documentation/docs/) on [LangSmith Deployment](/langsmith/deployments) and set up tracing for [LangSmith Observability](/langsmith/observability). You can follow the same approach with other frameworks like CrewAI, AutoGen, Google ADK.
Using Functional API and deploying to LangSmith Deployment provides several benefits:
* Production deployment: Deploy your integrated solution to [LangSmith Deployment](/langsmith/deployments) for scalable production use.
* Enhanced features: With Functional API, you can integrate your existing agents with [persistence](/oss/python/langgraph/persistence), [streaming](/langsmith/streaming), [short and long-term memory](/oss/python/concepts/memory) and more, with minimal changes to your existing code.
* Multi-agent systems: Build [multi-agent systems](/oss/python/langchain/multi-agent) where individual agents are built with different frameworks.
## Prerequisites
* Python 3.9+
* Dependencies: `pip install strands-agents strands-agents-tools langgraph`
* AWS Credentials in your environment
## 1. Define strands agent
Create a Strands Agent with pre-built tools.
```python theme={null}
from strands import Agent
from strands_tools import file_read, file_write, python_repl, shell, journal
agent = Agent(
tools=[file_read, file_write, python_repl, shell, journal],
system_prompt="You are an Expert Software Developer Assistant specializing in web frameworks. Your task is to analyze project structures and identify mappings.",
model="us.anthropic.claude-3-7-sonnet-20250219-v1:0",
)
```
## 2. Use Functional API to deploy on LangSmith Deployment
[Functional API](/oss/python/langgraph/functional-api) allows you to integrate and deploy with frameworks other than LangChain. Functional API also provides the additional benefit to leverage other key features — persistence, memory, human-in-the-loop, and streaming — coupled with your existing agent, with minimal changes to your existing code.
It uses two key building blocks:
* **[`@entrypoint`](https://reference.langchain.com/python/langgraph/func/#langgraph.func.entrypoint)**: Marks a function as the starting point of a workflow, encapsulating logic and managing execution flow, including handling long-running tasks and interrupts.
* **[`@task`](https://reference.langchain.com/python/langgraph/func/#langgraph.func.task)**: Represents a discrete unit of work, such as an API call or data processing step, that can be executed asynchronously within an entrypoint. Tasks return a future-like object that can be awaited or resolved synchronously.
```python theme={null}
from strands.types.content import Message
from langgraph.func import entrypoint, task
import operator
@task
def invoke_strands(messages: list[Message]):
# run the agent with existing messages; can invoke with the final message with messages[-1]
result = agent(messages)
# return the resulting message
return [result.message]
@entrypoint()
def workflow(messages: list[Message], previous: list[Message]):
messages = operator.add(previous or [], messages)
response = invoke_strands(messages).result()
return entrypoint.final(value=response, save=operator.add(messages, response))
```
## 3. Set up tracing with OpenTelemetry
In your environment variables, set up the following:
```python theme={null}
# Turn off LangSmith default tracing, as we want to only trace with OpenTelemetry
LANGSMITH_TRACING=false
OTEL_EXPORTER_OTLP_ENDPOINT = "https://api.smith.langchain.com/otel/"
OTEL_EXPORTER_OTLP_HEADERS = "x-api-key=your-langsmith-api-key,Langsmith-Project=your-tracing-project-name"
```
If you're [self-hosting LangSmith](/langsmith/self-hosted), replace the `OTEL_EXPORTER_OTLP_ENDPOINT` endpoint with your LangSmith API endpoint and append `/api/v1/otel`. For example: `OTEL_EXPORTER_OTLP_ENDPOINT = "https://ai-company.com/api/v1/otel"`
Strand's OTel tracing contains synchronous code. In this case, you may need to set `BG_JOB_ISOLATED_LOOPS=true` to execute background runs in an isolated event loop separate from the serving API event loop.
In your main agent, set up the following:
```python theme={null}
from strands.telemetry import StrandsTelemetry
strands_telemetry = StrandsTelemetry()
strands_telemetry.setup_otlp_exporter()
strands_telemetry.setup_meter()
```
## 4. Prepare for deployment
From here, to deploy to LangSmith, create a file structure like the following:
```
my-strands-agent/
├── agent.py # Your main agent code
├── requirements.txt # Python dependencies
└── langgraph.json # LangGraph configuration
```
To deploy your agent, follow the [Deploy to cloud](/langsmith/deploy-to-cloud) guide.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/deploy-other-frameworks.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Enable LangSmith Deployment
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/deploy-self-hosted-full-platform
This guide shows you how to enable **LangSmith Deployment** on your [self-hosted LangSmith instance](/langsmith/kubernetes). This adds a [control plane](/langsmith/control-plane) and [data plane](/langsmith/data-plane) that let you deploy, scale, and manage agents and applications directly through the LangSmith UI.
After completing this guide, you'll have access to LangSmith [Observability](/langsmith/observability), [Evaluation](/langsmith/evaluation), and [Deployment](/langsmith/deployments).
**Important** Enabling LangSmith Deployment requires an [Enterprise](https://langchain.com/pricing) plan.
**This setup page is for enabling [LangSmith Deployment](/langsmith/deployments) on an existing LangSmith instance.**
Review the [self-hosted options](/langsmith/self-hosted) to understand:
* [LangSmith (observability)](/langsmith/self-hosted#langsmith): What you should install first.
* [LangSmith Deployment](/langsmith/self-hosted#langsmith-deployment): What this guide enables.
* [Standalone Server](/langsmith/self-hosted#standalone-server): Lightweight alternative without the UI.
## Overview
This guide builds on top of the [Kubernetes installation guide](/langsmith/kubernetes). **You must complete that guide first** before continuing. This page covers the additional setup steps required to enable LangSmith Deployment:
* Installing the LangGraph operator
* Configuring your ingress
* Connecting to the control plane
## Prerequisites
1. You are using Kubernetes.
2. You have an instance of [self-hosted LangSmith](/langsmith/kubernetes) running.
3. `KEDA` is installed on your cluster.
```bash theme={null}
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --create-namespace
```
`KEDA` is used to automatically scale the deployment system based on queue size.
4. Ingress Configuration
1. You must set up an ingress, gateway, or use Istio for your LangSmith instance. All agents will be deployed as Kubernetes services behind this ingress. Use this guide to [set up an ingress](/langsmith/self-host-ingress) for your instance. To enable LangSmith Deployment, you will need to provide a `hostname` in your `values.yaml`
5. You must have slack space in your cluster for multiple deployments. `Cluster-Autoscaler` is recommended to automatically provision new nodes.
6. A valid Dynamic PV provisioner or PVs available on your cluster. You can verify this by running:
```bash theme={null}
kubectl get storageclass
```
7. Egress to `https://beacon.langchain.com` from your network. This is required for license verification and usage reporting if not running in air-gapped mode. See the [Egress documentation](/langsmith/self-host-egress) for more details.
## Setup
1. As part of configuring your self-hosted LangSmith instance, you enable the `deployment` option. This will provision a few key resources.
1. `listener`: This is a service that listens to the [control plane](/langsmith/control-plane) for changes to your deployments and creates/updates downstream CRDs.
2. `LangGraphPlatform CRD`: A CRD for LangSmith Deployment. This contains the spec for managing an instance of a LangSmith deployment.
3. `operator`: This operator handles changes to your LangSmith CRDs.
4. `host-backend`: This is the [control plane](/langsmith/control-plane).
As of v0.12.0, the `langgraphPlatform` option is deprecated. Use `config.deployment` for any version after v0.12.0.
2. Two additional images will be used by the chart. Use the images that are specified in the latest release.
```bash theme={null}
hostBackendImage:
repository: "docker.io/langchain/hosted-langserve-backend"
pullPolicy: IfNotPresent
operatorImage:
repository: "docker.io/langchain/langgraph-operator"
pullPolicy: IfNotPresent
```
3. In your config file for langsmith (usually `langsmith_config.yaml`), enable the `deployment` option. Note that you must also have a valid ingress setup:
```bash theme={null}
config:
deployment:
enabled: true
# As of v0.12.0, this section is deprecated. Use config.deployment for any version after v0.12.0.
langgraphPlatform:
enabled: true
langgraphPlatformLicenseKey: "YOUR_LANGGRAPH_PLATFORM_LICENSE_KEY"
```
4. In your `values.yaml` file, configure the `hostBackendImage` and `operatorImage` options (if you need to mirror images). If you are using a private container registry that requires authentication, you must also configure `imagePullSecrets`, refer to [Configure authentication for private registries](#optional-configure-authentication-for-private-registries).
5. You can also configure base templates for your agents by overriding the base templates [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/values.yaml#L898).
Your self-hosted infrastructure is now ready to create deployments.
## (Optional) Configure additional data planes
In addition to the existing data plane already created in the above steps, you can create more data planes that reside in different Kubernetes clusters or the same cluster in a different namespace.
### Prerequisites
1. Read through the cluster organization guide in the [hybrid deployment documentation](/langsmith/hybrid#listeners) to understand how to best organize this for your use case.
2. Verify the prerequisites mentioned in the [hybrid](/langsmith/deploy-hybrid#prerequisites) section are met for the new cluster. Note that in step 5 of [this section](/langsmith/deploy-hybrid#prerequisites), you need to enable egress to your [self-hosted LangSmith instance](/langsmith/self-host-usage#configuring-the-application-you-want-to-use-with-langsmith) instead of [https://api.host.langchain.com](https://api.host.langchain.com) and [https://api.smith.langchain.com](https://api.smith.langchain.com).
3. Run the following commands against your LangSmith Postgres instance to enable this feature. This is the [Postgres instance](/langsmith/kubernetes#validate-your-deployment%3A) that comes with your self-hosted LangSmith setup.
```
update organizations set config = config || '{"enable_lgp_listeners_page": true}' where id = '';
update tenants set config = config || '{"langgraph_remote_reconciler_enabled": true}' where id = '';
```
Note down the workspace ID you choose as you will need this for future steps.
### Deploying to a different cluster
1. Follow steps 2-6 in the [hybrid setup guide](/langsmith/deploy-hybrid#setup). The `config.langsmithWorkspaceId` value should be set to the workspace ID you noted in the prerequisites.
2. To deploy more than one data plane to the cluster, follow the rules listed [here](/langsmith/deploy-hybrid#configuring-additional-data-planes-in-the-same-cluster).
### Deploying to a different namespace in the same cluster
1. You will need to make some modifications to the `langsmith_config.yaml` file you created in step 3 of the [above setup instructions](/langsmith/deploy-self-hosted-full-platform#setup):
* Set the `operator.watchNamespaces` field to the current namespace your self-hosted LangSmith instance is running in. This is to prevent clashes with the operator that will be added as part of the new data plane.
* It is required to use the [Gateway API](/langsmith/self-host-ingress#option-2%3A-gateway-api) or an [Istio Gateway](/langsmith/self-host-ingress#option-3%3A-istio-gateway). Please adjust your `langsmith_config.yaml` file accordingly.
2. Run a `helm upgrade` to update your self hosted LangSmith instance with the new config.
3. Follow steps 2-6 in the [hybrid setup guide](/langsmith/deploy-hybrid#setup). The `config.langsmithWorkspaceId` value should be set to the workspace ID you noted in the prerequisites. Remember that `config.watchNamespaces` should be set to different namespaces than the one used by the existing data plane!
## (Optional) Configure authentication for private registries
If your [Agent Server deployments](/langsmith/agent-server) will use images from private container registries (e.g., AWS ECR, Azure ACR, GCP Artifact Registry, private Docker registry), configure image pull secrets. This is a one-time infrastructure configuration that allows all deployments to automatically authenticate with your private registry.
**Step 1: Create a Kubernetes image pull secret**
```bash theme={null}
kubectl create secret docker-registry langsmith-registry-secret \
--docker-server=myregistry.com \
--docker-username=your-username \
--docker-password=your-password \
--docker-email=your-email@example.com \
-n langsmith
```
Replace the values with your registry credentials:
* `myregistry.com`: Your registry URL
* `your-username`: Your registry username
* `your-password`: Your registry password or access token
* `langsmith`: The Kubernetes namespace where LangSmith is installed
**Step 2: Configure the deployment template in your `values.yaml`**
To enable agent server deployments to use the private registry secret, you must add `imagePullSecrets` to the operator's deployment template:
```yaml {21-22} theme={null}
operator:
templates:
deployment: |
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${name}
namespace: ${namespace}
spec:
replicas: ${replicas}
revisionHistoryLimit: 10
selector:
matchLabels:
app: ${name}
template:
metadata:
labels:
app: ${name}
spec:
enableServiceLinks: false
imagePullSecrets:
- name: langsmith-registry-secret
containers:
- name: api-server
image: ${image}
ports:
- name: api-server
containerPort: 8000
protocol: TCP
livenessProbe:
httpGet:
path: /ok
port: 8000
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 6
readinessProbe:
httpGet:
path: /ok
port: 8000
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 6
```
**Step 3: Apply during Helm installation/upgrade**
When you deploy or upgrade your LangSmith instance using Helm, this configuration will be applied. All user deployments created through the LangSmith UI will automatically inherit these registry credentials.
For registry-specific authentication methods (AWS ECR, Azure ACR, GCP Artifact Registry, etc.), refer to the [Kubernetes documentation on pulling images from private registries](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/).
## Next steps
Once your infrastructure is set up, you're ready to deploy applications. See the deployment guides in the [Deployment tab](/langsmith/deployments) for instructions on building and deploying your applications.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/deploy-self-hosted-full-platform.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Self-host standalone servers
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/deploy-standalone-server
This guide shows you how to deploy **standalone Agent Servers** without the LangSmith UI or control plane. This is the most lightweight self-hosting option for running one or a few agents as independent services.
This deployment option provides flexibility but requires you to manage your own infrastructure and configuration.
For production workloads, consider [self-hosting the full LangSmith platform](/langsmith/self-hosted) or [deploying with the control plane](/langsmith/deploy-with-control-plane), which offer standardized deployment patterns and UI-based management.
**This is the setup page for deploying Agent Servers directly without the LangSmith platform.**
Review the [self-hosted options](/langsmith/self-hosted) to understand:
* [Standalone Server](/langsmith/self-hosted#standalone-server): What this guide covers (no UI, just servers).
* [LangSmith](/langsmith/self-hosted#langsmith): For the full LangSmith platform with UI.
* [LangSmith Deployment](/langsmith/self-hosted#langsmith-deployment): For UI-based deployment management.
Before continuing, review the [standalone server overview](/langsmith/self-hosted#standalone-server).
## Prerequisites
1. Use the [LangGraph CLI](/langsmith/cli) to [test your application locally](/langsmith/local-server).
2. Use the [LangGraph CLI](/langsmith/cli) to build a Docker image (i.e. `langgraph build`).
3. The following environment variables are needed for a data plane deployment.
4. `REDIS_URI`: Connection details to a Redis instance. Redis will be used as a pub-sub broker to enable streaming real time output from background runs. The value of `REDIS_URI` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
**Shared Redis Instance**
Multiple self-hosted deployments can share the same Redis instance. For example, for `Deployment A`, `REDIS_URI` can be set to `redis://:/1` and for `Deployment B`, `REDIS_URI` can be set to `redis://:/2`.
`1` and `2` are different database numbers within the same instance, but `` is shared. **The same database number cannot be used for separate deployments**.
5. `DATABASE_URI`: Postgres connection details. Postgres will be used to store assistants, threads, runs, persist thread state and long term memory, and to manage the state of the background task queue with 'exactly once' semantics. The value of `DATABASE_URI` must be a valid [Postgres connection URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS).
**Shared Postgres Instance**
Multiple self-hosted deployments can share the same Postgres instance. For example, for `Deployment A`, `DATABASE_URI` can be set to `postgres://:@/?host=` and for `Deployment B`, `DATABASE_URI` can be set to `postgres://:@/?host=`.
`` and `database_name_2` are different databases within the same instance, but `` is shared. **The same database cannot be used for separate deployments**.
6. `LANGSMITH_API_KEY`: LangSmith API key.
7. `LANGGRAPH_CLOUD_LICENSE_KEY`: LangSmith license key. This will be used to authenticate ONCE at server start up.
8. `LANGSMITH_ENDPOINT`: To send traces to a [self-hosted LangSmith](/langsmith/self-hosted) instance, set `LANGSMITH_ENDPOINT` to the hostname of the self-hosted LangSmith instance.
9. Egress to `https://beacon.langchain.com` from your network. This is required for license verification and usage reporting if not running in air-gapped mode. See the [Egress documentation](/langsmith/self-host-egress) for more details.
## Kubernetes
Use this [Helm chart](https://github.com/langchain-ai/helm/blob/main/charts/langgraph-cloud/README.md) to deploy an Agent Server to a Kubernetes cluster.
## Docker
Run the following `docker` command:
```shell theme={null}
docker run \
--env-file .env \
-p 8123:8000 \
-e REDIS_URI="foo" \
-e DATABASE_URI="bar" \
-e LANGSMITH_API_KEY="baz" \
my-image
```
* You need to replace `my-image` with the name of the image you built in the prerequisite steps (from `langgraph build`)
and you should provide appropriate values for `REDIS_URI`, `DATABASE_URI`, and `LANGSMITH_API_KEY`.
* If your application requires additional environment variables, you can pass them in a similar way.
## Docker Compose
Docker Compose YAML file:
```yml theme={null}
volumes:
langgraph-data:
driver: local
services:
langgraph-redis:
image: redis:6
healthcheck:
test: redis-cli ping
interval: 5s
timeout: 1s
retries: 5
langgraph-postgres:
image: postgres:16
ports:
- "5432:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- langgraph-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U postgres
start_period: 10s
timeout: 1s
retries: 5
interval: 5s
langgraph-api:
image: ${IMAGE_NAME}
ports:
- "8123:8000"
depends_on:
langgraph-redis:
condition: service_healthy
langgraph-postgres:
condition: service_healthy
env_file:
- .env
environment:
REDIS_URI: redis://langgraph-redis:6379
LANGSMITH_API_KEY: ${LANGSMITH_API_KEY}
DATABASE_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable
```
You can run the command `docker compose up` with this Docker Compose file in the same folder.
This will launch an Agent Server on port `8123` (if you want to change this, you can change this by changing the ports in the `langgraph-api` volume). You can test if the application is healthy by running:
```shell theme={null}
curl --request GET --url 0.0.0.0:8123/ok
```
Assuming everything is running correctly, you should see a response like:
```shell theme={null}
{"ok":true}
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/deploy-standalone-server.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Deploy on Cloud
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/deploy-to-cloud
This is the comprehensive setup and management guide for deploying applications to LangSmith Cloud.
**If you're looking for a quick setup**, try the [quickstart guide](/langsmith/deployment-quickstart) first.
Before setting up, review the [Cloud overview page](/langsmith/cloud) to understand the Cloud hosting model.
## Prerequisites
* Applications are deployed from GitHub repositories. Configure and upload an application to a GitHub repository.
* [Verify that the LangGraph API runs locally](/langsmith/local-server). If the API does not run successfully (i.e., `langgraph dev`), deploying to LangSmith will fail as well.
**One-Time Setup Required**: A GitHub organization owner or admin must complete the OAuth flow in the LangSmith UI to authorize the `hosted-langserve` GitHub app. This only needs to be done once per workspace. After the initial OAuth authorization, all developers with deployment permissions can create and manage deployments without requiring GitHub admin access.
## Create new deployment
Starting from the [LangSmith UI](https://smith.langchain.com), select **Deployments** in the left-hand navigation panel, **Deployments**. In the top-right corner, select **+ New Deployment** to create a new deployment:
1. In the **Create New Deployment** panel, fill out the required fields. For **Deployment details**:
1. Select **Import from GitHub** and follow the GitHub OAuth workflow to install and authorize LangChain's `hosted-langserve` GitHub app to access the selected repositories. After installation is complete, return to the **Create New Deployment** panel and select the GitHub repository to deploy from the dropdown menu.
The GitHub user installing LangChain's `hosted-langserve` GitHub app must be an [owner](https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization#organization-owners) of the organization or account. This authorization only needs to be completed once per LangSmith workspace—subsequent deployments can be created by any user with deployment permissions.
2. Specify a name for the deployment.
3. Specify the desired **Git Branch**. A deployment is linked to a branch. When a new revision is created, code for the linked branch will be deployed. The branch can be updated later in the [Deployment Settings](#deployment-settings).
4. Specify the full path to the [LangGraph API config file](/langsmith/cli#configuration-file) including the file name. For example, if the file `langgraph.json` is in the root of the repository, specify `langgraph.json`.
5. Use the checkbox to **Automatically update deployment on push to branch**. If checked, the deployment will automatically be updated when changes are pushed to the specified **Git Branch**. You can enable or disable this setting on the [Deployment Settings](#deployment-settings) in [the UI](https://smith.langchain.com).
For **Deployment Type**:
* Development deployments are meant for non-production use cases and are provisioned with minimal resources.
* Production deployments can serve up to 500 requests/second and are provisioned with highly available storage with automatic backups.
6. Determine if the deployment should be **Shareable through Studio**.
1. If unchecked, the deployment will only be accessible with a valid LangSmith API key for the [workspace](/langsmith/administration-overview#workspaces).
2. If checked, the deployment will be accessible through [Studio](/langsmith/studio) to any LangSmith user. A direct URL to Studio for the deployment will be provided to share with other LangSmith users.
7. Specify **Environment Variables** and secrets. To configure additional variables for the deployment, refer to the [Environment Variables reference](/langsmith/env-var).
1. Sensitive values such as API keys (e.g., `OPENAI_API_KEY`) should be specified as secrets.
2. Additional non-secret environment variables can be specified as well.
8. A new LangSmith [tracing project](/langsmith/observability) is automatically created with the same name as the deployment.
2. In the top-right corner, select **Submit**. After a few seconds, the **Deployment** view appears and the new deployment will be queued for provisioning.
## Create new revision
When [creating a new deployment](#create-new-deployment), a new revision is created by default. You can create subsequent revisions to deploy new code changes.
Starting from the [LangSmith UI](https://smith.langchain.com), select **Deployments** in the left-hand navigation panel. Select an existing deployment to create a new revision for.
1. In the **Deployment** view, in the top-right corner, select **+ New Revision**.
2. In the **New Revision** modal, fill out the required fields.
1. Specify the full path to the [API config file](/langsmith/cli#configuration-file) including the file name. For example, if the file `langgraph.json` is in the root of the repository, specify `langgraph.json`.
2. Determine if the deployment should be **Shareable through Studio**.
* If unchecked, the deployment will only be accessible with a valid LangSmith API key for the [workspace](/langsmith/administration-overview#workspaces).
* If checked, the deployment will be accessible through [Studio](/langsmith/studio) to any LangSmith user. A direct URL to Studio for the deployment will be provided to share with other LangSmith users.
3. Specify **Environment Variables** and secrets. Existing secrets and environment variables are prepopulated. To configure additional variables for the revision, refer to the [Environment Variables reference](/langsmith/env-var).
1. Add new secrets or environment variables.
2. Remove existing secrets or environment variables.
3. Update the value of existing secrets or environment variables.
3. Select **Submit\`**. After a few seconds, the **New Revision** modal will close and the new revision will be queued for deployment.
## View build and server logs
Build and server logs are available for each revision.
Starting from the **Deployments** view:
1. Select the desired revision from the **Revisions** table. A panel slides open from the right-hand side and the **Build** tab is selected by default, which displays build logs for the revision.
2. In the panel, select the **Server** tab to view server logs for the revision. Server logs are only available after a revision has been deployed.
3. Within the **Server** tab, adjust the date/time range picker as needed. By default, the date/time range picker is set to the **Last 7 days**.
## View deployment metrics
Starting from the [LangSmith UI](https://smith.langchain.com):
1. In the left-hand navigation panel, select **Deployments**.
2. Select an existing deployment to monitor.
3. Select the **Monitoring** tab to view the deployment metrics. Refer to a list of [all available metrics](/langsmith/control-plane#monitoring).
4. Within the **Monitoring** tab, use the date/time range picker as needed. By default, the date/time range picker is set to the **Last 15 minutes**.
## Interrupt revision
Interrupting a revision will stop deployment of the revision.
**Undefined Behavior**
Interrupted revisions have undefined behavior. This is only useful if you need to deploy a new revision and you already have a revision "stuck" in progress. In the future, this feature may be removed.
Starting from the **Deployments** view:
1. Select the menu icon (three dots) on the right-hand side of the row for the desired revision from the **Revisions** table.
2. Select **Interrupt** from the menu.
3. A modal will appear. Review the confirmation message. Select **Interrupt revision**.
## Delete deployment
Starting from the [LangSmith UI](https://smith.langchain.com):
1. In the left-hand navigation panel, select **Deployments**, which contains a list of existing deployments.
2. Select the menu icon (three dots) on the right-hand side of the row for the desired deployment and select **Delete**.
3. A **Confirmation** modal will appear. Select **Delete**.
## Deployment settings
Starting from the **Deployments** view:
1. In the top-right corner, select the gear icon (**Deployment Settings**).
2. Update the `Git Branch` to the desired branch.
3. Check/uncheck checkbox to **Automatically update deployment on push to branch**.
1. Branch creation/deletion and tag creation/deletion events will not trigger an update. Only pushes to an existing branch will trigger an update.
2. Pushes in quick succession to a branch will queue subsequent updates. Once a build completes, the most recent commit will begin building and the other queued builds will be skipped.
## Add or remove GitHub repositories
After installing and authorizing LangChain's `hosted-langserve` GitHub app, repository access for the app can be modified to add new repositories or remove existing repositories. If a new repository is created, it may need to be added explicitly.
1. From the GitHub profile, navigate to **Settings** > **Applications** > `hosted-langserve` > click **Configure**.
2. Under **Repository access**, select **All repositories** or **Only select repositories**. If **Only select repositories** is selected, new repositories must be explicitly added.
3. Click **Save**.
4. When creating a new deployment, the list of GitHub repositories in the dropdown menu will be updated to reflect the repository access changes.
## Allowlist IP addresses
All traffic from LangSmith deployments created after January 6th 2025 will come through a NAT gateway.
This NAT gateway will have several static ip addresses depending on the region you are deploying in. Refer to the table below for the list of IP addresses to allowlist:
| US | EU |
| -------------- | -------------- |
| 35.197.29.146 | 34.90.213.236 |
| 34.145.102.123 | 34.13.244.114 |
| 34.169.45.153 | 34.32.180.189 |
| 34.82.222.17 | 34.34.69.108 |
| 35.227.171.135 | 34.32.145.240 |
| 34.169.88.30 | 34.90.157.44 |
| 34.19.93.202 | 34.141.242.180 |
| 34.19.34.50 | 34.32.141.108 |
| 34.59.244.194 | |
| 34.9.99.224 | |
| 34.68.27.146 | |
| 34.41.178.137 | |
| 34.123.151.210 | |
| 34.135.61.140 | |
| 34.121.166.52 | |
| 34.31.121.70 | |
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/deploy-to-cloud.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Deploy with control plane
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/deploy-with-control-plane
This guide shows you how to deploy your applications to [hybrid](/langsmith/hybrid) or [self-hosted](/langsmith/self-hosted) instances with a [control plane](/langsmith/control-plane). With a control plane, you build Docker images locally, push them to a registry that your Kubernetes cluster has access to, and deploy them with the [LangSmith UI](https://smith.langchain.com).
**This guide is for deploying applications, not setting up infrastructure.**
Before using this guide, you must have already completed infrastructure setup:
* **[Hybrid setup](/langsmith/deploy-hybrid)**: For hybrid hosting.
* **[Enable LangSmith Deployment](/langsmith/deploy-self-hosted-full-platform)**: For self-hosted with control plane.
If you haven't set up your infrastructure yet, start with the [Platform setup section](/langsmith/platform-setup).
## Overview
Applications deployed to hybrid or self-hosted LangSmith instances with control plane use Docker images. In this guide, the application deployment workflow is:
1. Test your application locally using `langgraph dev` or [Studio](/langsmith/studio).
2. Build a Docker image using the `langgraph build` command.
3. Push the image to a container registry accessible by your infrastructure.
4. Deploy from the [control plane UI](/langsmith/control-plane#control-plane-ui) by specifying the image URL.
## Prerequisites
Before completing this guide, you'll need the following:
* Completed infrastructure setup to enable your [data plane](/langsmith/data-plane) to receive application deployments:
* [Hybrid setup](/langsmith/deploy-hybrid): Installs data plane components (listener, operator, CRDs) in your Kubernetes cluster that connect to LangChain's managed control plane.
* [Enable LangSmith Deployment](/langsmith/deploy-self-hosted-full-platform): Enables LangSmith Deployment on your self-hosted LangSmith instance.
* Access to the [LangSmith UI](https://smith.langchain.com) with LangSmith Deployment enabled.
* A container registry accessible by your Kubernetes cluster. If using a private registry that requires authentication, you must configure image pull secrets as part of your infrastructure setup. Refer to [Private registry authentication](#private-registry-authentication).
## Step 1. Test locally
Before deploying, test your application locally. You can use the [LangGraph CLI](/langsmith/cli#dev) to run an Agent server in development mode:
```bash theme={null}
langgraph dev
```
For a full guide local testing, refer to the [Local server quickstart](/langsmith/local-server).
## Step 2. Build Docker image
Build a Docker image of your application using the [`langgraph build`](/langsmith/cli#build) command:
```bash theme={null}
langgraph build -t my-image
```
Build command options include:
| Option | Default | Description |
| -------------------- | ---------------- | ----------------------------------------------------------------- |
| `-t, --tag TEXT` | Required | Tag for the Docker image |
| `--platform TEXT` | | Target platform(s) to build for (e.g., `linux/amd64,linux/arm64`) |
| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image |
| `-c, --config FILE` | `langgraph.json` | Path to configuration file |
Example with platform specification:
```bash theme={null}
langgraph build --platform linux/amd64 -t my-image:v1.0.0
```
For full details, see the [CLI reference](/langsmith/cli#build).
## Step 3. Push to container registry
Push your image to a container registry accessible by your Kubernetes cluster. The specific commands depend on your registry provider.
Tag your images with version information (e.g., `my-registry.com/my-app:v1.0.0`) to make rollbacks easier.
## Step 4. Deploy with the control plane UI
The [control plane UI](/langsmith/control-plane#control-plane-ui) allows you to create and manage deployments, view logs and metrics, and update configurations. To create a new deployment in the [LangSmith UI](https://smith.langchain.com):
1. In the left-hand navigation panel, select **Deployments**.
2. In the top-right corner, select **+ New Deployment**.
3. In the deployment configuration panel, provide:
* **Image URL**: The full image URL you pushed in [Step 3](#step-3-push-to-container-registry).
* **Listener/Compute ID**: Select the listener configured for your infrastructure.
* **Namespace**: The Kubernetes namespace to deploy to.
* **Environment variables**: Any required configuration (API keys, etc.).
* Other deployment settings as needed.
4. Select **Submit**.
The control plane will coordinate with your [data plane](/langsmith/data-plane) listener to deploy your application.
After creating a deployment, the infrastructure is [provisioned asynchronously](/langsmith/control-plane#asynchronous-deployment). Deployment can take up to several minutes, with initial deployments taking longer due to database creation.
From the control plane UI, you can view build logs, server logs, and deployment metrics including CPU/memory usage, replicas, and API performance. For more details, refer to the [control plane monitoring documentation](/langsmith/control-plane#monitoring).
A [LangSmith Observability tracing project](/langsmith/observability) is automatically created for each deployment with the same name as the deployment. Tracing environment variables are set automatically by the control plane.
## Update deployment
To deploy a new version of your application, create a [new revision](/langsmith/control-plane#revisions):
Starting from the LangSmith UI:
1. In the left-hand navigation panel, select **Deployments**.
2. Select an existing deployment.
3. In the Deployment view, select **+ New Revision** in the top-right corner.
4. Update the configuration:
* Update the **Image URL** to your new image version.
* Update environment variables if needed.
* Adjust other settings as needed.
5. Select **Submit**.
## Private registry authentication
If your container registry requires authentication (e.g., AWS ECR, Azure ACR, GCP Artifact Registry, private Docker registry), you must configure Kubernetes image pull secrets before deploying applications. This is a one-time infrastructure configuration.
**This configuration is done at the infrastructure level, not per-deployment.** Once configured, all deployments automatically inherit the registry credentials.
The configuration steps depend on your deployment type:
* **Self-hosted with control plane**: Configure `imagePullSecrets` in your LangSmith Helm chart's `values.yaml` file. See the detailed steps in the [Enable LangSmith Deployment guide](/langsmith/deploy-self-hosted-full-platform#setup).
* **Hybrid**: Configure `imagePullSecrets` in your `langgraph-dataplane-values.yaml` file using the same format.
For detailed steps on creating image pull secrets for different registry providers, refer to the [Kubernetes documentation on pulling images from private registries](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/).
## Next steps
* **[Control plane](/langsmith/control-plane)**: Learn more about control plane features.
* **[Data plane](/langsmith/data-plane)**: Understand data plane architecture.
* **[Observability](/langsmith/observability)**: Monitor your deployments with automatic tracing.
* **[Studio](/langsmith/studio)**: Test and debug deployed applications.
* **[LangGraph CLI](/langsmith/cli)**: Full CLI reference documentation.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/deploy-with-control-plane.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Deploy your app to Cloud
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/deployment-quickstart
This is a quickstart guide for deploying your first application to LangSmith Cloud.
For a comprehensive Cloud deployment guide with all configuration options, refer to the [Cloud deployment setup guide](/langsmith/deploy-to-cloud).
## Prerequisites
Before you begin, ensure you have the following:
* A [GitHub account](https://github.com/)
* A [LangSmith account](https://smith.langchain.com/) (free to sign up)
## 1. Create a repository on GitHub
To deploy an application to **LangSmith**, your application code must reside in a GitHub repository. Both public and private repositories are supported. For this quickstart, use the [`new-langgraph-project` template](https://github.com/langchain-ai/react-agent) for your application:
1. Go to the [`new-langgraph-project` repository](https://github.com/langchain-ai/new-langgraph-project) or [`new-langgraphjs-project` template](https://github.com/langchain-ai/new-langgraphjs-project).
2. Click the `Fork` button in the top right corner to fork the repository to your GitHub account.
3. Click **Create fork**.
## 2. Deploy to LangSmith
1. Log in to [LangSmith](https://smith.langchain.com/).
2. In the left sidebar, select **Deployments**.
3. Click the **+ New Deployment** button. A pane will open where you can fill in the required fields.
4. If you are a first time user or adding a private repository that has not been previously connected, click the **Import from GitHub** button and follow the instructions to connect your GitHub account.
5. Select your New LangGraph Project repository.
6. Click **Submit** to deploy.
This may take about 15 minutes to complete. You can check the status in the **Deployment details** view.
## 3. Test your application in Studio
Once your application is deployed:
1. Select the deployment you just created to view more details.
2. Click the **Studio** button in the top right corner. [Studio](/langsmith/studio) will open to display your graph.
## 4. Get the API URL for your deployment
1. In the **Deployment details** view, click the **API URL** to copy it to your clipboard.
2. Click the `URL` to copy it to the clipboard.
## 5. Test the API
You can now test the API:
1. Install the LangGraph Python SDK:
```shell theme={null}
pip install langgraph-sdk
```
2. Send a message to the assistant (threadless run):
```python theme={null}
from langgraph_sdk import get_client
client = get_client(url="your-deployment-url", api_key="your-langsmith-api-key")
async for chunk in client.runs.stream(
None, # Threadless run
"agent", # Name of assistant. Defined in langgraph.json.
input={
"messages": [{
"role": "human",
"content": "What is LangGraph?",
}],
},
stream_mode="updates",
):
print(f"Receiving new event of type: {chunk.event}...")
print(chunk.data)
print("\n\n")
```
1. Install the LangGraph Python SDK:
```shell theme={null}
pip install langgraph-sdk
```
2. Send a message to the assistant (threadless run):
```python theme={null}
from langgraph_sdk import get_sync_client
client = get_sync_client(url="your-deployment-url", api_key="your-langsmith-api-key")
for chunk in client.runs.stream(
None, # Threadless run
"agent", # Name of assistant. Defined in langgraph.json.
input={
"messages": [{
"role": "human",
"content": "What is LangGraph?",
}],
},
stream_mode="updates",
):
print(f"Receiving new event of type: {chunk.event}...")
print(chunk.data)
print("\n\n")
```
1. Install the LangGraph JS SDK
```shell theme={null}
npm install @langchain/langgraph-sdk
```
2. Send a message to the assistant (threadless run):
```js theme={null}
const { Client } = await import("@langchain/langgraph-sdk");
const client = new Client({ apiUrl: "your-deployment-url", apiKey: "your-langsmith-api-key" });
const streamResponse = client.runs.stream(
null, // Threadless run
"agent", // Assistant ID
{
input: {
"messages": [
{ "role": "user", "content": "What is LangGraph?"}
]
},
streamMode: "messages",
}
);
for await (const chunk of streamResponse) {
console.log(`Receiving new event of type: ${chunk.event}...`);
console.log(JSON.stringify(chunk.data));
console.log("\n\n");
}
```
```bash theme={null}
curl -s --request POST \
--url /runs/stream \
--header 'Content-Type: application/json' \
--header "X-Api-Key: \
--data "{
\"assistant_id\": \"agent\",
\"input\": {
\"messages\": [
{
\"role\": \"human\",
\"content\": \"What is LangGraph?\"
}
]
},
\"stream_mode\": \"updates\"
}"
```
## Next steps
You've successfully deployed your application to LangSmith Cloud. Here are some next steps:
* **Explore Studio**: Use [Studio](/langsmith/studio) to visualize and debug your graph interactively.
* **Monitor your app**: Set up [observability](/langsmith/observability) with traces, dashboards, and alerts.
* **Learn more about Cloud**: See the [complete Cloud setup guide](/langsmith/deploy-to-cloud) for all configuration options.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/deployment-quickstart.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith Deployment
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/deployments
Deploy and manage agents with durable execution, real-time streaming, and horizontal scaling.
LangSmith Deployment is a workflow orchestration runtime purpose-built for agent workloads. It handles the infrastructure that agents need to run reliably: long-running stateful execution, human-in-the-loop pauses, real-time streaming, horizontal scaling — all with a first-class [Studio](/langsmith/studio) development environment.
**Start here if you're building or operating agent applications.** This section is about deploying **your application**. If you need to set up LangSmith infrastructure, the [Platform setup section](/langsmith/platform-setup) covers infrastructure options (cloud, hybrid, self-hosted) and setup guides.
A typical deployment workflow:
Test locally}>
Run your application on a local development server.
Configure app for deployment}>
Set up dependencies, project structure, and environment configuration.
Choose hosting}>
(Required for deployment) Select Cloud, Hybrid, or Self-hosted.
* [**Cloud**](/langsmith/deploy-to-cloud): Push code from a git repository
* [**Hybrid or Self-hosted with control plane**](/langsmith/deploy-with-control-plane): Build and push Docker images, deploy via UI
* [**Standalone servers**](/langsmith/deploy-standalone-server): Deploy directly without control plane
Monitor & manage}>
Track traces, alerts, and dashboards.
***
## Durable execution
At its core, LangSmith Deployment is a durable execution engine. Your agents run on a managed task queue with automatic checkpointing, so any run can be retried, replayed, or resumed from the exact point of interruption — not from scratch.
Because execution is durable, agents can do things that would be fragile or impossible in a stateless runtime:
* **Wait for external input.** An agent calls [`interrupt()`](/langsmith/add-human-in-the-loop) and the runtime checkpoints its state, frees resources, and waits — for a human to approve a transaction, for a reviewer to edit a draft, for another system to return results. When [`Command(resume=...)`](/langsmith/add-human-in-the-loop) arrives hours or days later, execution picks up exactly where it stopped. This is the primitive underneath [human-in-the-loop](/langsmith/add-human-in-the-loop) workflows and [time-travel debugging](/langsmith/human-in-the-loop-time-travel).
* **Run in the background.** [Background runs](/langsmith/background-run) execute without blocking the caller. The runtime manages the full lifecycle — queuing, execution, checkpointing, completion — while the client moves on.
* **Run on a schedule.** [Cron jobs](/langsmith/cron-jobs) trigger agent execution on a recurring cadence. A daily summary agent, a weekly report, a periodic data sync — the runtime starts a new execution on schedule with the same durability guarantees.
* **Handle concurrent input.** When a user sends new input while an agent is mid-run ([double-texting](/langsmith/double-texting)), the runtime can queue it, cancel the in-progress run, or process both in parallel — without data races or corrupted state.
* **Retry on failure.** Configurable [retry policies](/oss/python/langgraph/use-graph-api#retry-policies) control backoff, max attempts, and which exceptions trigger retries on a per-node basis. Runs survive process restarts, infrastructure failures, and code revisions mid-execution.
For details on how containers, processes, and the task queue work together, see [Agent Server: Runtime architecture](/langsmith/agent-server#runtime-architecture). For scaling and throughput tuning, see [Configure Agent Server for scale](/langsmith/agent-server-scale).
## Streaming
Agents need to show their work in real time. The runtime provides [resumable streaming](/langsmith/streaming) — if a client disconnects mid-stream (network switch, tab sleep, mobile backgrounding), it reconnects and picks up where it left off. Multiple [streaming modes](/langsmith/streaming) give you control over granularity, from full state snapshots after each step to token-by-token LLM output as it arrives from the provider.
## Studio
[LangGraph Studio](/langsmith/studio) connects to any Agent Server — local or deployed — and gives you an interactive environment for developing and debugging agents. Visualize execution graphs, inspect state at any checkpoint, step through runs, modify state mid-execution, and branch to explore alternative paths.
## Agent composition
Agents don't run in isolation. [RemoteGraph](/langsmith/use-remote-graph) lets any agent call other deployed agents using the same interface you use locally — a research agent delegates to a search agent on a different deployment, a routing agent dispatches to specialized sub-agents. The agents don't need to know whether they're calling something local or remote.
Native support for [MCP](/langsmith/server-mcp) and [A2A](/langsmith/server-a2a) means your deployed agents can expose and consume tool interfaces and agent-to-agent protocols alongside the broader ecosystem.
***
## Deployment options
* **[Cloud](/langsmith/deploy-to-cloud)** — Fully managed. Push from a git repo.
* **[Hybrid](/langsmith/deploy-with-control-plane)** — Runs in your cloud, managed by the LangSmith [control plane](/langsmith/control-plane).
* **[Self-hosted](/langsmith/deploy-standalone-server)** — Fully self-managed in your own infrastructure.
Same runtime, same APIs. What changes is who manages the infrastructure. See [Platform setup](/langsmith/platform-setup) for a comparison.
***
## Go deeper
### Securing and customizing your server
* [Custom auth](/langsmith/auth) — Authentication and multi-tenant access control
* [Server customization](/langsmith/custom-routes) — Custom routes, [middleware](/langsmith/custom-middleware), [lifespan hooks](/langsmith/custom-lifespan), [encryption](/langsmith/encryption)
### Operations
* [CI/CD pipelines](/langsmith/cicd-pipeline-example)
* [TTL configuration](/langsmith/configure-ttl) for state and thread management
* [Semantic search](/langsmith/semantic-search)
### Reference
* [Agent Server](/langsmith/agent-server) — Runtime architecture reference
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/deployments.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Troubleshooting for self-hosted deployments
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/diagnostics-self-hosted
This page provides diagnostic steps to help you troubleshoot issues with self-hosted [LangSmith Deployment](/langsmith/deployments) before reaching out to support. Follow these steps systematically to identify and resolve common deployment issues.
If you complete these diagnostic steps and still need assistance, refer to [Support](#support) at the end of this guide for information on what to gather before reaching out.
## Prerequisites
Before beginning the diagnostic steps, ensure you have:
* `kubectl` access to your Kubernetes cluster.
* Appropriate permissions to view pods, deployments, services, etc.
* Familiarity with your [Helm chart configuration](/langsmith/kubernetes#configure-your-helm-charts:).
## Step 1. Understand your deployment
Verify what was deployed and understand the baseline state of your system. This helps you recognize what normal operation looks like and identify deviations when issues occur.
Run the following commands to view all deployed Kubernetes resources.
Ensure that you're in the correct namespace when you run the commands in this section. Or, specify the namespace explicitly with the `-n` flag. For example: `kubectl get deployments -n langsmith`.
List all deployments:
```bash theme={null}
kubectl get deployments
```
List all pods:
```bash theme={null}
kubectl get pods
```
List all services:
```bash theme={null}
kubectl get services
```
List all `lgps` resources (only present after creating an [Agent Server](/langsmith/agent-server)):
```bash theme={null}
kubectl get lgps
```
### Key deployed components
Your deployment includes the following core components:
* **`langsmith-frontend`**: The LangSmith frontend UI where you create Agent Server deployments. This app makes API calls to `langsmith-host-backend`. Part of the [control plane](/langsmith/control-plane).
* **`langsmith-host-backend`**: The LangSmith Deployment [control plane](/langsmith/control-plane) that receives requests from `langsmith-frontend` and persists deployment requests to the control plane Postgres database.
* **`langsmith-listener`**: Part of the LangSmith Deployment [data plane](/langsmith/data-plane). Polls `langsmith-host-backend` via HTTP API for deployments to create, update, or delete. Enqueues tasks for worker processes to handle.
* **`langsmith-redis`**: The [Redis](/langsmith/data-plane#redis) instance serving as the task queue for `langsmith-listener`. The listener enqueues tasks here and workers pull tasks from this queue.
* **`langsmith-operator`**: The `lgps` Kubernetes operator that reconciles underlying Kubernetes resources for `lgps` resources. Part of the data plane infrastructure.
Additional components may be present in your deployment depending on your configuration. For an overview, refer to [LangSmith Deployment components](/langsmith/components).
## Step 2. Enable debug logging
When troubleshooting issues, the first step is typically to enable debug-level logging to gather more detailed information about what's happening in your system.
### For control plane or data plane deployments
If you are experiencing issues with a control plane deployment (for example, `langsmith-host-backend`) or a data plane deployment (for example, `langsmith-listener`), reinstall the Helm chart with the `LOG_LEVEL=DEBUG` environment variable. Add the following to your `values.yaml` file:
```yaml theme={null}
extraEnv:
- name: LOG_LEVEL
value: DEBUG
```
### For Agent Server deployments
If the issue is with an individual Agent Server deployment:
1. Navigate to the **Deployments** tab in the [LangSmith UI](https://smith.langchain.com).
2. On a deployment's view, select **+ New Revision**.
3. Add a new environment variable `LOG_LEVEL` and set it to `DEBUG`.
You can also find debug logs in the UI on a deployment's view, click on **Server Logs** and select **Debug** for the **Log level: Info** dropdown.
### For widespread issues
If you are unsure where the issue originates, enable `DEBUG` logging everywhere (control plane, data plane, and all Agent Server deployments).
### Review application logs
Tail the logs of each pod to understand baseline behavior:
```bash theme={null}
kubectl logs -f
```
Then look for these log lines:
* **`langsmith-listener`**: `Reconciling projects...` (appears every 10 seconds)
* **`langsmith-operator`**: `Starting reconciliation` (appears periodically)
In a healthy deployment, you should not see any errors. All logs should appear normal and routine.
### Interpret debug logs
Look for the following problem indicators:
* Exceptions or stack traces.
* Error messages (the word `"ERROR"`).
* Unusual patterns that differ from normal operation.
Based on the errors you find:
* **Configuration issue**: If you suspect a configuration problem, raise the issue with the person who ran [`helm install`](/langsmith/kubernetes).
* **User code bug**: If you suspect a bug in user code (for example, the LangGraph OSS graph implementation), raise the issue with the owner of the Agent Server application who created the [`langgraph.json`](/langsmith/application-structure#configuration-file) file.
## Step 3. Describe deployments and pods
Describing Kubernetes resources reveals error events and statuses that may not appear in application logs. These errors are typically caused by configuration or infrastructure issues rather than application code bugs. Describing resources also shows their configuration (such as environment variables), which is helpful for debugging.
Run the following commands to describe your resources.
Describe a Kubernetes deployment:
```bash theme={null}
kubectl describe deployment
```
Describe a Kubernetes pod:
```bash theme={null}
kubectl describe pod
```
Describe an `lgps` resource (only relevant after creating an Agent Server):
```bash theme={null}
kubectl describe lgps
```
### Interpret results
Review the `Events:` section of the output and verify that everything is normal. Common issues that appear include:
* Failed liveness or readiness probes
* Image pull errors
* Resource constraints (CPU, memory)
* Volume mount issues
* Configuration errors
Make sure there are no error events and that all events indicate healthy operation.
## Additional resources
For more troubleshooting information, refer to:
* [Troubleshooting](/langsmith/troubleshooting): General troubleshooting guide with solutions to common issues.
* [Architectural overview](/langsmith/architectural-overview): Details on system architecture and component interactions.
* [Self-hosted documentation](/langsmith/self-hosted)
## Support
If you have followed these diagnostic steps and still need assistance, gather the following information before contacting support:
* Output from the [diagnostic steps](#step-1-understand-your-deployment).
* Your Helm chart configuration.
* Relevant error messages and logs.
* Description of what you were trying to do when the issue occurred.
Having this information ready will help the [support](mailto:support@langchain.dev) team diagnose and resolve your issue more quickly.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/diagnostics-self-hosted.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Implement distributed tracing
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/distributed-tracing
Sometimes, you need to trace a request across multiple services.
LangSmith supports distributed tracing out of the box, linking runs within a trace across services using context propagation headers (`langsmith-trace` and optional `baggage` for metadata/tags).
Example client-server setup:
* Trace starts on client
* Continues on server
## Distributed tracing in Python
```python theme={null}
# client.py
from langsmith.run_helpers import get_current_run_tree, traceable
import httpx
@traceable
async def my_client_function():
headers = {}
async with httpx.AsyncClient(base_url="...") as client:
if run_tree := get_current_run_tree():
# add langsmith-id to headers
headers.update(run_tree.to_headers())
return await client.post("/my-route", headers=headers)
```
Then the server (or other service) can continue the trace by handling the headers appropriately. If you are using an asgi app Starlette or FastAPI, you can connect the distributed trace using LangSmith's `TracingMiddleware`.
The `TracingMiddleware` class was added in `langsmith==0.1.133`.
Example using FastAPI:
```python theme={null}
from langsmith import traceable
from langsmith.middleware import TracingMiddleware
from fastapi import FastAPI, Request
app = FastAPI() # Or Flask, Django, or any other framework
app.add_middleware(TracingMiddleware)
@traceable
async def some_function():
...
@app.post("/my-route")
async def fake_route(request: Request):
return await some_function()
```
Or in Starlette:
```python theme={null}
from starlette.applications import Starlette
from starlette.middleware import Middleware
from langsmith.middleware import TracingMiddleware
routes = ...
middleware = [
Middleware(TracingMiddleware),
]
app = Starlette(..., middleware=middleware)
```
If you are using other server frameworks, you can always "receive" the distributed trace by passing the headers in through `langsmith_extra`:
```python theme={null}
# server.py
import langsmith as ls
from fastapi import FastAPI, Request
@ls.traceable
async def my_application():
...
app = FastAPI() # Or Flask, Django, or any other framework
@app.post("/my-route")
async def fake_route(request: Request):
# request.headers: {"langsmith-trace": "..."}
# as well as optional metadata/tags in `baggage`
with ls.tracing_context(parent=request.headers):
return await my_application()
```
The example above uses the `tracing_context` context manager. You can also directly specify the parent run context in the `langsmith_extra` parameter of a method wrapped with `@traceable`.
```python theme={null}
# ... same as above
@app.post("/my-route")
async def fake_route(request: Request):
# request.headers: {"langsmith-trace": "..."}
my_application(langsmith_extra={"parent": request.headers})
```
## Distributed tracing in TypeScript
Distributed tracing in TypeScript requires `langsmith` version `>=0.1.31`
First, we obtain the current run tree from the client and convert it to `langsmith-trace` and `baggage` header values, which we can pass to the server:
```typescript theme={null}
// client.mts
import { getCurrentRunTree, traceable } from "langsmith/traceable";
const client = traceable(
async () => {
const runTree = getCurrentRunTree();
return await fetch("...", {
method: "POST",
headers: runTree.toHeaders(),
}).then((a) => a.text());
},
{ name: "client" }
);
await client();
```
Then, the server converts the headers back to a run tree, which it uses to further continue the tracing.
To pass the newly created run tree to a traceable function, we can use the `withRunTree` helper, which will ensure the run tree is propagated within traceable invocations.
```typescript Express.JS theme={null}
// server.mts
import { RunTree } from "langsmith";
import { traceable, withRunTree } from "langsmith/traceable";
import express from "express";
import bodyParser from "body-parser";
const server = traceable(
(text: string) => `Hello from the server! Received "${text}"`,
{ name: "server" }
);
const app = express();
app.use(bodyParser.text());
app.post("/", async (req, res) => {
const runTree = RunTree.fromHeaders(req.headers);
const result = await withRunTree(runTree, () => server(req.body));
res.send(result);
});
```
```typescript Hono theme={null}
// server.mts
import { RunTree } from "langsmith";
import { traceable, withRunTree } from "langsmith/traceable";
import { Hono } from "hono";
const server = traceable(
(text: string) => `Hello from the server! Received "${text}"`,
{ name: "server" }
);
const app = new Hono();
app.post("/", async (c) => {
const body = await c.req.text();
const runTree = RunTree.fromHeaders(c.req.raw.headers);
const result = await withRunTree(runTree, () => server(body));
return c.body(result);
});
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/distributed-tracing.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Self-host LangSmith with Docker
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/docker
Self-hosting LangSmith is an add-on to the Enterprise Plan designed for our largest, most security-conscious customers. See our [pricing page](https://www.langchain.com/pricing) for more detail, and [contact our sales team](https://www.langchain.com/contact-sales) if you want to get a license key to trial LangSmith in your environment.
This guide provides instructions for running the **LangSmith platform** locally using Docker for development and testing purposes.
**For development/testing only**. Do not use Docker Compose for production. For production deployments, use [Kubernetes](/langsmith/kubernetes).
This page describes how to install the base [LangSmith platform](/langsmith/self-hosted#langsmith) for local testing. It does **not** include deployment management features. For more details, review the [self-hosted options](/langsmith/self-hosted).
Note that Docker Compose is limited to local development environments only and does not extend support to container services such as AWS Elastic Container Service, Azure Container Instances, and Google Cloud Run.
## Prerequisites
1. Ensure Docker is installed and running on your system. You can verify this by running:
```bash theme={null}
docker info
```
If you don't see any server information in the output, make sure Docker is installed correctly and launch the Docker daemon.
1. Recommended: At least 4 vCPUs, 16GB Memory available on your machine.
* You may need to tune resource requests/limits for all of our different services based off of organization size/usage
2. Disk Space: LangSmith can potentially require a lot of disk space. Ensure you have enough disk space available.
2. LangSmith License Key
1. You can get this from your LangChain representative. [Contact our sales team](https://www.langchain.com/contact-sales) for more information.
3. Api Key Salt
1. This is a secret key that you can generate. It should be a random string of characters.
2. You can generate this using the following command:
```bash theme={null}
openssl rand -base64 32
```
4. Egress to `https://beacon.langchain.com` (if not running in offline mode)
1. LangSmith requires egress to `https://beacon.langchain.com` for license verification and usage reporting. This is required for LangSmith to function properly. You can find more information on egress requirements in the [Egress](/langsmith/self-host-egress) section.
5. Configuration
1. There are several configuration options that you can set in the `.env` file. You can find more information on the available configuration options in the [Configuration](/langsmith/self-host-scale) section.
## Running via Docker Compose
The following explains how to run the LangSmith using Docker Compose. This is the most flexible way to run LangSmith without Kubernetes. The default configuration for Docker Compose is intended for local testing only and not for instances where any services are exposed to the public internet. **In production, we highly recommend using a secured Kubernetes environment.**
### 1. Fetch the LangSmith `docker-compose.yml` file
You can find the `docker-compose.yml` file and related files in the LangSmith SDK repository here: [*LangSmith Docker Compose File*](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/docker-compose/docker-compose.yaml)
Copy the `docker-compose.yml` file and all files in that directory from the LangSmith SDK to your project directory.
* Ensure that you copy the `users.xml` file as well.
### 2. Configure environment variables
1. Copy the `.env.example` file from the LangSmith SDK to your project directory and rename it to `.env`.
2. Configure the appropriate values in the `.env` file. You can find the available configuration options in the [Configuration](/langsmith/self-hosted) section.
You can also set these environment variables in the `docker-compose.yml` file directly or export them in your terminal. We recommend setting them in the `.env` file.
### 3. Start server
Start the LangSmith application by executing the following command in your terminal:
```bash theme={null}
docker-compose up
```
You can also run the server in the background by running:
```bash theme={null}
docker-compose up -d
```
### Validate your deployment:
1. Curl the exposed port of the `cli-langchain-frontend-1` container:
```bash theme={null}
curl localhost:1980/info{"version":"0.5.7","license_expiration_time":"2033-05-20T20:08:06","batch_ingest_config":{"scale_up_qsize_trigger":1000,"scale_up_nthreads_limit":16,"scale_down_nempty_trigger":4,"size_limit":100,"size_limit_bytes":20971520}}
```
2. Visit the exposed port of the `cli-langchain-frontend-1` container on your browser
The LangSmith UI should be visible/operational at `http://localhost:1980`
### Checking the logs
If, at any point, you want to check if the server is running and see the logs, run
```bash theme={null}
docker-compose logs
```
### Stopping the server
```bash theme={null}
docker-compose down
```
## Using LangSmith
Now that LangSmith is running, you can start using it to trace your code. You can find more information on how to use self-hosted LangSmith in the [self-hosted usage guide](/langsmith/self-hosted).
Your LangSmith instance is now running but may not be fully setup yet.
If you used one of the basic configs, you may have deployed a no-auth configuration. In this state, there is no authentication or concept of user accounts nor API keys and traces can be submitted directly without an API key so long as the hostname is passed to the LangChain tracer/LangSmith SDK.
As a next step, it is strongly recommended you work with your infrastructure administrators to:
* Setup DNS for your LangSmith instance to enable easier access
* Configure SSL to ensure in-transit encryption of traces submitted to LangSmith
* Configure LangSmith for [oauth authentication](/langsmith/self-host-sso) or [basic authentication](/langsmith/self-host-basic-auth) to secure your LangSmith instance
* Secure access to your Docker environment to limit access to only the LangSmith frontend and API
* Connect LangSmith to secured Postgres and Redis instances
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/docker.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Double texting
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/double-texting
**Prerequisites**
* [Agent Server](/langsmith/agent-server)
Many times users might interact with your graph in unintended ways.
For instance, a user may send one message and before the graph has finished running send a second message.
More generally, users may invoke the graph a second time before the first run has finished.
We call this "double texting".
[Enqueue](#enqueue-default) is the default double texting (multi-tasking) strategy when creating runs in the [Agent Server](/langsmith/agent-server).
Double texting is a feature of LangSmith Deployment. It is not available in the [LangGraph open source framework](/oss/python/langgraph/overview).
## Enqueue (default)
This option allows the current run to finish before processing any new input. Incoming requests are queued and executed sequentially once prior runs complete.
For configuring the enqueue double text option, refer to the [how-to guide](/langsmith/enqueue-concurrent).
## Reject
This option rejects any additional incoming runs while a current run is in progress and prevents concurrent execution or double texting.
For configuring the reject double text option, refer to the [how-to guide](/langsmith/reject-concurrent).
## Interrupt
This option halts the current execution and preserves the progress made up to the interruption point. The new user input is then inserted, and execution continues from that state.
When using this option, your graph must account for potential edge cases. For example, a tool call may have been initiated but not yet completed at the time of interruption. In these cases, handling or removing partial tool calls may be necessary to avoid unresolved operations.
For configuring the interrupt double text option, refer to the [how-to guide](/langsmith/interrupt-concurrent).
## Rollback
This option halts the current execution and reverts all progress—including the initial run input—before processing the new user input. The new input is treated as a fresh run, starting from the initial state.
For configuring the rollback double text option, refer to the [how-to guide](/langsmith/rollback-concurrent).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/double-texting.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Add encryption at-rest
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/encryption
Agent Server supports encryption at-rest for checkpoint data and metadata. You can choose between basic encryption with a single key or custom encryption for advanced use cases.
## Choosing an encryption method
| Method | What's encrypted | Use case |
| --------------------- | -------------------------------------------------------- | ------------------------------------------------------------ |
| **Basic encryption** | Checkpoint blobs, optionally JSON fields | Single static key, automatic AES encryption |
| **Custom encryption** | Checkpoints, threads, runs, assistants, crons and stores | Per-tenant keys, KMS integration, selective field encryption |
## Basic encryption
For simple encryption with a single static key, set the `LANGGRAPH_AES_KEY` environment variable. LangGraph will automatically encrypt checkpoint blobs using AES.
1. Add `pycryptodome` to your dependencies in `langgraph.json`:
```json theme={null}
{
"dependencies": [".", "pycryptodome"],
"graphs": {
"agent": "./agent.py:graph"
}
}
```
2. Set the `LANGGRAPH_AES_KEY` environment variable to a 16, 24, or 32-byte key (for AES-128, AES-192, or AES-256 respectively).
### Encrypting JSON fields
To also encrypt specific JSON fields, set `LANGGRAPH_AES_JSON_KEYS` to a comma-separated list of keys to encrypt:
```bash theme={null}
export LANGGRAPH_AES_KEY="your-16-24-or-32-byte-key"
export LANGGRAPH_AES_JSON_KEYS="api_key,secret_token,user_credentials"
```
These keys are encrypted wherever they appear in thread, assistant, run, cron, and store data.
Encrypted fields cannot be searched or filtered.
System fields cannot be encrypted: `langgraph_version`, `langgraph_api_version`, `langgraph_plan`, `langgraph_host`, `langgraph_api_url`, `langgraph_request_id`, `langgraph_auth_user_id`, `langgraph_auth_permissions`.
## Custom encryption
Requires Agent Server version 0.6.22+ and Python SDK version `langgraph-sdk>=0.3.1`.
Agent Server versions 0.5.34–0.6.21 included a pre-release version of custom encryption. Data encrypted with these versions will be corrupted when upgrading to 0.6.22+. Do not use custom encryption on these versions.
Use custom encryption when you need:
* **Per-tenant key isolation** — different encryption keys for different customers
* **KMS integration** — AWS KMS, Google Cloud KMS, or HashiCorp Vault for key management, rotation, and audit logging
* **Selective field encryption** — encrypt sensitive metadata fields while keeping others searchable
### How it works
1. [Configure](#configuration) the encryption module path in `langgraph.json`
2. [Define your encryption module](#defining-your-encryption-module) with handlers for blob and JSON encryption
3. [Pass encryption context](#passing-encryption-context) (like tenant ID) via the `X-Encryption-Context` header
4. LangGraph calls your handlers before storing and after retrieving data
For production deployments with key rotation and audit logging, see [Envelope encryption with AWS Encryption SDK](#envelope-encryption-with-aws-encryption-sdk).
### Configuration
Add your encryption module to `langgraph.json`:
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"encryption": {
"path": "./encryption.py:encryption"
}
}
```
If you're migrating from basic encryption, keep `LANGGRAPH_AES_KEY` configured. Custom encryption handles new writes while existing AES-encrypted data remains readable.
### Defining your encryption module
#### Blob encryption (checkpoints)
Blob handlers encrypt checkpoint data—the serialized state from graph execution. Here's a simplified example using per-tenant keys with [Fernet](https://cryptography.io/en/latest/fernet/) (a symmetric encryption scheme from the `cryptography` library):
```python theme={null}
import os
from cryptography.fernet import Fernet
from langgraph_sdk import Encryption, EncryptionContext
encryption = Encryption()
# In production, fetch from a secrets manager
TENANT_KEYS = {
"tenant-a": Fernet(os.environ["TENANT_A_KEY"]),
"tenant-b": Fernet(os.environ["TENANT_B_KEY"]),
}
def _get_fernet(ctx: EncryptionContext) -> Fernet:
tenant_id = ctx.metadata.get("tenant_id")
if not tenant_id or tenant_id not in TENANT_KEYS:
raise ValueError(f"Unknown tenant: {tenant_id}")
return TENANT_KEYS[tenant_id]
@encryption.encrypt.blob
async def encrypt_blob(ctx: EncryptionContext, data: bytes) -> bytes:
return _get_fernet(ctx).encrypt(data)
@encryption.decrypt.blob
async def decrypt_blob(ctx: EncryptionContext, data: bytes) -> bytes:
return _get_fernet(ctx).decrypt(data)
```
The `ctx.metadata` dict comes from the `X-Encryption-Context` header and is stored in plaintext alongside encrypted data, so the correct key is used on decryption.
#### JSON encryption (metadata)
JSON handlers encrypt structured data like thread metadata, assistant context, and run kwargs. Unlike blob encryption, you choose which fields to encrypt—keeping some unencrypted for search and filtering.
```python theme={null}
import json
import os
from cryptography.fernet import Fernet
from langgraph_sdk import Encryption, EncryptionContext
encryption = Encryption()
TENANT_KEYS = {
"tenant-a": Fernet(os.environ["TENANT_A_KEY"]),
"tenant-b": Fernet(os.environ["TENANT_B_KEY"]),
}
SKIP_FIELDS = {
"tenant_id", "owner",
"run_id", "thread_id", "graph_id", "assistant_id", "user_id", "checkpoint_id",
"source", "step", "parents", "run_attempt",
"langgraph_version", "langgraph_api_version", "langgraph_plan", "langgraph_host",
"langgraph_api_url", "langgraph_request_id", "langgraph_auth_user",
"langgraph_auth_user_id", "langgraph_auth_permissions",
}
ENCRYPTED_PREFIX = "encrypted:"
def _get_fernet(ctx: EncryptionContext) -> Fernet:
tenant_id = ctx.metadata.get("tenant_id")
if not tenant_id or tenant_id not in TENANT_KEYS:
raise ValueError(f"Unknown tenant: {tenant_id}")
return TENANT_KEYS[tenant_id]
@encryption.encrypt.json
async def encrypt_json(ctx: EncryptionContext, data: dict) -> dict:
fernet = _get_fernet(ctx)
result = {}
for k, v in data.items():
if k in SKIP_FIELDS or v is None:
result[k] = v
else:
value_json = json.dumps(v)
encrypted = fernet.encrypt(value_json.encode()).decode()
result[k] = ENCRYPTED_PREFIX + encrypted
return result
@encryption.decrypt.json
async def decrypt_json(ctx: EncryptionContext, data: dict) -> dict:
fernet = _get_fernet(ctx)
result = {}
for k, v in data.items():
if isinstance(v, str) and v.startswith(ENCRYPTED_PREFIX):
encrypted_value = v[len(ENCRYPTED_PREFIX):]
decrypted = fernet.decrypt(encrypted_value.encode()).decode()
result[k] = json.loads(decrypted)
else:
result[k] = v
return result
```
#### JSON encryption considerations
**Encrypted fields cannot be searched or filtered.** Design your metadata schema so that fields you need to query remain unencrypted.
**JSON encryptors must preserve key structure.** SQL JSONB merge operations work at the key level. Encryptors that change keys—whether by consolidating fields (e.g., moving sensitive data into `__encrypted__`) or by encrypting key names themselves—cause data loss during merges. Use per-key encryption: transform values in-place while preserving keys.
**Migration consideration:** Use a recognizable prefix or format in encrypted values so your decryptor can detect and skip unencrypted data. This allows you to encrypt additional fields in the future without re-encrypting existing records. The example above uses this pattern.
**Performance consideration:** Per-key encryption means one encryption call per field. If your encryption involves round-trips to an external service (e.g., KMS), this can significantly impact latency. Consider caching data keys locally or using envelope encryption where you encrypt a local data key with KMS and use it for multiple fields.
Common fields to leave **unencrypted** for search and filtering:
* User-defined fields for access control queries (e.g., `tenant_id`, `owner`)
* `run_id`, `thread_id`, `graph_id`, `assistant_id`, `user_id`, `checkpoint_id` — system-populated identifiers
* `source`, `step`, `parents`, `run_attempt` — system-populated execution state
* `langgraph_version`, `langgraph_api_version`, `langgraph_plan`, `langgraph_host`, `langgraph_api_url`, `langgraph_request_id`, `langgraph_auth_user`, `langgraph_auth_user_id`, `langgraph_auth_permissions` — system-populated platform metadata
#### What gets encrypted
**JSON handlers** (`@encryption.encrypt.json` / `@encryption.decrypt.json`):
* `thread.metadata`, `thread.values`
* `assistant.metadata`, `assistant.context`
* `run.metadata`, `run.kwargs`
* `cron.metadata`, `cron.payload`
* `store.value`
**Blob handlers** (`@encryption.encrypt.blob` / `@encryption.decrypt.blob`):
* Checkpoint blobs (graph execution state)
#### Model-specific handlers
Register different handlers for different model types using `@encryption.encrypt.json.assistant`, `@encryption.encrypt.json.run`, etc. Use `ctx.field` to vary behavior by field:
```python theme={null}
from langgraph_sdk import Encryption, EncryptionContext
encryption = Encryption()
@encryption.encrypt.json
async def default_encrypt(ctx: EncryptionContext, data: dict) -> dict:
return encrypt_with_skip_fields(data, SKIP_FIELDS)
@encryption.encrypt.json.assistant
async def encrypt_assistant(ctx: EncryptionContext, data: dict) -> dict:
if ctx.field == "context":
# Assistant context may contain API keys or system prompts—encrypt everything
return encrypt_with_skip_fields(data, skip_fields=set())
# Assistant metadata—skip system fields
return encrypt_with_skip_fields(data, SKIP_FIELDS)
@encryption.decrypt.json
async def default_decrypt(ctx: EncryptionContext, data: dict) -> dict:
return decrypt_encrypted_fields(data)
@encryption.decrypt.json.assistant
async def decrypt_assistant(ctx: EncryptionContext, data: dict) -> dict:
return decrypt_encrypted_fields(data)
```
Supported model types: `thread`, `assistant`, `run`, `cron`, `store`, `checkpoint`.
#### Deriving context from authentication
Instead of passing `X-Encryption-Context` explicitly, derive encryption context from the authenticated user:
```python theme={null}
from langgraph_sdk import Encryption, EncryptionContext
from starlette.authentication import BaseUser
encryption = Encryption()
@encryption.context
async def get_encryption_context(user: BaseUser, ctx: EncryptionContext) -> dict:
return {
**ctx.metadata,
"tenant_id": user["tenant_id"],
}
```
This handler runs once per request after authentication. The returned dict becomes `ctx.metadata` for all encryption operations in that request.
### Passing encryption context
Pass encryption context via the `X-Encryption-Context` header. The context is arbitrary data that you define—you control the schema and can include any fields your encryption logic needs (e.g., `tenant_id`, `key_version`). The context is available in your handlers as `ctx.metadata` and is stored in plaintext for use during decryption.
```python theme={null}
import base64
import json
from langgraph_sdk import get_client
encryption_context = base64.b64encode(
json.dumps({"tenant_id": "tenant-a"}).encode()
).decode()
client = get_client(url="http://localhost:2024")
result = await client.runs.wait(
thread_id=None,
assistant_id="agent",
input={"messages": [{"role": "user", "content": "Hello"}]},
headers={"X-Encryption-Context": encryption_context},
)
```
The encryption context is stored in plaintext. On decryption, it's automatically restored—callers don't need to pass the header when reading.
### Envelope encryption with AWS Encryption SDK
For production deployments on AWS, use the [AWS Encryption SDK](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/python.html) with AWS KMS, or an equivalent within your cloud provider. This approach:
* Handles envelope encryption automatically (no manual key packing)
* Provides key rotation and audit logging
* Binds ciphertext to encryption context (tenant isolation)
* Caches data keys locally to avoid repeated KMS calls, latency and rate limits
#### Complete example
```python theme={null}
import base64
import json
import os
import aws_encryption_sdk
from aws_encryption_sdk import (
CachingCryptoMaterialsManager,
CommitmentPolicy,
LocalCryptoMaterialsCache,
StrictAwsKmsMasterKeyProvider,
)
from langgraph_sdk import Encryption, EncryptionContext
encryption = Encryption()
# The SDK uses envelope encryption: one KMS API call generates a data key,
# then encrypts/decrypts locally. The cache reuses data keys across operations.
client = aws_encryption_sdk.EncryptionSDKClient(
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
)
key_provider = StrictAwsKmsMasterKeyProvider(key_ids=[os.environ["KMS_KEY_ARN"]])
cache = LocalCryptoMaterialsCache(capacity=100)
cmm = CachingCryptoMaterialsManager(
master_key_provider=key_provider,
cache=cache,
max_age=300.0,
max_messages_encrypted=100,
)
SKIP_FIELDS = {
"tenant_id", "owner",
"run_id", "thread_id", "graph_id", "assistant_id", "user_id", "checkpoint_id",
"source", "step", "parents", "run_attempt",
"langgraph_version", "langgraph_api_version", "langgraph_plan", "langgraph_host",
"langgraph_api_url", "langgraph_request_id", "langgraph_auth_user",
"langgraph_auth_user_id", "langgraph_auth_permissions",
}
ENCRYPTED_PREFIX = "encrypted:"
@encryption.encrypt.blob
async def encrypt_blob(ctx: EncryptionContext, data: bytes) -> bytes:
ciphertext, _ = client.encrypt(
source=data,
materials_manager=cmm,
encryption_context={"tenant_id": ctx.metadata["tenant_id"]},
)
return ciphertext
@encryption.decrypt.blob
async def decrypt_blob(ctx: EncryptionContext, data: bytes) -> bytes:
plaintext, _ = client.decrypt(source=data, key_provider=key_provider)
return plaintext
@encryption.encrypt.json
async def encrypt_json(ctx: EncryptionContext, data: dict) -> dict:
tenant_id = ctx.metadata["tenant_id"]
result = {}
for k, v in data.items():
if k in SKIP_FIELDS or v is None:
result[k] = v
else:
ciphertext, _ = client.encrypt(
source=json.dumps(v).encode(),
materials_manager=cmm,
encryption_context={"tenant_id": tenant_id},
)
result[k] = ENCRYPTED_PREFIX + base64.b64encode(ciphertext).decode()
return result
@encryption.decrypt.json
async def decrypt_json(ctx: EncryptionContext, data: dict) -> dict:
result = {}
for k, v in data.items():
if isinstance(v, str) and v.startswith(ENCRYPTED_PREFIX):
ciphertext = base64.b64decode(v[len(ENCRYPTED_PREFIX):])
plaintext, _ = client.decrypt(source=ciphertext, key_provider=key_provider)
result[k] = json.loads(plaintext.decode())
else:
result[k] = v
return result
```
The `encryption_context` is cryptographically bound to the ciphertext via KMS—decryption fails if the context doesn't match. The context is embedded in the ciphertext, so decrypt handlers don't need to reference `ctx.metadata`.
#### Key rotation
KMS handles master key rotation automatically. When you enable automatic rotation on your KMS key, old encrypted data keys can still be decrypted while new operations use the rotated key material. No re-encryption of existing data is required.
## Related
* [Custom authentication](/langsmith/custom-auth)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/encryption.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Enqueue concurrent
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/enqueue-concurrent
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](/langsmith/double-texting).
The guide covers the `enqueue` option for double texting, which adds the interruptions to a queue and executes them in the order they are received by the client. Below is a quick example of using the `enqueue` option.
Enqueue is the default double texting (multi-tasking) strategy when creating runs in the [Agent Server](/langsmith/agent-server).
## Setup
First, we will define a quick helper function for printing out JS and CURL model outputs (you can skip this if using Python):
```js theme={null}
function prettyPrint(m) {
const padded = " " + m['type'] + " ";
const sepLen = Math.floor((80 - padded.length) / 2);
const sep = "=".repeat(sepLen);
const secondSep = sep + (padded.length % 2 ? "=" : "");
console.log(`${sep}${padded}${secondSep}`);
console.log("\n\n");
console.log(m.content);
}
```
```bash theme={null}
# PLACE THIS IN A FILE CALLED pretty_print.sh
pretty_print() {
local type="$1"
local content="$2"
local padded=" $type "
local total_width=80
local sep_len=$(( (total_width - ${#padded}) / 2 ))
local sep=$(printf '=%.0s' $(eval "echo {1.."${sep_len}"}"))
local second_sep=$sep
if (( (total_width - ${#padded}) % 2 )); then
second_sep="${second_sep}="
fi
echo "${sep}${padded}${second_sep}"
echo
echo "$content"
}
```
Then, let's import our required packages and instantiate our client, assistant, and thread.
```python theme={null}
import asyncio
import httpx
from langchain_core.messages import convert_to_messages
from langgraph_sdk import get_client
client = get_client(url=)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
thread = await client.threads.create()
```
```js theme={null}
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
const thread = await client.threads.create();
```
```bash theme={null}
curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}'
```
## Create runs
Now let's start two runs, with the second interrupting the first one with a multitask strategy of "enqueue":
```python theme={null}
first_run = await client.runs.create(
thread["thread_id"],
assistant_id,
input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]},
)
second_run = await client.runs.create(
thread["thread_id"],
assistant_id,
input={"messages": [{"role": "user", "content": "what's the weather in nyc?"}]},
multitask_strategy="enqueue",
)
```
```js theme={null}
const firstRun = await client.runs.create(
thread["thread_id"],
assistantId,
input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]},
)
const secondRun = await client.runs.create(
thread["thread_id"],
assistantId,
input={"messages": [{"role": "user", "content": "what's the weather in nyc?"}]},
multitask_strategy="enqueue",
)
```
```bash theme={null}
curl --request POST \
--url >/threads//runs \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in sf?\"}]},
}" && curl --request POST \
--url >/threads//runs \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in nyc?\"}]},
\"multitask_strategy\": \"enqueue\"
}"
```
## View run results
Verify that the thread has data from both runs:
```python theme={null}
# wait until the second run completes
await client.runs.join(thread["thread_id"], second_run["run_id"])
state = await client.threads.get_state(thread["thread_id"])
for m in convert_to_messages(state["values"]["messages"]):
m.pretty_print()
```
```js theme={null}
await client.runs.join(thread["thread_id"], secondRun["run_id"]);
const state = await client.threads.getState(thread["thread_id"]);
for (const m of state["values"]["messages"]) {
prettyPrint(m);
}
```
```bash theme={null}
source pretty_print.sh && curl --request GET \
--url /threads//runs//join && \
curl --request GET --url /threads//state | \
jq -c '.values.messages[]' | while read -r element; do
type=$(echo "$element" | jq -r '.type')
content=$(echo "$element" | jq -r '.content | if type == "array" then tostring else . end')
pretty_print "$type" "$content"
done
```
Output:
```
================================ Human Message =================================
what's the weather in sf?
================================== Ai Message ==================================
[{'id': 'toolu_01Dez1sJre4oA2Y7NsKJV6VT', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
Tool Calls:
tavily_search_results_json (toolu_01Dez1sJre4oA2Y7NsKJV6VT)
Call ID: toolu_01Dez1sJre4oA2Y7NsKJV6VT
Args:
query: weather in san francisco
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://www.accuweather.com/en/us/san-francisco/94103/weather-forecast/347629", "content": "Get the current and future weather conditions for San Francisco, CA, including temperature, precipitation, wind, air quality and more. See the hourly and 10-day outlook, radar maps, alerts and allergy information."}]
================================== Ai Message ==================================
According to AccuWeather, the current weather conditions in San Francisco are:
Temperature: 57°F (14°C)
Conditions: Mostly Sunny
Wind: WSW 10 mph
Humidity: 72%
The forecast for the next few days shows partly sunny skies with highs in the upper 50s to mid 60s F (14-18°C) and lows in the upper 40s to low 50s F (9-11°C). Typical mild, dry weather for San Francisco this time of year.
Some key details from the AccuWeather forecast:
Today: Mostly sunny, high of 62°F (17°C)
Tonight: Partly cloudy, low of 49°F (9°C)
Tomorrow: Partly sunny, high of 59°F (15°C)
Saturday: Mostly sunny, high of 64°F (18°C)
Sunday: Partly sunny, high of 61°F (16°C)
So in summary, expect seasonable spring weather in San Francisco over the next several days, with a mix of sun and clouds and temperatures ranging from the upper 40s at night to the low 60s during the days. Typical dry conditions with no rain in the forecast.
================================ Human Message =================================
what's the weather in nyc?
================================== Ai Message ==================================
[{'text': 'Here are the current weather conditions and forecast for New York City:', 'type': 'text'}, {'id': 'toolu_01FFft5Sx9oS6AdVJuRWWcGp', 'input': {'query': 'weather in new york city'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
Tool Calls:
tavily_search_results_json (toolu_01FFft5Sx9oS6AdVJuRWWcGp)
Call ID: toolu_01FFft5Sx9oS6AdVJuRWWcGp
Args:
query: weather in new york city
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://www.weatherapi.com/", "content": "{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.71, 'lon': -74.01, 'tz_id': 'America/New_York', 'localtime_epoch': 1718734479, 'localtime': '2024-06-18 14:14'}, 'current': {'last_updated_epoch': 1718733600, 'last_updated': '2024-06-18 14:00', 'temp_c': 29.4, 'temp_f': 84.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 158, 'wind_dir': 'SSE', 'pressure_mb': 1025.0, 'pressure_in': 30.26, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 63, 'cloud': 0, 'feelslike_c': 31.3, 'feelslike_f': 88.3, 'windchill_c': 28.3, 'windchill_f': 82.9, 'heatindex_c': 29.6, 'heatindex_f': 85.3, 'dewpoint_c': 18.4, 'dewpoint_f': 65.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 7.0, 'gust_mph': 16.5, 'gust_kph': 26.5}}"}]
================================== Ai Message ==================================
According to the weather data from WeatherAPI:
Current Conditions in New York City (as of 2:00 PM local time):
* Temperature: 85°F (29°C)
* Conditions: Sunny
* Wind: 2 mph (4 km/h) from the SSE
* Humidity: 63%
* Heat Index: 85°F (30°C)
The forecast shows sunny and warm conditions persisting over the next few days:
Today: Sunny, high of 85°F (29°C)
Tonight: Clear, low of 68°F (20°C)
Tomorrow: Sunny, high of 88°F (31°C)
Thursday: Mostly sunny, high of 90°F (32°C)
Friday: Partly cloudy, high of 87°F (31°C)
So New York City is experiencing beautiful sunny weather with seasonably warm temperatures in the mid-to-upper 80s Fahrenheit (around 30°C). Humidity is moderate in the 60% range. Overall, ideal late spring/early summer conditions for being outdoors in the city over the next several days.
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/enqueue-concurrent.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Environment variables
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/env-var
The Agent Server supports specific environment variables for configuring a deployment.
## `BG_JOB_ISOLATED_LOOPS`
Set `BG_JOB_ISOLATED_LOOPS` to `True` to execute background runs in an isolated event loop separate from the serving API event loop.
This environment variable should be set to `True` if the implementation of a graph/node contains synchronous code. In this situation, the synchronous code will block the serving API event loop, which may cause the API to be unavailable. A symptom of an unavailable API is continuous application restarts due to failing health checks.
Defaults to `False`.
## `BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS`
Specifies, in seconds, how long the server will wait for background jobs to finish after the queue receives a shutdown signal. After this period, the server will force termination. Defaults to `180` seconds. The maximum value is `3600` seconds. Set this to ensure jobs have enough time to complete cleanly during shutdown. Added in `langgraph-api==0.2.16`.
## `BG_JOB_TIMEOUT_SECS`
The timeout of a background run can be increased. However, the infrastructure for a Cloud deployment enforces a 1 hour timeout limit for API requests. This means the connection between client and server will timeout after 1 hour. This is not configurable.
A background run can execute for longer than 1 hour, but a client must reconnect to the server (e.g. join stream via `POST /threads/{thread_id}/runs/{run_id}/stream`) to retrieve output from the run if the run is taking longer than 1 hour.
Defaults to `3600`.
## `CORS_ALLOW_ORIGINS`
Set `CORS_ALLOW_ORIGINS` to specify allowed origins.
* Example for allowing a single origin: `CORS_ALLOW_ORIGINS=https://example.com`
* Example for allowing multiple origins: `CORS_ALLOW_ORIGINS=https://example.com,https://app.example.com`
For advanced CORS configuration, see [how to add custom CORS configuration](/langsmith/cli#customizing-http-middleware-and-headers).
Defaults to `*` (all origins).
## `DD_API_KEY`
Specify `DD_API_KEY` (your [Datadog API Key](https://docs.datadoghq.com/account_management/api-app-keys/)) to automatically enable Datadog tracing for the deployment. Specify other [`DD_*` environment variables](https://ddtrace.readthedocs.io/en/stable/configuration.html) to configure the tracing instrumentation.
If `DD_API_KEY` is specified, the application process is wrapped in the [`ddtrace-run` command](https://ddtrace.readthedocs.io/en/stable/installation_quickstart.html). Other `DD_*` environment variables (e.g. `DD_SITE`, `DD_ENV`, `DD_SERVICE`, `DD_TRACE_ENABLED`) are typically needed to properly configure the tracing instrumentation. See [`DD_*` environment variables](https://ddtrace.readthedocs.io/en/stable/configuration.html) for more details. You can enable `DD_TRACE_DEBUG=true` and set `DD_LOG_LEVEL=debug` to troubleshoot.
Enabling `DD_API_KEY` (and thus `ddtrace-run`) can override or interfere with other auto-instrumentation solutions (such as OpenTelemetry) that you may have instrumented into your application code.
## `LANGGRAPH_POSTGRES_POOL_MAX_SIZE`
Beginning with langgraph-api version `0.2.12`, the maximum size of the Postgres connection pool (per replica) can be controlled using the `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` environment variable. By setting this variable, you can determine the upper bound on the number of simultaneous connections the server will establish with the Postgres database.
For example, if a deployment is scaled up to 10 replicas and `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` is configured to `150`, then up to `1500` connections to Postgres can be established. This is particularly useful for deployments where database resources are limited (or more available) or where you need to tune connection behavior for performance or scaling reasons.
Defaults to `150` connections.
## `LANGSMITH_API_KEY`
For deployments with [self-hosted LangSmith](/langsmith/self-hosted) only.
To send traces to a self-hosted LangSmith instance, set `LANGSMITH_API_KEY` to an API key created from the self-hosted instance.
## `LANGSMITH_ENDPOINT`
For deployments with [self-hosted LangSmith](/langsmith/self-hosted) only.
To send traces to a self-hosted LangSmith instance, set `LANGSMITH_ENDPOINT` to the hostname of the self-hosted instance.
## `LANGSMITH_TRACING`
Set `LANGSMITH_TRACING` to `false` to disable tracing to LangSmith.
For selective tracing control based on runtime conditions (such as per-client requirements or data sensitivity), see [Conditional tracing](/langsmith/conditional-tracing).
Defaults to `true`.
## `LOG_COLOR`
This is mainly relevant in the context of using the dev server via the `langgraph dev` command. Set `LOG_COLOR` to `true` to enable ANSI-colored console output when using the default console renderer. Disabling color output by setting this variable to `false` produces monochrome logs. Defaults to `true`.
## `LOG_LEVEL`
Configure [log level](https://docs.python.org/3/library/logging.html#logging-levels). Defaults to `INFO`.
## `LOG_JSON`
Set `LOG_JSON` to `true` to render all log messages as JSON objects using the configured `JSONRenderer`. This produces structured logs that can be easily parsed or ingested by log management systems. Defaults to `false`.
## `MOUNT_PREFIX`
**Only Allowed in Self-Hosted Deployments**
The `MOUNT_PREFIX` environment variable is only allowed in Self-Hosted Deployment models, LangSmith SaaS will not allow this environment variable.
Set `MOUNT_PREFIX` to serve the Agent Server under a specific path prefix. This is useful for deployments where the server is behind a reverse proxy or load balancer that requires a specific path prefix.
For example, if the server is to be served under `https://example.com/langgraph`, set `MOUNT_PREFIX` to `/langgraph`.
## `N_JOBS_PER_WORKER`
Number of jobs per worker for the Agent Server task queue. Defaults to `10`.
## `LS_APM_OTEL_ENABLED`
To configure configure OpenTelemetry APM tracing for your deployment, set `LS_APM_OTEL_ENABLED` to `true` and `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` or `OTEL_EXPORTER_OTLP_ENDPOINT` to the target trace ingestion endpoint. Note that both `LS_APM_OTEL_ENABLED` and one of the other two export endpoints are required to activate OpenTelemetry APM tracing in server versions later than `0.7.17`.
Specify other [`OTEL_*` environment variables](https://opentelemetry.io/docs/collector/configuration/) to configure tracing, logging, and other instrumentation.
```shell theme={null}
# If you set LS_APM_OTEL_ENABLED AND (OTEL_EXPORTER_OTLP_TRACES_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT),
# the server starts with OpenTelemetry instrumentation enabled.
LS_APM_OTEL_ENABLED=true
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=
OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net
OTEL_SERVICE_NAME=MY_LANGSMITH_DEPLOYMENT
OTEL_EXPORTER_OTLP_HEADERS=api-key=
LANGSMITH_OTEL_ENABLED=true
# Common OTEL settings
OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT=4095
OTEL_EXPORTER_OTLP_COMPRESSION=gzip
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta
OTEL_PYTHON_EXCLUDED_URLS=/metrics,/ok,/info
# Optional: OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
```
For example, to submit OpenTelemetry traces to [New Relic's US region](https://docs.newrelic.com/docs/opentelemetry/best-practices/opentelemetry-otlp/), set the following:
```shell theme={null}
LS_APM_OTEL_ENABLED=true
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://otlp.nr-data.net/v1/traces
OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net
OTEL_EXPORTER_OTLP_HEADERS=api-key=
```
OTel APM tracing was added in Agent Server version `0.5.32` and is currently in Alpha.
## `POSTGRES_URI_CUSTOM`
**Only for Hybrid and Self-Hosted**
Custom Postgres instances are only available for [Hybrid](/langsmith/hybrid) and [Self-Hosted](/langsmith/self-hosted) deployments.
Specify `POSTGRES_URI_CUSTOM` to use a custom Postgres instance. The value of `POSTGRES_URI_CUSTOM` must be a valid [Postgres connection URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS).
Postgres:
* Version 15.8 or higher.
* An initial database must be present and the connection URI must reference the database.
Control Plane Functionality:
* If `POSTGRES_URI_CUSTOM` is specified, the control plane will not provision a database for the server.
* If `POSTGRES_URI_CUSTOM` is removed, the control plane will not provision a database for the server and will not delete the externally managed Postgres instance.
* If `POSTGRES_URI_CUSTOM` is removed, deployment of the revision will not succeed. Once `POSTGRES_URI_CUSTOM` is specified, it must always be set for the lifecycle of the deployment.
* If the deployment is deleted, the control plane will not delete the externally managed Postgres instance.
* The value of `POSTGRES_URI_CUSTOM` can be updated. For example, a password in the URI can be updated.
Database Connectivity:
* The custom Postgres instance must be accessible by the Agent Server. The user is responsible for ensuring connectivity.
## `REDIS_CLUSTER`
This feature is in Alpha.
**Only Allowed in Self-Hosted Deployments**
Redis Cluster mode is only available in Self-Hosted Deployment models, LangSmith SaaS will provision a redis instance for you by default.
Set `REDIS_CLUSTER` to `True` to enable Redis Cluster mode. When enabled, the system will connect to Redis using cluster mode. This is useful when connecting to a Redis Cluster deployment.
Defaults to `False`.
## `REDIS_KEY_PREFIX`
**Available in API Server version 0.1.9+**
This environment variable is supported in API Server version 0.1.9 and above.
Specify a prefix for Redis keys. This allows multiple Agent Server instances to share the same Redis instance by using different key prefixes.
Defaults to `''`.
## `REDIS_URI_CUSTOM`
**Only for Hybrid and Self-Hosted**
Custom Redis instances are only available for [Hybrid](/langsmith/hybrid) and [Self-Hosted](/langsmith/self-hosted) deployments.
Specify `REDIS_URI_CUSTOM` to use a custom Redis instance. The value of `REDIS_URI_CUSTOM` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
## `REDIS_MAX_CONNECTIONS`
The maximum size of the Redis connection pool (per replica) can be controlled using the `REDIS_MAX_CONNECTIONS` environment variable. By setting this variable, you can determine the upper bound on the number of simultaneous connections the server will establish with the Redis instance.
For example, if a deployment is scaled up to 10 replicas and `REDIS_MAX_CONNECTIONS` is configured to `150`, then up to `1500` connections to Redis can be established.
Defaults to `2000`.
## `RESUMABLE_STREAM_TTL_SECONDS`
Time-to-live in seconds for resumable stream data in Redis.
When a run is created and the output is streamed, the stream can be configured to be resumable (e.g. `stream_resumable=True`). If a stream is resumable, output from the stream is temporarily stored in Redis. The TTL for this data can be configured by setting `RESUMABLE_STREAM_TTL_SECONDS`.
See the [Python](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.client.RunsClient.stream) and [JS/TS](https://langchain-ai.github.io/langgraphjs/reference/classes/sdk_client.RunsClient.html#stream) SDKs for more details on how to implement resumable streams.
Defaults to `120` seconds.
Setting a very high value for `RESUMABLE_STREAM_TTL_SECONDS` can result in substantial Redis memory usage when there are many concurrent runs with large or frequent streaming output. Set this value to the minimum value to enable recovery during network interruptions and prefer checkpointing for long term durability and execution snapshotting.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/env-var.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Evaluate a chatbot
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluate-chatbot-tutorial
In this guide we will set up evaluations for a chatbot. These allow you to measure how well your application is performing over a set of data. Being able to get this insight quickly and reliably will allow you to iterate with confidence.
At a high level, in this tutorial we will:
* *Create an initial golden dataset to measure performance*
* *Define metrics to use to measure performance*
* *Run evaluations on a few different prompts or models*
* *Compare results manually*
* *Track results over time*
* *Set up automated testing to run in CI/CD*
For more information on the evaluation workflows LangSmith supports, check out the [how-to guides](/langsmith/evaluation), or see the reference docs for [evaluate](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client.evaluate) and its asynchronous [aevaluate](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client.aevaluate) counterpart.
Lots to cover, let's dive in!
## Setup
First install the required dependencies for this tutorial. We happen to use OpenAI, but LangSmith can be used with any model:
```bash pip theme={null}
pip install -U langsmith openai
```
```bash uv theme={null}
uv add langsmith openai
```
And set environment variables to enable LangSmith tracing:
```bash theme={null}
export LANGSMITH_TRACING="true"
export LANGSMITH_API_KEY=""
export OPENAI_API_KEY=""
```
## Create a dataset
The first step when getting ready to test and evaluate your application is to define the datapoints you want to evaluate. There are a few aspects to consider here:
* What should the schema of each datapoint be?
* How many datapoints should I gather?
* How should I gather those datapoints?
**Schema:** Each datapoint should consist of, at the very least, the inputs to the application. If you are able, it is also very helpful to define the expected outputs - these represent what you would expect a properly functioning application to output. Often times you cannot define the perfect output - that's okay! Evaluation is an iterative process. Sometimes you may also want to define more information for each example - like the expected documents to fetch in RAG, or the expected steps to take as an agent. LangSmith datasets are very flexible and allow you to define arbitrary schemas.
**How many:** There's no hard and fast rule for how many you should gather. The main thing is to make sure you have proper coverage of edge cases you may want to guard against. Even 10-50 examples can provide a lot of value! Don't worry about getting a large number to start - you can (and should) always add over time!
**How to get:** This is maybe the trickiest part. Once you know you want to gather a dataset... how do you actually go about it? For most teams that are starting a new project, we generally see them start by collecting the first 10-20 datapoints by hand. After starting with these datapoints, these datasets are generally *living* constructs and grow over time. They generally grow after seeing how real users will use your application, seeing the pain points that exist, and then moving a few of those datapoints into this set. There are also methods like synthetically generating data that can be used to augment your dataset. To start, we recommend not worrying about those and just hand labeling \~10-20 examples.
Once you've got your dataset, there are a few different ways to upload them to LangSmith. For this tutorial, we will use the client, but you can also upload via the UI (or even create them in the UI).
For this tutorial, we will create 5 datapoints to evaluate on. We will be evaluating a question-answering application. The input will be a question, and the output will be an answer. Since this is a question-answering application, we can define the expected answer. Let's show how to create and upload this dataset to LangSmith!
```python theme={null}
from langsmith import Client
client = Client()
# Define dataset: these are your test cases
dataset_name = "QA Example Dataset"
dataset = client.create_dataset(dataset_name)
client.create_examples(
dataset_id=dataset.id,
examples=[
{
"inputs": {"question": "What is LangChain?"},
"outputs": {"answer": "A framework for building LLM applications"},
},
{
"inputs": {"question": "What is LangSmith?"},
"outputs": {"answer": "A platform for observing and evaluating LLM applications"},
},
{
"inputs": {"question": "What is OpenAI?"},
"outputs": {"answer": "A company that creates Large Language Models"},
},
{
"inputs": {"question": "What is Google?"},
"outputs": {"answer": "A technology company known for search"},
},
{
"inputs": {"question": "What is Mistral?"},
"outputs": {"answer": "A company that creates Large Language Models"},
}
]
)
```
Now, if we go the LangSmith UI and look for `QA Example Dataset` in the `Datasets & Testing` page, when we click into it we should see that we have five new examples.
## Define metrics
After creating our dataset, we can now define some metrics to evaluate our responses on. Since we have an expected answer, we can compare to that as part of our evaluation. However, we do not expect our application to output those **exact** answers, but rather something that is similar. This makes our evaluation a little trickier.
In addition to evaluating correctness, let's also make sure our answers are short and concise. This will be a little easier - we can define a simple Python function to measure the length of the response.
Let's go ahead and define these two metrics.
For the first, we will use an LLM to **judge** whether the output is correct (with respect to the expected output). This **LLM-as-a-judge** is relatively common for cases that are too complex to measure with a simple function. We can define our own prompt and LLM to use for evaluation here:
```python theme={null}
import openai
from langsmith import wrappers
openai_client = wrappers.wrap_openai(openai.OpenAI())
eval_instructions = "You are an expert professor specialized in grading students' answers to questions."
def correctness(inputs: dict, outputs: dict, reference_outputs: dict) -> bool:
user_content = f"""You are grading the following question:
{inputs['question']}
Here is the real answer:
{reference_outputs['answer']}
You are grading the following predicted answer:
{outputs['response']}
Respond with CORRECT or INCORRECT:
Grade:"""
response = openai_client.chat.completions.create(
model="gpt-4.1-mini",
temperature=0,
messages=[
{"role": "system", "content": eval_instructions},
{"role": "user", "content": user_content},
],
).choices[0].message.content
return response == "CORRECT"
```
For evaluating the length of the response, this is a lot easier! We can just define a simple function that checks whether the actual output is less than 2x the length of the expected result.
```python theme={null}
def concision(outputs: dict, reference_outputs: dict) -> bool:
return int(len(outputs["response"]) < 2 * len(reference_outputs["answer"]))
```
## Run evaluations
Great! So now how do we run evaluations? Now that we have a dataset and evaluators, all that we need is our application! We will build a simple application that just has a system message with instructions on how to respond and then passes it to the LLM. We will build this using the OpenAI SDK directly:
```python theme={null}
default_instructions = "Respond to the users question in a short, concise manner (one short sentence)."
def my_app(question: str, model: str = "gpt-4.1-mini", instructions: str = default_instructions) -> str:
return openai_client.chat.completions.create(
model=model,
temperature=0,
messages=[
{"role": "system", "content": instructions},
{"role": "user", "content": question},
],
).choices[0].message.content
```
Before running this through LangSmith evaluations, we need to define a simple wrapper that maps the input keys from our dataset to the function we want to call, and then also maps the output of the function to the output key we expect.
```python theme={null}
def ls_target(inputs: str) -> dict:
return {"response": my_app(inputs["question"])}
```
Great! Now we're ready to run an evaluation. Let's do it!
```python theme={null}
experiment_results = client.evaluate(
ls_target, # Your AI system
data=dataset_name, # The data to predict and grade over
evaluators=[concision, correctness], # The evaluators to score the results
experiment_prefix="openai-4o-mini", # A prefix for your experiment names to easily identify them
)
```
This will output a URL. If we click on it, we should see results of our evaluation!
If we go back to the dataset page and select the `Experiments` tab, we can now see a summary of our one run!
Let's now try it out with a different model! Let's try `gpt-4-turbo`
```python theme={null}
def ls_target_v2(inputs: str) -> dict:
return {"response": my_app(inputs["question"], model="gpt-4-turbo")}
experiment_results = client.evaluate(
ls_target_v2,
data=dataset_name,
evaluators=[concision, correctness],
experiment_prefix="openai-4-turbo",
)
```
And now let's use GPT-4 but also update the prompt to be a bit more strict in requiring the answer to be short.
```python theme={null}
instructions_v3 = "Respond to the users question in a short, concise manner (one short sentence). Do NOT use more than ten words."
def ls_target_v3(inputs: str) -> dict:
response = my_app(
inputs["question"],
model="gpt-4-turbo",
instructions=instructions_v3
)
return {"response": response}
experiment_results = client.evaluate(
ls_target_v3,
data=dataset_name,
evaluators=[concision, correctness],
experiment_prefix="strict-openai-4-turbo",
)
```
If we go back to the `Experiments` tab on the datasets page, we should see that all three runs now show up!
## Comparing results
Awesome, we've evaluated three different runs. But how can we compare results? The first way we can do this is just by looking at the runs in the `Experiments` tab. If we do that, we can see a high level view of the metrics for each run:
Great! So we can tell that GPT-4 is better than GPT-3.5 at knowing who companies are, and we can see that the strict prompt helped a lot with the length. But what if we want to explore in more detail?
In order to do that, we can select all the runs we want to compare (in this case all three) and open them up in a comparison view. We immediately see all three tests side by side. Some of the cells are color coded - this is showing a regression of *a certain metric* compared to *a certain baseline*. We automatically choose defaults for the baseline and metric, but you can change those yourself. You can also choose which columns and which metrics you see by using the `Display` control. You can also automatically filter to only see the runs that have improvements/regressions by clicking on the icons at the top.
If we want to see more information, we can also select the `Expand` button that appears when hovering over a row to open up a side panel with more detailed information:
## Set up automated testing to run in CI/CD
Now that we've run this in a one-off manner, we can set it to run in an automated fashion. We can do this pretty easily by just including it as a pytest file that we run in CI/CD. As part of this, we can either just log the results OR set up some criteria to determine if it passes or not. For example, if I wanted to ensure that we always got at least 80% of generated responses passing the `length` check, we could set that up with a test like:
```python theme={null}
def test_length_score() -> None:
"""Test that the length score is at least 80%."""
experiment_results = evaluate(
ls_target, # Your AI system
data=dataset_name, # The data to predict and grade over
evaluators=[concision, correctness], # The evaluators to score the results
)
# This will be cleaned up in the next release:
feedback = client.list_feedback(
run_ids=[r.id for r in client.list_runs(project_name=experiment_results.experiment_name)],
feedback_key="concision"
)
scores = [f.score for f in feedback]
assert sum(scores) / len(scores) >= 0.8, "Aggregate score should be at least .8"
```
## Track results over time
Now that we've got these experiments running in an automated fashion, we want to track these results over time. We can do this from the overall `Experiments` tab in the datasets page. By default, we show evaluation metrics over time (highlighted in red). We also automatically track git metrics, to easily associate it with the branch of your code (highlighted in yellow).
## Conclusion
That's it for this tutorial!
We've gone over how to create an initial test set, define some evaluation metrics, run experiments, compare them manually, set up CI/CD, and track results over time. Hopefully this can help you iterate with confidence.
This is just the start. As mentioned earlier, evaluation is an ongoing process. For example - the datapoints you will want to evaluate on will likely continue to change over time. There are many types of evaluators you may wish to explore. For information on this, check out the [how-to guides](/langsmith/evaluation).
Additionally, there are other ways to evaluate data besides in this "offline" manner (e.g. you can evaluate production data). For more information on online evaluation, check out [this guide](/langsmith/online-evaluations-llm-as-judge).
## Reference code
```python theme={null}
import openai
from langsmith import Client, wrappers
# Application code
openai_client = wrappers.wrap_openai(openai.OpenAI())
default_instructions = "Respond to the users question in a short, concise manner (one short sentence)."
def my_app(question: str, model: str = "gpt-4.1-mini", instructions: str = default_instructions) -> str:
return openai_client.chat.completions.create(
model=model,
temperature=0,
messages=[
{"role": "system", "content": instructions},
{"role": "user", "content": question},
],
).choices[0].message.content
client = Client()
# Define dataset: these are your test cases
dataset_name = "QA Example Dataset"
dataset = client.create_dataset(dataset_name)
client.create_examples(
dataset_id=dataset.id,
examples=[
{
"inputs": {"question": "What is LangChain?"},
"outputs": {"answer": "A framework for building LLM applications"},
},
{
"inputs": {"question": "What is LangSmith?"},
"outputs": {"answer": "A platform for observing and evaluating LLM applications"},
},
{
"inputs": {"question": "What is OpenAI?"},
"outputs": {"answer": "A company that creates Large Language Models"},
},
{
"inputs": {"question": "What is Google?"},
"outputs": {"answer": "A technology company known for search"},
},
{
"inputs": {"question": "What is Mistral?"},
"outputs": {"answer": "A company that creates Large Language Models"},
}
]
)
# Define evaluators
eval_instructions = "You are an expert professor specialized in grading students' answers to questions."
def correctness(inputs: dict, outputs: dict, reference_outputs: dict) -> bool:
user_content = f"""You are grading the following question:
{inputs['question']}
Here is the real answer:
{reference_outputs['answer']}
You are grading the following predicted answer:
{outputs['response']}
Respond with CORRECT or INCORRECT:
Grade:"""
response = openai_client.chat.completions.create(
model="gpt-4.1-mini",
temperature=0,
messages=[
{"role": "system", "content": eval_instructions},
{"role": "user", "content": user_content},
],
).choices[0].message.content
return response == "CORRECT"
def concision(outputs: dict, reference_outputs: dict) -> bool:
return int(len(outputs["response"]) < 2 * len(reference_outputs["answer"]))
# Run evaluations
def ls_target(inputs: str) -> dict:
return {"response": my_app(inputs["question"])}
experiment_results_v1 = client.evaluate(
ls_target, # Your AI system
data=dataset_name, # The data to predict and grade over
evaluators=[concision, correctness], # The evaluators to score the results
experiment_prefix="openai-4o-mini", # A prefix for your experiment names to easily identify them
)
def ls_target_v2(inputs: str) -> dict:
return {"response": my_app(inputs["question"], model="gpt-4-turbo")}
experiment_results_v2 = client.evaluate(
ls_target_v2,
data=dataset_name,
evaluators=[concision, correctness],
experiment_prefix="openai-4-turbo",
)
instructions_v3 = "Respond to the users question in a short, concise manner (one short sentence). Do NOT use more than ten words."
def ls_target_v3(inputs: str) -> dict:
response = my_app(
inputs["question"],
model="gpt-4-turbo",
instructions=instructions_v3
)
return {"response": response}
experiment_results_v3 = client.evaluate(
ls_target_v3,
data=dataset_name,
evaluators=[concision, correctness],
experiment_prefix="strict-openai-4-turbo",
)
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluate-chatbot-tutorial.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Evaluate a complex agent
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluate-complex-agent
[Agent evaluation](/langsmith/evaluation-concepts#agents) | [Evaluators](/langsmith/evaluation-concepts#evaluators) | [LLM-as-judge evaluators](/langsmith/evaluation-concepts#llm-as-judge)
In this tutorial, we'll build a customer support bot that helps users navigate a digital music store. Then, we'll go through the three most effective types of evaluations to run on chat bots:
* [Final response](/langsmith/evaluation-concepts#evaluating-an-agents-final-response): Evaluate the agent's final response.
* [Trajectory](/langsmith/trajectory-evals): Evaluate whether the agent took the expected path (e.g., of tool calls) to arrive at the final answer.
* [Single step](/langsmith/evaluation-concepts#evaluating-a-single-step-of-an-agent): Evaluate any agent step in isolation (e.g., whether it selects the appropriate first tool for a given step).
We'll build our agent using [LangGraph](https://github.com/langchain-ai/langgraph), but the techniques and LangSmith functionality shown here are framework-agnostic.
## Setup
### Configure the environment
Let's install the required dependencies:
```bash pip theme={null}
pip install -U langgraph "langchain[openai]"
```
```bash uv theme={null}
uv add langgraph "langchain[openai]"
```
Let's set up environment variables for OpenAI and [LangSmith](https://smith.langchain.com):
```python theme={null}
import getpass
import os
def _set_env(var: str) -> None:
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"Set {var}: ")
os.environ["LANGSMITH_TRACING"] = "true"
_set_env("LANGSMITH_API_KEY")
_set_env("OPENAI_API_KEY")
```
### Download the database
We will create a SQLite database for this tutorial. SQLite is a lightweight database that is easy to set up and use. We will load the `chinook` database, which is a sample database that represents a digital media store. Find more information about the database [here](https://www.sqlitetutorial.net/sqlite-sample-database/).
For convenience, we have hosted the database in a public GCS bucket:
```python theme={null}
import requests
url = "https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db"
response = requests.get(url)
if response.status_code == 200:
# Open a local file in binary write mode
with open("chinook.db", "wb") as file:
# Write the content of the response (the file) to the local file
file.write(response.content)
print("File downloaded and saved as Chinook.db")
else:
print(f"Failed to download the file. Status code: {response.status_code}")
```
Here's a sample of the data in the db:
```python theme={null}
import sqlite3
# ... database connection and query code
```
```
[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]
```
And here's the database schema (image from [https://github.com/lerocha/chinook-database](https://github.com/lerocha/chinook-database)):
### Define the customer support agent
We'll create a [LangGraph](https://langchain-ai.github.io/langgraph/) agent with limited access to our database. For demo purposes, our agent will support two basic types of requests:
* Lookup: The customer can look up song titles, artist names, and albums based on other identifying information. For example: "What songs do you have by Jimi Hendrix?"
* Refund: The customer can request a refund on their past purchases. For example: "My name is Claude Shannon and I'd like a refund on a purchase I made last week, could you help me?"
For simplicity in this demo, we'll implement refunds by deleting the corresponding database records. We'll skip implementing user authentication and other production security measures.
The agent's logic will be structured as two separate subgraphs (one for lookups and one for refunds), with a parent graph that routes requests to the appropriate subgraph.
#### Refund agent
Let's build the refund processing agent. This agent needs to:
1. Find the customer's purchase records in the database
2. Delete the relevant Invoice and InvoiceLine records to process the refund
We'll create two SQL helper functions:
1. A function to execute the refund by deleting records
2. A function to look up a customer's purchase history
To make testing easier, we'll add a "mock" mode to these functions. When mock mode is enabled, the functions will simulate database operations without actually modifying any data.
```python theme={null}
import sqlite3
def _refund(invoice_id: int | None, invoice_line_ids: list[int] | None, mock: bool = False) -> float:
...
def _lookup( ...
```
Now let's define our graph. We'll use a simple architecture with three main paths:
1. Extract customer and purchase information from the conversation
2. Route the request to one of three paths:
* Refund path: If we have sufficient purchase details (Invoice ID or Invoice Line IDs) to process a refund
* Lookup path: If we have enough customer information (name and phone) to search their purchase history
* Response path: If we need more information, respond to the user requesting the specific details needed
The graph's state will track:
* The conversation history (messages between user and agent)
* All customer and purchase information extracted from the conversation
* The next message to send to the user (followup text)
````python theme={null}
from typing import Literal
import json
from langchain.chat_models import init_chat_model
from langchain_core.runnables import RunnableConfig
from langgraph.graph import END, StateGraph
from langgraph.graph.message import AnyMessage, add_messages
from langgraph.types import Command, interrupt
from tabulate import tabulate
from typing_extensions import Annotated, TypedDict
# Graph state.
class State(TypedDict):
"""Agent state."""
messages: Annotated[list[AnyMessage], add_messages]
followup: str | None
invoice_id: int | None
invoice_line_ids: list[int] | None
customer_first_name: str | None
customer_last_name: str | None
customer_phone: str | None
track_name: str | None
album_title: str | None
artist_name: str | None
purchase_date_iso_8601: str | None
# Instructions for extracting the user/purchase info from the conversation.
gather_info_instructions = """You are managing an online music store that sells song tracks. \
Customers can buy multiple tracks at a time and these purchases are recorded in a database as \
an Invoice per purchase and an associated set of Invoice Lines for each purchased track.
Your task is to help customers who would like a refund for one or more of the tracks they've \
purchased. In order for you to be able refund them, the customer must specify the Invoice ID \
to get a refund on all the tracks they bought in a single transaction, or one or more Invoice \
Line IDs if they would like refunds on individual tracks.
Often a user will not know the specific Invoice ID(s) or Invoice Line ID(s) for which they \
would like a refund. In this case you can help them look up their invoices by asking them to \
specify:
- Required: Their first name, last name, and phone number.
- Optionally: The track name, artist name, album name, or purchase date.
If the customer has not specified the required information (either Invoice/Invoice Line IDs \
or first name, last name, phone) then please ask them to specify it."""
# Extraction schema, mirrors the graph state.
class PurchaseInformation(TypedDict):
"""All of the known information about the invoice / invoice lines the customer would like refunded. Do not make up values, leave fields as null if you don't know their value."""
invoice_id: int | None
invoice_line_ids: list[int] | None
customer_first_name: str | None
customer_last_name: str | None
customer_phone: str | None
track_name: str | None
album_title: str | None
artist_name: str | None
purchase_date_iso_8601: str | None
followup: Annotated[
str | None,
...,
"If the user hasn't enough identifying information, please tell them what the required information is and ask them to specify it.",
]
# Model for performing extraction.
info_llm = init_chat_model("gpt-4.1-mini").with_structured_output(
PurchaseInformation, method="json_schema", include_raw=True
)
# Graph node for extracting user info and routing to lookup/refund/END.
async def gather_info(state: State) -> Command[Literal["lookup", "refund", END]]:
info = await info_llm.ainvoke(
[
{"role": "system", "content": gather_info_instructions},
*state["messages"],
]
)
parsed = info["parsed"]
if any(parsed[k] for k in ("invoice_id", "invoice_line_ids")):
goto = "refund"
elif all(
parsed[k]
for k in ("customer_first_name", "customer_last_name", "customer_phone")
):
goto = "lookup"
else:
goto = END
update = {"messages": [info["raw"]], **parsed}
return Command(update=update, goto=goto)
# Graph node for executing the refund.
# Note that here we inspect the runtime config for an "env" variable.
# If "env" is set to "test", then we don't actually delete any rows from our database.
# This will become important when we're running our evaluations.
def refund(state: State, config: RunnableConfig) -> dict:
# Whether to mock the deletion. True if the configurable var 'env' is set to 'test'.
mock = config.get("configurable", {}).get("env", "prod") == "test"
refunded = _refund(
invoice_id=state["invoice_id"], invoice_line_ids=state["invoice_line_ids"], mock=mock
)
response = f"You have been refunded a total of: ${refunded:.2f}. Is there anything else I can help with?"
return {
"messages": [{"role": "assistant", "content": response}],
"followup": response,
}
# Graph node for looking up the users purchases
def lookup(state: State) -> dict:
args = (
state[k]
for k in (
"customer_first_name",
"customer_last_name",
"customer_phone",
"track_name",
"album_title",
"artist_name",
"purchase_date_iso_8601",
)
)
results = _lookup(*args)
if not results:
response = "We did not find any purchases associated with the information you've provided. Are you sure you've entered all of your information correctly?"
followup = response
else:
response = f"Which of the following purchases would you like to be refunded for?\n\n```json{json.dumps(results, indent=2)}\n```"
followup = f"Which of the following purchases would you like to be refunded for?\n\n{tabulate(results, headers='keys')}"
return {
"messages": [{"role": "assistant", "content": response}],
"followup": followup,
"invoice_line_ids": [res["invoice_line_id"] for res in results],
}
# Building our graph
graph_builder = StateGraph(State)
graph_builder.add_node(gather_info)
graph_builder.add_node(refund)
graph_builder.add_node(lookup)
graph_builder.set_entry_point("gather_info")
graph_builder.add_edge("lookup", END)
graph_builder.add_edge("refund", END)
refund_graph = graph_builder.compile()
````
We can visualize our refund graph:
```
# Assumes you're in an interactive Python environmentfrom IPython.display import Image, display ...
```
#### Lookup agent
For the lookup (i.e. question-answering) agent, we'll use a simple ReACT architecture and give the agent tools for looking up track names, artist names, and album names based on various filters. For example, you can look up albums by a particular artist, artists who released songs with a specific name, etc.
```python theme={null}
from langchain.embeddings import init_embeddings
from langchain.tools import tool
from langchain_core.vectorstores import InMemoryVectorStore
from langchain.agents import create_agent
# Our SQL queries will only work if we filter on the exact string values that are in the DB.
# To ensure this, we'll create vectorstore indexes for all of the artists, tracks and albums
# ahead of time and use those to disambiguate the user input. E.g. if a user searches for
# songs by "prince" and our DB records the artist as "Prince", ideally when we query our
# artist vectorstore for "prince" we'll get back the value "Prince", which we can then
# use in our SQL queries.
def index_fields() -> tuple[InMemoryVectorStore, InMemoryVectorStore, InMemoryVectorStore]: ...
track_store, artist_store, album_store = index_fields()
# Agent tools
@tool
def lookup_track( ...
@tool
def lookup_album( ...
@tool
def lookup_artist( ...
# Agent model
qa_llm = init_chat_model("claude-sonnet-4-5-20250929")
# The prebuilt ReACT agent only expects State to have a 'messages' key, so the
# state we defined for the refund agent can also be passed to our lookup agent.
qa_graph = create_agent(qa_llm, tools=[lookup_track, lookup_artist, lookup_album])
```
```
display(Image(qa_graph.get_graph(xray=True).draw_mermaid_png()))
```
#### Parent agent
Now let's define a parent agent that combines our two task-specific agents. The only job of the parent agent is to route to one of the sub-agents by classifying the user's current intent, and to compile the output into a followup message.
```python theme={null}
# Schema for routing user intent.
# We'll use structured output to enforce that the model returns only
# the desired output.
class UserIntent(TypedDict):
"""The user's current intent in the conversation"""
intent: Literal["refund", "question_answering"]
# Routing model with structured output
router_llm = init_chat_model("gpt-4.1-mini").with_structured_output(
UserIntent, method="json_schema", strict=True
)
# Instructions for routing.
route_instructions = """You are managing an online music store that sells song tracks. \
You can help customers in two types of ways: (1) answering general questions about \
tracks sold at your store, (2) helping them get a refund on a purhcase they made at your store.
Based on the following conversation, determine if the user is currently seeking general \
information about song tracks or if they are trying to refund a specific purchase.
Return 'refund' if they are trying to get a refund and 'question_answering' if they are \
asking a general music question. Do NOT return anything else. Do NOT try to respond to \
the user.
"""
# Node for routing.
async def intent_classifier(
state: State,
) -> Command[Literal["refund_agent", "question_answering_agent"]]:
response = router_llm.invoke(
[{"role": "system", "content": route_instructions}, *state["messages"]]
)
return Command(goto=response["intent"] + "_agent")
# Node for making sure the 'followup' key is set before our agent run completes.
def compile_followup(state: State) -> dict:
"""Set the followup to be the last message if it hasn't explicitly been set."""
if not state.get("followup"):
return {"followup": state["messages"][-1].content}
return {}
# Agent definition
graph_builder = StateGraph(State)
graph_builder.add_node(intent_classifier)
# Since all of our subagents have compatible state,
# we can add them as nodes directly.
graph_builder.add_node("refund_agent", refund_graph)
graph_builder.add_node("question_answering_agent", qa_graph)
graph_builder.add_node(compile_followup)
graph_builder.set_entry_point("intent_classifier")
graph_builder.add_edge("refund_agent", "compile_followup")
graph_builder.add_edge("question_answering_agent", "compile_followup")
graph_builder.add_edge("compile_followup", END)
graph = graph_builder.compile()
```
We can visualize our compiled parent graph including all of its subgraphs:
```python theme={null}
display(Image(graph.get_graph().draw_mermaid_png()))
```
#### Try it out
Let's give our custom support agent a whirl!
```python theme={null}
state = await graph.ainvoke(
{"messages": [{"role": "user", "content": "what james brown songs do you have"}]}
)
print(state["followup"])
```
```
I found 20 James Brown songs in the database, all from the album "Sex Machine". Here they are: ...
```
```python theme={null}
state = await graph.ainvoke({"messages": [
{
"role": "user",
"content": "my name is Aaron Mitchell and my number is +1 (204) 452-6452. I bought some songs by Led Zeppelin that i'd like refunded",
}
]})
print(state["followup"])
```
```
Which of the following purchases would you like to be refunded for? ...
```
## Evaluations
Now that we've got a testable version of our agent, let's run some evaluations. Agent evaluation can focus on at least 3 things:
* [Final response](/langsmith/evaluation-concepts#evaluating-an-agents-final-response): The inputs are a prompt and an optional list of tools. The output is the final agent response.
* [Trajectory](/langsmith/trajectory-evals): As before, the inputs are a prompt and an optional list of tools. The output is the list of tool calls
* [Single step](/langsmith/evaluation-concepts#evaluating-a-single-step-of-an-agent): As before, the inputs are a prompt and an optional list of tools. The output is the tool call.
Let's run each type of evaluation:
### Final response evaluator
First, let's create a [dataset](/langsmith/evaluation-concepts#datasets) that evaluates end-to-end performance of the agent. For simplicity we'll use the same dataset for final response and trajectory evaluation, so we'll add both ground-truth responses and trajectories for each example question. We'll cover the trajectories in the next section.
```python theme={null}
from langsmith import Client
client = Client()
# Create a dataset
examples = [
{
"inputs": {
"question": "How many songs do you have by James Brown",
},
"outputs": {
"response": "We have 20 songs by James Brown",
"trajectory": ["question_answering_agent", "lookup_track"]
}
},
{
"inputs": {
"question": "My name is Aaron Mitchell and I'd like a refund.",
},
"outputs": {
"response": "I need some more information to help you with the refund. Please specify your phone number, the invoice ID, or the line item IDs for the purchase you'd like refunded.",
"trajectory": ["refund_agent"],
}
},
{
"inputs": {
"question": "My name is Aaron Mitchell and I'd like a refund on my Led Zeppelin purchases. My number is +1 (204) 452-6452",
},
"outputs": {
"response": 'Which of the following purchases would you like to be refunded for?\n\n invoice_line_id track_name artist_name purchase_date quantity_purchased price_per_unit\n----------------- -------------------------------- ------------- ------------------- -------------------- ----------------\n 267 How Many More Times Led Zeppelin 2009-08-06 00:00:00 1 0.99\n 268 What Is And What Should Never Be Led Zeppelin 2009-08-06 00:00:00 1 0.99',
"trajectory": ["refund_agent", "lookup"],
},
},
{
"inputs": {
"question": "Who recorded Wish You Were Here again? What other albums of there's do you have?",
},
"outputs": {
"response": "Wish You Were Here is an album by Pink Floyd",
"trajectory": ["question_answering_agent", "lookup_album"],
},
},
{
"inputs": {
"question": "I want a full refund for invoice 237",
},
"outputs": {
"response": "You have been refunded $0.99.",
"trajectory": ["refund_agent", "refund"],
}
},
]
dataset_name = "Chinook Customer Service Bot: E2E"
if not client.has_dataset(dataset_name=dataset_name):
dataset = client.create_dataset(dataset_name=dataset_name)
client.create_examples(
dataset_id=dataset.id,
examples=examples
)
```
We'll create a custom [LLM-as-judge](/langsmith/evaluation-concepts#llm-as-judge) evaluator that uses another model to compare our agent's output on each example to the reference response, and judge if they're equivalent or not:
```python theme={null}
# LLM-as-judge instructions
grader_instructions = """You are a teacher grading a quiz.
You will be given a QUESTION, the GROUND TRUTH (correct) RESPONSE, and the STUDENT RESPONSE.
Here is the grade criteria to follow:
(1) Grade the student responses based ONLY on their factual accuracy relative to the ground truth answer.
(2) Ensure that the student response does not contain any conflicting statements.
(3) It is OK if the student response contains more information than the ground truth response, as long as it is factually accurate relative to the ground truth response.
Correctness:
True means that the student's response meets all of the criteria.
False means that the student's response does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct."""
# LLM-as-judge output schema
class Grade(TypedDict):
"""Compare the expected and actual answers and grade the actual answer."""
reasoning: Annotated[str, ..., "Explain your reasoning for whether the actual response is correct or not."]
is_correct: Annotated[bool, ..., "True if the student response is mostly or exactly correct, otherwise False."]
# Judge LLM
grader_llm = init_chat_model("gpt-4.1-mini", temperature=0).with_structured_output(Grade, method="json_schema", strict=True)
# Evaluator function
async def final_answer_correct(inputs: dict, outputs: dict, reference_outputs: dict) -> bool:
"""Evaluate if the final response is equivalent to reference response."""
# Note that we assume the outputs has a 'response' dictionary. We'll need to make sure
# that the target function we define includes this key.
user = f"""QUESTION: {inputs['question']}
GROUND TRUTH RESPONSE: {reference_outputs['response']}
STUDENT RESPONSE: {outputs['response']}"""
grade = await grader_llm.ainvoke([{"role": "system", "content": grader_instructions}, {"role": "user", "content": user}])
return grade["is_correct"]
```
Now we can run our evaluation. Our evaluator assumes that our target function returns a 'response' key, so lets define a target function that does so.
Also remember that in our refund graph we made the refund node configurable, so that if we specified `config={"env": "test"}`, we would mock out the refunds without actually updating the DB. We'll use this configurable variable in our target `run_graph` method when invoking our graph:
```python theme={null}
# Target function
async def run_graph(inputs: dict) -> dict:
"""Run graph and track the trajectory it takes along with the final response."""
result = await graph.ainvoke({"messages": [
{ "role": "user", "content": inputs['question']},
]}, config={"env": "test"})
return {"response": result["followup"]}
# Evaluation job and results
experiment_results = await client.aevaluate(
run_graph,
data=dataset_name,
evaluators=[final_answer_correct],
experiment_prefix="sql-agent-gpt4o-e2e",
num_repetitions=1,
max_concurrency=4,
)
experiment_results.to_pandas()
```
You can see what these results look like here: [LangSmith link](https://smith.langchain.com/public/708d08f4-300e-4c75-9677-c6b71b0d28c9/d).
### Trajectory evaluator
As agents become more complex, they have more potential points of failure. Rather than using simple pass/fail evaluations, it's often better to use evaluations that can give partial credit when an agent takes some correct steps, even if it doesn't reach the right final answer.
This is where trajectory evaluations come in. A trajectory evaluation:
1. Compares the actual sequence of steps the agent took against an expected sequence
2. Calculates a score based on how many of the expected steps were completed correctly
For this example, our end-to-end dataset contains an ordered list of steps that we expect the agent to take. Let's create an evaluator that checks the agent's actual trajectory against these expected steps and calculates what percentage were completed:
```python theme={null}
def trajectory_subsequence(outputs: dict, reference_outputs: dict) -> float:
"""Check how many of the desired steps the agent took."""
if len(reference_outputs['trajectory']) > len(outputs['trajectory']):
return False
i = j = 0
while i < len(reference_outputs['trajectory']) and j < len(outputs['trajectory']):
if reference_outputs['trajectory'][i] == outputs['trajectory'][j]:
i += 1
j += 1
return i / len(reference_outputs['trajectory'])
```
Now we can run our evaluation. Our evaluator assumes that our target function returns a 'trajectory' key, so lets define a target function that does so. We'll need to usage [LangGraph's streaming capabilities](https://langchain-ai.github.io/langgra/langsmith/observability-concepts/streaming/) to record the trajectory.
Note that we are reusing the same dataset as for our final response evaluation, so we could have run both evaluators together and defined a target function that returns both "response" and "trajectory". In practice it's often useful to have separate datasets for each type of evaluation, which is why we show them separately here:
```python theme={null}
async def run_graph(inputs: dict) -> dict:
"""Run graph and track the trajectory it takes along with the final response."""
trajectory = []
# Set subgraph=True to stream events from subgraphs of the main graph: https://langchain-ai.github.io/langgraph/how-tos/streaming-subgraphs/
# Set stream_mode="debug" to stream all possible events: https://langchain-ai.github.io/langgra/langsmith/observability-concepts/streaming
async for namespace, chunk in graph.astream({"messages": [
{
"role": "user",
"content": inputs['question'],
}
]}, subgraphs=True, stream_mode="debug"):
# Event type for entering a node
if chunk['type'] == 'task':
# Record the node name
trajectory.append(chunk['payload']['name'])
# Given how we defined our dataset, we also need to track when specific tools are
# called by our question answering ReACT agent. These tool calls can be found
# when the ToolsNode (named "tools") is invoked by looking at the AIMessage.tool_calls
# of the latest input message.
if chunk['payload']['name'] == 'tools' and chunk['type'] == 'task':
for tc in chunk['payload']['input']['messages'][-1].tool_calls:
trajectory.append(tc['name'])
return {"trajectory": trajectory}
experiment_results = await client.aevaluate(
run_graph,
data=dataset_name,
evaluators=[trajectory_subsequence],
experiment_prefix="sql-agent-gpt4o-trajectory",
num_repetitions=1,
max_concurrency=4,
)
experiment_results.to_pandas()
```
You can see what these results look like here: [LangSmith link](https://smith.langchain.com/public/708d08f4-300e-4c75-9677-c6b71b0d28c9/d).
### Single step evaluators
While end-to-end tests give you the most signal about your agents performance, for the sake of debugging and iterating on your agent it can be helpful to pinpoint specific steps that are difficult and evaluate them directly.
In our case, a crucial part of our agent is that it routes the user's intention correctly into either the "refund" path or the "question answering" path. Let's create a dataset and run some evaluations to directly stress test this one component.
```python theme={null}
# Create dataset
examples = [
{
"inputs": {"messages": [{"role": "user", "content": "i bought some tracks recently and i dont like them"}]},
"outputs": {"route": "refund_agent"},
},
{
"inputs": {"messages": [{"role": "user", "content": "I was thinking of purchasing some Rolling Stones tunes, any recommendations?"}]},
"outputs": {"route": "question_answering_agent"},
},
{
"inputs": {"messages": [{"role": "user", "content": "i want a refund on purchase 237"}, {"role": "assistant", "content": "I've refunded you a total of $1.98. How else can I help you today?"}, {"role": "user", "content": "did prince release any albums in 2000?"}]},
"outputs": {"route": "question_answering_agent"},
},
{
"inputs": {"messages": [{"role": "user", "content": "i purchased a cover of Yesterday recently but can't remember who it was by, which versions of it do you have?"}]},
"outputs": {"route": "question_answering_agent"},
},
]
dataset_name = "Chinook Customer Service Bot: Intent Classifier"
if not client.has_dataset(dataset_name=dataset_name):
dataset = client.create_dataset(dataset_name=dataset_name)
client.create_examples(
dataset_id=dataset.id,
examples=examples
)
# Evaluator
def correct(outputs: dict, reference_outputs: dict) -> bool:
"""Check if the agent chose the correct route."""
return outputs["route"] == reference_outputs["route"]
# Target function for running the relevant step
async def run_intent_classifier(inputs: dict) -> dict:
# Note that we can access and run the intent_classifier node of our graph directly.
command = await graph.nodes['intent_classifier'].ainvoke(inputs)
return {"route": command.goto}
# Run evaluation
experiment_results = await client.aevaluate(
run_intent_classifier,
data=dataset_name,
evaluators=[correct],
experiment_prefix="sql-agent-gpt4o-intent-classifier",
max_concurrency=4,
)
```
You can see what these results look like here: [LangSmith link](https://smith.langchain.com/public/f133dae2-8a88-43a0-9bfd-ab45bfa3920b/d).
## Reference code
Here's a consolidated script with all the above code:
````python theme={null}
import json
import sqlite3
from typing import Literal
from langchain.chat_models import init_chat_model
from langchain.embeddings import init_embeddings
from langchain_core.runnables import RunnableConfig
from langchain.tools import tool
from langchain_core.vectorstores import InMemoryVectorStore
from langgraph.graph import END, StateGraph
from langgraph.graph.message import AnyMessage, add_messages
from langchain.agents import create_agent
from langgraph.types import Command, interrupt
from langsmith import Client
import requests
from tabulate import tabulate
from typing_extensions import Annotated, TypedDict
url = "https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db"
response = requests.get(url)
if response.status_code == 200:
# Open a local file in binary write mode
with open("chinook.db", "wb") as file:
# Write the content of the response (the file) to the local file
file.write(response.content)
print("File downloaded and saved as Chinook.db")
else:
print(f"Failed to download the file. Status code: {response.status_code}")
def _refund(
invoice_id: int | None, invoice_line_ids: list[int] | None, mock: bool = False
) -> float:
"""Given an Invoice ID and/or Invoice Line IDs, delete the relevant Invoice/InvoiceLine records in the Chinook DB.
Args:
invoice_id: The Invoice to delete.
invoice_line_ids: The Invoice Lines to delete.
mock: If True, do not actually delete the specified Invoice/Invoice Lines. Used for testing purposes.
Returns:
float: The total dollar amount that was deleted (or mock deleted).
"""
if invoice_id is None and invoice_line_ids is None:
return 0.0
# Connect to the Chinook database
conn = sqlite3.connect("chinook.db")
cursor = conn.cursor()
total_refund = 0.0
try:
# If invoice_id is provided, delete entire invoice and its lines
if invoice_id is not None:
# First get the total amount for the invoice
cursor.execute(
"""
SELECT Total
FROM Invoice
WHERE InvoiceId = ?
""",
(invoice_id,),
)
result = cursor.fetchone()
if result:
total_refund += result[0]
# Delete invoice lines first (due to foreign key constraints)
if not mock:
cursor.execute(
"""
DELETE FROM InvoiceLine
WHERE InvoiceId = ?
""",
(invoice_id,),
)
# Then delete the invoice
cursor.execute(
"""
DELETE FROM Invoice
WHERE InvoiceId = ?
""",
(invoice_id,),
)
# If specific invoice lines are provided
if invoice_line_ids is not None:
# Get the total amount for the specified invoice lines
placeholders = ",".join(["?" for _ in invoice_line_ids])
cursor.execute(
f"""
SELECT SUM(UnitPrice * Quantity)
FROM InvoiceLine
WHERE InvoiceLineId IN ({placeholders})
""",
invoice_line_ids,
)
result = cursor.fetchone()
if result and result[0]:
total_refund += result[0]
if not mock:
# Delete the specified invoice lines
cursor.execute(
f"""
DELETE FROM InvoiceLine
WHERE InvoiceLineId IN ({placeholders})
""",
invoice_line_ids,
)
# Commit the changes
conn.commit()
except sqlite3.Error as e:
# Roll back in case of error
conn.rollback()
raise e
finally:
# Close the connection
conn.close()
return float(total_refund)
def _lookup(
customer_first_name: str,
customer_last_name: str,
customer_phone: str,
track_name: str | None,
album_title: str | None,
artist_name: str | None,
purchase_date_iso_8601: str | None,
) -> list[dict]:
"""Find all of the Invoice Line IDs in the Chinook DB for the given filters.
Returns:
a list of dictionaries that contain keys: {
'invoice_line_id',
'track_name',
'artist_name',
'purchase_date',
'quantity_purchased',
'price_per_unit'
}
"""
# Connect to the database
conn = sqlite3.connect("chinook.db")
cursor = conn.cursor()
# Base query joining all necessary tables
query = """
SELECT
il.InvoiceLineId,
t.Name as track_name,
art.Name as artist_name,
i.InvoiceDate as purchase_date,
il.Quantity as quantity_purchased,
il.UnitPrice as price_per_unit
FROM InvoiceLine il
JOIN Invoice i ON il.InvoiceId = i.InvoiceId
JOIN Customer c ON i.CustomerId = c.CustomerId
JOIN Track t ON il.TrackId = t.TrackId
JOIN Album alb ON t.AlbumId = alb.AlbumId
JOIN Artist art ON alb.ArtistId = art.ArtistId
WHERE c.FirstName = ?
AND c.LastName = ?
AND c.Phone = ?
"""
# Parameters for the query
params = [customer_first_name, customer_last_name, customer_phone]
# Add optional filters
if track_name:
query += " AND t.Name = ?"
params.append(track_name)
if album_title:
query += " AND alb.Title = ?"
params.append(album_title)
if artist_name:
query += " AND art.Name = ?"
params.append(artist_name)
if purchase_date_iso_8601:
query += " AND date(i.InvoiceDate) = date(?)"
params.append(purchase_date_iso_8601)
# Execute query
cursor.execute(query, params)
# Fetch results
results = cursor.fetchall()
# Convert results to list of dictionaries
output = []
for row in results:
output.append(
{
"invoice_line_id": row[0],
"track_name": row[1],
"artist_name": row[2],
"purchase_date": row[3],
"quantity_purchased": row[4],
"price_per_unit": row[5],
}
)
# Close connection
conn.close()
return output
# Graph state.
class State(TypedDict):
"""Agent state."""
messages: Annotated[list[AnyMessage], add_messages]
followup: str | None
invoice_id: int | None
invoice_line_ids: list[int] | None
customer_first_name: str | None
customer_last_name: str | None
customer_phone: str | None
track_name: str | None
album_title: str | None
artist_name: str | None
purchase_date_iso_8601: str | None
# Instructions for extracting the user/purchase info from the conversation.
gather_info_instructions = """You are managing an online music store that sells song tracks. \
Customers can buy multiple tracks at a time and these purchases are recorded in a database as \
an Invoice per purchase and an associated set of Invoice Lines for each purchased track.
Your task is to help customers who would like a refund for one or more of the tracks they've \
purchased. In order for you to be able refund them, the customer must specify the Invoice ID \
to get a refund on all the tracks they bought in a single transaction, or one or more Invoice \
Line IDs if they would like refunds on individual tracks.
Often a user will not know the specific Invoice ID(s) or Invoice Line ID(s) for which they \
would like a refund. In this case you can help them look up their invoices by asking them to \
specify:
- Required: Their first name, last name, and phone number.
- Optionally: The track name, artist name, album name, or purchase date.
If the customer has not specified the required information (either Invoice/Invoice Line IDs \
or first name, last name, phone) then please ask them to specify it."""
# Extraction schema, mirrors the graph state.
class PurchaseInformation(TypedDict):
"""All of the known information about the invoice / invoice lines the customer would like refunded. Do not make up values, leave fields as null if you don't know their value."""
invoice_id: int | None
invoice_line_ids: list[int] | None
customer_first_name: str | None
customer_last_name: str | None
customer_phone: str | None
track_name: str | None
album_title: str | None
artist_name: str | None
purchase_date_iso_8601: str | None
followup: Annotated[
str | None,
...,
"If the user hasn't enough identifying information, please tell them what the required information is and ask them to specify it.",
]
# Model for performing extraction.
info_llm = init_chat_model("gpt-4.1-mini").with_structured_output(
PurchaseInformation, method="json_schema", include_raw=True
)
# Graph node for extracting user info and routing to lookup/refund/END.
async def gather_info(state: State) -> Command[Literal["lookup", "refund", END]]:
info = await info_llm.ainvoke(
[
{"role": "system", "content": gather_info_instructions},
*state["messages"],
]
)
parsed = info["parsed"]
if any(parsed[k] for k in ("invoice_id", "invoice_line_ids")):
goto = "refund"
elif all(
parsed[k]
for k in ("customer_first_name", "customer_last_name", "customer_phone")
):
goto = "lookup"
else:
goto = END
update = {"messages": [info["raw"]], **parsed}
return Command(update=update, goto=goto)
# Graph node for executing the refund.
# Note that here we inspect the runtime config for an "env" variable.
# If "env" is set to "test", then we don't actually delete any rows from our database.
# This will become important when we're running our evaluations.
def refund(state: State, config: RunnableConfig) -> dict:
# Whether to mock the deletion. True if the configurable var 'env' is set to 'test'.
mock = config.get("configurable", {}).get("env", "prod") == "test"
refunded = _refund(
invoice_id=state["invoice_id"],
invoice_line_ids=state["invoice_line_ids"],
mock=mock,
)
response = f"You have been refunded a total of: ${refunded:.2f}. Is there anything else I can help with?"
return {
"messages": [{"role": "assistant", "content": response}],
"followup": response,
}
# Graph node for looking up the users purchases
def lookup(state: State) -> dict:
args = (
state[k]
for k in (
"customer_first_name",
"customer_last_name",
"customer_phone",
"track_name",
"album_title",
"artist_name",
"purchase_date_iso_8601",
)
)
results = _lookup(*args)
if not results:
response = "We did not find any purchases associated with the information you've provided. Are you sure you've entered all of your information correctly?"
followup = response
else:
response = f"Which of the following purchases would you like to be refunded for?\n\n```json{json.dumps(results, indent=2)}\n```"
followup = f"Which of the following purchases would you like to be refunded for?\n\n{tabulate(results, headers='keys')}"
return {
"messages": [{"role": "assistant", "content": response}],
"followup": followup,
"invoice_line_ids": [res["invoice_line_id"] for res in results],
}
# Building our graph
graph_builder = StateGraph(State)
graph_builder.add_node(gather_info)
graph_builder.add_node(refund)
graph_builder.add_node(lookup)
graph_builder.set_entry_point("gather_info")
graph_builder.add_edge("lookup", END)
graph_builder.add_edge("refund", END)
refund_graph = graph_builder.compile()
# Our SQL queries will only work if we filter on the exact string values that are in the DB.
# To ensure this, we'll create vectorstore indexes for all of the artists, tracks and albums
# ahead of time and use those to disambiguate the user input. E.g. if a user searches for
# songs by "prince" and our DB records the artist as "Prince", ideally when we query our
# artist vectorstore for "prince" we'll get back the value "Prince", which we can then
# use in our SQL queries.
def index_fields() -> (
tuple[InMemoryVectorStore, InMemoryVectorStore, InMemoryVectorStore]
):
"""Create an index for all artists, an index for all albums, and an index for all songs."""
try:
# Connect to the chinook database
conn = sqlite3.connect("chinook.db")
cursor = conn.cursor()
# Fetch all results
tracks = cursor.execute("SELECT Name FROM Track").fetchall()
artists = cursor.execute("SELECT Name FROM Artist").fetchall()
albums = cursor.execute("SELECT Title FROM Album").fetchall()
finally:
# Close the connection
if conn:
conn.close()
embeddings = init_embeddings("openai:text-embedding-3-small")
track_store = InMemoryVectorStore(embeddings)
artist_store = InMemoryVectorStore(embeddings)
album_store = InMemoryVectorStore(embeddings)
track_store.add_texts([t[0] for t in tracks])
artist_store.add_texts([a[0] for a in artists])
album_store.add_texts([a[0] for a in albums])
return track_store, artist_store, album_store
track_store, artist_store, album_store = index_fields()
# Agent tools
@tool
def lookup_track(
track_name: str | None = None,
album_title: str | None = None,
artist_name: str | None = None,
) -> list[dict]:
"""Lookup a track in Chinook DB based on identifying information about.
Returns:
a list of dictionaries per matching track that contain keys {'track_name', 'artist_name', 'album_name'}
"""
conn = sqlite3.connect("chinook.db")
cursor = conn.cursor()
query = """
SELECT DISTINCT t.Name as track_name, ar.Name as artist_name, al.Title as album_name
FROM Track t
JOIN Album al ON t.AlbumId = al.AlbumId
JOIN Artist ar ON al.ArtistId = ar.ArtistId
WHERE 1=1
"""
params = []
if track_name:
track_name = track_store.similarity_search(track_name, k=1)[0].page_content
query += " AND t.Name LIKE ?"
params.append(f"%{track_name}%")
if album_title:
album_title = album_store.similarity_search(album_title, k=1)[0].page_content
query += " AND al.Title LIKE ?"
params.append(f"%{album_title}%")
if artist_name:
artist_name = artist_store.similarity_search(artist_name, k=1)[0].page_content
query += " AND ar.Name LIKE ?"
params.append(f"%{artist_name}%")
cursor.execute(query, params)
results = cursor.fetchall()
tracks = [
{"track_name": row[0], "artist_name": row[1], "album_name": row[2]}
for row in results
]
conn.close()
return tracks
@tool
def lookup_album(
track_name: str | None = None,
album_title: str | None = None,
artist_name: str | None = None,
) -> list[dict]:
"""Lookup an album in Chinook DB based on identifying information about.
Returns:
a list of dictionaries per matching album that contain keys {'album_name', 'artist_name'}
"""
conn = sqlite3.connect("chinook.db")
cursor = conn.cursor()
query = """
SELECT DISTINCT al.Title as album_name, ar.Name as artist_name
FROM Album al
JOIN Artist ar ON al.ArtistId = ar.ArtistId
LEFT JOIN Track t ON t.AlbumId = al.AlbumId
WHERE 1=1
"""
params = []
if track_name:
query += " AND t.Name LIKE ?"
params.append(f"%{track_name}%")
if album_title:
query += " AND al.Title LIKE ?"
params.append(f"%{album_title}%")
if artist_name:
query += " AND ar.Name LIKE ?"
params.append(f"%{artist_name}%")
cursor.execute(query, params)
results = cursor.fetchall()
albums = [{"album_name": row[0], "artist_name": row[1]} for row in results]
conn.close()
return albums
@tool
def lookup_artist(
track_name: str | None = None,
album_title: str | None = None,
artist_name: str | None = None,
) -> list[str]:
"""Lookup an album in Chinook DB based on identifying information about.
Returns:
a list of matching artist names
"""
conn = sqlite3.connect("chinook.db")
cursor = conn.cursor()
query = """
SELECT DISTINCT ar.Name as artist_name
FROM Artist ar
LEFT JOIN Album al ON al.ArtistId = ar.ArtistId
LEFT JOIN Track t ON t.AlbumId = al.AlbumId
WHERE 1=1
"""
params = []
if track_name:
query += " AND t.Name LIKE ?"
params.append(f"%{track_name}%")
if album_title:
query += " AND al.Title LIKE ?"
params.append(f"%{album_title}%")
if artist_name:
query += " AND ar.Name LIKE ?"
params.append(f"%{artist_name}%")
cursor.execute(query, params)
results = cursor.fetchall()
artists = [row[0] for row in results]
conn.close()
return artists
# Agent model
qa_llm = init_chat_model("claude-sonnet-4-5-20250929")
# The prebuilt ReACT agent only expects State to have a 'messages' key, so the
# state we defined for the refund agent can also be passed to our lookup agent.
qa_graph = create_agent(qa_llm, [lookup_track, lookup_artist, lookup_album])
# Schema for routing user intent.
# We'll use structured output to enforce that the model returns only
# the desired output.
class UserIntent(TypedDict):
"""The user's current intent in the conversation"""
intent: Literal["refund", "question_answering"]
# Routing model with structured output
router_llm = init_chat_model("gpt-4.1-mini").with_structured_output(
UserIntent, method="json_schema", strict=True
)
# Instructions for routing.
route_instructions = """You are managing an online music store that sells song tracks. \
You can help customers in two types of ways: (1) answering general questions about \
tracks sold at your store, (2) helping them get a refund on a purhcase they made at your store.
Based on the following conversation, determine if the user is currently seeking general \
information about song tracks or if they are trying to refund a specific purchase.
Return 'refund' if they are trying to get a refund and 'question_answering' if they are \
asking a general music question. Do NOT return anything else. Do NOT try to respond to \
the user.
"""
# Node for routing.
async def intent_classifier(
state: State,
) -> Command[Literal["refund_agent", "question_answering_agent"]]:
response = router_llm.invoke(
[{"role": "system", "content": route_instructions}, *state["messages"]]
)
return Command(goto=response["intent"] + "_agent")
# Node for making sure the 'followup' key is set before our agent run completes.
def compile_followup(state: State) -> dict:
"""Set the followup to be the last message if it hasn't explicitly been set."""
if not state.get("followup"):
return {"followup": state["messages"][-1].content}
return {}
# Agent definition
graph_builder = StateGraph(State)
graph_builder.add_node(intent_classifier)
# Since all of our subagents have compatible state,
# we can add them as nodes directly.
graph_builder.add_node("refund_agent", refund_graph)
graph_builder.add_node("question_answering_agent", qa_graph)
graph_builder.add_node(compile_followup)
graph_builder.set_entry_point("intent_classifier")
graph_builder.add_edge("refund_agent", "compile_followup")
graph_builder.add_edge("question_answering_agent", "compile_followup")
graph_builder.add_edge("compile_followup", END)
graph = graph_builder.compile()
client = Client()
# Create a dataset
examples = [
{
"inputs": {
"question": "How many songs do you have by James Brown"
},
"outputs": {
"response": "We have 20 songs by James Brown",
"trajectory": ["question_answering_agent", "lookup_tracks"]
},
},
{
"inputs": {
"question": "My name is Aaron Mitchell and I'd like a refund.",
},
"outputs": {
"response": "I need some more information to help you with the refund. Please specify your phone number, the invoice ID, or the line item IDs for the purchase you'd like refunded.",
"trajectory": ["refund_agent"],
}
},
{
"inputs": {
"question": "My name is Aaron Mitchell and I'd like a refund on my Led Zeppelin purchases. My number is +1 (204) 452-6452",
},
"outputs": {
"response": "Which of the following purchases would you like to be refunded for?\n\n invoice_line_id track_name artist_name purchase_date quantity_purchased price_per_unit\n----------------- -------------------------------- ------------- ------------------- -------------------- ----------------\n 267 How Many More Times Led Zeppelin 2009-08-06 00:00:00 1 0.99\n 268 What Is And What Should Never Be Led Zeppelin 2009-08-06 00:00:00 1 0.99",
"trajectory": ["refund_agent", "lookup"],
},
},
{
"inputs": {
"question": "Who recorded Wish You Were Here again? What other albums of there's do you have?",
},
"outputs": {
"response": "Wish You Were Here is an album by Pink Floyd",
"trajectory": ["question_answering_agent", "lookup_album"],
}
},
{
"inputs": {
"question": "I want a full refund for invoice 237",
},
"outputs": {
"response": "You have been refunded $2.97.",
"trajectory": ["refund_agent", "refund"],
},
},
]
dataset_name = "Chinook Customer Service Bot: E2E"
if not client.has_dataset(dataset_name=dataset_name):
dataset = client.create_dataset(dataset_name=dataset_name)
client.create_examples(
dataset_id=dataset.id,
examples=examples
)
# LLM-as-judge instructions
grader_instructions = """You are a teacher grading a quiz.
You will be given a QUESTION, the GROUND TRUTH (correct) RESPONSE, and the STUDENT RESPONSE.
Here is the grade criteria to follow:
(1) Grade the student responses based ONLY on their factual accuracy relative to the ground truth answer.
(2) Ensure that the student response does not contain any conflicting statements.
(3) It is OK if the student response contains more information than the ground truth response, as long as it is factually accurate relative to the ground truth response.
Correctness:
True means that the student's response meets all of the criteria.
False means that the student's response does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct."""
# LLM-as-judge output schema
class Grade(TypedDict):
"""Compare the expected and actual answers and grade the actual answer."""
reasoning: Annotated[
str,
...,
"Explain your reasoning for whether the actual response is correct or not.",
]
is_correct: Annotated[
bool,
...,
"True if the student response is mostly or exactly correct, otherwise False.",
]
# Judge LLM
grader_llm = init_chat_model("gpt-4.1-mini", temperature=0).with_structured_output(
Grade, method="json_schema", strict=True
)
# Evaluator function
async def final_answer_correct(
inputs: dict, outputs: dict, reference_outputs: dict
) -> bool:
"""Evaluate if the final response is equivalent to reference response."""
# Note that we assume the outputs has a 'response' dictionary. We'll need to make sure
# that the target function we define includes this key.
user = f"""QUESTION: {inputs['question']}
GROUND TRUTH RESPONSE: {reference_outputs['response']}
STUDENT RESPONSE: {outputs['response']}"""
grade = await grader_llm.ainvoke(
[
{"role": "system", "content": grader_instructions},
{"role": "user", "content": user},
]
)
return grade["is_correct"]
# Target function
async def run_graph(inputs: dict) -> dict:
"""Run graph and track the trajectory it takes along with the final response."""
result = await graph.ainvoke(
{
"messages": [
{"role": "user", "content": inputs["question"]},
]
},
config={"env": "test"},
)
return {"response": result["followup"]}
# Evaluation job and results
experiment_results = await client.aevaluate(
run_graph,
data=dataset_name,
evaluators=[final_answer_correct],
experiment_prefix="sql-agent-gpt4o-e2e",
num_repetitions=1,
max_concurrency=4,
)
experiment_results.to_pandas()
def trajectory_subsequence(outputs: dict, reference_outputs: dict) -> float:
"""Check how many of the desired steps the agent took."""
if len(reference_outputs["trajectory"]) > len(outputs["trajectory"]):
return False
i = j = 0
while i < len(reference_outputs["trajectory"]) and j < len(outputs["trajectory"]):
if reference_outputs["trajectory"][i] == outputs["trajectory"][j]:
i += 1
j += 1
return i / len(reference_outputs["trajectory"])
async def run_graph(inputs: dict) -> dict:
"""Run graph and track the trajectory it takes along with the final response."""
trajectory = []
# Set subgraph=True to stream events from subgraphs of the main graph: https://langchain-ai.github.io/langgraph/how-tos/streaming-subgraphs/
# Set stream_mode="debug" to stream all possible events: https://langchain-ai.github.io/langgra/langsmith/observability-concepts/streaming
async for namespace, chunk in graph.astream(
{
"messages": [
{
"role": "user",
"content": inputs["question"],
}
]
},
subgraphs=True,
stream_mode="debug",
):
# Event type for entering a node
if chunk["type"] == "task":
# Record the node name
trajectory.append(chunk["payload"]["name"])
# Given how we defined our dataset, we also need to track when specific tools are
# called by our question answering ReACT agent. These tool calls can be found
# when the ToolsNode (named "tools") is invoked by looking at the AIMessage.tool_calls
# of the latest input message.
if chunk["payload"]["name"] == "tools" and chunk["type"] == "task":
for tc in chunk["payload"]["input"]["messages"][-1].tool_calls:
trajectory.append(tc["name"])
return {"trajectory": trajectory}
experiment_results = await client.aevaluate(
run_graph,
data=dataset_name,
evaluators=[trajectory_subsequence],
experiment_prefix="sql-agent-gpt4o-trajectory",
num_repetitions=1,
max_concurrency=4,
)
experiment_results.to_pandas()
# Create dataset
examples = [
{
"inputs": {
"messages": [
{
"role": "user",
"content": "i bought some tracks recently and i dont like them",
}
],
}
"outputs": {"route": "refund_agent"},
},
{
"inputs": {
"messages": [
{
"role": "user",
"content": "I was thinking of purchasing some Rolling Stones tunes, any recommendations?",
}
],
},
"outputs": {"route": "question_answering_agent"},
},
{
"inputs": {
"messages": [
{"role": "user", "content": "i want a refund on purchase 237"},
{
"role": "assistant",
"content": "I've refunded you a total of $1.98. How else can I help you today?",
},
{"role": "user", "content": "did prince release any albums in 2000?"},
],
},
"outputs": {"route": "question_answering_agent"},
},
{
"inputs": {
"messages": [
{
"role": "user",
"content": "i purchased a cover of Yesterday recently but can't remember who it was by, which versions of it do you have?",
}
],
},
"outputs": {"route": "question_answering_agent"},
},
]
dataset_name = "Chinook Customer Service Bot: Intent Classifier"
if not client.has_dataset(dataset_name=dataset_name):
dataset = client.create_dataset(dataset_name=dataset_name)
client.create_examples(
dataset_id=dataset.id,
examples=examples,
)
# Evaluator
def correct(outputs: dict, reference_outputs: dict) -> bool:
"""Check if the agent chose the correct route."""
return outputs["route"] == reference_outputs["route"]
# Target function for running the relevant step
async def run_intent_classifier(inputs: dict) -> dict:
# Note that we can access and run the intent_classifier node of our graph directly.
command = await graph.nodes["intent_classifier"].ainvoke(inputs)
return {"route": command.goto}
# Run evaluation
experiment_results = await client.aevaluate(
run_intent_classifier,
data=dataset_name,
evaluators=[correct],
experiment_prefix="sql-agent-gpt4o-intent-classifier",
max_concurrency=4,
)
experiment_results.to_pandas()
````
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluate-complex-agent.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to add evaluators to an existing experiment (Python only)
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluate-existing-experiment
Evaluation of existing experiments is currently only supported in the Python SDK.
After running an experiment, you may want to **add new evaluation metrics without re-running your application**. This is useful when you've added new evaluators or want to apply different scoring criteria to existing results. Instead of re-executing your target function on all examples, you can evaluate the existing experiment traces directly.
To add evaluators to an existing experiment, pass the experiment name or ID to `evaluate()` / `aevaluate()` instead of a target function. The evaluators will run on the cached traces from the original experiment, accessing the inputs, outputs, and any intermediate steps that were logged.
## Example
```python theme={null}
from langsmith import evaluate
def always_half(inputs: dict, outputs: dict) -> float:
return 0.5
experiment_name = "my-experiment:abc" # Replace with an actual experiment name or ID
evaluate(experiment_name, evaluators=[always_half])
```
## Related topics
* [Retry failed examples in experiments](/langsmith/evaluate-with-retry)
* [Run an evaluation](/langsmith/evaluate-llm-application)
* [Run an evaluation asynchronously](/langsmith/evaluation-async)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluate-existing-experiment.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to evaluate a graph
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluate-graph
[langgraph](https://langchain-ai.github.io/langgraph/)
`langgraph` is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows. Evaluating `langgraph` graphs can be challenging because a single invocation can involve many LLM calls, and which LLM calls are made may depend on the outputs of preceding calls. In this guide we will focus on the mechanics of how to pass graphs and graph nodes to `evaluate()` / `aevaluate()`. For evaluation techniques and best practices when building agents head to the [langgraph docs](https://langchain-ai.github.io/langgraph/tutorials/#evaluation).
## End-to-end evaluations
The most common type of evaluation is an end-to-end one, where we want to evaluate the final graph output for each example input.
### Define a graph
Lets construct a simple ReACT agent to start:
```python theme={null}
from typing import Annotated, Literal, TypedDict
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langgraph.prebuilt import ToolNode
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
class State(TypedDict):
# Messages have the type "list". The 'add_messages' function
# in the annotation defines how this state key should be updated
# (in this case, it appends messages to the list, rather than overwriting them)
messages: Annotated[list, add_messages]
# Define the tools for the agent to use
@tool
def search(query: str) -> str:
"""Call to surf the web."""
# This is a placeholder, but don't tell the LLM that...
if "sf" in query.lower() or "san francisco" in query.lower():
return "It's 60 degrees and foggy."
return "It's 90 degrees and sunny."
tools = [search]
tool_node = ToolNode(tools)
model = init_chat_model("claude-sonnet-4-5-20250929").bind_tools(tools)
# Define the function that determines whether to continue or not
def should_continue(state: State) -> Literal["tools", END]:
messages = state['messages']
last_message = messages[-1]
# If the LLM makes a tool call, then we route to the "tools" node
if last_message.tool_calls:
return "tools"
# Otherwise, we stop (reply to the user)
return END
# Define the function that calls the model
def call_model(state: State):
messages = state['messages']
response = model.invoke(messages)
# We return a list, because this will get added to the existing list
return {"messages": [response]}
# Define a new graph
workflow = StateGraph(State)
# Define the two nodes we will cycle between
workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)
# Set the entrypoint as 'agent'
# This means that this node is the first one called
workflow.add_edge(START, "agent")
# We now add a conditional edge
workflow.add_conditional_edges(
# First, we define the start node. We use 'agent'.
# This means these are the edges taken after the 'agent' node is called.
"agent",
# Next, we pass in the function that will determine which node is called next.
should_continue,
)
# We now add a normal edge from 'tools' to 'agent'.
# This means that after 'tools' is called, 'agent' node is called next.
workflow.add_edge("tools", 'agent')
# Finally, we compile it!
# This compiles it into a LangChain Runnable,
# meaning you can use it as you would any other runnable.
# Note that we're (optionally) passing the memory when compiling the graph
app = workflow.compile()
```
### Create a dataset
Let's create a simple dataset of questions and expected responses:
```python theme={null}
from langsmith import Client
questions = [
"what's the weather in sf",
"whats the weather in san fran",
"whats the weather in tangier"
]
answers = [
"It's 60 degrees and foggy.",
"It's 60 degrees and foggy.",
"It's 90 degrees and sunny.",
]
ls_client = Client()
dataset = ls_client.create_dataset(
"weather agent",
inputs=[{"question": q} for q in questions],
outputs=[{"answers": a} for a in answers],
)
```
### Create an evaluator
And a simple evaluator:
Requires `langsmith>=0.2.0`
```python theme={null}
judge_llm = init_chat_model("gpt-4.1")
async def correct(outputs: dict, reference_outputs: dict) -> bool:
instructions = (
"Given an actual answer and an expected answer, determine whether"
" the actual answer contains all of the information in the"
" expected answer. Respond with 'CORRECT' if the actual answer"
" does contain all of the expected information and 'INCORRECT'"
" otherwise. Do not include anything else in your response."
)
# Our graph outputs a State dictionary, which in this case means
# we'll have a 'messages' key and the final message should
# be our actual answer.
actual_answer = outputs["messages"][-1].content
expected_answer = reference_outputs["answer"]
user_msg = (
f"ACTUAL ANSWER: {actual_answer}"
f"\n\nEXPECTED ANSWER: {expected_answer}"
)
response = await judge_llm.ainvoke(
[
{"role": "system", "content": instructions},
{"role": "user", "content": user_msg}
]
)
return response.content.upper() == "CORRECT"
```
### Run evaluations
Now we can run our evaluations and explore the results. We'll just need to wrap our graph function so that it can take inputs in the format they're stored on our example:
If all of your graph nodes are defined as sync functions then you can use `evaluate` or `aevaluate`. If any of you nodes are defined as async, you'll need to use `aevaluate`
Requires `langsmith>=0.2.0`
```python theme={null}
from langsmith import aevaluate
def example_to_state(inputs: dict) -> dict:
return {"messages": [{"role": "user", "content": inputs['question']}]}
# We use LCEL declarative syntax here.
# Remember that langgraph graphs are also langchain runnables.
target = example_to_state | app
experiment_results = await aevaluate(
target,
data="weather agent",
evaluators=[correct],
max_concurrency=4, # optional
experiment_prefix="claude-3.5-baseline", # optional
)
```
## Evaluating intermediate steps
Often it is valuable to evaluate not only the final output of an agent but also the intermediate steps it has taken. What's nice about `langgraph` is that the output of a graph is a state object that often already carries information about the intermediate steps taken. Usually we can evaluate whatever we're interested in just by looking at the messages in our state. For example, we can look at the messages to assert that the model invoked the 'search' tool upon as a first step.
Requires `langsmith>=0.2.0`
```python theme={null}
def right_tool(outputs: dict) -> bool:
tool_calls = outputs["messages"][1].tool_calls
return bool(tool_calls and tool_calls[0]["name"] == "search")
experiment_results = await aevaluate(
target,
data="weather agent",
evaluators=[correct, right_tool],
max_concurrency=4, # optional
experiment_prefix="claude-3.5-baseline", # optional
)
```
If we need access to information about intermediate steps that isn't in state, we can look at the Run object. This contains the full traces for all node inputs and outputs:
See more about what arguments you can pass to custom evaluators in this [how-to guide](/langsmith/code-evaluator-ui).
```python theme={null}
from langsmith.schemas import Run, Example
def right_tool_from_run(run: Run, example: Example) -> dict:
# Get documents and answer
first_model_run = next(run for run in root_run.child_runs if run.name == "agent")
tool_calls = first_model_run.outputs["messages"][-1].tool_calls
right_tool = bool(tool_calls and tool_calls[0]["name"] == "search")
return {"key": "right_tool", "value": right_tool}
experiment_results = await aevaluate(
target,
data="weather agent",
evaluators=[correct, right_tool_from_run],
max_concurrency=4, # optional
experiment_prefix="claude-3.5-baseline", # optional
)
```
## Running and evaluating individual nodes
Sometimes you want to evaluate a single node directly to save time and costs. `langgraph` makes it easy to do this. In this case we can even continue using the evaluators we've been using.
```python theme={null}
node_target = example_to_state | app.nodes["agent"]
node_experiment_results = await aevaluate(
node_target,
data="weather agent",
evaluators=[right_tool_from_run],
max_concurrency=4, # optional
experiment_prefix="claude-3.5-model-node", # optional
)
```
## Related
* [`langgraph` evaluation docs](https://langchain-ai.github.io/langgraph/tutorials/#evaluation)
## Reference code
```python theme={null}
from typing import Annotated, Literal, TypedDict
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langgraph.prebuilt import ToolNode
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
from langsmith import Client, aevaluate
# Define a graph
class State(TypedDict):
# Messages have the type "list". The 'add_messages' function
# in the annotation defines how this state key should be updated
# (in this case, it appends messages to the list, rather than overwriting them)
messages: Annotated[list, add_messages]
# Define the tools for the agent to use
@tool
def search(query: str) -> str:
"""Call to surf the web."""
# This is a placeholder, but don't tell the LLM that...
if "sf" in query.lower() or "san francisco" in query.lower():
return "It's 60 degrees and foggy."
return "It's 90 degrees and sunny."
tools = [search]
tool_node = ToolNode(tools)
model = init_chat_model("claude-sonnet-4-5-20250929").bind_tools(tools)
# Define the function that determines whether to continue or not
def should_continue(state: State) -> Literal["tools", END]:
messages = state['messages']
last_message = messages[-1]
# If the LLM makes a tool call, then we route to the "tools" node
if last_message.tool_calls:
return "tools"
# Otherwise, we stop (reply to the user)
return END
# Define the function that calls the model
def call_model(state: State):
messages = state['messages']
response = model.invoke(messages)
# We return a list, because this will get added to the existing list
return {"messages": [response]}
# Define a new graph
workflow = StateGraph(State)
# Define the two nodes we will cycle between
workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)
# Set the entrypoint as 'agent'
# This means that this node is the first one called
workflow.add_edge(START, "agent")
# We now add a conditional edge
workflow.add_conditional_edges(
# First, we define the start node. We use 'agent'.
# This means these are the edges taken after the 'agent' node is called.
"agent",
# Next, we pass in the function that will determine which node is called next.
should_continue,
)
# We now add a normal edge from 'tools' to 'agent'.
# This means that after 'tools' is called, 'agent' node is called next.
workflow.add_edge("tools", 'agent')
# Finally, we compile it!
# This compiles it into a LangChain Runnable,
# meaning you can use it as you would any other runnable.
# Note that we're (optionally) passing the memory when compiling the graph
app = workflow.compile()
questions = [
"what's the weather in sf",
"whats the weather in san fran",
"whats the weather in tangier"
]
answers = [
"It's 60 degrees and foggy.",
"It's 60 degrees and foggy.",
"It's 90 degrees and sunny.",
]
# Create a dataset
ls_client = Client()
dataset = ls_client.create_dataset(
"weather agent",
inputs=[{"question": q} for q in questions],
outputs=[{"answers": a} for a in answers],
)
# Define evaluators
async def correct(outputs: dict, reference_outputs: dict) -> bool:
instructions = (
"Given an actual answer and an expected answer, determine whether"
" the actual answer contains all of the information in the"
" expected answer. Respond with 'CORRECT' if the actual answer"
" does contain all of the expected information and 'INCORRECT'"
" otherwise. Do not include anything else in your response."
)
# Our graph outputs a State dictionary, which in this case means
# we'll have a 'messages' key and the final message should
# be our actual answer.
actual_answer = outputs["messages"][-1].content
expected_answer = reference_outputs["answer"]
user_msg = (
f"ACTUAL ANSWER: {actual_answer}"
f"\n\nEXPECTED ANSWER: {expected_answer}"
)
response = await judge_llm.ainvoke(
[
{"role": "system", "content": instructions},
{"role": "user", "content": user_msg}
]
)
return response.content.upper() == "CORRECT"
def right_tool(outputs: dict) -> bool:
tool_calls = outputs["messages"][1].tool_calls
return bool(tool_calls and tool_calls[0]["name"] == "search")
# Run evaluation
experiment_results = await aevaluate(
target,
data="weather agent",
evaluators=[correct, right_tool],
max_concurrency=4, # optional
experiment_prefix="claude-3.5-baseline", # optional
)
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluate-graph.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to evaluate an LLM application
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluate-llm-application
This guide shows you how to run an evaluation on an LLM application using the LangSmith SDK.
[Evaluations](/langsmith/evaluation-concepts#applying-evaluations) | [Evaluators](/langsmith/evaluation-concepts#evaluators) | [Datasets](/langsmith/evaluation-concepts#datasets)
In this guide we'll go over how to evaluate an application using the [evaluate()](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._runner.evaluate) method in the LangSmith SDK.
For larger evaluation jobs in Python we recommend using [aevaluate()](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._arunner.aevaluate), the asynchronous version of [evaluate()](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._runner.evaluate). It is still worthwhile to read this guide first, as the two have identical interfaces, before reading the how-to guide on [running an evaluation asynchronously](/langsmith/evaluation-async).
In JS/TS evaluate() is already asynchronous so no separate method is needed.
It is also important to configure the `max_concurrency`/`maxConcurrency` arg when running large jobs. This parallelizes evaluation by effectively splitting the dataset across threads.
## Define an application
First we need an application to evaluate. Let's create a simple toxicity classifier for this example.
```python Python theme={null}
from langsmith import traceable, wrappers
from openai import OpenAI
# Optionally wrap the OpenAI client to trace all model calls.
oai_client = wrappers.wrap_openai(OpenAI())
# Optionally add the 'traceable' decorator to trace the inputs/outputs of this function.
@traceable
def toxicity_classifier(inputs: dict) -> dict:
instructions = (
"Please review the user query below and determine if it contains any form of toxic behavior, "
"such as insults, threats, or highly negative comments. Respond with 'Toxic' if it does "
"and 'Not toxic' if it doesn't."
)
messages = [
{"role": "system", "content": instructions},
{"role": "user", "content": inputs["text"]},
]
result = oai_client.chat.completions.create(
messages=messages, model="gpt-4.1-mini", temperature=0
)
return {"class": result.choices[0].message.content}
```
```typescript TypeScript theme={null}
import { OpenAI } from "openai";
import { wrapOpenAI } from "langsmith/wrappers";
import { traceable } from "langsmith/traceable";
// Optionally wrap the OpenAI client to trace all model calls.
const oaiClient = wrapOpenAI(new OpenAI());
// Optionally add the 'traceable' wrapper to trace the inputs/outputs of this function.
const toxicityClassifier = traceable(
async (text: string) => {
const result = await oaiClient.chat.completions.create({
messages: [
{
role: "system",
content: "Please review the user query below and determine if it contains any form of toxic behavior, such as insults, threats, or highly negative comments. Respond with 'Toxic' if it does, and 'Not toxic' if it doesn't.",
},
{ role: "user", content: text },
],
model: "gpt-4.1-mini",
temperature: 0,
});
return result.choices[0].message.content;
},
{ name: "toxicityClassifier" }
);
```
We've optionally enabled tracing to capture the inputs and outputs of each step in the pipeline. To understand how to annotate your code for tracing, please refer to [this guide](/langsmith/annotate-code).
## Create or select a dataset
We need a [Dataset](/langsmith/evaluation-concepts#datasets) to evaluate our application on. Our dataset will contain labeled [examples](/langsmith/evaluation-concepts#examples) of toxic and non-toxic text.
Requires `langsmith>=0.3.13`
```python Python theme={null}
from langsmith import Client
ls_client = Client()
examples = [
{
"inputs": {"text": "Shut up, idiot"},
"outputs": {"label": "Toxic"},
},
{
"inputs": {"text": "You're a wonderful person"},
"outputs": {"label": "Not toxic"},
},
{
"inputs": {"text": "This is the worst thing ever"},
"outputs": {"label": "Toxic"},
},
{
"inputs": {"text": "I had a great day today"},
"outputs": {"label": "Not toxic"},
},
{
"inputs": {"text": "Nobody likes you"},
"outputs": {"label": "Toxic"},
},
{
"inputs": {"text": "This is unacceptable. I want to speak to the manager."},
"outputs": {"label": "Not toxic"},
},
]
dataset = ls_client.create_dataset(dataset_name="Toxic Queries")
ls_client.create_examples(
dataset_id=dataset.id,
examples=examples,
)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
const langsmith = new Client();
// create a dataset
const labeledTexts = [
["Shut up, idiot", "Toxic"],
["You're a wonderful person", "Not toxic"],
["This is the worst thing ever", "Toxic"],
["I had a great day today", "Not toxic"],
["Nobody likes you", "Toxic"],
["This is unacceptable. I want to speak to the manager.", "Not toxic"],
];
const [inputs, outputs] = labeledTexts.reduce<
[Array<{ input: string }>, Array<{ outputs: string }>]
>(
([inputs, outputs], item) => [
[...inputs, { input: item[0] }],
[...outputs, { outputs: item[1] }],
],
[[], []]
);
const datasetName = "Toxic Queries";
const toxicDataset = await langsmith.createDataset(datasetName);
await langsmith.createExamples({ inputs, outputs, datasetId: toxicDataset.id });
```
For more details on datasets, refer to the [Manage datasets](/langsmith/manage-datasets) page.
## Define an evaluator
There are two main ways to define an evaluator.
### Locally in code
You can also check out LangChain's open source evaluation package [openevals](https://github.com/langchain-ai/openevals) for common pre-built evaluators.
[Evaluators](/langsmith/evaluation-concepts#evaluators) are functions for scoring your application's outputs. They take in the example inputs, actual outputs, and, when present, the reference outputs. Since we have labels for this task, our evaluator can directly check if the actual outputs match the reference outputs.
* Python: Requires `langsmith>=0.3.13`
* TypeScript: Requires `langsmith>=0.2.9`
```python Python theme={null}
def correct(inputs: dict, outputs: dict, reference_outputs: dict) -> bool:
return outputs["class"] == reference_outputs["label"]
```
```typescript TypeScript theme={null}
import type { EvaluationResult } from "langsmith/evaluation";
function correct({
outputs,
referenceOutputs,
}: {
outputs: Record;
referenceOutputs?: Record;
}): EvaluationResult {
const score = outputs.output === referenceOutputs?.outputs;
return { key: "correct", score };
}
```
### In LangSmith UI
You can also define an evaluator in the [LangSmith UI](https://smith.langchain.com). You can [create evaluators in the UI](/langsmith/llm-as-judge#ui) under the **Evaluators** tab. These evaluators will be [automatically triggered with every new experiment](/langsmith/bind-evaluator-to-dataset).
## Run the evaluation
We'll use the [evaluate()](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._runner.evaluate) / [aevaluate()](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._arunner.aevaluate) methods to run the evaluation.
The key arguments are:
* a target function that takes an input dictionary and returns an output dictionary. The `example.inputs` field of each [Example](/langsmith/example-data-format) is what gets passed to the target function. In this case our `toxicity_classifier` is already set up to take in example inputs so we can use it directly.
* `data` - the name OR UUID of the LangSmith dataset to evaluate on, or an iterator of examples.
* `evaluators` - a list of evaluators to score the outputs of the function; dataset evaluators in the [Langsmith UI](https://smith.langchain.com) will also automatically get triggered.
Python: Requires `langsmith>=0.3.13`
```python Python theme={null}
# Can equivalently use the 'evaluate' function directly:
# from langsmith import evaluate; evaluate(...)
results = ls_client.evaluate(
toxicity_classifier,
data=dataset.name,
evaluators=[correct],
experiment_prefix="gpt-4.1-mini, baseline", # optional, experiment name prefix
description="Testing the baseline system.", # optional, experiment description
max_concurrency=4, # optional, add concurrency
)
```
```typescript TypeScript theme={null}
import { evaluate } from "langsmith/evaluation";
await evaluate((inputs) => toxicityClassifier(inputs["input"]), {
data: datasetName,
evaluators: [correct],
experimentPrefix: "gpt-4.1-mini, baseline", // optional, experiment name prefix
maxConcurrency: 4, // optional, add concurrency
});
```
## Explore the results[](#explore-the-results "Direct link to Explore the results")
Each invocation of `evaluate()` creates an [Experiment](/langsmith/evaluation-concepts#experiments) which can be viewed in the LangSmith UI or queried via the SDK. Evaluation scores are stored against each actual output as feedback.
*If you've annotated your code for tracing, you can open the trace of each row in a side panel view.*
## Reference code[](#reference-code "Direct link to Reference code")
```python Python theme={null}
from langsmith import Client, traceable, wrappers
from openai import OpenAI
# Step 1. Define an application
oai_client = wrappers.wrap_openai(OpenAI())
@traceable
def toxicity_classifier(inputs: dict) -> str:
system = (
"Please review the user query below and determine if it contains any form of toxic behavior, "
"such as insults, threats, or highly negative comments. Respond with 'Toxic' if it does "
"and 'Not toxic' if it doesn't."
)
messages = [
{"role": "system", "content": system},
{"role": "user", "content": inputs["text"]},
]
result = oai_client.chat.completions.create(
messages=messages, model="gpt-4.1-mini", temperature=0
)
return result.choices[0].message.content
# Step 2. Create a dataset
ls_client = Client()
dataset = ls_client.create_dataset(dataset_name="Toxic Queries")
examples = [
{
"inputs": {"text": "Shut up, idiot"},
"outputs": {"label": "Toxic"},
},
{
"inputs": {"text": "You're a wonderful person"},
"outputs": {"label": "Not toxic"},
},
{
"inputs": {"text": "This is the worst thing ever"},
"outputs": {"label": "Toxic"},
},
{
"inputs": {"text": "I had a great day today"},
"outputs": {"label": "Not toxic"},
},
{
"inputs": {"text": "Nobody likes you"},
"outputs": {"label": "Toxic"},
},
{
"inputs": {"text": "This is unacceptable. I want to speak to the manager."},
"outputs": {"label": "Not toxic"},
},
]
ls_client.create_examples(
dataset_id=dataset.id,
examples=examples,
)
# Step 3. Define an evaluator
def correct(inputs: dict, outputs: dict, reference_outputs: dict) -> bool:
return outputs["output"] == reference_outputs["label"]
# Step 4. Run the evaluation
# Client.evaluate() and evaluate() behave the same.
results = ls_client.evaluate(
toxicity_classifier,
data=dataset.name,
evaluators=[correct],
experiment_prefix="gpt-4.1-mini, simple", # optional, experiment name prefix
description="Testing the baseline system.", # optional, experiment description
max_concurrency=4, # optional, add concurrency
)
```
```typescript TypeScript theme={null}
import { OpenAI } from "openai";
import { Client } from "langsmith";
import { evaluate, EvaluationResult } from "langsmith/evaluation";
import type { Run, Example } from "langsmith/schemas";
import { traceable } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers";
const oaiClient = wrapOpenAI(new OpenAI());
const toxicityClassifier = traceable(
async (text: string) => {
const result = await oaiClient.chat.completions.create({
messages: [
{
role: "system",
content: "Please review the user query below and determine if it contains any form of toxic behavior, such as insults, threats, or highly negative comments. Respond with 'Toxic' if it does, and 'Not toxic' if it doesn't.",
},
{ role: "user", content: text },
],
model: "gpt-4.1-mini",
temperature: 0,
});
return result.choices[0].message.content;
},
{ name: "toxicityClassifier" }
);
const langsmith = new Client();
// create a dataset
const labeledTexts = [
["Shut up, idiot", "Toxic"],
["You're a wonderful person", "Not toxic"],
["This is the worst thing ever", "Toxic"],
["I had a great day today", "Not toxic"],
["Nobody likes you", "Toxic"],
["This is unacceptable. I want to speak to the manager.", "Not toxic"],
];
const [inputs, outputs] = labeledTexts.reduce<
[Array<{ input: string }>, Array<{ outputs: string }>]
>(
([inputs, outputs], item) => [
[...inputs, { input: item[0] }],
[...outputs, { outputs: item[1] }],
],
[[], []]
);
const datasetName = "Toxic Queries";
const toxicDataset = await langsmith.createDataset(datasetName);
await langsmith.createExamples({ inputs, outputs, datasetId: toxicDataset.id });
// Row-level evaluator
function correct({
outputs,
referenceOutputs,
}: {
outputs: Record;
referenceOutputs?: Record;
}): EvaluationResult {
const score = outputs.output === referenceOutputs?.outputs;
return { key: "correct", score };
}
await evaluate((inputs) => toxicityClassifier(inputs["input"]), {
data: datasetName,
evaluators: [correct],
experimentPrefix: "gpt-4.1-mini, simple", // optional, experiment name prefix
maxConcurrency: 4, // optional, add concurrency
});
```
## Related[](#related "Direct link to Related")
* [Run an evaluation asynchronously](/langsmith/evaluation-async)
* [Run an evaluation via the REST API](/langsmith/run-evals-api-only)
* [Run an evaluation from the prompt playground](/langsmith/run-evaluation-from-prompt-playground)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluate-llm-application.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to evaluate an application's intermediate steps
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluate-on-intermediate-steps
While, in many scenarios, it is sufficient to evaluate the final output of your task, in some cases you might want to evaluate the intermediate steps of your pipeline.
For example, for retrieval-augmented generation (RAG), you might want to
1. Evaluate the retrieval step to ensure that the correct documents are retrieved w\.r.t the input query.
2. Evaluate the generation step to ensure that the correct answer is generated w\.r.t the retrieved documents.
In this guide, we will use a simple, fully-custom evaluator for evaluating criteria 1 and an LLM-based evaluator for evaluating criteria 2 to highlight both scenarios.
In order to evaluate the intermediate steps of your pipeline, your evaluator function should traverse and process the `run`/`rootRun` argument, which is a `Run` object that contains the intermediate steps of your pipeline.
## 1. Define your LLM pipeline
The below RAG pipeline consists of 1) generating a Wikipedia query given the input question, 2) retrieving relevant documents from Wikipedia, and 3) generating an answer given the retrieved documents.
```bash Python theme={null}
pip install -U langsmith langchain[openai] wikipedia
```
```bash TypeScript theme={null}
yarn add langsmith langchain @langchain/openai wikipedia
```
Requires `langsmith>=0.3.13`
```python Python theme={null}
import wikipedia as wp
from openai import OpenAI
from langsmith import traceable, wrappers
oai_client = wrappers.wrap_openai(OpenAI())
@traceable
def generate_wiki_search(question: str) -> str:
"""Generate the query to search in wikipedia."""
instructions = (
"Generate a search query to pass into wikipedia to answer the user's question. "
"Return only the search query and nothing more. "
"This will passed in directly to the wikipedia search engine."
)
messages = [
{"role": "system", "content": instructions},
{"role": "user", "content": question}
]
result = oai_client.chat.completions.create(
messages=messages,
model="gpt-4.1-mini",
temperature=0,
)
return result.choices[0].message.content
@traceable(run_type="retriever")
def retrieve(query: str) -> list:
"""Get up to two search wikipedia results."""
results = []
for term in wp.search(query, results = 10):
try:
page = wp.page(term, auto_suggest=False)
results.append({
"page_content": page.summary,
"type": "Document",
"metadata": {"url": page.url}
})
except wp.DisambiguationError:
pass
if len(results) >= 2:
return results
@traceable
def generate_answer(question: str, context: str) -> str:
"""Answer the question based on the retrieved information."""
instructions = f"Answer the user's question based ONLY on the content below:\n\n{context}"
messages = [
{"role": "system", "content": instructions},
{"role": "user", "content": question}
]
result = oai_client.chat.completions.create(
messages=messages,
model="gpt-4.1-mini",
temperature=0
)
return result.choices[0].message.content
@traceable
def qa_pipeline(question: str) -> str:
"""The full pipeline."""
query = generate_wiki_search(question)
context = "\n\n".join([doc["page_content"] for doc in retrieve(query)])
return generate_answer(question, context)
```
```typescript TypeScript theme={null}
import OpenAI from "openai";
import wiki from "wikipedia";
import { Client } from "langsmith";
import { traceable } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers";
const openai = wrapOpenAI(new OpenAI());
const generateWikiSearch = traceable(
async (input: { question: string }) => {
const messages = [
{
role: "system" as const,
content:
"Generate a search query to pass into Wikipedia to answer the user's question. Return only the search query and nothing more. This will be passed in directly to the Wikipedia search engine.",
},
{ role: "user" as const, content: input.question },
];
const chatCompletion = await openai.chat.completions.create({
model: "gpt-4.1-mini",
messages: messages,
temperature: 0,
});
return chatCompletion.choices[0].message.content ?? "";
},
{ name: "generateWikiSearch" }
);
const retrieve = traceable(
async (input: { query: string; numDocuments: number }) => {
const { results } = await wiki.search(input.query, { limit: 10 });
const finalResults: Array<{
page_content: string;
type: "Document";
metadata: { url: string };
}> = [];
for (const result of results) {
if (finalResults.length >= input.numDocuments) {
// Just return the top 2 pages for now
break;
}
const page = await wiki.page(result.title, { autoSuggest: false });
const summary = await page.summary();
finalResults.push({
page_content: summary.extract,
type: "Document",
metadata: { url: page.fullurl },
});
}
return finalResults;
},
{ name: "retrieve", run_type: "retriever" }
);
const generateAnswer = traceable(
async (input: { question: string; context: string }) => {
const messages = [
{
role: "system" as const,
content: `Answer the user's question based only on the content below:\n\n${input.context}`,
},
{ role: "user" as const, content: input.question },
];
const chatCompletion = await openai.chat.completions.create({
model: "gpt-4.1-mini",
messages: messages,
temperature: 0,
});
return chatCompletion.choices[0].message.content ?? "";
},
{ name: "generateAnswer" }
);
const ragPipeline = traceable(
async ({ question }: { question: string }, numDocuments: number = 2) => {
const query = await generateWikiSearch({ question });
const retrieverResults = await retrieve({ query, numDocuments });
const context = retrieverResults
.map((result) => result.page_content)
.join("\n\n");
const answer = await generateAnswer({ question, context });
return answer;
},
{ name: "ragPipeline" }
);
```
This pipeline will produce a trace that looks something like:
## 2. Create a dataset and examples to evaluate the pipeline
We are building a very simple dataset with a couple of examples to evaluate the pipeline.
Requires `langsmith>=0.3.13`
```python Python theme={null}
from langsmith import Client
ls_client = Client()
dataset_name = "Wikipedia RAG"
if not ls_client.has_dataset(dataset_name=dataset_name):
dataset = ls_client.create_dataset(dataset_name=dataset_name)
examples = [
{"inputs": {"question": "What is LangChain?"}},
{"inputs": {"question": "What is LangSmith?"}},
]
ls_client.create_examples(
dataset_id=dataset.id,
examples=examples,
)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
const client = new Client();
const examples = [
[
"What is LangChain?",
"LangChain is an open-source framework for building applications using large language models.",
],
[
"What is LangSmith?",
"LangSmith is an observability and evaluation tool for LLM products, built by LangChain Inc.",
],
];
const datasetName = "Wikipedia RAG";
const inputs = examples.map(([input, _]) => ({ input }));
const outputs = examples.map(([_, expected]) => ({ expected }));
const dataset = await client.createDataset(datasetName);
await client.createExamples({ datasetId: dataset.id, inputs, outputs });
```
## 3. Define your custom evaluators
As mentioned above, we will define two evaluators: one that evaluates the relevance of the retrieved documents w\.r.t the input query and another that evaluates the hallucination of the generated answer w\.r.t the retrieved documents. We will be using LangChain LLM wrappers, along with [`with_structured_output`](https://reference.langchain.com/python/langchain_core/language_models/#langchain_core.language_models.chat_models.BaseChatModel.with_structured_output) to define the evaluator for hallucination.
The key here is that the evaluator function should traverse the `run` / `rootRun` argument to access the intermediate steps of the pipeline. The evaluator can then process the inputs and outputs of the intermediate steps to evaluate according to the desired criteria.
Example uses `langchain` for convenience, this is not required.
```python Python theme={null}
from langchain.chat_models import init_chat_model
from langsmith.schemas import Run
from pydantic import BaseModel, Field
def document_relevance(run: Run) -> bool:
"""Checks if retriever input exists in the retrieved docs."""
qa_pipeline_run = next(
r for run in run.child_runs if r.name == "qa_pipeline"
)
retrieve_run = next(
r for run in qa_pipeline_run.child_runs if r.name == "retrieve"
)
page_contents = "\n\n".join(
doc["page_content"] for doc in retrieve_run.outputs["output"]
)
return retrieve_run.inputs["query"] in page_contents
# Data model
class GradeHallucinations(BaseModel):
"""Binary score for hallucination present in generation answer."""
is_grounded: bool = Field(..., description="True if the answer is grounded in the facts, False otherwise.")
# LLM with structured output for grading hallucinations
# For more see: https://docs.langchain.com/oss/python/langchain/structured-output
grader_llm= init_chat_model("gpt-4.1-mini", temperature=0).with_structured_output(
GradeHallucinations,
method="json_schema",
strict=True,
)
def no_hallucination(run: Run) -> bool:
"""Check if the answer is grounded in the documents.
Return True if there is no hallucination, False otherwise.
"""
# Get documents and answer
qa_pipeline_run = next(
r for r in run.child_runs if r.name == "qa_pipeline"
)
retrieve_run = next(
r for r in qa_pipeline_run.child_runs if r.name == "retrieve"
)
retrieved_content = "\n\n".join(
doc["page_content"] for doc in retrieve_run.outputs["output"]
)
# Construct prompt
instructions = (
"You are a grader assessing whether an LLM generation is grounded in / "
"supported by a set of retrieved facts. Give a binary score 1 or 0, "
"where 1 means that the answer is grounded in / supported by the set of facts."
)
messages = [
{"role": "system", "content": instructions},
{"role": "user", "content": f"Set of facts:\n{retrieved_content}\n\nLLM generation: {run.outputs['answer']}"},
]
grade = grader_llm.invoke(messages)
return grade.is_grounded
```
```typescript TypeScript theme={null}
import { EvaluationResult } from "langsmith/evaluation";
import { Run, Example } from "langsmith/schemas";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
function findNestedRun(run: Run, search: (run: Run) => boolean): Run | null {
const queue: Run[] = [run];
while (queue.length > 0) {
const currentRun = queue.shift()!;
if (search(currentRun)) return currentRun;
queue.push(...currentRun.child_runs);
}
return null;
}
// A very simple evaluator that checks to see if the input of the retrieval step exists
// in the retrieved docs.
function documentRelevance(rootRun: Run, example: Example): EvaluationResult {
const retrieveRun = findNestedRun(rootRun, (run) => run.name === "retrieve");
const docs: Array<{ page_content: string }> | undefined =
retrieveRun.outputs?.outputs;
const pageContents = docs?.map((doc) => doc.page_content).join("\n\n");
const score = pageContents.includes(retrieveRun.inputs?.query);
return { key: "simple_document_relevance", score };
}
async function hallucination(
rootRun: Run,
example: Example
): Promise {
const rag = findNestedRun(rootRun, (run) => run.name === "ragPipeline");
const retrieve = findNestedRun(rootRun, (run) => run.name === "retrieve");
const docs: Array<{ page_content: string }> | undefined =
retrieve.outputs?.outputs;
const documents = docs?.map((doc) => doc.page_content).join("\n\n");
const prompt = ChatPromptTemplate.fromMessages<{
documents: string;
generation: string;
}>([
[
"system",
[
`You are a grader assessing whether an LLM generation is grounded in / supported by a set of retrieved facts. \n`,
`Give a binary score 1 or 0, where 1 means that the answer is grounded in / supported by the set of facts.`,
].join("\n"),
],
[
"human",
"Set of facts: \n\n {documents} \n\n LLM generation: {generation}",
],
]);
const llm = new ChatOpenAI({
model: "gpt-4.1-mini",
temperature: 0,
}).withStructuredOutput(
z
.object({
binary_score: z
.number()
.describe("Answer is grounded in the facts, 1 or 0"),
})
.describe("Binary score for hallucination present in generation answer.")
);
const grader = prompt.pipe(llm);
const score = await grader.invoke({
documents,
generation: rag.outputs?.outputs,
});
return { key: "answer_hallucination", score: score.binary_score };
}
```
## 4. Evaluate the pipeline
Finally, we'll run `evaluate` with the custom evaluators defined above.
```python Python theme={null}
def qa_wrapper(inputs: dict) -> dict:
"""Wrap the qa_pipeline so it can accept the Example.inputs dict as input."""
return {"answer": qa_pipeline(inputs["question"])}
experiment_results = ls_client.evaluate(
qa_wrapper,
data=dataset_name,
evaluators=[document_relevance, no_hallucination],
experiment_prefix="rag-wiki-oai"
)
```
```typescript TypeScript theme={null}
import { evaluate } from "langsmith/evaluation";
await evaluate((inputs) => ragPipeline({ question: inputs.input }), {
data: datasetName,
evaluators: [hallucination, documentRelevance],
experimentPrefix: "rag-wiki-oai",
});
```
The experiment will contain the results of the evaluation, including the scores and comments from the evaluators:
## Related
* [Evaluate a `langgraph` graph](/langsmith/evaluate-on-intermediate-steps)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluate-on-intermediate-steps.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to run a pairwise evaluation
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluate-pairwise
Concept: [Pairwise evaluations](/langsmith/evaluation-concepts#pairwise)
LangSmith supports evaluating **existing** experiments in a comparative manner. Instead of evaluating one output at a time, you can score the output from multiple experiments against each other. In this guide, you'll use [`evaluate()`](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._runner.evaluate) with two existing experiments to [define an evaluator](#define-a-pairwise-evaluator) and [run a pairwise evaluation](#run-a-pairwise-evaluation). Finally, you'll use the LangSmith UI to [view the pairwise experiments](#view-pairwise-experiments).
## Prerequisites
* If you haven't already created experiments to compare, check out the [quick start](/langsmith/evaluation-quickstart) or the [how-to guide](/langsmith/evaluate-llm-application) to get started with evaluations.
* This guide requires `langsmith` Python version `>=0.2.0` or JS version `>=0.2.9`.
You can also use [`evaluate_comparative()`](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._runner.evaluate_comparative) with more than two existing experiments.
## `evaluate()` comparative args
At its simplest, `evaluate` / `aevaluate` function takes the following arguments:
| Argument | Description |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `target` | A list of the two **existing experiments** you would like to evaluate against each other. These can be uuids or experiment names. |
| `evaluators` | A list of the pairwise evaluators that you would like to attach to this evaluation. See the section below for how to define these. |
Along with these, you can also pass in the following optional args:
| Argument | Description |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `randomize_order` / `randomizeOrder` | An optional boolean indicating whether the order of the outputs should be randomized for each evaluation. This is a strategy for minimizing positional bias in your prompt: often, the LLM will be biased towards one of the responses based on the order. This should mainly be addressed via prompt engineering, but this is another optional mitigation. Defaults to False. |
| `experiment_prefix` / `experimentPrefix` | A prefix to be attached to the beginning of the pairwise experiment name. Defaults to None. |
| `description` | A description of the pairwise experiment. Defaults to None. |
| `max_concurrency` / `maxConcurrency` | The maximum number of concurrent evaluations to run. Defaults to 5. |
| `client` | The LangSmith client to use. Defaults to None. |
| `metadata` | Metadata to attach to your pairwise experiment. Defaults to None. |
| `load_nested` / `loadNested` | Whether to load all child runs for the experiment. When False, only the root trace will be passed to your evaluator. Defaults to False. |
## Define a pairwise evaluator
Pairwise evaluators are just functions with an expected signature.
### Evaluator args
Custom evaluator functions must have specific argument names. They can take any subset of the following arguments:
* `inputs: dict`: A dictionary of the inputs corresponding to a single example in a dataset.
* `outputs: list[dict]`: A two-item list of the dict outputs produced by each experiment on the given inputs.
* `reference_outputs` / `referenceOutputs: dict`: A dictionary of the reference outputs associated with the example, if available.
* `runs: list[Run]`: A two-item list of the full [Run](/langsmith/run-data-format) objects generated by the two experiments on the given example. Use this if you need access to intermediate steps or metadata about each run.
* `example: Example`: The full dataset [Example](/langsmith/example-data-format), including the example inputs, outputs (if available), and metadata (if available).
For most use cases you'll only need `inputs`, `outputs`, and `reference_outputs` / `referenceOutputs`. `runs` and `example` are useful only if you need some extra trace or example metadata outside of the actual inputs and outputs of the application.
### Evaluator output
Custom evaluators are expected to return one of the following types:
Python and JS/TS
* `dict`: dictionary with keys:
* `key`, which represents the feedback key that will be logged
* `scores`, which is a mapping from run ID to score for that run.
* `comment`, which is a string. Most commonly used for model reasoning.
Currently Python only
* `list[int | float | bool]`: a two-item list of scores. The list is assumed to have the same order as the `runs` / `outputs` evaluator args. The evaluator function name is used for the feedback key.
Note that you should choose a feedback key that is distinct from standard feedbacks on your run. We recommend prefixing pairwise feedback keys with `pairwise_` or `ranked_`.
## Run a pairwise evaluation
The following example uses [a prompt](https://smith.langchain.com/hub/langchain-ai/pairwise-evaluation-2) which asks the LLM to decide which is better between two AI assistant responses. It uses structured output to parse the AI's response: 0, 1, or 2.
In the Python example below, we are pulling [this structured prompt](https://smith.langchain.com/hub/langchain-ai/pairwise-evaluation-2) from the [LangChain Hub](/langsmith/manage-prompts#public-prompt-hub) and using it with a LangChain chat model wrapper.
**Usage of LangChain is totally optional.** To illustrate this point, the TypeScript example uses the OpenAI SDK directly.
* Python: Requires `langsmith>=0.2.0`
* TypeScript: Requires `langsmith>=0.2.9`
```python Python theme={null}
from langchain_classic import hub
from langchain.chat_models import init_chat_model
from langsmith import evaluate
# See the prompt: https://smith.langchain.com/hub/langchain-ai/pairwise-evaluation-2
prompt = hub.pull("langchain-ai/pairwise-evaluation-2")
model = init_chat_model("gpt-4.1")
chain = prompt | model
def ranked_preference(inputs: dict, outputs: list[dict]) -> list:
# Assumes example inputs have a 'question' key and experiment
# outputs have an 'answer' key.
response = chain.invoke({
"question": inputs["question"],
"answer_a": outputs[0].get("answer", "N/A"),
"answer_b": outputs[1].get("answer", "N/A"),
})
if response["Preference"] == 1:
scores = [1, 0]
elif response["Preference"] == 2:
scores = [0, 1]
else:
scores = [0, 0]
return scores
evaluate(
("experiment-1", "experiment-2"), # Replace with the names/IDs of your experiments
evaluators=[ranked_preference],
randomize_order=True,
max_concurrency=4,
)
```
```typescript TypeScript theme={null}
import { evaluate} from "langsmith/evaluation";
import { Run } from "langsmith/schemas";
import { wrapOpenAI } from "langsmith/wrappers";
import OpenAI from "openai";
import { z } from "zod";
const openai = wrapOpenAI(new OpenAI());
async function rankedPreference({
inputs,
runs,
}: {
inputs: Record;
runs: Run[];
}) {
const scores: Record = {};
const [runA, runB] = runs;
if (!runA || !runB) throw new Error("Expected at least two runs");
const payload = {
question: inputs.question,
answer_a: runA?.outputs?.output ?? "N/A",
answer_b: runB?.outputs?.output ?? "N/A",
};
const output = await openai.chat.completions.create({
model: "gpt-4-turbo",
messages: [
{
role: "system",
content: [
"Please act as an impartial judge and evaluate the quality of the responses provided by two AI assistants to the user question displayed below.",
"You should choose the assistant that follows the user's instructions and answers the user's question better.",
"Your evaluation should consider factors such as the helpfulness, relevance, accuracy, depth, creativity, and level of detail of their responses.",
"Begin your evaluation by comparing the two responses and provide a short explanation.",
"Avoid any position biases and ensure that the order in which the responses were presented does not influence your decision.",
"Do not allow the length of the responses to influence your evaluation. Do not favor certain names of the assistants. Be as objective as possible.",
].join(" "),
},
{
role: "user",
content: [
`[User Question] ${payload.question}`,
`[The Start of Assistant A's Answer] ${payload.answer_a} [The End of Assistant A's Answer]`,
`The Start of Assistant B's Answer] ${payload.answer_b} [The End of Assistant B's Answer]`,
].join("\n\n"),
},
],
tool_choice: {
type: "function",
function: { name: "Score" },
},
tools: [
{
type: "function",
function: {
name: "Score",
description: [
`After providing your explanation, output your final verdict by strictly following this format:`,
`Output "1" if Assistant A answer is better based upon the factors above.`,
`Output "2" if Assistant B answer is better based upon the factors above.`,
`Output "0" if it is a tie.`,
].join(" "),
parameters: {
type: "object",
properties: {
Preference: {
type: "integer",
description: "Which assistant answer is preferred?",
},
},
},
},
},
],
});
const { Preference } = z
.object({ Preference: z.number() })
.parse(
JSON.parse(output.choices[0].message.tool_calls[0].function.arguments)
);
if (Preference === 1) {
scores[runA.id] = 1;
scores[runB.id] = 0;
} else if (Preference === 2) {
scores[runA.id] = 0;
scores[runB.id] = 1;
} else {
scores[runA.id] = 0;
scores[runB.id] = 0;
}
return { key: "ranked_preference", scores };
}
await evaluate(["earnest-name-40", "reflecting-pump-91"], {
evaluators: [rankedPreference],
});
```
## View pairwise experiments
Navigate to the "Pairwise Experiments" tab from the dataset page:
Click on a pairwise experiment that you would like to inspect, and you will be brought to the Comparison View:
You may filter to runs where the first experiment was better or vice versa by clicking the thumbs up/thumbs down buttons in the table header:
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluate-pairwise.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Evaluate a RAG application
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluate-rag-tutorial
[RAG evaluation](/langsmith/evaluation-concepts#retrieval-augmented-generation-rag) | [Evaluators](/langsmith/evaluation-concepts#evaluators) | [LLM-as-judge evaluators](/langsmith/evaluation-concepts#llm-as-judge)
Retrieval Augmented Generation (RAG) is a technique that enhances Large Language Models (LLMs) by providing them with relevant external knowledge. It has become one of the most widely used approaches for building LLM applications.
This tutorial will show you how to evaluate your RAG applications using LangSmith. You'll learn:
1. How to create test datasets
2. How to run your RAG application on those datasets
3. How to measure your application's performance using different evaluation metrics
## Overview
A typical RAG evaluation workflow consists of three main steps:
1. Creating a dataset with questions and their expected answers
2. Running your RAG application on those questions
3. Using evaluators to measure how well your application performed, looking at factors like:
* Answer relevance
* Answer accuracy
* Retrieval quality
For this tutorial, we'll create and evaluate a bot that answers questions about a few of [Lilian Weng's](https://lilianweng.github.io/) insightful blog posts.
## Setup
### Environment
First, let's set our environment variables:
```python Python theme={null}
import os
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "YOUR LANGSMITH API KEY"
os.environ["OPENAI_API_KEY"] = "YOUR OPENAI API KEY"
```
```typescript TypeScript theme={null}
process.env.LANGSMITH_TRACING = "true";
process.env.LANGSMITH_API_KEY = "YOUR LANGSMITH API KEY";
process.env.OPENAI_API_KEY = "YOUR OPENAI API KEY";
```
And install the dependencies we'll need:
```bash Python theme={null}
pip install -U langsmith langchain[openai] langchain-community
```
```bash TypeScript theme={null}
yarn add langsmith langchain @langchain/community @langchain/openai
```
### Application
While this tutorial uses LangChain, the evaluation techniques and LangSmith functionality demonstrated here work with any framework. Feel free to use your preferred tools and libraries.
In this section, we'll build a basic Retrieval-Augmented Generation (RAG) application.
We'll stick to a simple implementation that:
* Indexing: chunks and indexes a few of Lilian Weng's blogs in a vector store
* Retrieval: retrieves those chunks based on the user question
* Generation: passes the question and retrieved docs to an LLM.
#### Indexing and retrieval
First, lets load the blog posts we want to build a chatbot for and index them.
```python Python theme={null}
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
# List of URLs to load documents from
urls = [
"https://lilianweng.github.io/posts/2023-06-23-agent/",
"https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/",
"https://lilianweng.github.io/posts/2023-10-25-adv-attack-llm/",
]
# Load documents from the URLs
docs = [WebBaseLoader(url).load() for url in urls]
docs_list = [item for sublist in docs for item in sublist]
# Initialize a text splitter with specified chunk size and overlap
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=250, chunk_overlap=0
)
# Split the documents into chunks
doc_splits = text_splitter.split_documents(docs_list)
# Add the document chunks to the "vector store" using OpenAIEmbeddings
vectorstore = InMemoryVectorStore.from_documents(
documents=doc_splits,
embedding=OpenAIEmbeddings(),
)
# With langchain we can easily turn any vector store into a retrieval component:
retriever = vectorstore.as_retriever(k=6)
```
```typescript TypeScript theme={null}
import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";
import { BrowserbaseLoader } from "@langchain/community/document_loaders/web/browserbase";
import { RecursiveCharacterTextSplitter } from "@langchain/text_splitters";
// List of URLs to load documents from
const urls = [
"https://lilianweng.github.io/posts/2023-06-23-agent/",
"https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/",
"https://lilianweng.github.io/posts/2023-10-25-adv-attack-llm/",
]
const loader = new BrowserbaseLoader(urls, {
textContent: true,
});
const docs = await loader.load();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000, chunkOverlap: 200
});
const allSplits = await splitter.splitDocuments(docs);
const embeddings = new OpenAIEmbeddings({
model: "text-embedding-3-large"
});
const vectorStore = new MemoryVectorStore(embeddings); // Index chunks
await vectorStore.addDocuments(allSplits)
```
#### Generation
We can now define the generative pipeline.
```python Python theme={null}
from langchain_openai import ChatOpenAI
from langsmith import traceable
llm = ChatOpenAI(model="gpt-4.1", temperature=1)
# Add decorator so this function is traced in LangSmith
@traceable()
def rag_bot(question: str) -> dict:
# LangChain retriever will be automatically traced
docs = retriever.invoke(question)
docs_string = "".join(doc.page_content for doc in docs)
instructions = f"""You are a helpful assistant who is good at analyzing source information and answering questions.
Use the following source documents to answer the user's questions.
If you don't know the answer, just say that you don't know.
Use three sentences maximum and keep the answer concise.
Documents:
{docs_string}"""
# langchain ChatModel will be automatically traced
ai_msg = llm.invoke([
{"role": "system", "content": instructions},
{"role": "user", "content": question},
],
)
return {"answer": ai_msg.content, "documents": docs}
```
```typescript TypeScript theme={null}
import { ChatOpenAI } from "@langchain/openai";
import { traceable } from "langsmith/traceable";
const llm = new ChatOpenAI({
model: "gpt-4.1",
temperature: 1,
})
// Add decorator so this function is traced in LangSmith
const ragBot = traceable(
async (question: string) => {
// LangChain retriever will be automatically traced
const retrievedDocs = await vectorStore.similaritySearch(question);
const docsContent = retrievedDocs.map((doc) => doc.pageContent).join("");
const instructions = `You are a helpful assistant who is good at analyzing source information and answering questions
Use the following source documents to answer the user's questions.
If you don't know the answer, just say that you don't know.
Use three sentences maximum and keep the answer concise.
Documents:
${docsContent}`;
const aiMsg = await llm.invoke([
{
role: "system",
content: instructions
},
{
role: "user",
content: question
}
])
return {"answer": aiMsg.content, "documents": retrievedDocs}
}
)
```
## Dataset
Now that we've got our application, let's build a dataset to evaluate it. Our dataset will be very simple in this case: we'll have example questions and reference answers.
```python Python theme={null}
from langsmith import Client
client = Client()
# Define the examples for the dataset
examples = [
{
"inputs": {"question": "How does the ReAct agent use self-reflection? "},
"outputs": {"answer": "ReAct integrates reasoning and acting, performing actions - such tools like Wikipedia search API - and then observing / reasoning about the tool outputs."},
},
{
"inputs": {"question": "What are the types of biases that can arise with few-shot prompting?"},
"outputs": {"answer": "The biases that can arise with few-shot prompting include (1) Majority label bias, (2) Recency bias, and (3) Common token bias."},
},
{
"inputs": {"question": "What are five types of adversarial attacks?"},
"outputs": {"answer": "Five types of adversarial attacks are (1) Token manipulation, (2) Gradient based attack, (3) Jailbreak prompting, (4) Human red-teaming, (5) Model red-teaming."},
},
]
# Create the dataset and examples in LangSmith
dataset_name = "Lilian Weng Blogs Q&A"
dataset = client.create_dataset(dataset_name=dataset_name)
client.create_examples(
dataset_id=dataset.id,
examples=examples
)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
const client = new Client();
// Define the examples for the dataset
const examples = [
[
"How does the ReAct agent use self-reflection? ",
"ReAct integrates reasoning and acting, performing actions - such tools like Wikipedia search API - and then observing / reasoning about the tool outputs.",
],
[
"What are the types of biases that can arise with few-shot prompting?",
"The biases that can arise with few-shot prompting include (1) Majority label bias, (2) Recency bias, and (3) Common token bias.",
],
[
"What are five types of adversarial attacks?",
"Five types of adversarial attacks are (1) Token manipulation, (2) Gradient based attack, (3) Jailbreak prompting, (4) Human red-teaming, (5) Model red-teaming.",
]
]
const [inputs, outputs] = examples.reduce<[Array<{ input: string }>, Array<{ outputs: string }>]>(
([inputs, outputs], item) => [
[...inputs, { input: item[0] }],
[...outputs, { outputs: item[1] }],
],
[[], []]
);
const datasetName = "Lilian Weng Blogs Q&A";
const dataset = await client.createDataset(datasetName);
await client.createExamples({ inputs, outputs, datasetId: dataset.id })
```
## Evaluators
One way to think about different types of RAG evaluators is as a tuple of what is being evaluated X what its being evaluated against:
1. **Correctness**: Response vs reference answer
* `Goal`: Measure "*how similar/correct is the RAG chain answer, relative to a ground-truth answer*"
* `Mode`: Requires a ground truth (reference) answer supplied through a dataset
* `Evaluator`: Use LLM-as-judge to assess answer correctness.
2. **Relevance**: Response vs input
* `Goal`: Measure "*how well does the generated response address the initial user input*"
* `Mode`: Does not require reference answer, because it will compare the answer to the input question
* `Evaluator`: Use LLM-as-judge to assess answer relevance, helpfulness, etc.
3. **Groundedness**: Response vs retrieved docs
* `Goal`: Measure "*to what extent does the generated response agree with the retrieved context*"
* `Mode`: Does not require reference answer, because it will compare the answer to the retrieved context
* `Evaluator`: Use LLM-as-judge to assess faithfulness, hallucinations, etc.
4. **Retrieval relevance**: Retrieved docs vs input
* `Goal`: Measure "*how relevant are my retrieved results for this query*"
* `Mode`: Does not require reference answer, because it will compare the question to the retrieved context
* `Evaluator`: Use LLM-as-judge to assess relevance
### Correctness: Response vs reference answer
```python Python theme={null}
from typing_extensions import Annotated, TypedDict
# Grade output schema
class CorrectnessGrade(TypedDict):
# Note that the order in the fields are defined is the order in which the model will generate them.
# It is useful to put explanations before responses because it forces the model to think through
# its final response before generating it:
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
correct: Annotated[bool, ..., "True if the answer is correct, False otherwise."]
# Grade prompt
correctness_instructions = """You are a teacher grading a quiz. You will be given a QUESTION, the GROUND TRUTH (correct) ANSWER, and the STUDENT ANSWER. Here is the grade criteria to follow:
(1) Grade the student answers based ONLY on their factual accuracy relative to the ground truth answer. (2) Ensure that the student answer does not contain any conflicting statements.
(3) It is OK if the student answer contains more information than the ground truth answer, as long as it is factually accurate relative to the ground truth answer.
Correctness:
A correctness value of True means that the student's answer meets all of the criteria.
A correctness value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset."""
# Grader LLM
grader_llm = ChatOpenAI(model="gpt-4.1", temperature=0).with_structured_output(
CorrectnessGrade, method="json_schema", strict=True
)
def correctness(inputs: dict, outputs: dict, reference_outputs: dict) -> bool:
"""An evaluator for RAG answer accuracy"""
answers = f"""\
QUESTION: {inputs['question']}
GROUND TRUTH ANSWER: {reference_outputs['answer']}
STUDENT ANSWER: {outputs['answer']}"""
# Run evaluator
grade = grader_llm.invoke([
{"role": "system", "content": correctness_instructions},
{"role": "user", "content": answers}
])
return grade["correct"]
```
```typescript TypeScript theme={null}
import type { EvaluationResult } from "langsmith/evaluation";
import { z } from "zod";
// Grade prompt
const correctnessInstructions = `You are a teacher grading a quiz. You will be given a QUESTION, the GROUND TRUTH (correct) ANSWER, and the STUDENT ANSWER. Here is the grade criteria to follow:
(1) Grade the student answers based ONLY on their factual accuracy relative to the ground truth answer. (2) Ensure that the student answer does not contain any conflicting statements.
(3) It is OK if the student answer contains more information than the ground truth answer, as long as it is factually accurate relative to the ground truth answer.
Correctness:
A correctness value of True means that the student's answer meets all of the criteria.
A correctness value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset.`
const graderLLM = new ChatOpenAI({
model: "gpt-4.1",
temperature: 0,
}).withStructuredOutput(
z
.object({
explanation: z
.string()
.describe("Explain your reasoning for the score"),
correct: z
.boolean()
.describe("True if the answer is correct, False otherwise.")
})
.describe("Correctness score for reference answer v.s. generated answer.")
);
async function correctness({
inputs,
outputs,
referenceOutputs,
}: {
inputs: Record;
outputs: Record;
referenceOutputs?: Record;
}): Promise => {
const answer = `QUESTION: ${inputs.question}
GROUND TRUTH ANSWER: ${referenceOutputs.answer}
STUDENT ANSWER: ${outputs.answer}`
// Run evaluator
const grade = graderLLM.invoke([{role: "system", content: correctnessInstructions}, {role: "user", content: answer}])
return grade.score;
};
```
### Relevance: Response vs input
The flow is similar to above, but we simply look at the `inputs` and `outputs` without needing the `reference_outputs`. Without a reference answer we can't grade accuracy, but can still grade relevance—as in, did the model address the user's question or not.
```python Python theme={null}
# Grade output schema
class RelevanceGrade(TypedDict):
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
relevant: Annotated[
bool, ..., "Provide the score on whether the answer addresses the question"
]
# Grade prompt
relevance_instructions = """You are a teacher grading a quiz. You will be given a QUESTION and a STUDENT ANSWER. Here is the grade criteria to follow:
(1) Ensure the STUDENT ANSWER is concise and relevant to the QUESTION
(2) Ensure the STUDENT ANSWER helps to answer the QUESTION
Relevance:
A relevance value of True means that the student's answer meets all of the criteria.
A relevance value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset."""
# Grader LLM
relevance_llm = ChatOpenAI(model="gpt-4.1", temperature=0).with_structured_output(
RelevanceGrade, method="json_schema", strict=True
)
# Evaluator
def relevance(inputs: dict, outputs: dict) -> bool:
"""A simple evaluator for RAG answer helpfulness."""
answer = f"QUESTION: {inputs['question']}\nSTUDENT ANSWER: {outputs['answer']}"
grade = relevance_llm.invoke([
{"role": "system", "content": relevance_instructions},
{"role": "user", "content": answer}
])
return grade["relevant"]
```
```typescript TypeScript theme={null}
import type { EvaluationResult } from "langsmith/evaluation";
import { z } from "zod";
// Grade prompt
const relevanceInstructions = `You are a teacher grading a quiz. You will be given a QUESTION and a STUDENT ANSWER. Here is the grade criteria to follow:
(1) Ensure the STUDENT ANSWER is concise and relevant to the QUESTION
(2) Ensure the STUDENT ANSWER helps to answer the QUESTION
Relevance:
A relevance value of True means that the student's answer meets all of the criteria.
A relevance value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset.`
const relevanceLLM = new ChatOpenAI({
model: "gpt-4.1",
temperature: 0,
}).withStructuredOutput(
z
.object({
explanation: z
.string()
.describe("Explain your reasoning for the score"),
relevant: z
.boolean()
.describe("Provide the score on whether the answer addresses the question")
})
.describe("Relevance score for generated answer v.s. input question.")
);
async function relevance({
inputs,
outputs,
}: {
inputs: Record;
outputs: Record;
}): Promise => {
const answer = `QUESTION: ${inputs.question}
STUDENT ANSWER: ${outputs.answer}`
// Run evaluator
const grade = relevanceLLM.invoke([{role: "system", content: relevanceInstructions}, {role: "user", content: answer}])
return grade.relevant;
};
```
### Groundedness: Response vs retrieved docs
Another useful way to evaluate responses without needing reference answers is to check if the response is justified by (or "grounded in") the retrieved documents.
```python Python theme={null}
# Grade output schema
class GroundedGrade(TypedDict):
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
grounded: Annotated[
bool, ..., "Provide the score on if the answer hallucinates from the documents"
]
# Grade prompt
grounded_instructions = """You are a teacher grading a quiz. You will be given FACTS and a STUDENT ANSWER. Here is the grade criteria to follow:
(1) Ensure the STUDENT ANSWER is grounded in the FACTS. (2) Ensure the STUDENT ANSWER does not contain "hallucinated" information outside the scope of the FACTS.
Grounded:
A grounded value of True means that the student's answer meets all of the criteria.
A grounded value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset."""
# Grader LLM
grounded_llm = ChatOpenAI(model="gpt-4.1", temperature=0).with_structured_output(
GroundedGrade, method="json_schema", strict=True
)
# Evaluator
def groundedness(inputs: dict, outputs: dict) -> bool:
"""A simple evaluator for RAG answer groundedness."""
doc_string = "\n\n".join(doc.page_content for doc in outputs["documents"])
answer = f"FACTS: {doc_string}\nSTUDENT ANSWER: {outputs['answer']}"
grade = grounded_llm.invoke([
{"role": "system", "content": grounded_instructions},
{"role": "user", "content": answer}
])
return grade["grounded"]
```
```typescript TypeScript theme={null}
import type { EvaluationResult } from "langsmith/evaluation";
import { z } from "zod";
// Grade prompt
const groundedInstructions = `You are a teacher grading a quiz. You will be given FACTS and a STUDENT ANSWER. Here is the grade criteria to follow:
(1) Ensure the STUDENT ANSWER is grounded in the FACTS. (2) Ensure the STUDENT ANSWER does not contain "hallucinated" information outside the scope of the FACTS.
Grounded:
A grounded value of True means that the student's answer meets all of the criteria.
A grounded value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset.`
const groundedLLM = new ChatOpenAI({
model: "gpt-4.1",
temperature: 0,
}).withStructuredOutput(
z
.object({
explanation: z
.string()
.describe("Explain your reasoning for the score"),
grounded: z
.boolean()
.describe("Provide the score on if the answer hallucinates from the documents")
})
.describe("Grounded score for the answer from the retrieved documents.")
);
async function grounded({
inputs,
outputs,
}: {
inputs: Record;
outputs: Record;
}): Promise => {
const docString = outputs.documents.map((doc) => doc.pageContent).join("");
const answer = `FACTS: ${docString}
STUDENT ANSWER: ${outputs.answer}`
// Run evaluator
const grade = groundedLLM.invoke([{role: "system", content: groundedInstructions}, {role: "user", content: answer}])
return grade.grounded;
};
```
### Retrieval relevance: Retrieved docs vs input
```python Python theme={null}
# Grade output schema
class RetrievalRelevanceGrade(TypedDict):
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
relevant: Annotated[
bool,
...,
"True if the retrieved documents are relevant to the question, False otherwise",
]
# Grade prompt
retrieval_relevance_instructions = """You are a teacher grading a quiz. You will be given a QUESTION and a set of FACTS provided by the student. Here is the grade criteria to follow:
(1) You goal is to identify FACTS that are completely unrelated to the QUESTION
(2) If the facts contain ANY keywords or semantic meaning related to the question, consider them relevant
(3) It is OK if the facts have SOME information that is unrelated to the question as long as (2) is met
Relevance:
A relevance value of True means that the FACTS contain ANY keywords or semantic meaning related to the QUESTION and are therefore relevant.
A relevance value of False means that the FACTS are completely unrelated to the QUESTION.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset."""
# Grader LLM
retrieval_relevance_llm = ChatOpenAI(
model="gpt-4.1", temperature=0
).with_structured_output(RetrievalRelevanceGrade, method="json_schema", strict=True)
def retrieval_relevance(inputs: dict, outputs: dict) -> bool:
"""An evaluator for document relevance"""
doc_string = "\n\n".join(doc.page_content for doc in outputs["documents"])
answer = f"FACTS: {doc_string}\nQUESTION: {inputs['question']}"
# Run evaluator
grade = retrieval_relevance_llm.invoke([
{"role": "system", "content": retrieval_relevance_instructions},
{"role": "user", "content": answer}
])
return grade["relevant"]
```
```typescript TypeScript theme={null}
import type { EvaluationResult } from "langsmith/evaluation";
import { z } from "zod";
// Grade prompt
const retrievalRelevanceInstructions = `You are a teacher grading a quiz. You will be given a QUESTION and a set of FACTS provided by the student. Here is the grade criteria to follow:
(1) You goal is to identify FACTS that are completely unrelated to the QUESTION
(2) If the facts contain ANY keywords or semantic meaning related to the question, consider them relevant
(3) It is OK if the facts have SOME information that is unrelated to the question as long as (2) is met
Relevance:
A relevance value of True means that the FACTS contain ANY keywords or semantic meaning related to the QUESTION and are therefore relevant.
A relevance value of False means that the FACTS are completely unrelated to the QUESTION.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset.`
const retrievalRelevanceLLM = new ChatOpenAI({
model: "gpt-4.1",
temperature: 0,
}).withStructuredOutput(
z
.object({
explanation: z
.string()
.describe("Explain your reasoning for the score"),
relevant: z
.boolean()
.describe("True if the retrieved documents are relevant to the question, False otherwise")
})
.describe("Retrieval relevance score for the retrieved documents v.s. the question.")
);
async function retrievalRelevance({
inputs,
outputs,
}: {
inputs: Record;
outputs: Record;
}): Promise => {
const docString = outputs.documents.map((doc) => doc.pageContent).join("");
const answer = `FACTS: ${docString}
QUESTION: ${inputs.question}`
// Run evaluator
const grade = retrievalRelevanceLLM.invoke([{role: "system", content: retrievalRelevanceInstructions}, {role: "user", content: answer}])
return grade.relevant;
};
```
## Run evaluation
We can now kick off our evaluation job with all of our different evaluators.
```python Python theme={null}
def target(inputs: dict) -> dict:
return rag_bot(inputs["question"])
experiment_results = client.evaluate(
target,
data=dataset_name,
evaluators=[correctness, groundedness, relevance, retrieval_relevance],
experiment_prefix="rag-doc-relevance",
metadata={"version": "LCEL context, gpt-4-0125-preview"},
)
# Explore results locally as a dataframe if you have pandas installed
# experiment_results.to_pandas()
```
```typescript TypeScript theme={null}
import { evaluate } from "langsmith/evaluation";
const targetFunc = (inputs: Record) => {
return ragBot(inputs.question)
};
const experimentResults = await evaluate(targetFunc, {
data: datasetName,
evaluators: [correctness, groundedness, relevance, retrievalRelevance],
experimentPrefix: "rag-doc-relevance",
metadata: {version: "LCEL context, gpt-4-0125-preview"},
});
```
You can see an example of what these results look like here: [LangSmith link](https://smith.langchain.com/public/302573e2-20bf-4f8c-bdad-e97c20f33f1b/d)
## Reference code
```python Python theme={null}
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langsmith import Client, traceable
from typing_extensions import Annotated, TypedDict
# List of URLs to load documents from
urls = [
"https://lilianweng.github.io/posts/2023-06-23-agent/",
"https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/",
"https://lilianweng.github.io/posts/2023-10-25-adv-attack-llm/",
]
# Load documents from the URLs
docs = [WebBaseLoader(url).load() for url in urls]
docs_list = [item for sublist in docs for item in sublist]
# Initialize a text splitter with specified chunk size and overlap
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=250, chunk_overlap=0
)
# Split the documents into chunks
doc_splits = text_splitter.split_documents(docs_list)
# Add the document chunks to the "vector store" using OpenAIEmbeddings
vectorstore = InMemoryVectorStore.from_documents(
documents=doc_splits,
embedding=OpenAIEmbeddings(),
)
# With langchain we can easily turn any vector store into a retrieval component:
retriever = vectorstore.as_retriever(k=6)
llm = ChatOpenAI(model="gpt-4.1", temperature=1)
# Add decorator so this function is traced in LangSmith
@traceable()
def rag_bot(question: str) -> dict:
# langchain Retriever will be automatically traced
docs = retriever.invoke(question)
docs_string = "".join(doc.page_content for doc in docs)
instructions = f"""You are a helpful assistant who is good at analyzing source information and answering questions.
Use the following source documents to answer the user's questions.
If you don't know the answer, just say that you don't know.
Use three sentences maximum and keep the answer concise.
Documents:
{docs_string}"""
# langchain ChatModel will be automatically traced
ai_msg = llm.invoke([
{"role": "system", "content": instructions},
{"role": "user", "content": question},
],
)
return {"answer": ai_msg.content, "documents": docs}
client = Client()
# Define the examples for the dataset
examples = [
{
"inputs": {"question": "How does the ReAct agent use self-reflection? "},
"outputs": {"answer": "ReAct integrates reasoning and acting, performing actions - such tools like Wikipedia search API - and then observing / reasoning about the tool outputs."},
},
{
"inputs": {"question": "What are the types of biases that can arise with few-shot prompting?"},
"outputs": {"answer": "The biases that can arise with few-shot prompting include (1) Majority label bias, (2) Recency bias, and (3) Common token bias."},
},
{
"inputs": {"question": "What are five types of adversarial attacks?"},
"outputs": {"answer": "Five types of adversarial attacks are (1) Token manipulation, (2) Gradient based attack, (3) Jailbreak prompting, (4) Human red-teaming, (5) Model red-teaming."},
},
]
# Create the dataset and examples in LangSmith
dataset_name = "Lilian Weng Blogs Q&A"
if not client.has_dataset(dataset_name=dataset_name):
dataset = client.create_dataset(dataset_name=dataset_name)
client.create_examples(
dataset_id=dataset.id,
examples=examples
)
# Grade output schema
class CorrectnessGrade(TypedDict):
# Note that the order in the fields are defined is the order in which the model will generate them.
# It is useful to put explanations before responses because it forces the model to think through
# its final response before generating it:
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
correct: Annotated[bool, ..., "True if the answer is correct, False otherwise."]
# Grade prompt
correctness_instructions = """You are a teacher grading a quiz. You will be given a QUESTION, the GROUND TRUTH (correct) ANSWER, and the STUDENT ANSWER. Here is the grade criteria to follow:
(1) Grade the student answers based ONLY on their factual accuracy relative to the ground truth answer. (2) Ensure that the student answer does not contain any conflicting statements.
(3) It is OK if the student answer contains more information than the ground truth answer, as long as it is factually accurate relative to the ground truth answer.
Correctness:
A correctness value of True means that the student's answer meets all of the criteria.
A correctness value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset."""
# Grader LLM
grader_llm = ChatOpenAI(model="gpt-4.1", temperature=0).with_structured_output(
CorrectnessGrade, method="json_schema", strict=True
)
def correctness(inputs: dict, outputs: dict, reference_outputs: dict) -> bool:
"""An evaluator for RAG answer accuracy"""
answers = f"""\
QUESTION: {inputs['question']}
GROUND TRUTH ANSWER: {reference_outputs['answer']}
STUDENT ANSWER: {outputs['answer']}"""
# Run evaluator
grade = grader_llm.invoke([
{"role": "system", "content": correctness_instructions},
{"role": "user", "content": answers},
]
)
return grade["correct"]
# Grade output schema
class RelevanceGrade(TypedDict):
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
relevant: Annotated[
bool, ..., "Provide the score on whether the answer addresses the question"
]
# Grade prompt
relevance_instructions = """You are a teacher grading a quiz. You will be given a QUESTION and a STUDENT ANSWER. Here is the grade criteria to follow:
(1) Ensure the STUDENT ANSWER is concise and relevant to the QUESTION
(2) Ensure the STUDENT ANSWER helps to answer the QUESTION
Relevance:
A relevance value of True means that the student's answer meets all of the criteria.
A relevance value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset."""
# Grader LLM
relevance_llm = ChatOpenAI(model="gpt-4.1", temperature=0).with_structured_output(
RelevanceGrade, method="json_schema", strict=True
)
# Evaluator
def relevance(inputs: dict, outputs: dict) -> bool:
"""A simple evaluator for RAG answer helpfulness."""
answer = f"QUESTION: {inputs['question']}\nSTUDENT ANSWER: {outputs['answer']}"
grade = relevance_llm.invoke([
{"role": "system", "content": relevance_instructions},
{"role": "user", "content": answer},
]
)
return grade["relevant"]
# Grade output schema
class GroundedGrade(TypedDict):
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
grounded: Annotated[
bool, ..., "Provide the score on if the answer hallucinates from the documents"
]
# Grade prompt
grounded_instructions = """You are a teacher grading a quiz. You will be given FACTS and a STUDENT ANSWER. Here is the grade criteria to follow:
(1) Ensure the STUDENT ANSWER is grounded in the FACTS. (2) Ensure the STUDENT ANSWER does not contain "hallucinated" information outside the scope of the FACTS.
Grounded:
A grounded value of True means that the student's answer meets all of the criteria.
A grounded value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset."""
# Grader LLM
grounded_llm = ChatOpenAI(model="gpt-4.1", temperature=0).with_structured_output(
GroundedGrade, method="json_schema", strict=True
)
# Evaluator
def groundedness(inputs: dict, outputs: dict) -> bool:
"""A simple evaluator for RAG answer groundedness."""
doc_string = "\n\n".join(doc.page_content for doc in outputs["documents"])
answer = f"FACTS: {doc_string}\nSTUDENT ANSWER: {outputs['answer']}"
grade = grounded_llm.invoke([
{"role": "system", "content": grounded_instructions},
{"role": "user", "content": answer},
]
)
return grade["grounded"]
# Grade output schema
class RetrievalRelevanceGrade(TypedDict):
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
relevant: Annotated[
bool,
...,
"True if the retrieved documents are relevant to the question, False otherwise",
]
# Grade prompt
retrieval_relevance_instructions = """You are a teacher grading a quiz. You will be given a QUESTION and a set of FACTS provided by the student. Here is the grade criteria to follow:
(1) You goal is to identify FACTS that are completely unrelated to the QUESTION
(2) If the facts contain ANY keywords or semantic meaning related to the question, consider them relevant
(3) It is OK if the facts have SOME information that is unrelated to the question as long as (2) is met
Relevance:
A relevance value of True means that the FACTS contain ANY keywords or semantic meaning related to the QUESTION and are therefore relevant.
A relevance value of False means that the FACTS are completely unrelated to the QUESTION.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset."""
# Grader LLM
retrieval_relevance_llm = ChatOpenAI(
model="gpt-4.1", temperature=0
).with_structured_output(RetrievalRelevanceGrade, method="json_schema", strict=True)
def retrieval_relevance(inputs: dict, outputs: dict) -> bool:
"""An evaluator for document relevance"""
doc_string = "\n\n".join(doc.page_content for doc in outputs["documents"])
answer = f"FACTS: {doc_string}\nQUESTION: {inputs['question']}"
# Run evaluator
grade = retrieval_relevance_llm.invoke([
{"role": "system", "content": retrieval_relevance_instructions},
{"role": "user", "content": answer},
]
)
return grade["relevant"]
def target(inputs: dict) -> dict:
return rag_bot(inputs["question"])
experiment_results = client.evaluate(
target,
data=dataset_name,
evaluators=[correctness, groundedness, relevance, retrieval_relevance],
experiment_prefix="rag-doc-relevance",
metadata={"version": "LCEL context, gpt-4-0125-preview"},
)
# Explore results locally as a dataframe if you have pandas installed
# experiment_results.to_pandas()
```
```typescript TypeScript theme={null}
import { OpenAIEmbeddings, ChatOpenAI } from "@langchain/openai";
import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";
import { BrowserbaseLoader } from "@langchain/community/document_loaders/web/browserbase";
import { traceable } from "langsmith/traceable";
import { Client } from "langsmith";
import { evaluate, type EvaluationResult } from "langsmith/evaluation";
import { z } from "zod";
// List of URLs to load documents from
const urls = [
"https://lilianweng.github.io/posts/2023-06-23-agent/",
"https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/",
"https://lilianweng.github.io/posts/2023-10-25-adv-attack-llm/",
]
const loader = new BrowserbaseLoader(urls, {
textContent: true,
});
const docs = await loader.load();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000, chunkOverlap: 200
});
const allSplits = await splitter.splitDocuments(docs);
const embeddings = new OpenAIEmbeddings({
model: "text-embedding-3-large"
});
const vectorStore = new MemoryVectorStore(embeddings); // Index chunks
await vectorStore.addDocuments(allSplits)
const llm = new ChatOpenAI({
model: "gpt-4.1",
temperature: 1,
})
// Add decorator so this function is traced in LangSmith
const ragBot = traceable(
async (question: string) => {
// LangChain retriever will be automatically traced
const retrievedDocs = await vectorStore.similaritySearch(question);
const docsContent = retrievedDocs.map((doc) => doc.pageContent).join("");
const instructions = `You are a helpful assistant who is good at analyzing source information and answering questions
Use the following source documents to answer the user's questions.
If you don't know the answer, just say that you don't know.
Use three sentences maximum and keep the answer concise.
Documents:
${docsContent}`;
const aiMsg = await llm.invoke([
{
role: "system",
content: instructions
},
{
role: "user",
content: question
}
])
return {"answer": aiMsg.content, "documents": retrievedDocs}
}
)
const client = new Client();
// Define the examples for the dataset
const examples = [
[
"How does the ReAct agent use self-reflection? ",
"ReAct integrates reasoning and acting, performing actions - such tools like Wikipedia search API - and then observing / reasoning about the tool outputs.",
],
[
"What are the types of biases that can arise with few-shot prompting?",
"The biases that can arise with few-shot prompting include (1) Majority label bias, (2) Recency bias, and (3) Common token bias.",
],
[
"What are five types of adversarial attacks?",
"Five types of adversarial attacks are (1) Token manipulation, (2) Gradient based attack, (3) Jailbreak prompting, (4) Human red-teaming, (5) Model red-teaming.",
]
]
const [inputs, outputs] = examples.reduce<[Array<{ input: string }>, Array<{ outputs: string }>]>(
([inputs, outputs], item) => [
[...inputs, { input: item[0] }],
[...outputs, { outputs: item[1] }],
],
[[], []]
);
const datasetName = "Lilian Weng Blogs Q&A";
const dataset = await client.createDataset(datasetName);
await client.createExamples({ inputs, outputs, datasetId: dataset.id })
// Grade prompt
const correctnessInstructions = `You are a teacher grading a quiz. You will be given a QUESTION, the GROUND TRUTH (correct) ANSWER, and the STUDENT ANSWER. Here is the grade criteria to follow:
(1) Grade the student answers based ONLY on their factual accuracy relative to the ground truth answer. (2) Ensure that the student answer does not contain any conflicting statements.
(3) It is OK if the student answer contains more information than the ground truth answer, as long as it is factually accurate relative to the ground truth answer.
Correctness:
A correctness value of True means that the student's answer meets all of the criteria.
A correctness value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset.`
const graderLLM = new ChatOpenAI({
model: "gpt-4.1",
temperature: 0,
}).withStructuredOutput(
z
.object({
explanation: z
.string()
.describe("Explain your reasoning for the score"),
correct: z
.boolean()
.describe("True if the answer is correct, False otherwise.")
})
.describe("Correctness score for reference answer v.s. generated answer.")
);
async function correctness({
inputs,
outputs,
referenceOutputs,
}: {
inputs: Record;
outputs: Record;
referenceOutputs?: Record;
}): Promise => {
const answer = `QUESTION: ${inputs.question}
GROUND TRUTH ANSWER: ${referenceOutputs.answer}
STUDENT ANSWER: ${outputs.answer}`
// Run evaluator
const grade = graderLLM.invoke([{role: "system", content: correctnessInstructions}, {role: "user", content: answer}])
return grade.score;
};
// Grade prompt
const relevanceInstructions = `You are a teacher grading a quiz. You will be given a QUESTION and a STUDENT ANSWER. Here is the grade criteria to follow:
(1) Ensure the STUDENT ANSWER is concise and relevant to the QUESTION
(2) Ensure the STUDENT ANSWER helps to answer the QUESTION
Relevance:
A relevance value of True means that the student's answer meets all of the criteria.
A relevance value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset.`
const relevanceLLM = new ChatOpenAI({
model: "gpt-4.1",
temperature: 0,
}).withStructuredOutput(
z
.object({
explanation: z
.string()
.describe("Explain your reasoning for the score"),
relevant: z
.boolean()
.describe("Provide the score on whether the answer addresses the question")
})
.describe("Relevance score for generated answer v.s. input question.")
);
async function relevance({
inputs,
outputs,
}: {
inputs: Record;
outputs: Record;
}): Promise => {
const answer = `QUESTION: ${inputs.question}
STUDENT ANSWER: ${outputs.answer}`
// Run evaluator
const grade = relevanceLLM.invoke([{role: "system", content: relevanceInstructions}, {role: "user", content: answer}])
return grade.relevant;
};
// Grade prompt
const groundedInstructions = `You are a teacher grading a quiz. You will be given FACTS and a STUDENT ANSWER. Here is the grade criteria to follow:
(1) Ensure the STUDENT ANSWER is grounded in the FACTS. (2) Ensure the STUDENT ANSWER does not contain "hallucinated" information outside the scope of the FACTS.
Grounded:
A grounded value of True means that the student's answer meets all of the criteria.
A grounded value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset.`
const groundedLLM = new ChatOpenAI({
model: "gpt-4.1",
temperature: 0,
}).withStructuredOutput(
z
.object({
explanation: z
.string()
.describe("Explain your reasoning for the score"),
grounded: z
.boolean()
.describe("Provide the score on if the answer hallucinates from the documents")
})
.describe("Grounded score for the answer from the retrieved documents.")
);
async function grounded({
inputs,
outputs,
}: {
inputs: Record;
outputs: Record;
}): Promise => {
const docString = outputs.documents.map((doc) => doc.pageContent).join("");
const answer = `FACTS: ${docString}
STUDENT ANSWER: ${outputs.answer}`
// Run evaluator
const grade = groundedLLM.invoke([{role: "system", content: groundedInstructions}, {role: "user", content: answer}])
return grade.grounded;
};
// Grade prompt
const retrievalRelevanceInstructions = `You are a teacher grading a quiz. You will be given a QUESTION and a set of FACTS provided by the student. Here is the grade criteria to follow:
(1) You goal is to identify FACTS that are completely unrelated to the QUESTION
(2) If the facts contain ANY keywords or semantic meaning related to the question, consider them relevant
(3) It is OK if the facts have SOME information that is unrelated to the question as long as (2) is met
Relevance:
A relevance value of True means that the FACTS contain ANY keywords or semantic meaning related to the QUESTION and are therefore relevant.
A relevance value of False means that the FACTS are completely unrelated to the QUESTION.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct. Avoid simply stating the correct answer at the outset.`
const retrievalRelevanceLLM = new ChatOpenAI({
model: "gpt-4.1",
temperature: 0,
}).withStructuredOutput(
z
.object({
explanation: z
.string()
.describe("Explain your reasoning for the score"),
relevant: z
.boolean()
.describe("True if the retrieved documents are relevant to the question, False otherwise")
})
.describe("Retrieval relevance score for the retrieved documents v.s. the question.")
);
async function retrievalRelevance({
inputs,
outputs,
}: {
inputs: Record;
outputs: Record;
}): Promise => {
const docString = outputs.documents.map((doc) => doc.pageContent).join("");
const answer = `FACTS: ${docString}
QUESTION: ${inputs.question}`
// Run evaluator
const grade = retrievalRelevanceLLM.invoke([{role: "system", content: retrievalRelevanceInstructions}, {role: "user", content: answer}])
return grade.relevant;
};
const targetFunc = (inputs: Record) => {
return ragBot(inputs.question)
};
const experimentResults = await evaluate(targetFunc, {
data: datasetName,
evaluators: [correctness, groundedness, relevance, retrievalRelevance],
experimentPrefix: "rag-doc-relevance",
metadata: {version: "LCEL context, gpt-4-0125-preview"},
});
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluate-rag-tutorial.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Run an evaluation with multimodal content
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluate-with-attachments
LangSmith lets you create dataset examples with file attachments—like images, audio files, or documents—so you can reference them when evaluating an application that uses multimodal inputs or outputs.
While you can include multimodal data in your examples by base64 encoding it, this approach is inefficient - the encoded data takes up more space than the original binary files, resulting in slower transfers to and from LangSmith. Using attachments instead provides two key benefits:
1. Faster upload and download speeds due to more efficient binary file transfers
2. Enhanced visualization of different file types in the LangSmith UI
## SDK
### 1. Create examples with attachments
To upload examples with attachments using the SDK, use the [create\_examples](https://docs.smith.langchain.com/reference/python/client/langsmith.client.Client#langsmith.client.Client.create_examples) / [update\_examples](https://docs.smith.langchain.com/reference/python/client/langsmith.client.Client#langsmith.client.Client.update_examples) Python methods or the [uploadExamplesMultipart](https://docs.smith.langchain.com/reference/js/classes/client.Client#uploadexamplesmultipart) / [updateExamplesMultipart](https://docs.smith.langchain.com/reference/js/classes/client.Client#updateexamplesmultipart) TypeScript methods.
#### Python
Requires `langsmith>=0.3.13`
```python theme={null}
import requests
import uuid
from pathlib import Path
from langsmith import Client
# Publicly available test files
pdf_url = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
wav_url = "https://openaiassets.blob.core.windows.net/$web/API/docs/audio/alloy.wav"
img_url = "https://www.w3.org/Graphics/PNG/nurbcup2si.png"
# Fetch the files as bytes
pdf_bytes = requests.get(pdf_url).content
wav_bytes = requests.get(wav_url).content
img_bytes = requests.get(img_url).content
# Create the dataset
ls_client = Client()
dataset_name = "attachment-test-dataset"
dataset = ls_client.create_dataset(
dataset_name=dataset_name,
description="Test dataset for evals with publicly available attachments",
)
inputs = {
"audio_question": "What is in this audio clip?",
"image_question": "What is in this image?",
}
outputs = {
"audio_answer": "The sun rises in the east and sets in the west. This simple fact has been observed by humans for thousands of years.",
"image_answer": "A mug with a blanket over it.",
}
# Define an example with attachments
example_id = uuid.uuid4()
example = {
"id": example_id,
"inputs": inputs,
"outputs": outputs,
"attachments": {
"my_pdf": {"mime_type": "application/pdf", "data": pdf_bytes},
"my_wav": {"mime_type": "audio/wav", "data": wav_bytes},
"my_img": {"mime_type": "image/png", "data": img_bytes},
# Example of an attachment specified via a local file path:
# "my_local_img": {"mime_type": "image/png", "data": Path(__file__).parent / "my_local_img.png"},
},
}
# Create the example
ls_client.create_examples(
dataset_id=dataset.id,
examples=[example],
# Uncomment this flag if you'd like to upload attachments from local files:
# dangerously_allow_filesystem=True
)
```
#### TypeScript
Requires version >= 0.2.13
You can use the `uploadExamplesMultipart` method to upload examples with attachments.
Note that this is a different method from the standard `createExamples` method, which currently does not support attachments. Each attachment requires either a `Uint8Array` or an `ArrayBuffer` as the data type.
* `Uint8Array`: Useful for handling binary data directly.
* `ArrayBuffer`: Represents fixed-length binary data, which can be converted to `Uint8Array` as needed.
Note that you cannot directly pass in a file path in the TypeScript SDK, as accessing local files is not supported in all runtime environments.
```typescript theme={null}
import { Client } from "langsmith";
import { v4 as uuid4 } from "uuid";
// Publicly available test files
const pdfUrl = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
const wavUrl = "https://openaiassets.blob.core.windows.net/$web/API/docs/audio/alloy.wav";
const pngUrl = "https://www.w3.org/Graphics/PNG/nurbcup2si.png";
// Helper function to fetch file as ArrayBuffer
async function fetchArrayBuffer(url: string): Promise {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
}
return response.arrayBuffer();
}
// Fetch files as ArrayBuffer
const pdfArrayBuffer = await fetchArrayBuffer(pdfUrl);
const wavArrayBuffer = await fetchArrayBuffer(wavUrl);
const pngArrayBuffer = await fetchArrayBuffer(pngUrl);
// Create the LangSmith client (Ensure LANGSMITH_API_KEY is set in env)
const langsmithClient = new Client();
// Create a unique dataset name
const datasetName = "attachment-test-dataset:" + uuid4().substring(0, 8);
// Create the dataset
const dataset = await langsmithClient.createDataset(datasetName, {
description: "Test dataset for evals with publicly available attachments",
});
// Define the example with attachments
const exampleId = uuid4();
const example = {
id: exampleId,
inputs: {
audio_question: "What is in this audio clip?",
image_question: "What is in this image?",
},
outputs: {
audio_answer: "The sun rises in the east and sets in the west. This simple fact has been observed by humans for thousands of years.",
image_answer: "A mug with a blanket over it.",
},
attachments: {
my_pdf: {
mimeType: "application/pdf",
data: pdfArrayBuffer
},
my_wav: {
mimeType: "audio/wav",
data: wavArrayBuffer
},
my_img: {
mimeType: "image/png",
data: pngArrayBuffer
},
},
};
// Upload the example with attachments to the dataset
await langsmithClient.uploadExamplesMultipart(dataset.id, [example]);
```
Along with being passed in as bytes, attachments can be specified as paths to local files. To do so pass in a path for the attachment `data` value and specify arg `dangerously_allow_filesystem=True`:
```python theme={null}
client.create_examples(..., dangerously_allow_filesystem=True)
```
## 2. Run evaluations
### Define a target function
Now that we have a dataset that includes examples with attachments, we can define a target function to run over these examples. The following example simply uses OpenAI's GPT-4o model to answer questions about an image and an audio clip.
#### Python
The target function you are evaluating must have two positional arguments in order to consume the attachments associated with the example, the first must be called `inputs` and the second must be called `attachments`.
* The `inputs` argument is a dictionary that contains the input data for the example, excluding the attachments.
* The `attachments` argument is a dictionary that maps the attachment name to a dictionary containing a presigned url, mime\_type, and a reader of the bytes content of the file. You can use either the presigned url or the reader to get the file contents. Each value in the attachments dictionary is a dictionary with the following structure:
```python theme={null}
{
"presigned_url": str,
"mime_type": str,
"reader": BinaryIO
}
```
```python theme={null}
from langsmith.wrappers import wrap_openai
import base64
from openai import OpenAI
client = wrap_openai(OpenAI())
# Define target function that uses attachments
def file_qa(inputs, attachments):
# Read the audio bytes from the reader and encode them in base64
audio_reader = attachments["my_wav"]["reader"]
audio_b64 = base64.b64encode(audio_reader.read()).decode('utf-8')
audio_completion = client.chat.completions.create(
model="gpt-4o-audio-preview",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": inputs["audio_question"]
},
{
"type": "input_audio",
"input_audio": {
"data": audio_b64,
"format": "wav"
}
}
]
}
]
)
# Most models support taking in an image URL directly in addition to base64 encoded images
# You can pipe the image pre-signed URL directly to the model
image_url = attachments["my_img"]["presigned_url"]
image_completion = client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": inputs["image_question"]},
{
"type": "image_url",
"image_url": {
"url": image_url,
},
},
],
}
],
)
return {
"audio_answer": audio_completion.choices[0].message.content,
"image_answer": image_completion.choices[0].message.content,
}
```
#### TypeScript
In the TypeScript SDK, the `config` argument is used to pass in the attachments to the target function if `includeAttachments` is set to `true`.
The `config` will contain `attachments` which is an object mapping the attachment name to an object of the form:
```typescript theme={null}
{
presigned_url: string,
mime_type: string,
}
```
```typescript theme={null}
import OpenAI from "openai";
import { wrapOpenAI } from "langsmith/wrappers";
const client: any = wrapOpenAI(new OpenAI());
async function fileQA(inputs: Record, config?: Record) {
const presignedUrl = config?.attachments?.["my_wav"]?.presigned_url;
if (!presignedUrl) {
throw new Error("No presigned URL provided for audio.");
}
const response = await fetch(presignedUrl);
if (!response.ok) {
throw new Error(`Failed to fetch audio: ${response.statusText}`);
}
const arrayBuffer = await response.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
const audioB64 = Buffer.from(uint8Array).toString("base64");
const audioCompletion = await client.chat.completions.create({
model: "gpt-4o-audio-preview",
messages: [
{
role: "user",
content: [
{ type: "text", text: inputs["audio_question"] },
{
type: "input_audio",
input_audio: {
data: audioB64,
format: "wav",
},
},
],
},
],
});
const imageUrl = config?.attachments?.["my_img"]?.presigned_url
const imageCompletion = await client.chat.completions.create({
model: "gpt-4.1-mini",
messages: [
{
role: "user",
content: [
{ type: "text", text: inputs["image_question"] },
{
type: "image_url",
image_url: {
url: imageUrl,
},
},
],
},
],
});
return {
audio_answer: audioCompletion.choices[0].message.content,
image_answer: imageCompletion.choices[0].message.content,
};
}
```
### Define custom evaluators
The exact same rules apply as above to determine whether the evaluator should receive attachments.
The evaluator below uses an LLM to judge if the reasoning and the answer are consistent. To learn more about how to define llm-based evaluators, please see [this guide](/langsmith/llm-as-judge).
```python Python theme={null}
# Assumes you've installed pydantic
from pydantic import BaseModel
def valid_image_description(outputs: dict, attachments: dict) -> bool:
"""Use an LLM to judge if the image description and images are consistent."""
instructions = """
Does the description of the following image make sense?
Please carefully review the image and the description to determine if the description is valid.
"""
class Response(BaseModel):
description_is_valid: bool
image_url = attachments["my_img"]["presigned_url"]
response = client.beta.chat.completions.parse(
model="gpt-4.1",
messages=[
{
"role": "system",
"content": instructions
},
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": image_url}},
{"type": "text", "text": outputs["image_answer"]}
]
}
],
response_format=Response
)
return response.choices[0].message.parsed.description_is_valid
ls_client.evaluate(
file_qa,
data=dataset_name,
evaluators=[valid_image_description],
)
```
```typescript TypeScript theme={null}
import { zodResponseFormat } from 'openai/helpers/zod';
import { z } from 'zod';
import { evaluate } from "langsmith/evaluation";
const DescriptionResponse = z.object({
description_is_valid: z.boolean(),
});
async function validImageDescription({
outputs,
attachments,
}: {
outputs?: any;
attachments?: any;
}): Promise<{ key: string; score: boolean}> {
const instructions = `Does the description of the following image make sense?
Please carefully review the image and the description to determine if the description is valid.`;
const imageUrl = attachments?.["my_img"]?.presigned_url
const completion = await client.beta.chat.completions.parse({
model: "gpt-4.1",
messages: [
{
role: "system",
content: instructions,
},
{
role: "user",
content: [
{ type: "image_url", image_url: { url: imageUrl } },
{ type: "text", text: outputs?.image_answer },
],
},
],
response_format: zodResponseFormat(DescriptionResponse, 'imageResponse'),
});
const score: boolean = completion.choices[0]?.message?.parsed?.description_is_valid ?? false;
return { key: "valid_image_description", score };
}
const resp = await evaluate(fileQA, {
data: datasetName,
// Need to pass flag to include attachments
includeAttachments: true,
evaluators: [validImageDescription],
client: langsmithClient
});
```
## Update examples with attachments
In the code above, we showed how to add examples with attachments to a dataset. It is also possible to update these same examples using the SDK.
As with existing examples, datasets are versioned when you update them with attachments. Therefore, you can navigate to the dataset version history to see the changes made to each example. To learn more, please see [this guide](/langsmith/manage-datasets-in-application).
When updating an example with attachments, you can update attachments in a few different ways:
* Pass in new attachments
* Rename existing attachments
* Delete existing attachments
Note that:
* Any existing attachments that are not explicitly renamed or retained **will be deleted**.
* An error will be raised if you pass in a non-existent attachment name to `retain` or `rename`.
* New attachments take precedence over existing attachments in case the same attachment name appears in the `attachments` and `attachment_operations` fields.
```python Python theme={null}
example_update = {
"id": example_id,
"attachments": {
# These are net new attachments
"my_new_file": ("text/plain", b"foo bar"),
},
"inputs": inputs,
"outputs": outputs,
# Any attachments not in rename/retain will be deleted.
# In this case, that would be "my_img" if we uploaded it.
"attachments_operations": {
# Retained attachments will stay exactly the same
"retain": ["my_pdf"],
# Renaming attachments preserves the original data
"rename": {
"my_wav": "my_new_wav",
}
},
}
ls_client.update_examples(dataset_id=dataset.id, updates=[example_update])
```
```typescript TypeScript theme={null}
import { ExampleUpdateWithAttachments } from "langsmith/schemas";
const exampleUpdate: ExampleUpdateWithAttachments = {
id: exampleId,
attachments: {
// These are net new attachments
"my_new_file": {
mimeType: "text/plain",
data: Buffer.from("foo bar")
},
},
attachments_operations: {
// Retained attachments will stay exactly the same
retain: ["my_img"],
// Renaming attachments preserves the original data
rename: {
"my_wav": "my_new_wav",
},
// Any attachments not in rename/retain will be deleted
// In this case, that would be "my_pdf"
},
};
await langsmithClient.updateExamplesMultipart(dataset.id, [exampleUpdate]);
```
## UI
### 1. Create examples with attachments
You can add examples with attachments to a dataset in a few different ways.
#### From existing runs
When adding runs to a LangSmith dataset, attachments can be selectively propagated from the source run to the destination example. To learn more, please see [this guide](/langsmith/manage-datasets-in-application#add-runs-from-the-tracing-project-ui).
#### From scratch
You can create examples with attachments directly from the LangSmith UI. Click the `+ Example` button in the `Examples` tab of the dataset UI. Then upload attachments using the "Upload Files" button:
Once uploaded, you can view examples with attachments in the LangSmith UI. Each attachment will be rendered with a preview for easy inspection.
### 2. Create a multimodal prompt
The LangSmith UI allows you to include attachments in your prompts when evaluating multimodal models:
First, click the file icon in the message where you want to add multimodal content. Next, add a template variable for the attachment(s) you want to include for each example.
* For a single attachment type: Use the suggested variable name. Note: all examples must have an attachment with this name.
* For multiple attachments or if your attachments have varying names from one example to another: Use the `All attachments` variable to include all available attachments for each example.
### Define custom evaluators
The LangSmith playground does not currently support pulling multimodal content into evaluators. If this would be helpful for your use case, please let us know in the [LangChain Forum](https://forum.langchain.com/) (sign up [here](https://www.langchain.com/join-community) if you're not already a member)!
You can evaluate a model's text output by adding an evaluator that takes in the example's inputs and outputs. Even without multimodal support in your evaluators, you can still run text-only evaluations. For example:
* OCR → text correction: Use a vision model to extract text from a document, then evaluate the accuracy of the extracted output.
* Speech-to-text → transcription quality: Use a voice model to transcribe audio to text, then evaluate the transcription against your reference.
For more information on defining custom evaluators, see the [LLM as Judge](/langsmith/llm-as-judge) guide.
### Update examples with attachments
Attachments are limited to 20MB in size in the UI.
When editing an example in the UI, you can:
* Upload new attachments
* Rename and delete attachments
* Reset attachments to their previous state using the quick reset button
Changes are not saved until you click submit.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluate-with-attachments.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to evaluate with OpenTelemetry
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluate-with-opentelemetry
This guide shows you how to run an evaluation using OpenTelemetry tracing with LangSmith.
[Evaluations](/langsmith/evaluation-concepts#applying-evaluations) | [Datasets](/langsmith/evaluation-concepts#datasets) | [Trace with OpenTelemetry](/langsmith/trace-with-opentelemetry)
If you're already using OpenTelemetry for tracing your LLM application, you can run evaluations by routing traces to an experiment session. This approach is useful when you want to evaluate applications that are instrumented with OpenTelemetry but don't use the LangSmith SDK's [`evaluate()`](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client.evaluate) function.
## Overview
When evaluating with OpenTelemetry, you need to:
1. Create an experiment session in LangSmith.
2. Configure OpenTelemetry to send traces to LangSmith.
3. Add specific span attributes to link traces to the experiment and dataset examples.
4. Run your application for each example in the dataset.
## Prerequisites
This guide assumes you have:
* An application instrumented with OpenTelemetry that sends traces to LangSmith.
* A dataset created in LangSmith with examples to evaluate. You can create a dataset via the [LangSmith UI](/langsmith/evaluation-concepts#datasets) or via the [SDK](/langsmith/manage-datasets-programmatically).
This tutorial uses Strands agents as example implementations, but the approach works with any OpenTelemetry-instrumentation.
Install dependencies:
```bash Python theme={null}
pip install langsmith strands-agents strands-agents-tools opentelemetry-sdk opentelemetry-exporter-otlp
```
```bash TypeScript theme={null}
npm install langsmith @strands-agents/sdk @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources
```
Set the following environment variables:
```bash theme={null}
# Tracing configuration
LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
LANGSMITH_API_KEY=""
OTEL_EXPORTER_OTLP_ENDPOINT = "https://api.smith.langchain.com/otel/"
# AWS Credentials
AWS_ACCESS_KEY_ID=""
AWS_SECRET_ACCESS_KEY=""
AWS_REGION_NAME=""
```
If you're [self-hosting LangSmith](/langsmith/self-hosted), replace `OTEL_EXPORTER_OTLP_ENDPOINT` with your self-hosted URL and append `/api/v1/otel`. For example: `OTEL_EXPORTER_OTLP_ENDPOINT = "https://ai-company.com/api/v1/otel"`.
Replace `LANGSMITH_ENDPOINT` with your LangSmith API endpoint. For example: `LANGSMITH_ENDPOINT = "https://ai-company.com/api/v1"`.
## Step 1. Create an experiment session
This guide assumes that a dataset has been created in LangSmith with examples to evaluate. You can create a dataset via the [LangSmith UI](/langsmith/evaluation-concepts#datasets) or via the [SDK](/langsmith/manage-datasets-programmatically).
An experiment session groups all evaluation traces together. Create one using the LangSmith client:
```python Python theme={null}
from langsmith import Client
# Initialize LangSmith client
client = Client()
experiment_name = "strands-agent-experiment"
# Assumes a dataset has been created. You can find the dataset ID in the LangSmith UI or via the SDK.
dataset_id = ""
# Create an experiment session linked to the dataset
project = client.create_project(
project_name=experiment_name,
reference_dataset_id=dataset_id
)
experiment_id = str(project.id)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
// Initialize LangSmith client
const client = new Client({
apiKey: process.env.LANGSMITH_API_KEY,
});
const experimentName = "strands-agent-experiment";
const datasetId = "your-dataset-id";
// Create an experiment session linked to the dataset
const project = await client.createProject({
projectName: experimentName,
referenceDatasetId: datasetId,
});
const experimentId = project.id;
```
Additionally, you can create evaluators in the LangSmith UI and bind them to your dataset. For evaluators defined in the UI and bound to your dataset, they will automatically run on experiment traces.
To learn more about evaluators, see [Evaluators](/langsmith/evaluation-concepts#evaluators).
## Step 2. Define an application and configure OpenTelemetry
First, you need an application that uses OpenTelemetry for tracing. This example uses a Strands agent, but you can use any OpenTelemetry-instrumented application. Set up OpenTelemetry to route traces to your experiment session by including the experiment ID in the OTEL headers. The general idea in this step is to have an agent or application that has been instrumented with OpenTelemetry.
TypeScript examples are not provided for this step as the `Strands TypeScript SDK` does not currently support `OpenTelemetry` observability (as of February 2026).
```python Python theme={null}
import os
from strands import Agent
from strands_tools import file_read, file_write, python_repl, shell, journal
from strands.telemetry import StrandsTelemetry
# Set OTEL headers with experiment ID as the project
api_key = os.getenv('LANGSMITH_API_KEY')
os.environ['OTEL_EXPORTER_OTLP_HEADERS'] = f"x-api-key={api_key},Langsmith-Project={experiment_id}"
# Initialize telemetry
strands_telemetry = StrandsTelemetry()
strands_telemetry.setup_otlp_exporter()
# Create an agent (Strands automatically creates OTel spans)
agent = Agent(
tools=[file_read, file_write, python_repl, shell, journal],
system_prompt="You are an Expert Software Developer.",
model="us.anthropic.claude-3-7-sonnet-20250219-v1:0",
)
```
For details on setting up OpenTelemetry tracing with LangSmith, see [Trace with OpenTelemetry](/langsmith/trace-with-opentelemetry).
## Step 3. Set up key span attributes
Add the required span attributes to each application run. These attributes link each trace to the experiment and the specific dataset example.
The following attributes are relevant for experiment evaluation:
| Attribute | Purpose |
| -------------------------------- | ------------------------------------------------- |
| `langsmith.trace.session_id` | Routes the trace to your experiment session |
| `langsmith.reference_example_id` | Links the trace to a specific dataset example |
| `langsmith.span.kind` | Sets the span type (e.g., "llm", "chain", "tool") |
| `inputs` | Records the input to your application |
| `outputs` | Records the output from your application |
For a complete list of supported OpenTelemetry attributes, see [Trace with OpenTelemetry](/langsmith/trace-with-opentelemetry#supported-opentelemetry-attribute-and-event-mapping).
```python Python theme={null}
from opentelemetry import trace
def evaluate_with_opentelemetry(agent, example_id: str, example_input: str, experiment_id: str):
tracer = trace.get_tracer(__name__)
# Wrapper span to add experiment metadata
with tracer.start_as_current_span("experiment_evaluation") as span:
# Route trace to the experiment
span.set_attribute("langsmith.trace.session_id", experiment_id)
# Link trace to the specific dataset example
span.set_attribute("langsmith.reference_example_id", example_id)
# Record input
span.set_attribute("inputs", example_input)
# Run the application
response = agent(example_input)
# Record output
output_text = getattr(response, "output", str(response))
span.set_attribute("outputs", output_text)
return output_text
```
```typescript TypeScript theme={null}
import { trace, Span } from "@opentelemetry/api";
async function evaluateWithAgent(
agent: Agent,
exampleId: string,
exampleInput: string,
experimentId: string
): Promise {
const tracer = trace.getTracer("experiment-runner");
return await tracer.startActiveSpan(
"experiment_evaluation",
async (span: Span) => {
try {
// Route trace to the experiment
span.setAttribute("langsmith.trace.session_id", experimentId);
// Link trace to the specific dataset example
span.setAttribute("langsmith.reference_example_id", exampleId);
// Record input
span.setAttribute("inputs", exampleInput);
// Run the application
const result = await agent.invoke(exampleInput);
// Record output
const response = String(result);
span.setAttribute("outputs", response);
return response;
} finally {
span.end();
}
}
);
}
```
## Step 4. Run evaluation by iterating through dataset examples
Each experiment run creates traces in LangSmith that are linked to your dataset examples.
```python Python theme={null}
# Iterate through dataset examples
for example in client.list_examples(dataset_name=dataset_name):
# Extract input from the example inputs dictionary
# Adjust the key based on your dataset structure
# (e.g., "input", "question", etc.)
example_input = example.inputs.get("input")
evaluate_with_opentelemetry(
agent=agent,
example_id=str(example.id),
example_input=str(example_input),
experiment_id=experiment_id
)
```
```typescript TypeScript theme={null}
// Iterate through dataset examples
for await (const example of client.listExamples({ datasetName })) {
// Extract input from the example inputs dictionary
// Adjust the key based on your dataset structure
// (e.g., "input", "question", etc.)
const exampleInput = example.inputs.input;
await evaluateWithAgent(
agent,
example.id,
String(exampleInput),
experimentId
);
}
```
After running the evaluation, you can [analyze the experiment](/langsmith/analyze-an-experiment) in the LangSmith UI to see:
* Individual trace details for each example
* Evaluator scores and feedback
* Comparisons between different experiment runs
Navigate to your experiment in the [LangSmith UI](https://smith.langchain.com) to analyze the results.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluate-with-opentelemetry.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to retry failed runs in experiments (Python only)
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluate-with-retry
When running [evaluations](/langsmith/evaluation-concepts#applying-evaluations) on large [datasets](/langsmith/evaluation-concepts#datasets), you may encounter failures on a small subset of examples due to rate limits, network issues, or other transient errors. Rather than re-running the entire evaluation, you can identify and retry only the failed examples on an [experiment](/langsmith/evaluation-concepts#experiments).
This guide shows an approach to build retry logic into your evaluation workflow and to retry only the failed examples. You can use the `error_handling='ignore'` parameter to skip logging errored runs, then automatically identify unsuccessful examples and re-run them in Python.
## Step 1. Run the initial evaluation
Run the initial evaluation, ignoring errors to prevent errored runs from being logged:
```python theme={null}
from langsmith import Client
client = Client()
# Run initial evaluation, ignoring errors
# error_handling='ignore' prevents errored runs from being logged
results = await client.aevaluate(
target,
data="dataset",
evaluators=[your_evaluators],
error_handling='ignore'
)
```
## Step 2. Retry on failed examples and log to same experiment
Fetch all the unsuccessful examples:
```python theme={null}
# Identify unsuccessful examples
runs = client.list_runs(project_name=results.experiment_name)
successful_example_ids = [r.reference_example_id for r in runs]
unsuccessful_examples = (e for e in client.list_examples(dataset_name="dataset") if e.id not in successful_examples)
```
Next, re-run all the failed examples and log them to the same experiment:
```python theme={null}
# Retry only the failed examples, log
results_retry = await client.aevaluate(
target,
unsuccessful_examples,
evaluators=[your_evaluators],
experiment=results.experiment_name,
error_handling='ignore'
)
```
## Related topics
* [Run an evaluation](/langsmith/evaluate-llm-application)
* [Run an evaluation asynchronously](/langsmith/evaluation-async)
* [Handle model rate limits](/langsmith/rate-limiting)
* [Experiment configuration](/langsmith/experiment-configuration)
* [Evaluate existing experiment](/langsmith/evaluate-existing-experiment)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluate-with-retry.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith Evaluation
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluation
LangSmith supports two types of evaluations based on when and where they run:
**Test before you ship**
Run evaluations on curated datasets during development to compare versions, benchmark performance, and catch regressions.
**Monitor in production**
Evaluate real user interactions in real-time to detect issues and measure quality on live traffic.
## Evaluation workflow
Create a [dataset](/langsmith/manage-datasets) with [examples](/langsmith/evaluation-concepts#examples) from manually curated test cases, historical production traces, or synthetic data generation.
Create [evaluators](/langsmith/evaluation-concepts#evaluators) to score performance:
* [Human](/langsmith/evaluation-concepts#human) review
* [Code](/langsmith/evaluation-concepts#code) rules
* [LLM-as-judge](/langsmith/llm-as-judge)
* [Pairwise](/langsmith/evaluate-pairwise) comparison
Execute your application on the dataset to create an [experiment](/langsmith/evaluation-concepts#experiment). Configure [repetitions, concurrency, and caching](/langsmith/experiment-configuration) to optimize runs.
Compare experiments for [benchmarking](/langsmith/evaluation-types#benchmarking), [unit tests](/langsmith/evaluation-types#unit-tests), [regression tests](/langsmith/evaluation-types#regression-tests), or [backtesting](/langsmith/evaluation-types#backtesting).
Each interaction creates a [run](/langsmith/evaluation-concepts#runs) without reference outputs.
Set up [evaluators](/langsmith/online-evaluations-llm-as-judge) to run automatically on production traces: safety checks, format validation, quality heuristics, and reference-free LLM-as-judge. Apply [filters and sampling rates](/langsmith/online-evaluations-llm-as-judge#4-optional-configure-a-sampling-rate) to control costs.
Evaluators run automatically on [runs](/langsmith/evaluation-concepts#runs) or [threads](/langsmith/online-evaluations-llm-as-judge#configure-multi-turn-online-evaluators), providing real-time monitoring, anomaly detection, and alerting.
Add failing production traces to your [dataset](/langsmith/manage-datasets), create targeted evaluators, validate fixes with offline experiments, and redeploy.
For more on the differences between offline and online evaluation, refer to the [Evaluation concepts](/langsmith/evaluation-concepts#quick-reference-offline-vs-online-evaluation) page.
## Get started
Get started with offline evaluation.
Create and manage datasets for evaluation through the UI or SDK.
Explore evaluation types, techniques, and frameworks for comprehensive testing.
View and analyze evaluation results, compare experiments, filter data, and export findings.
Monitor production quality in real-time from the Observability tab.
Learn by following step-by-step tutorials, from simple chatbots to complex agent evaluations.
To set up a LangSmith instance, visit the [Platform setup section](/langsmith/platform-setup) to choose between cloud, hybrid, or self-hosted. All options include observability, evaluation, prompt engineering, and deployment.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluation.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Application-specific evaluation approaches
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluation-approaches
Below, we will discuss evaluation of a few popular types of LLM applications.
## Agents
[LLM-powered autonomous agents](https://lilianweng.github.io/posts/2023-06-23-agent/) combine three components (1) Tool calling, (2) Memory, and (3) Planning. Agents [use tool calling](https://docs.langchain.com/oss/python/langchain/tools) with planning (e.g., often via prompting) and memory (e.g., often short-term message history) to generate responses. [Tool calling](https://docs.langchain.com/oss/python/langchain/tools) allows a model to respond to a given prompt by generating two things: (1) a tool to invoke and (2) the input arguments required.
Below is a tool-calling agent in [LangGraph](https://langchain-ai.github.io/langgraph/tutorials/introduction/). The `assistant node` is an LLM that determines whether to invoke a tool based upon the input. The `tool condition` sees if a tool was selected by the `assistant node` and, if so, routes to the `tool node`. The `tool node` executes the tool and returns the output as a tool message to the `assistant node`. This loop continues until as long as the `assistant node` selects a tool. If no tool is selected, then the agent directly returns the LLM response.
This sets up three general types of agent evaluations that users are often interested in:
* `Final Response`: Evaluate the agent's final response.
* `Single step`: Evaluate any agent step in isolation (e.g., whether it selects the appropriate tool).
* `Trajectory`: Evaluate whether the agent took the expected path (e.g., of tool calls) to arrive at the final answer.
Below we will cover what these are, the components (inputs, outputs, evaluators) needed for each one, and when you should consider this. Note that you likely will want to do multiple (if not all!) of these types of evaluations - they are not mutually exclusive!
### Evaluating an agent's final response
One way to evaluate an agent is to assess its overall performance on a task. This basically involves treating the agent as a black box and simply evaluating whether or not it gets the job done.
The inputs should be the user input and (optionally) a list of tools. In some cases, tool are hardcoded as part of the agent and they don't need to be passed in. In other cases, the agent is more generic, meaning it does not have a fixed set of tools and tools need to be passed in at run time.
The output should be the agent's final response.
The evaluator varies depending on the task you are asking the agent to do. Many agents perform a relatively complex set of steps and the output a final text response. Similar to RAG, LLM-as-judge evaluators are often effective for evaluation in these cases because they can assess whether the agent got a job done directly from the text response.
However, there are several downsides to this type of evaluation. First, it usually takes a while to run. Second, you are not evaluating anything that happens inside the agent, so it can be hard to debug when failures occur. Third, it can sometimes be hard to define appropriate evaluation metrics.
### Evaluating a single step of an agent
Agents generally perform multiple actions. While it is useful to evaluate them end-to-end, it can also be useful to evaluate these individual actions. This generally involves evaluating a single step of the agent - the LLM call where it decides what to do.
The inputs should be the input to a single step. Depending on what you are testing, this could just be the raw user input (e.g., a prompt and / or a set of tools) or it can also include previously completed steps.
The outputs are just the output of that step, which is usually the LLM response. The LLM response often contains tool calls, indicating what action the agent should take next.
The evaluator for this is usually some binary score for whether the correct tool call was selected, as well as some heuristic for whether the input to the tool was correct. The reference tool can be simply specified as a string.
There are several benefits to this type of evaluation. It allows you to evaluate individual actions, which lets you hone in where your application may be failing. They are also relatively fast to run (because they only involve a single LLM call) and evaluation often uses simple heuristic evaluation of the selected tool relative to the reference tool. One downside is that they don't capture the full agent - only one particular step. Another downside is that dataset creation can be challenging, particular if you want to include past history in the agent input. It is pretty easy to generate a dataset for steps early on in an agent's trajectory (e.g., this may only include the input prompt), but it can be difficult to generate a dataset for steps later on in the trajectory (e.g., including numerous prior agent actions and responses).
### Evaluating an agent's trajectory
Evaluating an agent's trajectory involves evaluating all the steps an agent took.
The inputs are again the inputs to the overall agent (the user input, and optionally a list of tools).
The outputs are a list of tool calls, which can be formulated as an "exact" trajectory (e.g., an expected sequence of tool calls) or simply a set of tool calls that are expected (in any order).
The evaluator here is some function over the steps taken. Assessing the "exact" trajectory can use a single binary score that confirms an exact match for each tool name in the sequence. This is simple, but has some flaws. Sometimes there can be multiple correct paths. This evaluation also does not capture the difference between a trajectory being off by a single step versus being completely wrong.
To address these flaws, evaluation metrics can focused on the number of "incorrect" steps taken, which better accounts for trajectories that are close versus ones that deviate significantly. Evaluation metrics can also focus on whether all of the expected tools are called in any order.
However, none of these approaches evaluate the input to the tools; they only focus on the tools selected. In order to account for this, another evaluation technique is to pass the full agent's trajectory (along with a reference trajectory) as a set of messages (e.g., all LLM responses and tool calls) an LLM-as-judge. This can evaluate the complete behavior of the agent, but it is the most challenging reference to compile (luckily, using a framework like LangGraph can help with this!). Another downside is that evaluation metrics can be somewhat tricky to come up with.
## Retrieval augmented generation (RAG)
Retrieval Augmented Generation (RAG) is a powerful technique that involves retrieving relevant documents based on a user's input and passing them to a language model for processing. RAG enables AI applications to generate more informed and context-aware responses by leveraging external knowledge.
For a comprehensive review of RAG concepts, see our [`RAG From Scratch` series](https://github.com/langchain-ai/rag-from-scratch).
### Dataset
When evaluating RAG applications, a key consideration is whether you have (or can easily obtain) reference answers for each input question. Reference answers serve as ground truth for assessing the correctness of the generated responses. However, even in the absence of reference answers, various evaluations can still be performed using reference-free RAG evaluation prompts (examples provided below).
### Evaluator
`LLM-as-judge` is a commonly used evaluator for RAG because it's an effective way to evaluate factual accuracy or consistency between texts.
When evaluating RAG applications, you can have evaluators that require reference outputs and those that don't:
1. **Require reference output**: Compare the RAG chain's generated answer or retrievals against a reference answer (or retrievals) to assess its correctness.
2. **Don't require reference output**: Perform self-consistency checks using prompts that don't require a reference answer (represented by orange, green, and red in the above figure).
### Applying RAG evaluation
When applying RAG evaluation, consider the following approaches:
1. `Offline evaluation`: Use offline evaluation for any prompts that rely on a reference answer. This is most commonly used for RAG answer correctness evaluation, where the reference is a ground truth (correct) answer.
2. `Online evaluation`: Employ online evaluation for any reference-free prompts. This allows you to assess the RAG application's performance in real-time scenarios.
3. `Pairwise evaluation`: Utilize pairwise evaluation to compare answers produced by different RAG chains. This evaluation focuses on user-specified criteria (e.g., answer format or style) rather than correctness, which can be evaluated using self-consistency or a ground truth reference.
### RAG evaluation summary
| Evaluator | Detail | Needs reference output | LLM-as-judge? | Pairwise relevant |
| ------------------- | ------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------- | ----------------- |
| Document relevance | Are documents relevant to the question? | No | Yes - [prompt](https://smith.langchain.com/hub/langchain-ai/rag-document-relevance) | No |
| Answer faithfulness | Is the answer grounded in the documents? | No | Yes - [prompt](https://smith.langchain.com/hub/langchain-ai/rag-answer-hallucination) | No |
| Answer helpfulness | Does the answer help address the question? | No | Yes - [prompt](https://smith.langchain.com/hub/langchain-ai/rag-answer-helpfulness) | No |
| Answer correctness | Is the answer consistent with a reference answer? | Yes | Yes - [prompt](https://smith.langchain.com/hub/langchain-ai/rag-answer-vs-reference) | No |
| Pairwise comparison | How do multiple answer versions compare? | No | Yes - [prompt](https://smith.langchain.com/hub/langchain-ai/pairwise-evaluation-rag) | Yes |
## Summarization
Summarization is one specific type of free-form writing. The evaluation aim is typically to examine the writing (summary) relative to a set of criteria.
`Developer curated examples` of texts to summarize are commonly used for evaluation (see a dataset example [here](https://smith.langchain.com/public/659b07af-1cab-4e18-b21a-91a69a4c3990/d)). However, `user logs` from a production (summarization) app can be used for online evaluation with any of the `Reference-free` evaluation prompts below.
`LLM-as-judge` is typically used for evaluation of summarization (as well as other types of writing) using `Reference-free` prompts that follow provided criteria to grade a summary. It is less common to provide a particular `Reference` summary, because summarization is a creative task and there are many possible correct answers.
`Online` or `Offline` evaluation are feasible because of the `Reference-free` prompt used. `Pairwise` evaluation is also a powerful way to perform comparisons between different summarization chains (e.g., different summarization prompts or LLMs):
| Use Case | Detail | Needs reference output | LLM-as-judge? | Pairwise relevant |
| ---------------- | -------------------------------------------------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------- | ----------------- |
| Factual accuracy | Is the summary accurate relative to the source documents? | No | Yes - [prompt](https://smith.langchain.com/hub/langchain-ai/summary-accurancy-evaluator) | Yes |
| Faithfulness | Is the summary grounded in the source documents (e.g., no hallucinations)? | No | Yes - [prompt](https://smith.langchain.com/hub/langchain-ai/summary-hallucination-evaluator) | Yes |
| Helpfulness | Is summary helpful relative to user need? | No | Yes - [prompt](https://smith.langchain.com/hub/langchain-ai/summary-helpfulness-evaluator) | Yes |
## Classification and tagging
Classification and tagging apply a label to a given input (e.g., for toxicity detection, sentiment analysis, etc). Classification/tagging evaluation typically employs the following components, which we will review in detail below:
A central consideration for classification/tagging evaluation is whether you have a dataset with `reference` labels or not. If not, users frequently want to define an evaluator that uses criteria to apply label (e.g., toxicity, etc) to an input (e.g., text, user-question, etc). However, if ground truth class labels are provided, then the evaluation objective is focused on scoring a classification/tagging chain relative to the ground truth class label (e.g., using metrics such as precision, recall, etc).
If ground truth reference labels are provided, then it's common to simply define a [custom heuristic evaluator](/langsmith/code-evaluator-ui) to compare ground truth labels to the chain output. However, it is increasingly common given the emergence of LLMs simply use `LLM-as-judge` to perform the classification/tagging of an input based upon specified criteria (without a ground truth reference).
`Online` or `Offline` evaluation is feasible when using `LLM-as-judge` with the `Reference-free` prompt used. In particular, this is well suited to `Online` evaluation when a user wants to tag / classify application input (e.g., for toxicity, etc).
| Use Case | Detail | Needs reference output | LLM-as-judge? | Pairwise relevant |
| --------- | ------------------- | ---------------------- | ------------- | ----------------- |
| Accuracy | Standard definition | Yes | No | No |
| Precision | Standard definition | Yes | No | No |
| Recall | Standard definition | Yes | No | No |
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluation-approaches.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to run an evaluation asynchronously
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluation-async
[Evaluations](/langsmith/evaluation-concepts#applying-evaluations) | [Evaluators](/langsmith/evaluation-concepts#evaluators) | [Datasets](/langsmith/evaluation-concepts#datasets) | [Experiments](/langsmith/evaluation-concepts#experiments)
We can run evaluations asynchronously via the SDK using [aevaluate()](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._arunner.aevaluate), which accepts all of the same arguments as [evaluate()](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._runner.evaluate) but expects the application function to be asynchronous. You can learn more about how to use the `evaluate()` function [here](/langsmith/evaluate-llm-application).
This guide is only relevant when using the Python SDK. In JS/TS the `evaluate()` function is already async. You can see how to use it [here](/langsmith/evaluate-llm-application).
## Use `aevaluate()`
* Python
Requires `langsmith>=0.3.13`
```python theme={null}
from langsmith import wrappers, Client
from openai import AsyncOpenAI
# Optionally wrap the OpenAI client to trace all model calls.
oai_client = wrappers.wrap_openai(AsyncOpenAI())
# Optionally add the 'traceable' decorator to trace the inputs/outputs of this function.
@traceable
async def researcher_app(inputs: dict) -> str:
instructions = """You are an excellent researcher. Given a high-level research idea, \
list 5 concrete questions that should be investigated to determine if the idea is worth pursuing."""
response = await oai_client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": instructions},
{"role": "user", "content": inputs["idea"]},
],
)
return response.choices[0].message.content
# Evaluator functions can be sync or async
def concise(inputs: dict, outputs: dict) -> bool:
return len(outputs["output"]) < 3 * len(inputs["idea"])
ls_client = Client()
ideas = [
"universal basic income",
"nuclear fusion",
"hyperloop",
"nuclear powered rockets",
]
dataset = ls_client.create_dataset("research ideas")
ls_client.create_examples(
dataset_name=dataset.name,
examples=[{"inputs": {"idea": i}} for i in ideas],
)
# Can equivalently use the 'aevaluate' function directly:
# from langsmith import aevaluate
# await aevaluate(...)
results = await ls_client.aevaluate(
researcher_app,
data=dataset,
evaluators=[concise],
# Optional, add concurrency.
max_concurrency=2, # Optional, add concurrency.
experiment_prefix="gpt-4.1-mini-baseline" # Optional, random by default.
)
```
## Related
* [Run an evaluation (synchronously)](/langsmith/evaluate-llm-application)
* [Handle model rate limits](/langsmith/rate-limiting)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluation-async.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Evaluation concepts
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluation-concepts
LLM outputs are non-deterministic, which makes response quality hard to assess. Evaluations (evals) are a way to breakdown what "good" looks like and measure it. LangSmith Evaluation provides a framework for measuring quality throughout the application lifecycle, from pre-deployment testing to production monitoring.
## What to evaluate
Before building evaluations, identify what matters for your application. Break down your system into its critical components—LLM calls, retrieval steps, tool invocations, output formatting—and determine quality criteria for each.
**Start with manually curated examples.** Create 5-10 examples of what "good" looks like for each critical component. These examples serve as your ground truth and inform which evaluation approaches to use. For instance:
* **RAG system**: Examples of good retrievals (relevant documents) and good answers (accurate, complete).
* **Agent**: Examples of correct tool selection and proper argument formatting or trajectory that the agent took.
* **Chatbot**: Examples of helpful, on-brand responses that address user intent.
Once you've defined "good" through examples, you can measure how often your system produces similar quality outputs.
## Offline and online evaluations
LangSmith supports two types of evaluations that serve different purposes in your development workflow:
### Offline evaluations
Use offline evaluations for **pre-deployment testing**:
* **Benchmarking**: Compare multiple versions to find the best performer.
* **Regression testing**: Ensure new versions don't degrade quality.
* **Unit testing**: Verify correctness of individual components.
* **Backtesting**: Test new versions against historical data.
Offline evaluations target [*examples*](#examples) from [*datasets*](#datasets)—curated test cases with reference outputs that define what "good" looks like.
### Online evaluations
Use online evaluations for **production monitoring**:
* **Real-time monitoring**: Track quality continuously on live traffic.
* **Anomaly detection**: Flag unusual patterns or edge cases.
* **Production feedback**: Identify issues to add to offline datasets.
Online evaluations target [*runs*](#runs) and [*threads*](#threads) from [tracing](/langsmith/observability-quickstart)—real production traces without reference outputs.
This difference in targets determines what you can evaluate: offline evaluations can check correctness against expected answers, while online evaluations focus on quality patterns, safety, and real-world behavior.
## Evaluation lifecycle
As you develop and [deploy your application](/langsmith/deployments), your evaluation strategy evolves from pre-deployment testing to production monitoring. During development and testing, offline evaluations validate functionality against curated datasets. After deployment, online evaluations monitor production behavior on live traffic. As applications mature, both evaluation types work together in an iterative feedback loop to improve quality continuously.
```mermaid theme={null}
graph LR
A[Development] --> B[Testing]
B --> C[Deployment]
C --> D[Monitoring]
D --> E[Iteration]
A -.-> F[Offline]
B -.-> F
C -.-> G[Online]
D -.-> G
E -.-> H[Both]
style F fill:#8b5cf6,stroke:#7c3aed,color:#fff
style G fill:#8b5cf6,stroke:#7c3aed,color:#fff
style H fill:#8b5cf6,stroke:#7c3aed,color:#fff
```
### 1. Development with offline evaluation
Before production deployment, use offline evaluations to validate functionality, benchmark different approaches, and build confidence.
Follow the [quickstart](/langsmith/evaluation-quickstart) to run your first offline evaluation.
### 2. Initial deployment with online evaluation
After deployment, use online evaluations to monitor production quality, detect unexpected issues, and collect real-world data.
Learn how to [configure online evaluations](/langsmith/online-evaluations-llm-as-judge) for production monitoring.
### 3. Continuous improvement
Use both evaluation types together in an iterative feedback loop. Online evaluations surface issues that become offline test cases, offline evaluations validate fixes, and online evaluations confirm production improvements.
## Core evaluation targets
Evaluations run on different targets depending on whether they are offline or online.
### Targets for offline evaluation
Offline evaluations run on datasets and examples. The presence of reference outputs enables comparison between expected and actual results.
#### Datasets
A dataset is a *collection of examples* used for evaluating an application. An example is a test input, reference output pair.
#### Examples
Each example consists of:
* **Inputs**: a dictionary of input variables to pass to your application.
* **Reference outputs** (optional): a dictionary of reference outputs. These do not get passed to your application, they are only used in evaluators.
* **Metadata** (optional): a dictionary of additional information that can be used to create filtered views of a dataset.
Learn more about [managing datasets](/langsmith/manage-datasets).
#### Experiment
An *experiment* represents the results of evaluating a specific application version on a dataset. Each experiment captures outputs, evaluator scores, and execution traces for every example in the dataset.
Multiple experiments typically run on a given dataset to test different application configurations (e.g., different prompts or LLMs). LangSmith displays all experiments associated with a dataset and supports [comparing multiple experiments](/langsmith/compare-experiment-results) side-by-side.
Learn [how to analyze experiment results](/langsmith/analyze-an-experiment).
### Targets for online evaluation
Online evaluations run on runs and threads from production traffic. Without reference outputs, evaluators focus on detecting issues, anomalies, and quality degradation in real-time.
#### Runs
A *run* is a single execution trace from your [deployed application](/langsmith/deployments). Each run contains:
* **Inputs**: The actual user inputs your application received.
* **Outputs**: What your application actually returned.
* **Intermediate steps**: All the child runs (tool calls, LLM calls, and so on).
* **Metadata**: Tags, user feedback, latency metrics, etc.
Unlike examples in datasets, runs do not include reference outputs. Online evaluators must assess quality without knowing what the "correct" answer should be, relying instead on quality heuristics, safety checks, and reference-free evaluation techniques.
Learn more about [runs and traces in the Observability concepts](/langsmith/observability-concepts#runs).
#### Threads
*Threads* are collections of related runs representing multi-turn conversations. Online evaluators can run at the thread level to evaluate entire conversations rather than individual turns. This enables assessment of conversation-level properties like coherence across turns, topic maintenance, and user satisfaction throughout an interaction.
## Evaluators
*Evaluators* are functions that score application performance. They provide the measurement layer for both offline and online evaluation, adapting their inputs based on what data is available.
Run evaluators using the LangSmith SDK ([Python](https://docs.smith.langchain.com/reference/python/reference) and [TypeScript](https://docs.smith.langchain.com/reference/js)), via the [Prompt Playground](/langsmith/observability-concepts#prompt-playground), or by configuring [rules](/langsmith/rules) to run them automatically on tracing projects or datasets.
### Evaluator inputs
Evaluator inputs differ based on evaluation type:
**Offline evaluators** receive:
* [Example](#examples): The example from your [dataset](#datasets), containing inputs, reference outputs, and metadata.
* [Run](/langsmith/observability-concepts#runs): The actual outputs and intermediate steps from running the application on the example inputs.
**Online evaluators** receive:
* [Run](/langsmith/observability-concepts#runs): The production trace containing inputs, outputs, and intermediate steps (no reference outputs available).
### Evaluator outputs
Evaluators return **feedback**, which is the scores from evaluation. Feedback is a dictionary or list of dictionaries. Each dictionary contains:
* `key`: The metric name.
* `score` | `value`: The metric value (`score` for numerical metrics, `value` for categorical metrics).
* `comment` (optional): Additional reasoning or explanation for the score.
### Evaluation techniques
LangSmith supports several evaluation approaches:
* [Human](#human)
* [Code](#code)
* [LLM-as-judge](#llm-as-judge)
* [Pairwise](#pairwise)
#### Human
*Human evaluation* involves manual review of application outputs and execution traces. This approach is [often an effective starting point for evaluation](https://hamel.dev/blog/posts/evals/#looking-at-your-traces). LangSmith provides tools to review application outputs and traces (all intermediate steps).
**Annotation queues**
[Annotation queues](/langsmith/annotation-queues) streamline structured collection of human feedback on runs. They complement [inline annotation](/langsmith/annotate-traces-inline) by providing organized workflows with prescribed rubrics, team collaboration features, and progress tracking.
LangSmith supports two queue types:
* **Single-run queues**: Review one run at a time against custom rubric items. Useful for triaging issues or building datasets from production traces.
* **Pairwise queues**: Compare two runs side-by-side to judge which is better. Designed for fast A/B comparisons between experiments.
Key features include configuring multiple reviewers per run, enabling reservations to prevent conflicts, and exporting annotated runs directly to datasets for future evaluations.
#### Code
*Code evaluators* are deterministic, rule-based functions. They work well for checks such as verifying the structure of a chatbot's response is not empty, that generated code compiles, or that a classification matches exactly.
#### LLM-as-judge
*LLM-as-judge evaluators* use LLMs to score application outputs. The grading rules and criteria are typically encoded in the LLM prompt. These evaluators can be:
* **Reference-free**: Check if output contains offensive content or adheres to specific criteria.
* **Reference-based**: Compare output to a reference (e.g., check factual accuracy relative to the reference).
LLM-as-judge evaluators require careful review of scores and prompt tuning. Few-shot evaluators, which include examples of inputs, outputs, and expected grades in the grader prompt, often improve performance.
Learn about [how to define an LLM-as-a-judge evaluator](/langsmith/llm-as-judge).
#### Pairwise
*Pairwise evaluators* compare outputs from two application versions using heuristics (e.g., which response is longer), LLMs (with pairwise prompts), or human reviewers.
Pairwise evaluation works well when directly scoring an output is difficult but comparing two outputs is straightforward. For example, in summarization tasks, choosing the more informative of two summaries is often easier than assigning an absolute score to a single summary.
Learn [how run pairwise evaluations](/langsmith/evaluate-pairwise).
### Reference-free vs reference-based evaluators
Understanding whether an evaluator requires reference outputs is essential for determining when it can be used.
**Reference-free evaluators** assess quality without comparing to expected outputs. These work for both offline and online evaluation:
* **Safety checks**: Toxicity detection, PII detection, content policy violations
* **Format validation**: JSON structure, required fields, schema compliance
* **Quality heuristics**: Response length, latency, specific keywords
* **Reference-free LLM-as-judge**: Clarity, coherence, helpfulness, tone
**Reference-based evaluators** require reference outputs and only work for offline evaluation:
* **Correctness**: Semantic similarity to reference answer
* **Factual accuracy**: Fact-checking against ground truth
* **Exact match**: Classification tasks with known labels
* **Reference-based LLM-as-judge**: Comparing output quality to a reference
When designing an evaluation strategy, reference-free evaluators provide consistency across both offline testing and online monitoring, while reference-based evaluators enable more precise correctness checks during development.
## Evaluation types
LangSmith supports various evaluation approaches for different stages of development and deployment. Understanding when to use each type helps build a comprehensive evaluation strategy.
Offline and online evaluations serve different purposes:
* **Offline evaluation types** test pre-deployment on curated datasets with reference outputs
* **Online evaluation types** monitor production behavior on live traffic without reference outputs
Learn more about [evaluation types and when to use each](/langsmith/evaluation-types).
## Best practices
### Building datasets
There are various strategies for building datasets:
**Manually curated examples**
This is the recommended starting point. Create 10–20 high-quality examples covering common scenarios and edge cases. These examples define what "good" looks like for your application.
**Historical traces**
Once in production, convert real traces into examples. For high-traffic applications:
* **User feedback**: Add runs that received negative feedback to test against.
* **Heuristics**: Identify interesting runs (e.g., long latency, errors).
* **LLM feedback**: Use LLMs to detect noteworthy conversations.
**Synthetic data**
Generate additional examples from existing ones. Works best when starting with several high-quality, hand-crafted examples as templates.
### Dataset organization
**Splits**
Partition datasets into subsets for targeted evaluation. Use splits for performance optimization (smaller splits for rapid iteration) and interpretability (evaluate different input types separately).
Learn how to [create and manage dataset splits](/langsmith/manage-datasets-in-application#create-and-manage-dataset-splits).
**Versions**
LangSmith automatically creates dataset [versions](/langsmith/manage-datasets#version-a-dataset) when examples change. [Tag versions](/langsmith/manage-datasets#tag-a-version) to mark important milestones. Target specific versions in CI pipelines to ensure dataset updates don't break workflows.
### Human feedback collection
Human feedback often provides the most valuable assessment, particularly for subjective quality dimensions.
**Annotation queues**
[Annotation queues](/langsmith/annotation-queues) enable structured collection of human feedback. Flag specific runs for review, collect annotations in a streamlined interface, and transfer annotated runs to datasets for future evaluations.
Annotation queues complement [inline annotation](/langsmith/annotate-traces-inline) by offering additional capabilities: grouping runs, specifying criteria, and configuring reviewer permissions.
### Evaluations vs testing
Testing and evaluation are similar but distinct concepts.
**Evaluation measures performance according to metrics.** Metrics can be fuzzy or subjective, and prove more useful in relative terms. They typically compare systems against each other.
**Testing asserts correctness.** A system can only be deployed if it passes all tests.
Evaluation metrics can be converted into tests. For example, regression tests can assert that new versions must outperform baseline versions on relevant metrics. Run tests and evaluations together for efficiency when systems are expensive to run.
Evaluations can be written using standard testing tools like [pytest](/langsmith/pytest) or [Vitest/Jest](/langsmith/vitest-jest).
## Quick reference: Offline vs online evaluation
The following table summarizes the key differences between offline and online evaluations:
| | **Offline Evaluation** | **Online Evaluation** |
| --------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------- |
| **Runs on** | Dataset (Examples) | Tracing Project (Runs/Threads) |
| **Data access** | Inputs, Outputs, Reference Outputs | Inputs, Outputs only |
| **When to use** | Pre-deployment, during development | Production, post-deployment |
| **Primary use cases** | Benchmarking, unit testing, regression testing, backtesting | Real-time monitoring, production feedback, anomaly detection |
| **Evaluation timing** | Batch processing on curated test sets | Real-time or near real-time on live traffic |
| **Setup location** | Evaluation tab (SDK, UI, Prompt Playground) | [Observability tab](/langsmith/online-evaluations-llm-as-judge) (automated rules) |
| **Data requirements** | Requires dataset curation | No dataset needed, evaluates live traces |
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluation-concepts.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Evaluation quickstart
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluation-quickstart
[*Evaluations*](/langsmith/evaluation-concepts) are a quantitative way to measure the performance of LLM applications. LLMs can behave unpredictably, even small changes to prompts, models, or inputs can significantly affect results. Evaluations provide a structured way to identify failures, compare versions, and build more reliable AI applications.
Running an evaluation in LangSmith requires three key components:
* [*Dataset*](/langsmith/evaluation-concepts#datasets): A set of test inputs (and optionally, expected outputs).
* [*Target function*](/langsmith/define-target-function): The part of your application you want to test—this might be a single LLM call with a new prompt, one module, or your entire workflow.
* [*Evaluators*](/langsmith/evaluation-concepts#evaluators): Functions that score your target function’s outputs.
This quickstart guides you through running a starter evaluation that checks the correctness of LLM responses, using either the LangSmith SDK or UI.
If you prefer to watch a video on getting started with tracing, refer to the datasets and evaluations [Video guide](#video-guide).
## Prerequisites
Before you begin, make sure you have:
* **A LangSmith account**: Sign up or log in at [smith.langchain.com](https://smith.langchain.com).
* **A LangSmith API key**: Follow the [Create an API key](/langsmith/create-account-api-key#create-an-api-key) guide.
* **An OpenAI API key**: Generate this from the [OpenAI dashboard](https://platform.openai.com/account/api-keys).
**Select the UI or SDK filter for instructions:**
## 1. Set workspace secrets
In the [LangSmith UI](https://smith.langchain.com), ensure that your API key is set as a [workspace secret](/langsmith/administration-overview#workspace-secrets).
1. Navigate to **Settings** and then move to the **Secrets** tab.
2. Select **Add secret** and enter the key environment variable (e.g.,`OPENAI_API_KEY` or `ANTHROPIC_API_KEY`) and your API key as the **Value**.
3. Select **Save secret**.
When adding workspace secrets in the LangSmith UI, make sure the secret keys match the environment variable names expected by your model provider.
## 2. Create a prompt
LangSmith's [Prompt Playground](/langsmith/observability-concepts#prompt-playground) makes it possible to run evaluations over different prompts, new models, or test different model configurations.
1. In the [LangSmith UI](https://smith.langchain.com), navigate to the **Playground** under **Prompt Engineering**.
2. Under the **Prompts** panel, modify the **system** prompt to:
```
Answer the following question accurately:
```
Leave the **Human** message as is: `{question}`.
## 3. Create a dataset
1. Click **Set up Evaluation**, which will open a **New Experiment** table at the bottom of the page.
2. In the **Select or create a new dataset** dropdown, click the **+ New** button to create a new dataset.
3. Add the following examples to the dataset:
| Inputs | Reference Outputs |
| -------------------------------------------------------- | ------------------------------------------------- |
| question: Which country is Mount Kilimanjaro located in? | output: Mount Kilimanjaro is located in Tanzania. |
| question: What is Earth's lowest point? | output: Earth's lowest point is The Dead Sea. |
4. Click **Save** and enter a name to save your newly created dataset.
## 4. Add an evaluator
1. Click **+ Evaluator** and select **Correctness** from the **Pre-built Evaluator** options.
2. In the **Correctness** panel, click **Save**.
## 5. Run your evaluation
1. Select **Start** on the top right to run your evaluation. This will create an [*experiment*](/langsmith/evaluation-concepts#experiment) with a preview in the **New Experiment** table. You can view in full by clicking the experiment name.
## Next steps
To learn more about running experiments in LangSmith, read the [evaluation conceptual guide](/langsmith/evaluation-concepts).
* For more details on evaluations, refer to the [Evaluation documentation](/langsmith/evaluation).
* Learn how to [create and manage datasets in the UI](/langsmith/manage-datasets-in-application#set-up-your-dataset).
* Learn how to [run an evaluation from the prompt playground](/langsmith/run-evaluation-from-prompt-playground).
This guide uses prebuilt LLM-as-judge evaluators from the open-source [`openevals`](https://github.com/langchain-ai/openevals) package. OpenEvals includes a set of commonly used evaluators and is a great starting point if you're new to evaluations. If you want greater flexibility in how you evaluate your apps, you can also [define completely custom evaluators](/langsmith/code-evaluator-ui).
## 1. Install dependencies
In your terminal, create a directory for your project and install the dependencies in your environment:
```bash Python theme={null}
mkdir ls-evaluation-quickstart && cd ls-evaluation-quickstart
python -m venv .venv && source .venv/bin/activate
python -m pip install --upgrade pip
pip install -U langsmith openevals openai
```
```bash TypeScript theme={null}
mkdir ls-evaluation-quickstart-ts && cd ls-evaluation-quickstart-ts
npm init -y
npm install langsmith openevals openai
npx tsc --init
```
If you are using `yarn` as your package manager, you will also need to manually install `@langchain/core` as a peer dependency of `openevals`. This is not required for LangSmith evals in general, you may define evaluators [using arbitrary custom code](/langsmith/code-evaluator-ui).
## 2. Set up environment variables
Set the following environment variables:
* `LANGSMITH_TRACING`
* `LANGSMITH_API_KEY`
* `OPENAI_API_KEY` (or your LLM provider's API key)
* (optional) `LANGSMITH_WORKSPACE_ID`: If your LangSmith API key is linked to multiple [workspaces](/langsmith/administration-overview#workspaces), set this variable to specify which workspace to use.
```bash theme={null}
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=""
export OPENAI_API_KEY=""
export LANGSMITH_WORKSPACE_ID=""
```
If you're using Anthropic, use the [Anthropic wrapper](/langsmith/trace-anthropic) to trace your calls. For other providers, use [the traceable wrapper](/langsmith/annotate-code#use-%40traceable-%2F-traceable).
## 3. Create a dataset
1. Create a file and add the following code, which will:
* Import the `Client` to connect to LangSmith.
* Create a dataset.
* Define example [*inputs* and *outputs*](/langsmith/evaluation-concepts#examples).
* Associate the input and output pairs with that dataset in LangSmith so they can be used in evaluations.
```python Python theme={null}
# dataset.py
from langsmith import Client
def main():
client = Client()
# Programmatically create a dataset in LangSmith
dataset = client.create_dataset(
dataset_name="Sample dataset",
description="A sample dataset in LangSmith."
)
# Create examples
examples = [
{
"inputs": {"question": "Which country is Mount Kilimanjaro located in?"},
"outputs": {"answer": "Mount Kilimanjaro is located in Tanzania."},
},
{
"inputs": {"question": "What is Earth's lowest point?"},
"outputs": {"answer": "Earth's lowest point is The Dead Sea."},
},
]
# Add examples to the dataset
client.create_examples(dataset_id=dataset.id, examples=examples)
print("Created dataset:", dataset.name)
if __name__ == "__main__":
main()
```
```typescript TypeScript theme={null}
// dataset.ts
import { Client } from "langsmith";
async function main() {
const client = new Client();
const dataset = await client.createDataset(
"Sample dataset",
{ description: "A sample dataset in LangSmith." }
);
// Define examples
const inputs = [
{ question: "Which country is Mount Kilimanjaro located in?" },
{ question: "What is Earth's lowest point?" },
];
const outputs = [
{ answer: "Mount Kilimanjaro is located in Tanzania." },
{ answer: "Earth's lowest point is The Dead Sea." },
];
await client.createExamples({
datasetId: dataset.id,
inputs,
outputs,
});
console.log("Created dataset:", dataset.name);
}
if (require.main === module) {
main().catch((e) => {
console.error(e);
process.exit(1);
});
}
```
2. In your terminal, run the `dataset` file to create the datasets you'll use to evaluate your app:
```bash Python theme={null}
python dataset.py
```
```bash TypeScript theme={null}
npx ts-node dataset.ts
```
You'll see the following output:
```bash theme={null}
Created dataset: Sample dataset
```
## 4. Create your target function
Define a [target function](/langsmith/define-target-function) that contains what you're evaluating. In this guide, you'll define a target function that contains a single LLM call to answer a question.
Add the following to an `eval` file:
```python Python theme={null}
# eval.py
from langsmith import Client, wrappers
from openai import OpenAI
# Wrap the OpenAI client for LangSmith tracing
openai_client = wrappers.wrap_openai(OpenAI())
# Define the application logic you want to evaluate inside a target function
# The SDK will automatically send the inputs from the dataset to your target function
def target(inputs: dict) -> dict:
response = openai_client.chat.completions.create(
model="gpt-5-mini",
messages=[
{"role": "system", "content": "Answer the following question accurately"},
{"role": "user", "content": inputs["question"]},
],
)
return {"answer": response.choices[0].message.content.strip()}
```
```typescript TypeScript theme={null}
// eval.ts
import { evaluate } from "langsmith/evaluation";
import { wrapOpenAI } from "langsmith/wrappers/openai";
import OpenAI from "openai";
const openaiClient = wrapOpenAI(new OpenAI());
async function target(inputs: Record): Promise> {
const question = String(inputs.question ?? "");
const resp = await openaiClient.chat.completions.create({
model: "gpt-5-mini",
messages: [
{ role: "system", content: "Answer the following question accurately" },
{ role: "user", content: question },
],
});
return { answer: resp.choices[0].message.content?.trim() ?? "" };
}
```
## 5. Define an evaluator
In this step, you’re telling LangSmith how to grade the answers your app produces.
Import a prebuilt evaluation prompt (`CORRECTNESS_PROMPT`) from [`openevals`](https://github.com/langchain-ai/openevals) and a helper that wraps it into an [*LLM-as-judge evaluator*](/langsmith/evaluation-concepts#llm-as-judge), which will score the application's output.
`CORRECTNESS_PROMPT` is just an f-string with variables for `"inputs"`, `"outputs"`, and `"reference_outputs"`. See [here](https://github.com/langchain-ai/openevals#customizing-prompts) for more information on customizing OpenEvals prompts.
The evaluator compares:
* `inputs`: what was passed into your target function (e.g., the question text).
* `outputs`: what your target function returned (e.g., the model’s answer).
* `reference_outputs`: the ground truth answers you attached to each dataset example in [Step 3](#3-create-a-dataset).
Add the following highlighted code to your `eval` file:
```python Python highlight={3,4,21-31} theme={null}
from langsmith import Client, wrappers
from openai import OpenAI
from openevals.llm import create_llm_as_judge
from openevals.prompts import CORRECTNESS_PROMPT
# Wrap the OpenAI client for LangSmith tracing
openai_client = wrappers.wrap_openai(OpenAI())
# Define the application logic you want to evaluate inside a target function
# The SDK will automatically send the inputs from the dataset to your target function
def target(inputs: dict) -> dict:
response = openai_client.chat.completions.create(
model="gpt-5-mini",
messages=[
{"role": "system", "content": "Answer the following question accurately"},
{"role": "user", "content": inputs["question"]},
],
)
return {"answer": response.choices[0].message.content.strip()}
def correctness_evaluator(inputs: dict, outputs: dict, reference_outputs: dict):
evaluator = create_llm_as_judge(
prompt=CORRECTNESS_PROMPT,
model="openai:o3-mini",
feedback_key="correctness",
)
return evaluator(
inputs=inputs,
outputs=outputs,
reference_outputs=reference_outputs
)
```
```typescript TypeScript highlight={4,20-37} theme={null}
import { evaluate } from "langsmith/evaluation";
import { wrapOpenAI } from "langsmith/wrappers/openai";
import OpenAI from "openai";
import { createLLMAsJudge, CORRECTNESS_PROMPT } from "openevals";
const openaiClient = wrapOpenAI(new OpenAI());
async function target(inputs: Record): Promise> {
const question = String(inputs.question ?? "");
const resp = await openaiClient.chat.completions.create({
model: "gpt-5-mini",
messages: [
{ role: "system", content: "Answer the following question accurately" },
{ role: "user", content: question },
],
});
return { answer: resp.choices[0].message.content?.trim() ?? "" };
}
const judge = createLLMAsJudge({
prompt: CORRECTNESS_PROMPT,
model: "openai:o3-mini",
feedbackKey: "correctness",
});
async function correctnessEvaluator(run: {
inputs: Record;
outputs: Record;
referenceOutputs?: Record;
}) {
return judge({
inputs: run.inputs,
outputs: run.outputs,
// OpenEvals expects snake_case here:
reference_outputs: run.referenceOutputs,
});
}
```
## 6. Run and view results
To run the evaluation experiment, you'll call `evaluate(...)`, which:
* Pulls example from the dataset you created in [Step 3](#3-create-a-dataset).
* Sends each example's inputs to your target function from [Step 4](#4-add-an-evaluator).
* Collects the outputs (the model's answers).
* Passes the outputs along with the `reference_outputs` to your evaluator from [Step 5](#5-define-an-evaluator).
* Records all results in LangSmith as an experiment, so you can view them in the UI.
1. Add the highlighted code to your `eval` file:
```python Python highlight={33-49} theme={null}
from langsmith import Client, wrappers
from openai import OpenAI
from openevals.llm import create_llm_as_judge
from openevals.prompts import CORRECTNESS_PROMPT
# Wrap the OpenAI client for LangSmith tracing
openai_client = wrappers.wrap_openai(OpenAI())
# Define the application logic you want to evaluate inside a target function
# The SDK will automatically send the inputs from the dataset to your target function
def target(inputs: dict) -> dict:
response = openai_client.chat.completions.create(
model="gpt-5-mini",
messages=[
{"role": "system", "content": "Answer the following question accurately"},
{"role": "user", "content": inputs["question"]},
],
)
return {"answer": response.choices[0].message.content.strip()}
def correctness_evaluator(inputs: dict, outputs: dict, reference_outputs: dict):
evaluator = create_llm_as_judge(
prompt=CORRECTNESS_PROMPT,
model="openai:o3-mini",
feedback_key="correctness",
)
return evaluator(
inputs=inputs,
outputs=outputs,
reference_outputs=reference_outputs
)
# After running the evaluation, a link will be provided to view the results in langsmith
def main():
client = Client()
experiment_results = client.evaluate(
target,
data="Sample dataset",
evaluators=[
correctness_evaluator,
# can add multiple evaluators here
],
experiment_prefix="first-eval-in-langsmith",
max_concurrency=2,
)
print(experiment_results)
if __name__ == "__main__":
main()
```
```typescript TypeScript highlight={39-57} theme={null}
import { evaluate } from "langsmith/evaluation";
import { wrapOpenAI } from "langsmith/wrappers/openai"; // helper to wrap OpenAI client
import OpenAI from "openai"; // model provider
import { createLLMAsJudge, CORRECTNESS_PROMPT } from "openevals"; // evaluator tools
const openaiClient = wrapOpenAI(new OpenAI());
async function target(inputs: Record): Promise> {
const question = String(inputs.question ?? "");
const resp = await openaiClient.chat.completions.create({
model: "gpt-5-mini",
messages: [
{ role: "system", content: "Answer the following question accurately" },
{ role: "user", content: question },
],
});
return { answer: resp.choices[0].message.content?.trim() ?? "" };
}
const judge = createLLMAsJudge({
prompt: CORRECTNESS_PROMPT,
model: "openai:o3-mini",
feedbackKey: "correctness",
});
async function correctnessEvaluator(run: {
inputs: Record;
outputs: Record;
referenceOutputs?: Record;
}) {
return judge({
inputs: run.inputs,
outputs: run.outputs,
// OpenEvals expects snake_case here:
reference_outputs: run.referenceOutputs,
});
}
async function main() {
const datasetName = process.env.DATASET_NAME ?? "Sample dataset";
const results = await evaluate(target, {
data: datasetName,
evaluators: [correctnessEvaluator],
experimentPrefix: "first-eval-in-langsmith",
maxConcurrency: 2,
});
console.log(results);
}
if (require.main === module) {
main().catch((e) => {
console.error(e);
process.exit(1);
});
}
```
2. Run your evaluator:
```bash Python theme={null}
python eval.py
```
```bash TypeScript theme={null}
npx ts-node eval.ts
```
3. You'll receive a link to view the evaluation results and metadata for the experiment results:
```
View the evaluation results for experiment: 'first-eval-in-langsmith-00000000' at: https://smith.langchain.com/o/6551f9c4-2685-4a08-86b9-1b29643deb3d/datasets/e5fde557-c274-4e49-b39d-000000000000/compare?selectedSessions=70b11778-6a28-4cdb-be81-000000000000
```
4. Follow the link in the output of your evaluation run to access the **Datasets & Experiments** page in the [LangSmith UI](https://smith.langchain.com), and explore the results of the experiment. This will direct you to the created experiment with a table showing the **Inputs**, **Reference Output**, and **Outputs**. You can select a dataset to open an expanded view of the results.
## Next steps
Here are some topics you might want to explore next:
* [Evaluation concepts](/langsmith/evaluation-concepts) provides descriptions of the key terminology for evaluations in LangSmith.
* [OpenEvals README](https://github.com/langchain-ai/openevals) to see all available prebuilt evaluators and how to customize them.
* [Define custom evaluators](/langsmith/code-evaluator-ui).
* [Python](https://docs.smith.langchain.com/reference/python/reference) or [TypeScript](https://docs.smith.langchain.com/reference/js) SDK references for comprehensive descriptions of every class and function.
## Video guide
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluation-quickstart.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Evaluation types
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/evaluation-types
This page covers two aspects of evaluation in LangSmith:
1. **[Evaluation types](#offline-evaluation-types)**: *When and why* to evaluate. Offline evaluation types (benchmarking, unit tests, regression tests) for pre-deployment testing, and online evaluation types (monitoring, anomaly detection) for production.
2. **[Evaluator implementations](#implementing-evaluations)**: *How* to evaluate. The available evaluator approaches (LLM-as-judge, code, composite, summary, pairwise) and where to configure them (UI or SDK, offline or online).
Understanding both aspects helps you build a comprehensive evaluation strategy that validates functionality before deployment and monitors quality in production.
## Offline evaluation types
Offline evaluation tests applications on curated datasets before deployment. By running evaluations on examples with reference outputs, teams can compare versions, validate functionality, and build confidence before exposing changes to users.
Run offline evaluations client-side using the LangSmith SDK ([Python](https://reference.langchain.com/python/langsmith/observability/sdk/) or [TypeScript](https://reference.langchain.com/javascript/modules/langsmith.html)) or server-side via the [Prompt Playground](/langsmith/prompt-engineering-concepts#prompt-playground) or by [binding evaluators to a dataset](/langsmith/bind-evaluator-to-dataset).
### Benchmarking
*Benchmarking* compares multiple application versions on a curated dataset to identify the best performer. This process involves creating a dataset of representative inputs, defining performance metrics, and testing each version.
Benchmarking requires dataset curation with gold-standard reference outputs and well-designed comparison metrics. Examples:
* **RAG Q\&A bot**: Dataset of questions and reference answers, with an LLM-as-judge evaluator checking semantic equivalence between actual and reference answers.
* **ReACT agent**: Dataset of user requests and reference tool calls, with a heuristic evaluator verifying all expected tool calls were made.
### Unit tests
*Unit tests* verify the correctness of individual system components. In LLM contexts, [unit tests are often rule-based assertions](https://hamel.dev/blog/posts/evals/#level-1-unit-tests) on inputs or outputs (e.g., verifying LLM-generated code compiles, JSON loads successfully) that validate basic functionality.
Unit tests typically expect consistent passing results, making them suitable for CI pipelines. When running in CI, configure caching to minimize LLM API calls and associated costs.
For more details, refer to the [Pytest](/langsmith/pytest) and [Vitest/Jest](/langsmith/vitest-jest) pages.
### Regression tests
*Regression tests* measure performance consistency across application versions over time. They ensure new versions do not degrade performance on cases the current version handles correctly, and ideally demonstrate improvements over the baseline. These tests typically run when making updates expected to affect user experience (e.g., model or architecture changes).
LangSmith's comparison view highlights regressions (red) and improvements (green) relative to the baseline, enabling quick identification of changes.
### Backtesting
*Backtesting* evaluates new application versions against historical production data. Production logs are converted into a dataset, then newer versions process these examples to assess performance on past, realistic user inputs.
This approach is commonly used for evaluating new model releases. For example, when a new model becomes available, test it on the most recent production runs and compare results to actual production outcomes.
### Pairwise evaluation
*Pairwise evaluation* compares outputs from two versions by determining relative quality rather than assigning absolute scores. For some tasks, [determining "version A is better than B"](https://www.oreilly.com/radar/what-we-learned-from-a-year-of-building-with-llms-part-i/) is easier than scoring each version independently.
This approach proves particularly useful for LLM-as-judge evaluations on subjective tasks. For example, in summarization, determining "Which summary is clearer and more concise?" is often simpler than assigning numeric clarity scores.
Learn [how run pairwise evaluations](/langsmith/evaluate-pairwise).
## Online evaluation types
Online evaluation assesses production application outputs in near real-time. Without reference outputs, these evaluations focus on detecting issues, monitoring quality trends, and identifying edge cases that inform future offline testing.
Online evaluators typically run server-side. LangSmith provides built-in [LLM-as-judge evaluators](/langsmith/llm-as-judge) for configuration, and supports custom code evaluators that run within LangSmith.
### Real-time monitoring
Monitor application quality continuously as users interact with the system. Online evaluations run automatically on production traffic, providing immediate feedback on each interaction. This enables detection of quality degradation, unusual patterns, or unexpected behaviors before they impact significant user populations.
### Anomaly detection
Identify outliers and edge cases that deviate from expected patterns. Online evaluators can flag runs with unusual characteristics—extremely long or short responses, unexpected error rates, or outputs that fail safety checks—for human review and potential addition to offline datasets.
### Production feedback loop
Use insights from production to improve offline evaluation. Online evaluations surface real-world issues and usage patterns that may not appear in curated datasets. Failed production runs become candidates for dataset examples, creating an iterative cycle where production experience continuously refines testing coverage.
## Implement evaluators
The evaluation types above describe *when* to evaluate. LangSmith provides several approaches for *how* to implement evaluators that work across these evaluation types.
### LLM-as-a-judge
Use an LLM to score outputs based on criteria defined in a prompt. This approach works well for subjective qualities like tone, clarity, or semantic correctness that are difficult to capture with deterministic rules.
Common use cases include assessing factual accuracy against reference outputs (offline) or checking for toxicity in production responses (online). For example, benchmarking a RAG system might use an LLM-as-judge evaluator to check semantic equivalence between generated and reference answers.
Configure LLM-as-a-judge evaluators for:
* Programmatic offline evaluation: [With the SDK](/langsmith/llm-as-judge-sdk)
* Offline evaluation on datasets: [In the UI](/langsmith/llm-as-judge)
* Online evaluation on production traces: [In the UI](/langsmith/online-evaluations-llm-as-judge)
### Code evaluators
Write deterministic, rule-based functions that check specific conditions. These evaluators execute custom logic to validate structure, check for patterns, or apply business rules.
Code evaluators are particularly useful for unit tests—verifying generated code compiles, JSON parses correctly, or required fields are present. In regression testing, they can track consistency of structured outputs. For online monitoring, they catch format violations in real-time.
Define code evaluators for:
* Offline evaluation on datasets: [In the UI](/langsmith/code-evaluator-ui)
* Programmatic offline evaluation: [With the SDK](/langsmith/code-evaluator-sdk)
* Online evaluation on production traces: [In the UI](/langsmith/online-evaluations-code)
### Composite evaluators
Combine multiple evaluator scores into a single metric using weighted averages or sums. This creates aggregate quality scores that reflect multiple evaluation criteria simultaneously.
For benchmarking, composite scores help compare versions on multiple dimensions (e.g., 70% accuracy + 20% clarity + 10% conciseness). In online monitoring, they provide single metrics for dashboards and alerts. For example, track overall chatbot quality as a weighted combination of helpfulness, correctness, and tone scores.
Set up composite evaluators for:
* Offline evaluation with predefined aggregation: [In the UI](/langsmith/composite-evaluators-ui)
* Offline evaluation with custom aggregation logic: [With the SDK](/langsmith/composite-evaluators-sdk)
* Online evaluation on production traces: [In the UI](/langsmith/online-evaluations-composite)
### Summary evaluators
Compute metrics across an entire experiment rather than individual examples. These evaluators receive all outputs from a dataset and calculate aggregate statistics like precision, recall, F1 scores, or distribution analysis.
Summary evaluators are essential for benchmarking when you need dataset-level metrics—comparing overall performance across versions rather than example-by-example scores. They work exclusively with offline evaluation because they require processing complete datasets.
Implement summary evaluators for:
* Custom aggregation functions for offline evaluation: [With the SDK](/langsmith/summary)
### Pairwise evaluators
Compare outputs from two versions to determine relative quality. This approach, covered earlier under [pairwise evaluation](#pairwise-evaluation), helps when absolute scoring is difficult but determining "which is better" is straightforward.
Run pairwise evaluations for:
* Compare existing experiments: [With the SDK](/langsmith/evaluate-pairwise)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/evaluation-types.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Example data format
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/example-data-format
Before diving into this content, it might be helpful to read the following:
* [Conceptual guide on evaluation](/langsmith/evaluation-concepts)
LangSmith stores examples in datasets as follows:
| Field Name | Type | Description |
| ------------------- | -------- | ---------------------------------------------------------------------------------------------------- |
| **id** | UUID | Unique identifier for the example. |
| **name** | string | The name of the example. |
| **created\_at** | datetime | The time this example was created |
| **modified\_at** | datetime | The last time this example was modified |
| **inputs** | object | A map of inputs for the example. |
| **outputs** | object | A map or set of outputs generated by the run. |
| **dataset\_id** | UUID | The dataset the example belongs to |
| **source\_run\_id** | UUID | If this example was created from a LangSmith [`Run`](/langsmith/run-data-format), the ID of said run |
| **metadata** | object | A map of additional, user or SDK defined information that can be stored on an example. |
To learn more about how examples are used in evaluation, read our how-to guide on [evaluating LLM applications](/langsmith/evaluate-llm-application).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/example-data-format.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Experiment configuration
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/experiment-configuration
LangSmith supports several configuration options for experiments:
* [Repetitions](#repetitions)
* [Concurrency](#concurrency)
* [Caching](#caching)
### Repetitions
*Repetitions* run an experiment multiple times to account for LLM output variability. Since LLM outputs are non-deterministic, multiple repetitions provide a more accurate performance estimate.
Configure repetitions by passing the `num_repetitions` argument to `evaluate` / `aevaluate` ([Python](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._runner.evaluate), [TypeScript](https://docs.smith.langchain.com/reference/js/interfaces/evaluation.EvaluateOptions#numrepetitions)). Each repetition re-runs both the target function and all evaluators.
Learn more in the [repetitions how-to guide](/langsmith/repetition).
### Concurrency
*Concurrency* controls how many examples run simultaneously during an experiment. Configure it by passing the `max_concurrency` argument to `evaluate` / `aevaluate`. The semantics differ between the two functions:
#### `evaluate`
The `max_concurrency` argument specifies the maximum number of concurrent threads for running both the target function and evaluators.
#### `aevaluate`
The `max_concurrency` argument uses a semaphore to limit concurrent tasks. `aevaluate` creates a task for each example, where each task runs the target function and all evaluators for that example. The `max_concurrency` argument specifies the maximum number of concurrent examples to process.
### Caching
*Caching* stores API call results to disk to speed up future experiments. Set the `LANGSMITH_TEST_CACHE` environment variable to a valid folder path with write access. Future experiments that make identical API calls will reuse cached results instead of making new requests.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/experiment-configuration.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Export LangSmith telemetry to your observability backend
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/export-backend
**This section is only applicable for Kubernetes deployments.**
Self-Hosted LangSmith instances produce telemetry data in the form of logs, metrics and traces. This section will show you how to access and export that data to an observability collector or backend.
This section assumes that you have monitoring infrastructure set up already, or you will set up this infrastructure and want to know how to configure LangSmith to collect data from it.
Infrastructure refers to:
* Collectors, such as [OpenTelemetry](https://opentelemetry.io/docs/collector/), [FluentBit](https://docs.fluentbit.io/manual) or [Prometheus](https://prometheus.io/).
* Observability backends, such as [Datadog](https://www.datadoghq.com/) or the [Grafana](https://grafana.com/) ecosystem.
# Logs: [OTel example](/langsmith/langsmith-collector#logs)
All services that are part of the LangSmith self-hosted deployment write logs to their node's filesystem and to stdout. In order to access these logs, you need to set up your collector to read from either the filesystem or stdout. Most popular collectors support reading logs from filesystems.
* **OpenTelemetry**: [File Log Receiver](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/filelogreceiver)
* **FluentBit**: [Tail Input](https://docs.fluentbit.io/manual/pipeline/inputs/tail)
* **Datadog**: [Kubernetes Log Collection](https://docs.datadoghq.com/containers/kubernetes/log/?tab=datadogoperator)
# Metrics: [OTel example](/langsmith/langsmith-collector#metrics)
## LangSmith services
The following LangSmith services expose metrics at an endpoint, in the Prometheus metrics format. The frontend does not currently expose metrics.
* **Backend**: `http://-backend..svc.cluster.local:1984/metrics`
* **Platform Backend**: `http://-platform-backend..svc.cluster.local:1986/metrics`
* **Playground**: `http://-playground..svc.cluster.local:1988/metrics`
* **(LangSmith Control Plane only) Host Backend**: `http://-host-backend..svc.cluster.local:1985/metrics`
You can use a [Prometheus](https://prometheus.io/docs/prometheus/latest/getting_started/#configure-prometheus-to-monitor-the-sample-targets) or [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/prometheusreceiver) collector to scrape the endpoints, and export metrics to the backend of your choice.
## Frontend Nginx
The frontend service exposes its Nginx metrics at the following endpoint: `langsmith-frontend.langsmith.svc.cluster.local:80/nginx_status`. You can either scrape them yourself, or bring up a Prometheus Nginx exporter using the [LangSmith Observability Helm Chart](/langsmith/observability-stack)
**The following sections apply for in-cluster databases only. If you are using external databases, you will need to configure exposing and fetching metrics.**
## Postgres + Redis
If you are using in-cluster Postgres/Redis instances, you can use a Prometheus exporter to expose metrics from your instance. You can deploy your own, or if you would like, you can use the [LangSmith Observability Helm Chart](/langsmith/observability-stack) to deploy an exporter for you.
## Clickhouse
The in-cluster Clickhouse is configured to expose metrics without the need for an exporter. You can use your collector to scrape metrics at `http://-clickhouse..svc.cluster.local:9363/metrics`
# Traces: [OTel example](/langsmith/langsmith-collector#traces)
The LangSmith Backend, Platform Backend, Playground and LangSmith Queue deployments have been instrumented to emit [Otel](https://opentelemetry.io/do/langsmith/observability-concepts/signals/traces/) traces. Tracing is toggled off by default, and can be enabled for all LangSmith services with the following in your `langsmith_config.yaml` (or equivalent) file:
```yaml theme={null}
config:
tracing:
enabled: true
endpoint: ""
useTls: true # / false
env: "ls_self_hosted" # This value will be set as an "env" attribute in your spans
exporter: "http" # must be either http or grpc
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/export-backend.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Query traces (SDK)
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/export-traces
**Recommended Reading**
Before diving into this content, it might be helpful to read the following:
* [Run (span) data format](/langsmith/run-data-format)
*
* [LangSmith trace query syntax](/langsmith/trace-query-syntax)
**If you are looking to export a large volume of traces, we recommend that you use the [Bulk Data Export](./data-export) functionality, as it will better handle large data volumes and will support automatic retries and parallelization across partitions.**
The recommended way to query runs (the span data in LangSmith traces) is to use the `list_runs` method in the SDK or `/runs/query` endpoint in the API.
LangSmith stores traces in a simple format that is specified in the [Run (span) data format](/langsmith/run-data-format).
## Use filter arguments
For simple queries, you don't have to rely on our query syntax. You can use the filter arguments specified in the [filter arguments reference](/langsmith/trace-query-syntax#filter-arguments).
**Prerequisites**
Initialize the client before running the below code snippets.
```python Python theme={null}
from langsmith import Client
client = Client()
```
```typescript TypeScript theme={null}
import { Client, Run } from "langsmith";
const client = new Client();
```
```java Java theme={null}
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
LangsmithClient client = LangsmithOkHttpClient.fromEnv();
```
Below are some examples of ways to list runs using keyword arguments:
### List all runs in a project
```python Python theme={null}
project_runs = client.list_runs(project_name="")
```
```typescript TypeScript theme={null}
// Download runs in a project
const projectRuns: Run[] = [];
for await (const run of client.listRuns({
projectName: "",
})) {
projectRuns.push(run);
};
```
```java Java theme={null}
import com.langchain.smith.models.runs.RunQueryParams;
RunQueryParams projectRuns = RunQueryParams.builder()
.addSession("")
.build();
```
### List LLM and chat runs in the last 24 hours
```python Python theme={null}
todays_llm_runs = client.list_runs(
project_name="",
start_time=datetime.now() - timedelta(days=1),
run_type="llm",
)
```
```typescript TypeScript theme={null}
const todaysLlmRuns: Run[] = [];
for await (const run of client.listRuns({
projectName: "",
startTime: new Date(Date.now() - 1000 * 60 * 60 * 24),
runType: "llm",
})) {
todaysLlmRuns.push(run);
};
```
```java Java theme={null}
OffsetDateTime now = OffsetDateTime.now();
OffsetDateTime twentyFourHoursAgo = now.minus(24, ChronoUnit.HOURS);
RunQueryParams todaysLlmRuns = RunQueryParams.builder()
.runType(RunQueryParams.RunType.LLM)
.startTime(twentyFourHoursAgo)
.addSession("")
.limit(50L)
.build();
```
### List root runs in a project
Root runs are runs that have no parents. These are assigned a value of `True` for `is_root`. You can use this to filter for root runs.
```python Python theme={null}
root_runs = client.list_runs(
project_name="",
is_root=True
)
```
```typescript TypeScript theme={null}
const rootRuns: Run[] = [];
for await (const run of client.listRuns({
projectName: "",
isRoot: 1,
})) {
rootRuns.push(run);
};
```
```java Java theme={null}
import com.langchain.smith.models.runs.RunQueryParams;
RunQueryParams rootRuns = RunQueryParams.builder()
.addSession("")
.isRoot(true)
.build();
```
### List runs without errors
```python Python theme={null}
correct_runs = client.list_runs(project_name="", error=False)
```
```typescript TypeScript theme={null}
const correctRuns: Run[] = [];
for await (const run of client.listRuns({
projectName: "",
error: false,
})) {
correctRuns.push(run);
};
```
```java Java theme={null}
import com.langchain.smith.models.runs.RunQueryParams;
RunQueryParams noErrorRuns = RunQueryParams.builder()
.addSession("")
.error(false)
.build();
```
### List runs by run ID
**Ignores Other Arguments**
If you provide a list of run IDs in the way described above, it will ignore all other filtering arguments like `project_name`, `run_type`, etc. and directly return the runs matching the given IDs.
If you have a list of run IDs, you can list them directly:
```python Python theme={null}
run_ids = ['a36092d2-4ad5-4fb4-9c0d-0dba9a2ed836','9398e6be-964f-4aa4-8ae9-ad78cd4b7074']
selected_runs = client.list_runs(id=run_ids)
```
```typescript TypeScript theme={null}
const runIds = [
"a36092d2-4ad5-4fb4-9c0d-0dba9a2ed836",
"9398e6be-964f-4aa4-8ae9-ad78cd4b7074",
];
const selectedRuns: Run[] = [];
for await (const run of client.listRuns({
id: runIds,
})) {
selectedRuns.push(run);
};
```
```java Java theme={null}
import com.langchain.smith.models.runs.RunQueryParams;
RunQueryParams runIdsRuns = RunQueryParams.builder()
.addSession("")
.id(runIds)
.build();
```
### Fetch a single run by ID
To fetch a single run (trace) by its ID, use the `read_run` method. This is useful when you have a specific trace ID (for example, from a LangSmith share link like `https://smith.langchain.com/public//r`) and want to retrieve its full data.
```python Python theme={null}
run_id = "a36092d2-4ad5-4fb4-9c0d-0dba9a2ed836"
run = client.read_run(run_id)
# Access run data
print(run.inputs)
print(run.outputs)
print(run.name)
```
```typescript TypeScript theme={null}
const runId = "a36092d2-4ad5-4fb4-9c0d-0dba9a2ed836";
const run = await client.readRun(runId);
// Access run data
console.log(run.inputs);
console.log(run.outputs);
console.log(run.name);
```
```java Java theme={null}
import com.langchain.smith.models.runs.RunQueryParams;
RunQueryParams runIdRun = RunQueryParams.builder()
.addSession("")
.addId(runId)
.build();
```
**Replay traces locally with LangGraph**
If you're using LangGraph with checkpointing, you can fetch a trace from LangSmith and replay it locally for debugging. See [LangGraph's time travel and replay documentation](/oss/python/langgraph/use-time-travel) for details on resuming execution from checkpoints.
## Use filter query language
For more complex queries, you can use the query language described in the [filter query language reference](/langsmith/trace-query-syntax#filter-query-language).
### List all root runs in a conversational thread
This is the way to fetch runs in a conversational thread. For more information on setting up threads, refer to our [how-to guide on setting up threads](./threads).
Threads are grouped by setting a shared thread ID. The LangSmith UI lets you use any one of the following three metadata keys: `session_id`, `conversation_id`, or `thread_id`. The session ID is also known as the tracing project ID. The following query matches on any of them.
```python Python theme={null}
group_key = ""
filter_string = f'and(in(metadata_key, ["session_id","conversation_id","thread_id"]), eq(metadata_value, "{group_key}"))'
thread_runs = client.list_runs(
project_name="",
filter=filter_string,
is_root=True
)
```
```typescript TypeScript theme={null}
const groupKey = "";
const filterString = `and(in(metadata_key, ["session_id","conversation_id","thread_id"]), eq(metadata_value, "${groupKey}"))`;
const threadRuns: Run[] = [];
for await (const run of client.listRuns({
projectName: "",
filter: filterString,
isRoot: true
})) {
threadRuns.push(run);
};
```
```java Java theme={null}
import com.langchain.smith.models.runs.RunQueryParams;
String groupKey = "";
String filterString = String.format(
"and(in(metadata_key, [\"session_id\",\"conversation_id\",\"thread_id\"]), eq(metadata_value, \"%s\"))",
groupKey
);
RunQueryParams threadRuns = RunQueryParams.builder()
.addSession("")
.filter(filterString)
.build();
```
### List all runs called "extractor" whose root of the trace was assigned feedback "user\_score" score of 1
```python Python theme={null}
client.list_runs(
project_name="",
filter='eq(name, "extractor")',
trace_filter='and(eq(feedback_key, "user_score"), eq(feedback_score, 1))'
)
```
```typescript TypeScript theme={null}
client.listRuns({
projectName: "",
filter: 'eq(name, "extractor")',
traceFilter: 'and(eq(feedback_key, "user_score"), eq(feedback_score, 1))'
})
```
```java Java theme={null}
RunQueryParams extractorRuns = RunQueryParams.builder()
.addSession("")
.filter("eq(name, \"extractor\")")
.traceFilter("and(eq(feedback_key, \"user_score\"), eq(feedback_score, 1))")
.build();
```
### List runs with "star\_rating" key whose score is greater than 4
```python Python theme={null}
client.list_runs(
project_name="",
filter='and(eq(feedback_key, "star_rating"), gt(feedback_score, 4))'
)
```
```typescript TypeScript theme={null}
client.listRuns({
projectName: "",
filter: 'and(eq(feedback_key, "star_rating"), gt(feedback_score, 4))'
})
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("and(eq(feedback_key, \"star_rating\"), gt(feedback_score, 4))")
.build();
```
### List runs that took longer than 5 seconds to complete
```python Python theme={null}
client.list_runs(project_name="", filter='gt(latency, "5s")')
```
```typescript TypeScript theme={null}
client.listRuns({projectName: "", filter: 'gt(latency, "5s")'})
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("gt(latency, \"5s\")")
.build();
```
### List all runs that have "error" not equal to null
```python Python theme={null}
client.list_runs(project_name="", filter='neq(error, null)')
```
```typescript TypeScript theme={null}
client.listRuns({projectName: "", filter: 'neq(error, null)'})
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("neq(error, null)")
.build();
```
### List all runs where start\_time is greater than a specific timestamp
```python Python theme={null}
client.list_runs(project_name="", filter='gt(start_time, "2023-07-15T12:34:56Z")')
```
```typescript TypeScript theme={null}
client.listRuns({projectName: "", filter: 'gt(start_time, "2023-07-15T12:34:56Z")'})
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("gt(start_time, \"2023-07-15T12:34:56Z\")")
.build();
```
### List all runs that contain the string "substring"
```python Python theme={null}
client.list_runs(project_name="", filter='search("substring")')
```
```typescript TypeScript theme={null}
client.listRuns({projectName: "", filter: 'search("substring")'})
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("search(\"substring\")")
.build();
```
### List all runs that are tagged with the git hash "2aa1cf4"
```python Python theme={null}
client.list_runs(project_name="", filter='has(tags, "2aa1cf4")')
```
```typescript TypeScript theme={null}
client.listRuns({projectName: "", filter: 'has(tags, "2aa1cf4")'})
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("has(tags, \"2aa1cf4\")")
.build();
```
### List all runs that started after a specific timestamp and either have "error" not equal to null or a "Correctness" feedback score equal to 0
```python Python theme={null}
client.list_runs(
project_name="",
filter='and(gt(start_time, "2023-07-15T12:34:56Z"), or(neq(error, null), and(eq(feedback_key, "Correctness"), eq(feedback_score, 0.0))))'
)
```
```typescript TypeScript theme={null}
client.listRuns({
projectName: "",
filter: 'and(gt(start_time, "2023-07-15T12:34:56Z"), or(neq(error, null), and(eq(feedback_key, "Correctness"), eq(feedback_score, 0.0))))'
})
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("and(gt(start_time, \"2023-07-15T12:34:56Z\"), or(neq(error, null), and(eq(feedback_key, \"Correctness\"), eq(feedback_score, 0.0))))")
.build();
```
### Complex query: List all runs where tags include "experimental" or "beta" and latency is greater than 2 seconds
```python Python theme={null}
client.list_runs(
project_name="",
filter='and(or(has(tags, "experimental"), has(tags, "beta")), gt(latency, 2))'
)
```
```typescript TypeScript theme={null}
client.listRuns({
projectName: "",
filter: 'and(or(has(tags, "experimental"), has(tags, "beta")), gt(latency, 2))'
})
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("and(or(has(tags, 'experimental'), has(tags, 'beta')), gt(latency, 2))")
.build();
```
### Search trace trees by full text
You can use the `search()` function without any specific field to do a full text search across all string fields in a run. This allows you to quickly find traces that match a search term.
```python Python theme={null}
client.list_runs(
project_name="",
filter='search("image classification")'
)
```
```typescript TypeScript theme={null}
client.listRuns({
projectName: "",
filter: 'search("image classification")'
})
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("search(\"image classification\")")
.build();
```
### Check for presence of metadata
If you want to check for the presence of metadata, you can use the `eq` operator, optionally with an `and` statement to match by value. This is useful if you want to log more structured information about your runs.
```python Python theme={null}
to_search = {
"user_id": ""
}
# Check for any run with the "user_id" metadata key
client.list_runs(
project_name="default",
filter="eq(metadata_key, 'user_id')"
)
# Check for runs with user_id=4070f233-f61e-44eb-bff1-da3c163895a3
client.list_runs(
project_name="default",
filter="and(eq(metadata_key, 'user_id'), eq(metadata_value, '4070f233-f61e-44eb-bff1-da3c163895a3'))"
)
```
```typescript TypeScript theme={null}
// Check for any run with the "user_id" metadata key
client.listRuns({
projectName: 'default',
filter: `eq(metadata_key, 'user_id')`
});
// Check for runs with user_id=4070f233-f61e-44eb-bff1-da3c163895a3
client.listRuns({
projectName: 'default',
filter: `and(eq(metadata_key, 'user_id'), eq(metadata_value, '4070f233-f61e-44eb-bff1-da3c163895a3'))`
});
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("eq(metadata_key, 'user_id')")
.build();
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("and(eq(metadata_key, 'user_id'), eq(metadata_value, '4070f233-f61e-44eb-bff1-da3c163895a3'))")
.build();
```
### Check for environment details in metadata
A common pattern is to add environment information to your traces via metadata. If you want to filter for runs containing environment metadata, you can use the same pattern as above:
```python Python theme={null}
client.list_runs(
project_name="default",
filter="and(eq(metadata_key, 'environment'), eq(metadata_value, 'production'))"
)
```
```typescript TypeScript theme={null}
client.listRuns({
projectName: 'default',
filter: `and(eq(metadata_key, 'environment'), eq(metadata_value, 'production'))`
});
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("and(eq(metadata_key, 'environment'), eq(metadata_value, 'production'))")
.build();
```
### Check for conversation ID in metadata
Another common way to associate traces in the same conversation is by using a shared conversation ID. If you want to filter runs based on a conversation ID in this way, you can search for that ID in the metadata.
```python Python theme={null}
client.list_runs(
project_name="default",
filter="and(eq(metadata_key, 'conversation_id'), eq(metadata_value, 'a1b2c3d4-e5f6-7890'))"
)
```
```typescript TypeScript theme={null}
client.listRuns({
projectName: 'default',
filter: `and(eq(metadata_key, 'conversation_id'), eq(metadata_value, 'a1b2c3d4-e5f6-7890'))`
});
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("and(eq(metadata_key, 'conversation_id'), eq(metadata_value, 'a1b2c3d4-e5f6-7890'))")
.build();
```
### Negative filtering on key-value pairs
You can use negative filtering on metadata, input, and output key-value pairs to exclude specific runs from your results. Here are some examples for metadata key-value pairs but the same logic applies to input and output key-value pairs.
```python Python theme={null}
# Find all runs where the metadata does not contain a "conversation_id" key
client.list_runs(
project_name="default",
filter="and(neq(metadata_key, 'conversation_id'))"
)
# Find all runs where the conversation_id in metadata is not "a1b2c3d4-e5f6-7890"
client.list_runs(
project_name="default",
filter="and(eq(metadata_key, 'conversation_id'), neq(metadata_value, 'a1b2c3d4-e5f6-7890'))"
)
# Find all runs where there is no "conversation_id" metadata key and the "a1b2c3d4-e5f6-7890" value is not present
client.list_runs(
project_name="default",
filter="and(neq(metadata_key, 'conversation_id'), neq(metadata_value, 'a1b2c3d4-e5f6-7890'))"
)
# Find all runs where the conversation_id metadata key is not present but the "a1b2c3d4-e5f6-7890" value is present
client.list_runs(
project_name="default",
filter="and(neq(metadata_key, 'conversation_id'), eq(metadata_value, 'a1b2c3d4-e5f6-7890'))"
)
```
```typescript TypeScript theme={null}
// Find all runs where the metadata does not contain a "conversation_id" key
client.listRuns({
projectName: 'default',
filter: `and(neq(metadata_key, 'conversation_id'))`
});
// Find all runs where the conversation_id in metadata is not "a1b2c3d4-e5f6-7890"
client.listRuns({
projectName: 'default',
filter: `and(eq(metadata_key, 'conversation_id'), neq(metadata_value, 'a1b2c3d4-e5f6-7890'))`
});
// Find all runs where there is no "conversation_id" metadata key and the "a1b2c3d4-e5f6-7890" value is not present
client.listRuns({
projectName: 'default',
filter: `and(neq(metadata_key, 'conversation_id'), neq(metadata_value, 'a1b2c3d4-e5f6-7890'))`
});
// Find all runs where the conversation_id metadata key is not present but the "a1b2c3d4-e5f6-7890" value is present
client.listRuns({
projectName: 'default',
filter: `and(neq(metadata_key, 'conversation_id'), eq(metadata_value, 'a1b2c3d4-e5f6-7890'))`
});
```
```java Java theme={null}
// Find all runs where the metadata does not contain a "conversation_id" key
RunQueryParams runs = RunQueryParams.builder()
.addSession("default")
.filter("and(neq(metadata_key, 'conversation_id'))")
.build();
// Find all runs where the conversation_id in metadata is not "a1b2c3d4-e5f6-7890"
RunQueryParams runs = RunQueryParams.builder()
.addSession("default")
.filter("and(eq(metadata_key, 'conversation_id'), neq(metadata_value, 'a1b2c3d4-e5f6-7890'))")
.build();
// Find all runs where there is no "conversation_id" metadata key and the "a1b2c3d4-e5f6-7890" value is not present
RunQueryParams runs = RunQueryParams.builder()
.addSession("default")
.filter("and(neq(metadata_key, 'conversation_id'), neq(metadata_value, 'a1b2c3d4-e5f6-7890'))")
.build();
// Find all runs where the conversation_id metadata key is not present but the "a1b2c3d4-e5f6-7890" value is present
RunQueryParams runs = RunQueryParams.builder()
.addSession("default")
.filter("and(neq(metadata_key, 'conversation_id'), eq(metadata_value, 'a1b2c3d4-e5f6-7890'))")
.build();
```
### Combine multiple filters
If you want to combine multiple conditions to refine your search, you can use the `and` operator along with other filtering functions. Here's how you can search for runs named "ChatOpenAI" that also have a specific `conversation_id` in their metadata:
```python Python theme={null}
client.list_runs(
project_name="default",
filter="and(eq(name, 'ChatOpenAI'), eq(metadata_key, 'conversation_id'), eq(metadata_value, '69b12c91-b1e2-46ce-91de-794c077e8151'))"
)
```
```typescript TypeScript theme={null}
client.listRuns({
projectName: 'default',
filter: `and(eq(name, 'ChatOpenAI'), eq(metadata_key, 'conversation_id'), eq(metadata_value, '69b12c91-b1e2-46ce-91de-794c077e8151'))`
});
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("and(eq(name, 'ChatOpenAI'), eq(metadata_key, 'conversation_id'), eq(metadata_value, '69b12c91-b1e2-46ce-91de-794c077e8151'))")
.build();
```
### Tree filter
List all runs named "RetrieveDocs" whose root run has a "user\_score" feedback of 1 and any run in the full trace is named "ExpandQuery".
This type of query is useful if you want to extract a specific run conditional on various states or steps being reached within the trace.
```python Python theme={null}
client.list_runs(
project_name="",
filter='eq(name, "RetrieveDocs")',
trace_filter='and(eq(feedback_key, "user_score"), eq(feedback_score, 1))',
tree_filter='eq(name, "ExpandQuery")'
)
```
```typescript TypeScript theme={null}
client.listRuns({
projectName: "",
filter: 'eq(name, "RetrieveDocs")',
traceFilter: 'and(eq(feedback_key, "user_score"), eq(feedback_score, 1))',
treeFilter: 'eq(name, "ExpandQuery")'
})
```
```java Java theme={null}
RunQueryParams runs = RunQueryParams.builder()
.addSession("")
.filter("eq(name, \"RetrieveDocs\")")
.traceFilter("and(eq(feedback_key, 'user_score'), eq(feedback_score, 1))")
.treeFilter("eq(name, 'ExpandQuery')")
.build();
```
### Advanced: export flattened trace view with child tool usage
The following Python example demonstrates how to export a flattened view of traces, including information on the tools (from nested runs) used by the agent within each trace.
This can be used to analyze the behavior of your agents across multiple traces.
This example queries all tool runs within a specified number of days and groups them by their parent (root) run ID. It then fetches the relevant information for each root run, such as the run name, inputs, outputs, and combines that information with the child run information.
To optimize the query, the example:
1. Selects only the necessary fields when querying tool runs to reduce query time.
2. Fetches root runs in batches while processing tool runs concurrently.
```python Python theme={null}
from collections import defaultdict
from concurrent.futures import Future, ThreadPoolExecutor
from datetime import datetime, timedelta
from langsmith import Client
from tqdm.auto import tqdm
client = Client()
project_name = "my-project"
num_days = 30
# List all tool runs
tool_runs = client.list_runs(
project_name=project_name,
start_time=datetime.now() - timedelta(days=num_days),
run_type="tool",
# We don't need to fetch inputs, outputs, and other values that # may increase the query time
select=["trace_id", "name", "run_type"],
)
data = []
futures: list[Future] = []
trace_cursor = 0
trace_batch_size = 50
tool_runs_by_parent = defaultdict(lambda: defaultdict(set))
# Do not exceed rate limit
with ThreadPoolExecutor(max_workers=2) as executor:
# Group tool runs by parent run ID
for run in tqdm(tool_runs):
# Collect all tools invoked within a given trace
tool_runs_by_parent[run.trace_id]["tools_involved"].add(run.name)
# maybe send a batch of parent run IDs to the server
# this lets us query for the root runs in batches
# while still processing the tool runs
if len(tool_runs_by_parent) % trace_batch_size == 0:
if this_batch := list(tool_runs_by_parent.keys())[
trace_cursor : trace_cursor + trace_batch_size
]:
trace_cursor += trace_batch_size
futures.append(
executor.submit(
client.list_runs,
project_name=project_name,
run_ids=this_batch,
select=["name", "inputs", "outputs", "run_type"],
)
)
if this_batch := list(tool_runs_by_parent.keys())[trace_cursor:]:
futures.append(
executor.submit(
client.list_runs,
project_name=project_name,
run_ids=this_batch,
select=["name", "inputs", "outputs", "run_type"],
)
)
for future in tqdm(futures):
root_runs = future.result()
for root_run in root_runs:
root_data = tool_runs_by_parent[root_run.id]
data.append(
{
"run_id": root_run.id,
"run_name": root_run.name,
"run_type": root_run.run_type,
"inputs": root_run.inputs,
"outputs": root_run.outputs,
"tools_involved": list(root_data["tools_involved"]),
}
)
# (Optional): Convert to a pandas DataFrame
import pandas as pd
df = pd.DataFrame(data)
df.head()
```
### Advanced: export retriever IO for traces with feedback
This query is useful if you want to fine-tune embeddings or diagnose end-to-end system performance issues based on retriever behavior.
The following Python example demonstrates how to export retriever inputs and outputs within traces that have a specific feedback score.
```python Python theme={null}
from collections import defaultdict
from concurrent.futures import Future, ThreadPoolExecutor
from datetime import datetime, timedelta
import pandas as pd
from langsmith import Client
from tqdm.auto import tqdm
client = Client()
project_name = "your-project-name"
num_days = 1
# List all tool runs
retriever_runs = client.list_runs(
project_name=project_name,
start_time=datetime.now() - timedelta(days=num_days),
run_type="retriever",
# This time we do want to fetch the inputs and outputs, since they
# may be adjusted by query expansion steps.
select=["trace_id", "name", "run_type", "inputs", "outputs"],
trace_filter='eq(feedback_key, "user_score")',
)
data = []
futures: list[Future] = []
trace_cursor = 0
trace_batch_size = 50
retriever_runs_by_parent = defaultdict(lambda: defaultdict(list))
# Do not exceed rate limit
with ThreadPoolExecutor(max_workers=2) as executor:
# Group retriever runs by parent run ID
for run in tqdm(retriever_runs):
# Collect all retriever calls invoked within a given trace
for k, v in run.inputs.items():
retriever_runs_by_parent[run.trace_id][f"retriever.inputs.{k}"].append(v)
for k, v in (run.outputs or {}).items():
# Extend the docs
retriever_runs_by_parent[run.trace_id][f"retriever.outputs.{k}"].extend(v)
# maybe send a batch of parent run IDs to the server
# this lets us query for the root runs in batches
# while still processing the retriever runs
if len(retriever_runs_by_parent) % trace_batch_size == 0:
if this_batch := list(retriever_runs_by_parent.keys())[
trace_cursor : trace_cursor + trace_batch_size
]:
trace_cursor += trace_batch_size
futures.append(
executor.submit(
client.list_runs,
project_name=project_name,
run_ids=this_batch,
select=[
"name",
"inputs",
"outputs",
"run_type",
"feedback_stats",
],
)
)
if this_batch := list(retriever_runs_by_parent.keys())[trace_cursor:]:
futures.append(
executor.submit(
client.list_runs,
project_name=project_name,
run_ids=this_batch,
select=["name", "inputs", "outputs", "run_type"],
)
)
for future in tqdm(futures):
root_runs = future.result()
for root_run in root_runs:
root_data = retriever_runs_by_parent[root_run.id]
feedback = {
f"feedback.{k}": v.get("avg")
for k, v in (root_run.feedback_stats or {}).items()
}
inputs = {f"inputs.{k}": v for k, v in root_run.inputs.items()}
outputs = {f"outputs.{k}": v for k, v in (root_run.outputs or {}).items()}
data.append(
{
"run_id": root_run.id,
"run_name": root_run.name,
**inputs,
**outputs,
**feedback,
**root_data,
}
)
# (Optional): Convert to a pandas DataFrame
import pandas as pd
df = pd.DataFrame(data)
df.head()
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/export-traces.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Frequently asked questions
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/faq
## Observability
### *I can't create API keys or manage users in the UI, what's wrong?*
* You have likely deployed LangSmith without setting up SSO. LangSmith requires SSO to manage users and API keys. You can find more information on setting up SSO in the [configuration section.](/langsmith/self-host-sso)
### *How does load balancing/ingress work*?
* You will need to expose the frontend container/service to your applications/users. This will handle routing to all downstream services.
* You will need to terminate SSL at the ingress level. We recommend using a managed service like AWS ALB, GCP Load Balancer, or Nginx.
### *How can we authenticate to the application?*
* Currently, our self-hosted solution supports SSO with OAuth2.0 and OIDC as an authn solution. Note, we do offer a no-auth solution but highly recommend setting up oauth before moving into production.
You can find more information on setting up SSO in the [configuration section.](/langsmith/self-host-sso)
### *Can I use external storage services?*
* You can configure LangSmith to use external versions of all storage services. In a production setting, we strongly recommend using external storage services. Check out the [configuration section](/langsmith/self-hosted) for more information.
### *Does my application need egress to function properly?*
Our deployment only needs egress for a few things (most of which can reside within your VPC):
* Fetching images (If mirroring your images, this may not be needed)
* Talking to any LLM endpoints
* Talking to any external storage services you may have configured
* Fetching OAuth information
* Subscription Metrics and Operational Metadata (if not running in offline mode)
* Requires egress to `https://beacon.langchain.com`
* See [Egress](/langsmith/self-host-egress) for more information
Your VPC can set up rules to limit any other access. Note: We require the `X-Organization-Id` and `X-Tenant-Id` headers to be allowed to be passed through to the backend service. These are used to determine which organization and workspace (previously called "tenant") the request is for.
### *Resource requirements for the application?*
* In kubernetes, we recommend a minimum helm configuration which can be found in [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/examples/medium_size.yaml). For docker, we recommend a minimum of 16GB of RAM and 4 CPUs.
* For Postgres, we recommend a minimum of 8GB of RAM and 2 CPUs.
* For Redis, we recommend 4GB of RAM and 2 CPUs.
* For Clickhouse, we recommend 32GB of RAM and 8 CPUs.
### SAML SSO FAQs
#### *How do I change a SAML SSO user's email address?*
Some identity providers retain the original `User ID` through an email change while others do not, so we recommend that you follow these steps to avoid duplicate users in LangSmith:
1. Remove the user from the organization (see [here](/langsmith/set-up-hierarchy#manage-users))
2. Change their email address in the IdP
3. Have them login to LangSmith again via SAML SSO - this will trigger the usual [JIT provisioning](#just-in-time-jit-provisioning) flow with their new email address
Changing email address via SCIM or otherwise is not currently supported for users with multiple linked login methods. This error message is shown: `email update not supported with linked login methods`. For example, if a user previously logged in via email/password or Google social login, and then is added with the same email address via SSO, changing their email address is not supported. This applies to both self-hosted and cloud.
#### *Can I change identity providers?*
Reach out to the LangChain support team through our portal at [https://support.langchain.com](https://support.langchain.com) for support on migration.
#### *How do I fix "405 method not allowed"?*
Ensure you're using the correct ACS URL: [https://auth.langchain.com/auth/v1/sso/saml/acs](https://auth.langchain.com/auth/v1/sso/saml/acs)
### SCIM FAQs
#### *Can I use SCIM without SAML SSO?*
* **Cloud**: No, SAML SSO is required for SCIM in cloud deployments
* **Self-hosted**: Yes, SCIM works with OAuth with Client Secret authentication mode
#### *What happens if I have both JIT provisioning and SCIM enabled?*
JIT provisioning and SCIM can conflict with each other. We recommend disabling JIT provisioning before enabling SCIM to ensure consistent user provisioning behavior.
#### *How do I change a user's role or workspace access?*
Update the user's group membership in your IdP. The changes will be synchronized to LangSmith according to the [role precedence rules](#role-precedence).
#### *What happens when a user is removed from all groups?*
The user will be deprovisioned from your LangSmith organization according to your IdP's deprovisioning settings.
#### *Can I use custom group names?*
Yes. If your identity provider supports syncing alternate fields to the `displayName` group attribute, you may use an alternate attribute (like `description`) as the `displayName` in LangSmith and retain full customizability of the identity provider group name. Otherwise, groups must follow the specific naming convention described in the [Group Naming Convention](#group-naming-convention) section to properly map to LangSmith roles and workspaces.
#### *Why is my Okta integration not working?*
See Okta's troubleshooting guide here: [https://help.okta.com/en-us/content/topics/users-groups-profiles/usgp-group-push-troubleshoot.htm](https://help.okta.com/en-us/content/topics/users-groups-profiles/usgp-group-push-troubleshoot.htm).
## Deployment
### Do I need to use LangChain to use LangGraph? what's the difference?
No. LangGraph is an orchestration framework for complex agentic systems and is more low-level and controllable than LangChain agents. LangChain provides a standard interface to interact with models and other components, useful for straight-forward chains and retrieval flows.
### How is LangGraph different from other agent frameworks?
Other agentic frameworks can work for simple, generic tasks but fall short for complex tasks bespoke to a company’s needs. LangGraph provides a more expressive framework to handle companies’ unique tasks without restricting users to a single black-box cognitive architecture.
### Does LangGraph impact the performance of my app?
LangGraph will not add any overhead to your code and is specifically designed with streaming workflows in mind.
### Is LangGraph open source? is it free?
Yes. LangGraph is an MIT-licensed open-source library and is free to use.
### How are LangGraph and LangSmith different?
LangGraph is a stateful, orchestration framework that brings added control to agent workflows. LangSmith is a service for deploying and scaling agentic applications, with an opinionated API for building agent UXs, plus an integrated developer UI.
| Features | LangGraph (open source) | LangSmith |
| ------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Description | Stateful orchestration framework for agentic applications | Scalable infrastructure for deploying LangGraph applications |
| SDKs | Python and JavaScript | Python and JavaScript |
| HTTP APIs | None | Yes - useful for retrieving & updating state or long-term memory, or creating a configurable assistant |
| Streaming | Basic | Dedicated mode for token-by-token messages |
| Checkpointer | Community contributed | Supported out-of-the-box |
| Persistence Layer | Self-managed | Managed Postgres with efficient storage |
| Deployment | Self-managed | • Cloud • Free self-hosted • Enterprise (paid self-hosted) |
| Scalability | Self-managed | Auto-scaling of task queues and servers |
| Fault-tolerance | Self-managed | Automated retries |
| Concurrency Control | Simple threading | Supports double-texting |
| Scheduling | None | Cron scheduling |
| Monitoring | None | Integrated with LangSmith for observability |
| IDE integration | Studio | Studio |
### Is LangSmith open source?
No. LangSmith is proprietary software.
There is a free, self-hosted version of LangSmith with access to basic features. The Cloud deployment option and the Self-Hosted deployment options are paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more.
For more information, see our [LangSmith pricing page](https://www.langchain.com/pricing).
### Does LangGraph work with LLMs that don't support tool calling?
Yes! You can use LangGraph with any LLMs. The main reason we use LLMs that support tool calling is that this is often the most convenient way to have the LLM make its decision about what to do. If your LLM does not support tool calling, you can still use it - you just need to write a bit of logic to convert the raw LLM string response to a decision about what to do.
### Does LangGraph work with OSS LLMs?
Yes! LangGraph is totally ambivalent to what LLMs are used under the hood. The main reason we use closed LLMs in most of the tutorials is that they seamlessly support tool calling, while OSS LLMs often don't. But tool calling is not necessary (see [this section](#does-langgraph-work-with-llms-that-dont-support-tool-calling)) so you can totally use LangGraph with OSS LLMs.
### Can I use Studio without logging in to LangSmith?
Yes! You can use the [development version of Agent Server](/langsmith/local-server) to run the backend locally.
This will connect to the Studio frontend hosted as part of LangSmith.
If you set an environment variable of `LANGSMITH_TRACING=false`, then no traces will be sent to LangSmith.
### What is a Deployment Run?
An Deployment Run is one end-to-end invocation of a LangGraph agent deployed via LangSmith Deployment. Nodes and subgraphs are not charged separately. Calls to other LangGraph agents (through RemoteGraph or the LangGraph SDK or the API directly) are charged separately, to the deployment that hosts the agent being called. An interrupt for human-in-the-loop creates a separate Deployment Run when resuming.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/faq.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Feedback data format
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/feedback-data-format
Before diving into this content, it might be helpful to read the following:
* [Conceptual guide on tracing and feedback](/langsmith/observability-concepts)
**Feedback** is LangSmith's way of storing the criteria and scores from evaluation on a particular trace or intermediate run (span). Feedback can be produced from a variety of ways, such as:
1. [Sent up along with a trace](/langsmith/attach-user-feedback) from the LLM application
2. Generated by a user in the app [inline](/langsmith/annotate-traces-inline) or in an [annotation queue](/langsmith/annotation-queues)
3. Generated by an automatic evaluator during [offline evaluation](/langsmith/evaluate-llm-application)
4. Generated by an [online evaluator](/langsmith/online-evaluations-llm-as-judge)
Feedback is stored in a simple format with the following fields:
| Field Name | Type | Description |
| -------------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `id` | UUID | Unique identifier for the record itself |
| `created_at` | datetime | Timestamp when the record was created |
| `modified_at` | datetime | Timestamp when the record was last modified |
| `session_id` | UUID | Unique identifier for the experiment or tracing project the run was a part of |
| `run_id` | UUID | Unique identifier for a specific run within a session |
| `key` | string | A key describing the criteria of the feedback, e.g. `'correctness'` |
| `score` | number | Numerical score associated with the feedback key |
| `value` | string | Reserved for storing a value associated with the score. Useful for categorical feedback. |
| `comment` | string | Any comment or annotation associated with the record. This can be a justification for the score given. |
| `correction` | object | Reserved for storing correction details, if any |
| `feedback_source` | object | Object containing information about the feedback source |
| `feedback_source.type` | string | The type of source where the feedback originated, e.g. `'api'`, `'app'`, `'evaluator'` |
| `feedback_source.metadata` | object | Reserved for additional metadata, currently |
| `feedback_source.user_id` | UUID | Unique identifier for the user providing feedback |
Here is an example JSON representation of a feedback record in the above format:
```json theme={null}
{
"created_at": "2024-05-05T23:23:11.077838",
"modified_at": "2024-05-05T23:23:11.232962",
"session_id": "c919298b-0af2-4517-97a2-0f98ed4a48f8",
"run_id": "e26174e5-2190-4566-b970-7c3d9a621baa",
"key": "correctness",
"score": 1.0,
"value": null,
"comment": "I gave this score because the answer was correct.",
"correction": null,
"id": "62104630-c7f5-41dc-8ee2-0acee5c14224",
"feedback_source": {
"type": "app",
"metadata": null,
"user_id": "ad52b092-1346-42f4-a934-6e5521562fab"
}
}
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/feedback-data-format.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to fetch performance metrics for an experiment
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/fetch-perf-metrics-experiment
Tracing projects and experiments use the same underlying data structure in our backend, which is called a "session."
You might see these terms interchangeably in our documentation, but they all refer to the same underlying data structure.
We are working on unifying the terminology across our documentation and APIs.
When you run an experiment using `evaluate` with the Python or TypeScript SDK, you can fetch the performance metrics for the experiment using the `read_project`/`readProject` methods.
The payload for experiment details includes the following values:
```json theme={null}
{
"start_time": "2024-06-06T01:02:51.299960",
"end_time": "2024-06-06T01:03:04.557530+00:00",
"extra": {
"metadata": {
"git": {
"tags": null,
"dirty": true,
"branch": "ankush/agent-eval",
"commit": "...",
"repo_name": "...",
"remote_url": "...",
"author_name": "Ankush Gola",
"commit_time": "...",
"author_email": "..."
},
"revision_id": null,
"dataset_splits": ["base"],
"dataset_version": "2024-06-05T04:57:01.535578+00:00",
"num_repetitions": 3
}
},
"name": "SQL Database Agent-ae9ad229",
"description": null,
"default_dataset_id": null,
"reference_dataset_id": "...",
"id": "...",
"run_count": 9,
"latency_p50": 7.896,
"latency_p99": 13.09332,
"first_token_p50": null,
"first_token_p99": null,
"total_tokens": 35573,
"prompt_tokens": 32711,
"completion_tokens": 2862,
"total_cost": 0.206485,
"prompt_cost": 0.163555,
"completion_cost": 0.04293,
"tenant_id": "...",
"last_run_start_time": "2024-06-06T01:02:51.366397",
"last_run_start_time_live": null,
"feedback_stats": {
"cot contextual accuracy": {
"n": 9,
"avg": 0.6666666666666666,
"values": {
"CORRECT": 6,
"INCORRECT": 3
}
}
},
"session_feedback_stats": {},
"run_facets": [],
"error_rate": 0,
"streaming_rate": 0,
"test_run_number": 11
}
```
From here, you can extract performance metrics such as:
* `latency_p50`: The 50th percentile latency in seconds.
* `latency_p99`: The 99th percentile latency in seconds.
* `total_tokens`: The total number of tokens used.
* `prompt_tokens`: The number of prompt tokens used.
* `completion_tokens`: The number of completion tokens used.
* `total_cost`: The total cost of the experiment.
* `prompt_cost`: The cost of the prompt tokens.
* `completion_cost`: The cost of the completion tokens.
* `feedback_stats`: The feedback statistics for the experiment.
* `error_rate`: The error rate for the experiment.
* `first_token_p50`: The 50th percentile latency for the time to generate the first token (if using streaming).
* `first_token_p99`: The 99th percentile latency for the time to generate the first token (if using streaming).
Here is an example of how you can fetch the performance metrics for an experiment using the Python and TypeScript SDKs.
First, as a prerequisite, we will create a trivial dataset. Here, we only demonstrate this in Python, but you can do the same in TypeScript. Please view the [how-to guide](/langsmith/evaluate-llm-application) on evaluation for more details.
```python theme={null}
from langsmith import Client
client = Client()
# Create a dataset
dataset_name = "HelloDataset"
dataset = client.create_dataset(dataset_name=dataset_name)
examples = [
{
"inputs": {"input": "Harrison"},
"outputs": {"expected": "Hello Harrison"},
},
{
"inputs": {"input": "Ankush"},
"outputs": {"expected": "Hello Ankush"},
},
]
client.create_examples(dataset_id=dataset.id, examples=examples)
```
Next, we will create an experiment, retrieve the experiment name from the result of `evaluate`, then fetch the performance metrics for the experiment.
```python Python theme={null}
from langsmith.schemas import Example, Run
dataset_name = "HelloDataset"
def foo_label(root_run: Run, example: Example) -> dict:
return {"score": 1, "key": "foo"}
from langsmith import evaluate
results = evaluate(
lambda inputs: "Hello " + inputs["input"],
data=dataset_name,
evaluators=[foo_label],
experiment_prefix="Hello",
)
resp = client.read_project(project_name=results.experiment_name, include_stats=True)
print(resp.model_dump_json(indent=2))
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
import { evaluate } from "langsmith/evaluation";
import type { EvaluationResult } from "langsmith/evaluation";
import type { Run, Example } from "langsmith/schemas";
// Row-level evaluator
function fooLabel(rootRun: Run, example: Example): EvaluationResult {
return {score: 1, key: "foo"};
}
const client = new Client();
const results = await evaluate(
(inputs) => {
return { output: "Hello " + inputs.input };
},
{
data: "HelloDataset",
experimentPrefix: "Hello",
evaluators: [fooLabel],
}
);
const resp = await client.readProject({
projectName: results.experimentName,
includeStats: true
})
console.log(JSON.stringify(resp, null, 2))
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/fetch-perf-metrics-experiment.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to filter experiments in the UI
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/filter-experiments-ui
LangSmith lets you filter your previous experiments by feedback scores and metadata to make it easy to find only the experiments you care about.
## Background: add metadata to your experiments
When you run an experiment in the SDK, you can attach metadata to make it easier to filter in UI. This is helpful if you know what axes you want to drill down into when running experiments.
In our example, we are going to attach metadata to our experiment around the model used, the model provider, and a known ID of the prompt:
```python theme={null}
models = {
"openai-gpt-4.1": ChatOpenAI(model="gpt-4.1", temperature=0),
"openai-gpt-4.1-mini": ChatOpenAI(model="gpt-4.1-mini", temperature=0),
"anthropic-claude-3-sonnet-20240229": ChatAnthropic(temperature=0, model_name="claude-3-sonnet-20240229")
}
prompts = {
"singleminded": "always answer questions with the word banana.",
"fruitminded": "always discuss fruit in your answers.",
"basic": "you are a chatbot."
}
def answer_evaluator(run, example) -> dict:
llm = ChatOpenAI(model="gpt-4.1", temperature=0)
answer_grader = hub.pull("langchain-ai/rag-answer-vs-reference") | llm
score = answer_grader.invoke(
{
"question": example.inputs["question"],
"correct_answer": example.outputs["answer"],
"student_answer": run.outputs,
}
)
return {"key": "correctness", "score": score["Score"]}
dataset_name = "Filterable Dataset"
for model_type, model in models.items():
for prompt_type, prompt in prompts.items():
def predict(example):
return model.invoke(
[("system", prompt), ("user", example["question"])]
)
model_provider = model_type.split("-")[0]
model_name = model_type[len(model_provider) + 1:]
evaluate(
predict,
data=dataset_name,
evaluators=[answer_evaluator],
# ADD IN METADATA HERE!!
metadata={
"model_provider": model_provider,
"model_name": model_name,
"prompt_id": prompt_type
}
)
```
## Filter experiments in the UI
In the UI, we see all experiments that have been run by default.
If we, say, have a preference for openai models, we can easily filter down and see scores within just openai models first:
We can stack filters, allowing us to filter out low scores on correctness to make sure we only compare relevant experiments:
Finally, we can clear and reset filters. For example, if we see there is clear there's a winner with the `singleminded` prompt, we can change filtering settings to see if any other model providers' models work as well with it:
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/filter-experiments-ui.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Filter traces
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/filter-traces-in-application
**Recommended reading**: For more details about the concepts mentioned on this page, refer to the [Conceptual guide on tracing](/langsmith/observability-concepts).
Tracing projects can contain a significant amount of data. You can use filters to navigate and analyze this data, which allows you to:
* Quickly narrow down to specific runs for ad-hoc analysis.
* Identify and examine errors, failed runs, and performance bottlenecks.
This page describes how to filter runs in a tracing project. If you are programmatically exporting runs for analysis via the [API](https://api.smith.langchain.com/redoc#tag/run/operation/query_runs_api_v1_runs_query_post) or [SDK](https://docs.smith.langchain.com/reference/python/client/langsmith.client.Client#langsmith.client.Client.list_runs), refer to the [exporting traces guide](./export-traces) for more information.
## Create and apply filters
### Filter by run attributes
There are two ways to filter runs in a tracing project:
1. **Filters**: Located at the top left of the **Tracing** project page. This is where you construct and manage filter criteria.
* The first dropdown filters for default and [saved views](#save-a-filter).
* Quick filter by **Traces** or **Runs**.
* **+ Add filter** to [configure a filter based](#specific-filtering-techniques) on an attribute or full-text search.
2. **Filter Shortcuts**: Positioned on the right sidebar of the **Tracing** project page. The filter shortcuts bar provides quick access to filters based on the most frequently occurring attributes in your project's runs.
By default, the **Traces** filter is applied, which displays only top-level root runs. You can switch to **Runs** to show all runs.
### Filter operators
The available filter operators depend on the data type of the attribute you are filtering on. Here's an overview of common operators:
* **is**: Exact match on the filter value
* **is not**: Negative match on the filter value
* **contains**: Partial match on the filter value
* **does not contain**: Negative partial match on the filter value
* **is one of**: Match on any of the values in the list
* `>` / `<`: Available for numeric fields
## Specific filtering techniques
### Filter for runs (spans)
To filter for runs (spans), change the default from **Traces** to **Runs**. For example, you would do this if you wanted to filter by **run name** for runs or filter by **run type**.
Run metadata and tags are also useful to filter on. These rely on good tagging across all parts of your pipeline. To learn more, refer to [Add metadata and tags to traces](/langsmith/add-metadata-tags).
As you specify more filters, you can click each filter individually to update the attributes you're searching on.
### Filter based on inputs and outputs
You can filter runs based on the content in the inputs and outputs of the run.
To filter either inputs or outputs, you can use the ** Full-Text Search** filter, which will match keywords in either field. For a more targeted search, you can use the ** Input** or ** Output** filters, which will only match content based on the respective field.
For performance, we index up to 250 characters of data for full-text search. If your search query exceeds this limit, we recommend using [Input/Output key-value search](/langsmith/filter-traces-in-application#filter-based-on-input-%2F-output-key-value-pairs) instead.
You can also specify multiple to match all terms provided, either by:
* Including multiple terms separated by whitespace with the **Full-Text Search**.
* Adding multiple filters with the button after you've added the first filter.
LangSmith completes keyword search by splitting the text and finding any partial matches on the search keywords, so it is not done in specific order. LangSmith excludes common stop words from the search (from the nltk stop word list along with a few other common JSON keywords).
Based on the filters in the image, the system will search for `python` and `tensorflow` in either inputs or outputs, and `embedding` in the inputs along with `fine` and `tune` in the outputs.
You can remove filters as needed from the filter path, which will widen the search to the remaining filters.
### Filter based on input / output key-value pairs
In addition to full-text search, you can filter runs based on specific key-value pairs in the inputs and outputs. This allows for more precise filtering, especially when dealing with structured data.
LangSmith indexes up to 100 unique keys per run to keep your data organized and searchable. Each key also has a character limit of 250 characters per value. If your data exceeds either of these limits, the text won't be indexed. This helps ensure fast, reliable performance.
To filter based on key-value pairs, for example, to match the following input:
```json theme={null}
{
"input": "What is the capital of France?"
}
```
1. Select **+ Add filter**.
2. Select **Input** from the first dropdown and leave **Key** as the second dropdown and select **input** as the key.
3. Click **+ Value** and enter the value: `What is the capital of France?` as the value.
You can also match nested keys by using dot notation to select the nested key name. For example, to match nested keys in the output:
```json theme={null}
{
"documents": [
{
"page_content": "The capital of France is Paris",
"metadata": {},
"type": "Document"
}
]
}
```
Select `Output Key`, enter `documents.page_content` as the key and enter `The capital of France is Paris` as the value. This will match the nested key `documents.page_content` with the specified value.
You can add multiple key-value filters to create more complex queries. You can also use the **Filter Shortcuts** on the right side to filter based on common key-value pairs quickly:
### Example: Filtering for tool calls
It's common to want to search for traces that contain specific tool calls. Tool calls are typically indicated in the output of an LLM run. To filter for tool calls, you would use the **Output Key** filter.
While this example will show you how to filter for tool calls, the same logic can be applied to filter for any key-value pair in the output.
In this case, let's assume this is the output you want to filter for:
```json theme={null}
{
"generations": [
[
{
"text": "",
"type": "ChatGeneration",
"message": {
"lc": 1,
"type": "constructor",
"id": [],
"kwargs": {
"type": "ai",
"id": "run-ca7f7531-f4de-4790-9c3e-960be7f8b109",
"tool_calls": [
{
"name": "Plan",
"args": {
"steps": [
"Research LangGraph's node configuration capabilities",
"Investigate how to add a Python code execution node",
"Find an example or create a sample implementation of a code execution node"
]
},
"id": "toolu_01XexPzAVknT3gRmUB5PK5BP",
"type": "tool_call"
}
]
}
}
}
]
],
"llm_output": null,
"run": null,
"type": "LLMResult"
}
```
With the example, the KV search will map each nested JSON path as a key-value pair that you can use to search and filter.
LangSmith will break it into the following set of searchable key-value pairs:
| Key | Value |
| -------------------------------------------------- | ---------------------------------------------------------------------------- |
| `generations.type` | `ChatGeneration` |
| `generations.message.type` | `constructor` |
| `generations.message.kwargs.type` | `ai` |
| `generations.message.kwargs.id` | `run-ca7f7531-f4de-4790-9c3e-960be7f8b109` |
| `generations.message.kwargs.tool_calls.name` | `Plan` |
| `generations.message.kwargs.tool_calls.args.steps` | `Research LangGraph's node configuration capabilities` |
| `generations.message.kwargs.tool_calls.args.steps` | `Investigate how to add a Python code execution node` |
| `generations.message.kwargs.tool_calls.args.steps` | `Find an example or create a sample implementation of a code execution node` |
| `generations.message.kwargs.tool_calls.id` | `toolu_01XexPzAVknT3gRmUB5PK5BP` |
| `generations.message.kwargs.tool_calls.type` | `tool_call` |
| `type` | `LLMResult` |
To search for a specific tool call, you can use the following **Output Key** search while removing the root runs filter:
`generations.message.kwargs.tool_calls.name` = `Plan`
This will match root and non-root runs where the `tool_calls` name is `Plan`.
### Negative filtering on key-value pairs
Different types of negative filtering can be applied to ** Metadata**, ** Input**, and ** Output** fields to exclude specific runs from your results.
For example, to find all runs where the metadata key `phone` is not equal to `1234567890`:
1. Set the **Metadata Key** operator to `is` and **Key** field to `phone`.
2. Set the **Value** operator to `is not` and the **Value** field to `1234567890`.
This will match all runs that have a metadata key `phone` with any value except `1234567890`.
To find runs that don't have a specific metadata key: set the **Key** operator to `is not`. For example, setting the `Key` operator to `is not` with `phone` as the key will match all runs that don't have a `phone` field in their metadata.
You can also filter for runs that neither have a specific key nor a specific value. To find runs where the metadata has neither the key `phone` nor any field with the value `1234567890`, set the **Key** operator to `is not` with key `phone`, and the **Value** operator to `is not` with value `1234567890`.
Finally, you can also filter for runs that do not have a specific key but have a specific value. To find runs where there is no `phone` key but there is a value of `1234567890` for some other key, set the **Key** operator to `is not` with key `phone`, and the **Value** operator to `is` with value `1234567890`.
You can use the `does not contain` operator instead of `is not` to perform a substring match.
## Save a filter
Saving filters allows you to store and reuse frequently used filter configurations. Saved filters are specific to a tracing project.
After you have constructed your filter, click the **Save as** button to save it. This will bring up a dialog to specify the name and a description of the filter.
After saving a filter, it is available in the view dropdown as a quick filter for you to use.
### Update a saved filter
With the filter selected in the dropdown, you can make any changes to filter parameters. Then, click **Save** to update the filter.
### Delete a saved filter
Click the icon next to the saved filter in the dropdown, and delete the filter using the trash icon.
## Copy a filter
You can copy a constructed filter to share it with colleagues, reuse it later, or query runs programmatically in the [API](https://api.smith.langchain.com/redoc#tag/run/operation/query_runs_api_v1_runs_query_post) or [SDK](https://docs.smith.langchain.com/reference/python/client/langsmith.client.Client#langsmith.client.Client.list_runs).
To copy the filter:
1. Create it in the UI.
2. Click the icon in the filter bar. If you have constructed tree or trace filters, you can also copy those.
3. This will give you a string representing the filter in the LangSmith query language. For example: `and(eq(is_root, true), and(eq(feedback_key, "user_score"), eq(feedback_score, 1)))`.
For more information on the query language syntax, refer to the [Trace query syntax](/langsmith/trace-query-syntax#filter-query-language).
## Filter runs within the trace view
You can also apply filters directly within the trace view, which is useful for sifting through traces with a large number of runs. The same filters available in the main runs table view can be applied here.
By default, only the runs that match the filters will be shown. To see the matched runs within the broader context of the trace tree, switch the view option from "Filtered Only" to "Show All" or "Most relevant".
## Manually specify a raw query in LangSmith query language
If you have [copied a previously constructed filter](#copy-the-filter), you may want to manually apply this raw query in a future session.
In order to do this, you can click on **Switch to raw query** on the bottom of the filters popover in the trace view. From there you can paste a raw query into the text box.
This will add that query to the existing queries, not overwrite it.
## Advanced filters
### Filter for runs (spans) on properties of the root
A common concept is to filter for runs which are part of a trace whose root run has some attribute. An example is filtering for runs of a particular type whose root run has positive (or negative) feedback associated with it. To do this:
1. Click **Runs** in the Traces/Runs toggle.
2. Add another filter rule. You can then click the **Advanced** filters link at the bottom of the filter dropdown.
3. A modal will open where you can add **Trace** filters. These filters will apply to the traces of all the parent runs of the individual runs you've already filtered for.
### Filter for runs (spans) whose child runs have some attribute
You may want to search for runs who have specific types of sub runs. An example of this could be searching for all traces that had a sub run with name `Foo`. This is useful when `Foo` is not always called, but you want to analyze the cases where it is.
1. Click **Runs** in the Traces/Runs toggle.
2. Add another filter rule. You can then click the **Advanced** filters link at the bottom of the filter dropdown.
3. A modal will open where you can add **Tree** filters. This will make the rule you specify apply to all child runs of the individual runs you've already filtered for.
### Example: Filtering on all runs whose tree contains the tool call filter
Extending the [tool call filtering example](#example-filtering-for-tool-calls), if you would like to filter for all runs *whose tree contains* the tool filter call, you can use the tree filter in the **Advanced** filters setting.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/filter-traces-in-application.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Self-hosted LangSmith on GCP
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/gcp-self-hosted
When running LangSmith on [Google Cloud Platform (GCP)](https://cloud.google.com/), you can set up in either [full self-hosted](/langsmith/self-hosted) or [hybrid](/langsmith/hybrid) mode. Full self-hosted mode deploys a complete LangSmith platform with observability functionality as well as the option to create agent deployments. Hybrid mode entails just the infrastructure to run agents in a data plane within your cloud, while our SaaS provides the control plane and observability functionality.
This page provides:
* [Initial setup steps](#initial-setup) for deploying to GKE, configuring managed services, and setting up authentication.
* [GCP-specific architecture patterns](#reference-architecture) and reference diagrams.
* [Service recommendations](#compute-options) and best practices.
* [Google Cloud Well-Architected best practices](#google-cloud-well-architected-best-practices) for operational excellence, security, and reliability.
LangChain provides Terraform modules specifically for GCP to help provision infrastructure for LangSmith. These modules can quickly set up GKE clusters, Cloud SQL, Memorystore Redis, Cloud Storage, and networking resources.
View the [GCP Terraform modules](https://github.com/langchain-ai/terraform/tree/main/modules/gcp) for documentation and examples.
## Initial setup
Follow the [Kubernetes installation guide](/langsmith/kubernetes). LangSmith is tested on Google Kubernetes Engine (GKE).
**GKE-specific notes:**
* LangSmith works with standard GKE clusters
* Use GCE persistent disk storage class
For production deployments, connect to GCP managed services:
Store trace data in GCS
PostgreSQL database
Redis for caching
Analytics database
Use [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) to authenticate LangSmith pods to GCP services.
**Key pages:**
* [GCS HMAC key authentication](/langsmith/self-host-blob-storage#google-cloud-storage)
* [Cloud SQL IAM authentication](/langsmith/self-host-external-postgres#iam-authentication)
* [Memorystore IAM authentication](/langsmith/self-host-external-redis#iam-authentication)
After completing these initial setup steps, you can review the complete GCP architecture and best practices below.
## Reference architecture
We recommend leveraging GCP's managed services to provide a scalable, secure, and resilient platform. The following architecture applies to both self-hosted and hybrid and aligns with the [Google Cloud Well-Architected Framework](https://docs.cloud.google.com/architecture/framework):
* **Ingress & networking**: Requests enter via [Cloud Load Balancing](https://cloud.google.com/load-balancing) within your [VPC](https://cloud.google.com/vpc), secured using [Cloud Armor](https://cloud.google.com/armor) and [IAM](https://cloud.google.com/iam)-based authentication.
* **Frontend & backend services:** Containers run on [Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine), orchestrated behind the load balancer. Routes requests to other services within the cluster as necessary.
* **Storage & databases:**
* [Cloud SQL for PostgreSQL](https://cloud.google.com/sql/docs/postgres): metadata, projects, users, and short-term and long-term memory for deployed agents. LangSmith supports PostgreSQL version 14 or higher.
* [Memorystore for Redis](https://cloud.google.com/memorystore/docs/redis): caching and job queues. Memorystore can be in single-instance or cluster mode, running Redis OSS version 5 or higher.
* ClickHouse + [Persistent Disks](https://cloud.google.com/compute/docs/disks): analytics and trace storage.
* We recommend using an [externally managed ClickHouse solution](/langsmith/self-host-external-clickhouse) unless security or compliance reasons
prevent you from doing so.
* ClickHouse is not required for hybrid deployments.
* [Cloud Storage](https://cloud.google.com/storage): object storage for trace artifacts and telemetry.
* **LLM integration:** Optionally proxy requests to [Vertex AI](https://cloud.google.com/vertex-ai) for LLM inference.
* **Monitoring & observability:** Integrate with [Cloud Monitoring](https://cloud.google.com/monitoring) and [Cloud Logging](https://cloud.google.com/logging)
## Compute options
LangSmith supports multiple compute options depending on your requirements:
| Compute option | Description | Suitable for |
| ---------------------------------------- | ----------------------------------------- | ------------------------------------ |
| **Google Kubernetes Engine (preferred)** | Advanced scaling and multi-tenant support | Large enterprises |
| **Compute Engine-based** | Full control, BYO-infra | Regulated or air-gapped environments |
## Google cloud Well-Architected best practices
This reference is designed to align with the six pillars of the Google Cloud Well-Architected Framework:
### Operational excellence
* Automate deployments with IaC ([Terraform](https://www.terraform.io/) / [Deployment Manager](https://cloud.google.com/deployment-manager)).
* Use [Secret Manager](https://cloud.google.com/secret-manager) for configuration and sensitive data.
* Configure your LangSmith instance to [export telemetry data](/langsmith/export-backend) and continuously monitor via [Cloud Logging](https://cloud.google.com/logging).
* The preferred method to manage [LangSmith deployments](/langsmith/deployments) is to create a CI process that builds [Agent Server](/langsmith/agent-server) images and pushes them to [Artifact Registry](https://cloud.google.com/artifact-registry). Create a test deployment for pull requests before deploying a new revision to staging or production upon PR merge.
### Security
* Use [IAM](https://cloud.google.com/iam) roles with least-privilege policies and [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) for secure pod-to-GCP-service authentication.
* Enable encryption at rest ([Cloud SQL](https://docs.cloud.google.com/sql/docs/postgres/cmek), [Cloud Storage](https://cloud.google.com/storage/docs/encryption), Persistent Disks) and in transit (TLS 1.2+).
* Integrate with [Secret Manager](https://cloud.google.com/secret-manager) for credentials.
* Use [Identity Platform](https://cloud.google.com/identity-platform) or [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation) as an IDP in conjunction with LangSmith's built-in authentication and authorization features to secure access to agents and their tools.
### Reliability
* Replicate the LangSmith [data plane](/langsmith/data-plane) across regions: Deploy identical data planes to Kubernetes clusters in different regions for LangSmith Deployment. Deploy [Cloud SQL](https://cloud.google.com/sql/docs/postgres/high-availability) and [GKE](https://docs.cloud.google.com/kubernetes-engine/docs/concepts/configuration-overview) services across multiple zones.
* Implement [autoscaling](https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-autoscaler) for backend workers using [Horizontal Pod Autoscaler](https://cloud.google.com/kubernetes-engine/docs/concepts/horizontalpodautoscaler) and [Cluster Autoscaler](https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-autoscaler).
* Use [Cloud DNS](https://cloud.google.com/dns) health checks and failover policies.
### Performance optimization
* Leverage [Compute Engine](https://cloud.google.com/compute) instances for optimized compute with [machine type selection](https://cloud.google.com/compute/docs/machine-types).
* Use [Cloud Storage lifecycle policies](https://cloud.google.com/storage/docs/lifecycle) for infrequently accessed trace data, moving to [Nearline](https://cloud.google.com/storage/docs/storage-classes#nearline) or [Coldline](https://cloud.google.com/storage/docs/storage-classes#coldline) storage classes.
### Cost optimization
* Right-size [GKE](https://cloud.google.com/kubernetes-engine) clusters using [Committed Use Discounts](https://cloud.google.com/compute/docs/instances/signing-up-committed-use-discounts) and [Sustained Use Discounts](https://cloud.google.com/compute/docs/sustained-use-discounts).
* Monitor cost KPIs using [Cloud Billing](https://cloud.google.com/billing/docs) dashboards and [Cost Management](https://cloud.google.com/cost-management) tools.
### Sustainability
* Minimize idle workloads with on-demand compute and [autoscaling](https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-autoscaler).
* Store telemetry in low-latency, low-cost tiers using [Cloud Storage lifecycle policies](https://cloud.google.com/storage/docs/lifecycle).
* Enable auto-shutdown for non-prod environments using [scheduled actions](https://cloud.google.com/compute/docs/instances/schedule-instance-start-stop).
## Security and compliance
LangSmith can be configured for:
* [Private Service Connect](https://cloud.google.com/vpc/docs/private-service-connect)-only access (no public internet exposure, besides egress necessary for billing).
* [Cloud KMS](https://cloud.google.com/kms)-based encryption keys for Cloud Storage, Cloud SQL, and Persistent Disks.
* Audit logging to [Cloud Logging](https://cloud.google.com/logging) and [Cloud Audit Logs](https://cloud.google.com/logging/docs/audit).
Customers can deploy in [Assured Workloads](https://cloud.google.com/assured-workloads) regions for compliance with ISO, HIPAA, or other regulatory requirements as needed.
## Monitoring and evals
Use LangSmith to:
* Capture traces from LLM apps running on [Vertex AI](https://cloud.google.com/vertex-ai).
* Evaluate model outputs via [LangSmith datasets](/langsmith/manage-datasets).
* Track latency, token usage, and success rates.
Integrate with:
* [Cloud Monitoring](https://cloud.google.com/monitoring) dashboards.
* [OpenTelemetry](https://opentelemetry.io/) and [Prometheus](https://prometheus.io/) exporters.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/gcp-self-hosted.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to implement generative user interfaces with LangGraph
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/generative-ui-react
**Prerequisites**
* [LangSmith](/langsmith/home)
* [Agent Server](/langsmith/agent-server)
* [`useStream()` React Hook](/oss/python/langchain/streaming/frontend)
Generative user interfaces (Generative UI) allows agents to go beyond text and generate rich user interfaces. This enables creating more interactive and context-aware applications where the UI adapts based on the conversation flow and AI responses.
LangSmith supports colocating your React components with your graph code. This allows you to focus on building specific UI components for your graph while easily plugging into existing chat interfaces such as [Agent Chat](https://agentchat.vercel.app) and loading the code only when actually needed.
## Tutorial
### 1. Define and configure UI components
First, create your first UI component. For each component you need to provide an unique identifier that will be used to reference the component in your graph code.
```tsx title="src/agent/ui.tsx" theme={null}
const WeatherComponent = (props: { city: string }) => {
return
Weather for {props.city}
;
};
export default {
weather: WeatherComponent,
};
```
Next, define your UI components in your `langgraph.json` configuration:
```json theme={null}
{
"node_version": "20",
"graphs": {
"agent": "./src/agent/index.ts:graph"
},
"ui": {
"agent": "./src/agent/ui.tsx"
}
}
```
The `ui` section points to the UI components that will be used by graphs. By default, we recommend using the same key as the graph name, but you can split out the components however you like, see [Customise the namespace of UI components](#customise-the-namespace-of-ui-components) for more details.
LangSmith will automatically bundle your UI components code and styles and serve them as external assets that can be loaded by the `LoadExternalComponent` component. Some dependencies such as `react` and `react-dom` will be automatically excluded from the bundle.
CSS and Tailwind 4.x is also supported out of the box, so you can freely use Tailwind classes as well as `shadcn/ui` in your UI components.
```tsx theme={null}
import "./styles.css";
const WeatherComponent = (props: { city: string }) => {
return
Weather for {props.city}
;
};
export default {
weather: WeatherComponent,
};
```
```css theme={null}
@import "tailwindcss";
```
### 2. Send the UI components in your graph
```python title="src/agent.py" theme={null}
import uuid
from typing import Annotated, Sequence, TypedDict
from langchain.messages import AIMessage
from langchain_core.messages import BaseMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
from langgraph.graph.ui import AnyUIMessage, ui_message_reducer, push_ui_message
class AgentState(TypedDict): # noqa: D101
messages: Annotated[Sequence[BaseMessage], add_messages]
ui: Annotated[Sequence[AnyUIMessage], ui_message_reducer]
async def weather(state: AgentState):
class WeatherOutput(TypedDict):
city: str
weather: WeatherOutput = (
await ChatOpenAI(model="gpt-4.1-mini")
.with_structured_output(WeatherOutput)
.with_config({"tags": ["nostream"]})
.ainvoke(state["messages"])
)
message = AIMessage(
id=str(uuid.uuid4()),
content=f"Here's the weather for {weather['city']}",
)
# Emit UI elements associated with the message
push_ui_message("weather", weather, message=message)
return {"messages": [message]}
workflow = StateGraph(AgentState)
workflow.add_node(weather)
workflow.add_edge("__start__", "weather")
graph = workflow.compile()
```
Use the `typedUi` utility to emit UI elements from your agent nodes:
```typescript title="src/agent/index.ts" theme={null}
import {
typedUi,
uiMessageReducer,
} from "@langchain/langgraph-sdk/react-ui/server";
import { ChatOpenAI } from "@langchain/openai";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
import type ComponentMap from "./ui.js";
import {
Annotation,
MessagesAnnotation,
StateGraph,
type LangGraphRunnableConfig,
} from "@langchain/langgraph";
const AgentState = Annotation.Root({
...MessagesAnnotation.spec,
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
});
export const graph = new StateGraph(AgentState)
.addNode("weather", async (state, config) => {
// Provide the type of the component map to ensure
// type safety of `ui.push()` calls as well as
// pushing the messages to the `ui` and sending a custom event as well.
const ui = typedUi(config);
const weather = await new ChatOpenAI({ model: "gpt-4.1-mini" })
.withStructuredOutput(z.object({ city: z.string() }))
.withConfig({ tags: ["nostream"] })
.invoke(state.messages);
const response = {
id: uuidv4(),
type: "ai",
content: `Here's the weather for ${weather.city}`,
};
// Emit UI elements associated with the AI message
ui.push({ name: "weather", props: weather }, { message: response });
return { messages: [response] };
})
.addEdge("__start__", "weather")
.compile();
```
### 3. Handle UI elements in your React application
On the client side, you can use `useStream()` and `LoadExternalComponent` to display the UI elements.
```tsx title="src/app/page.tsx" theme={null}
"use client";
import { useStream } from "@langchain/langgraph-sdk/react";
import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui";
export default function Page() {
const { thread, values } = useStream({
apiUrl: "http://localhost:2024",
assistantId: "agent",
});
return (
);
}
```
Behind the scenes, `LoadExternalComponent` will fetch the JS and CSS for the UI components from LangSmith and render them in a shadow DOM, thus ensuring style isolation from the rest of your application.
## How-to guides
### Provide custom components on the client side
If you already have the components loaded in your client application, you can provide a map of such components to be rendered directly without fetching the UI code from LangSmith.
```tsx theme={null}
const clientComponents = {
weather: WeatherComponent,
};
;
```
### Show loading UI when components are loading
You can provide a fallback UI to be rendered when the components are loading.
```tsx theme={null}
Loading...}
/>
```
### Customise the namespace of UI components.
By default `LoadExternalComponent` will use the `assistantId` from `useStream()` hook to fetch the code for UI components. You can customise this by providing a `namespace` prop to the `LoadExternalComponent` component.
```tsx theme={null}
```
```json theme={null}
{
"ui": {
"custom-namespace": "./src/agent/ui.tsx"
}
}
```
### Access and interact with the thread state from the UI component
You can access the thread state inside the UI component by using the `useStreamContext` hook.
```tsx theme={null}
import { useStreamContext } from "@langchain/langgraph-sdk/react-ui";
const WeatherComponent = (props: { city: string }) => {
const { thread, submit } = useStreamContext();
return (
<>
Weather for {props.city}
>
);
};
```
### Pass additional context to the client components
You can pass additional context to the client components by providing a `meta` prop to the `LoadExternalComponent` component.
```tsx theme={null}
```
Then, you can access the `meta` prop in the UI component by using the `useStreamContext` hook.
```tsx theme={null}
import { useStreamContext } from "@langchain/langgraph-sdk/react-ui";
const WeatherComponent = (props: { city: string }) => {
const { meta } = useStreamContext<
{ city: string },
{ MetaType: { userId?: string } }
>();
return (
Weather for {props.city} (user: {meta?.userId})
);
};
```
### Streaming UI messages from the server
You can stream UI messages before the node execution is finished by using the `onCustomEvent` callback of the `useStream()` hook. This is especially useful when updating the UI component as the LLM is generating the response.
```tsx theme={null}
import { uiMessageReducer } from "@langchain/langgraph-sdk/react-ui";
const { thread, submit } = useStream({
apiUrl: "http://localhost:2024",
assistantId: "agent",
onCustomEvent: (event, options) => {
options.mutate((prev) => {
const ui = uiMessageReducer(prev.ui ?? [], event);
return { ...prev, ui };
});
},
});
```
Then you can push updates to the UI component by calling `ui.push()` / `push_ui_message()` with the same ID as the UI message you wish to update.
```python theme={null}
from typing import Annotated, Sequence, TypedDict
from langchain_anthropic import ChatAnthropic
from langchain.messages import AIMessage, AIMessageChunk, BaseMessage
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
from langgraph.graph.ui import AnyUIMessage, push_ui_message, ui_message_reducer
class AgentState(TypedDict): # noqa: D101
messages: Annotated[Sequence[BaseMessage], add_messages]
ui: Annotated[Sequence[AnyUIMessage], ui_message_reducer]
class CreateTextDocument(TypedDict):
"""Prepare a document heading for the user."""
title: str
async def writer_node(state: AgentState):
model = ChatAnthropic(model="claude-sonnet-4-5-20250929")
message: AIMessage = await model.bind_tools(
tools=[CreateTextDocument],
tool_choice={"type": "tool", "name": "CreateTextDocument"},
).ainvoke(state["messages"])
tool_call = next(
(x["args"] for x in message.tool_calls if x["name"] == "CreateTextDocument"),
None,
)
if tool_call:
ui_message = push_ui_message("writer", tool_call, message=message)
ui_message_id = ui_message["id"]
# We're already streaming the LLM response to the client through UI messages
# so we don't need to stream it again to the `messages` stream mode.
content_stream = model.with_config({"tags": ["nostream"]}).astream(
f"Create a document with the title: {tool_call['title']}"
)
content: AIMessageChunk | None = None
async for chunk in content_stream:
content = content + chunk if content else chunk
push_ui_message(
"writer",
{"content": content.text()},
id=ui_message_id,
message=message,
# Use `merge=True` to merge props with the existing UI message
merge=True,
)
return {"messages": [message]}
```
```tsx theme={null}
import {
Annotation,
MessagesAnnotation,
type LangGraphRunnableConfig,
} from "@langchain/langgraph";
import { z } from "zod";
import { ChatAnthropic } from "@langchain/anthropic";
import {
typedUi,
uiMessageReducer,
} from "@langchain/langgraph-sdk/react-ui/server";
import type { AIMessageChunk } from "@langchain/core/messages";
import type ComponentMap from "./ui";
const AgentState = Annotation.Root({
...MessagesAnnotation.spec,
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
});
async function writerNode(
state: typeof AgentState.State,
config: LangGraphRunnableConfig
): Promise {
const ui = typedUi(config);
const model = new ChatAnthropic({ model: "claude-sonnet-4-5-20250929" });
const message = await model
.bindTools(
[
{
name: "create_text_document",
description: "Prepare a document heading for the user.",
schema: z.object({ title: z.string() }),
},
],
{ tool_choice: { type: "tool", name: "create_text_document" } }
)
.invoke(state.messages);
type ToolCall = { name: "create_text_document"; args: { title: string } };
const toolCall = message.tool_calls?.find(
(tool): tool is ToolCall => tool.name === "create_text_document"
);
if (toolCall) {
const { id, name } = ui.push(
{ name: "writer", props: { title: toolCall.args.title } },
{ message }
);
const contentStream = await model
// We're already streaming the LLM response to the client through UI messages
// so we don't need to stream it again to the `messages` stream mode.
.withConfig({ tags: ["nostream"] })
.stream(`Create a short poem with the topic: ${message.text}`);
let content: AIMessageChunk | undefined;
for await (const chunk of contentStream) {
content = content?.concat(chunk) ?? chunk;
ui.push(
{ id, name, props: { content: content?.text } },
// Use `merge: true` to merge props with the existing UI message
{ message, merge: true }
);
}
}
return { messages: [message] };
}
```
```tsx theme={null}
function WriterComponent(props: { title: string; content?: string }) {
return (
{props.title}
{props.content}
);
}
export default {
weather: WriterComponent,
};
```
### Remove UI messages from state
Similar to how messages can be removed from the state by appending a RemoveMessage you can remove an UI message from the state by calling `remove_ui_message` / `ui.delete` with the ID of the UI message.
```python theme={null}
from langgraph.graph.ui import push_ui_message, delete_ui_message
# push message
message = push_ui_message("weather", {"city": "London"})
# remove said message
delete_ui_message(message["id"])
```
```tsx theme={null}
// push message
const message = ui.push({ name: "weather", props: { city: "London" } });
// remove said message
ui.delete(message.id);
```
## Learn more
* [JS/TS SDK Reference](https://langchain-ai.github.io/langgraphjs/reference/modules/sdk.html)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/generative-ui-react.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Integrations
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/get-started-integrations
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/get-started-integrations.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Granular billable usage
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/granular-usage
Retrieve detailed trace usage data broken down by workspace, project, user, or API key.
For LangSmith Cloud, granular billable usage data collection started on January 5, 2026. Data is not available for usage before this date.
For self-hosted instances, data collection begins when the feature is enabled via environment variable or after upgrading to a version with it enabled by default.
LangSmith provides granular billable usage APIs that allow you to retrieve detailed trace usage data broken down by workspace, project, user, or API key. This enables you to:
* Track usage across different teams or workspaces
* Identify which users or API keys are consuming the most traces
* Analyze usage patterns over time
* Export usage data for internal reporting
## Prerequisites
* You must have the [`organization:read` permission](/langsmith/organization-workspace-operations) to access granular usage data.
* You can only view usage for workspaces you have read access to.
## View in the UI
You can also view granular usage data in the [LangSmith UI](https://smith.langchain.com):
1. Navigate to **Settings** > **Billing and Usage**
2. Select the **Granular Usage** tab
3. Use the controls to:
* Select a time range:
* Last 7 days, 30 days, 3 months, 6 months, 1 year, or custom
* Choose aggregation level (daily, weekly, or monthly)
* Group by workspace, project, user, or API key
* Filter to specific workspaces
4. Click **Export CSV** to download the data
## API endpoints
### Get granular usage data
Retrieve granular usage data with flexible grouping options.
```
GET /api/v1/orgs/current/billing/granular-usage
```
#### Query parameters
| Parameter | Type | Required | Description |
| --------------- | -------------- | -------- | ---------------------------------------------------------------------------------------------- |
| `start_time` | datetime | Yes | Start of the time range (ISO 8601 format) |
| `end_time` | datetime | Yes | End of the time range (must be after start\_time) |
| `workspace_ids` | array of UUIDs | Yes | Filter results to specific workspaces |
| `group_by` | string | No | Dimension to group by. One of: `workspace`, `project`, `user`, `api_key`. Default: `workspace` |
#### Response
```json theme={null}
{
"stride": {
"days": 1,
"hours": 0
},
"usage": [
{
"time_bucket": "2026-01-15T00:00:00Z",
"dimensions": {
"workspace_id": "uuid",
"workspace_name": "My Workspace"
},
"traces": 1500
}
]
}
```
The `stride` field indicates the time bucket size used for aggregation, calculated based on the requested time range:
| Time range | Aggregation | Stride |
| ----------------------- | ----------- | ----------- |
| Less than 1 day | Hourly | `hours: 1` |
| 1-31 days | Daily | `days: 1` |
| 32-93 days (\~3 months) | Weekly | `days: 7` |
| 94-366 days (\~1 year) | Monthly | `days: 30` |
| More than 366 days | Yearly | `days: 365` |
#### Example: Get usage by workspace
```python Python theme={null}
import httpx
from datetime import datetime, timedelta, timezone
client = httpx.Client(
base_url="https://api.smith.langchain.com",
headers={"x-api-key": ""}
)
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(days=30)
response = client.get(
"/api/v1/orgs/current/billing/granular-usage",
params={
"start_time": start_time.isoformat(),
"end_time": end_time.isoformat(),
"workspace_ids": [""],
"group_by": "workspace"
}
)
data = response.json()
for record in data["usage"]:
print(f"{record['time_bucket']}: {record['traces']} traces")
```
```typescript TypeScript theme={null}
const response = await fetch(
`https://api.smith.langchain.com/api/v1/orgs/current/billing/granular-usage?` +
new URLSearchParams({
start_time: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
end_time: new Date().toISOString(),
workspace_ids: "",
group_by: "workspace"
}),
{
headers: {
"x-api-key": ""
}
}
);
const data = await response.json();
for (const record of data.usage) {
console.log(`${record.time_bucket}: ${record.traces} traces`);
}
```
```bash cURL theme={null}
curl -X GET "https://api.smith.langchain.com/api/v1/orgs/current/billing/granular-usage?\
start_time=2026-01-01T00:00:00Z&\
end_time=2026-01-15T00:00:00Z&\
workspace_ids=&\
group_by=workspace" \
-H "x-api-key: "
```
#### Example: Get usage by user
```python Python theme={null}
response = client.get(
"/api/v1/orgs/current/billing/granular-usage",
params={
"start_time": start_time.isoformat(),
"end_time": end_time.isoformat(),
"workspace_ids": [""],
"group_by": "user"
}
)
data = response.json()
for record in data["usage"]:
user_email = record["dimensions"].get("user_email", "Unknown")
print(f"{user_email}: {record['traces']} traces")
```
### Export usage as CSV
Export granular usage data as a downloadable CSV file.
```
GET /api/v1/orgs/current/billing/granular-usage/export
```
This endpoint accepts the same query parameters as the granular usage endpoint and returns a CSV file.
The CSV always includes all columns, but only the columns relevant to the selected `group_by` option are populated:
| Column | Description |
| ----------------- | -------------------------------------------------- |
| Time Bucket Start | Start of the time bucket |
| Time Bucket End | End of the time bucket |
| Workspace ID | UUID of the workspace (when grouping by workspace) |
| Workspace Name | Name of the workspace (when grouping by workspace) |
| Project ID | UUID of the project (when grouping by project) |
| Project Name | Name of the project (when grouping by project) |
| User ID | UUID of the user (when grouping by user) |
| User Email | Email of the user (when grouping by user) |
| API Key Short Key | Short key identifier (when grouping by API key) |
| Traces | Number of traces in the time bucket |
#### Example: Export to CSV
```python Python theme={null}
response = client.get(
"/api/v1/orgs/current/billing/granular-usage/export",
params={
"start_time": start_time.isoformat(),
"end_time": end_time.isoformat(),
"workspace_ids": [""],
"group_by": "workspace"
}
)
# Save to file
with open("usage_report.csv", "wb") as f:
f.write(response.content)
```
```bash cURL theme={null}
curl -X GET "https://api.smith.langchain.com/api/v1/orgs/current/billing/granular-usage/export?\
start_time=2026-01-01T00:00:00Z&\
end_time=2026-01-15T00:00:00Z&\
workspace_ids=&\
group_by=workspace" \
-H "x-api-key: " \
-o usage_report.csv
```
## Grouping options
The `group_by` parameter determines how usage data is aggregated:
| Value | Description | Dimensions returned |
| ----------- | ------------------ | -------------------------------- |
| `workspace` | Group by workspace | `workspace_id`, `workspace_name` |
| `project` | Group by project | `project_id`, `project_name` |
| `user` | Group by user | `user_id`, `user_email` |
| `api_key` | Group by API key | `api_key_short_key` |
## Related resources
* [Manage billing in your account](/langsmith/billing)
* [Organization and workspace operations](/langsmith/organization-workspace-operations)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/granular-usage.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Rebuild graph at runtime
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/graph-rebuild
Rebuild your graph with different configurations for each run using ServerRuntime.
You might need to rebuild your graph with a different configuration for a new run. For example, you might want to load different tools depending on the user's credentials. This guide shows how you can do this using `ServerRuntime`.
In most cases, customization is best handled by conditioning on the config within individual nodes rather than dynamically changing the whole graph structure. This makes it easier to test and manage.
## Prerequisites
* Make sure to check out [this how-to guide](/langsmith/setup-app-requirements-txt) on setting up your app for deployment first.
* `ServerRuntime` requires `langgraph-api >= 0.7.31` and `langgraph-sdk >= 0.3.5`. Prior to that, graph factories only accepted a single `config: RunnableConfig` argument.
## Define graphs
Let's say you have an app with a simple graph that calls an LLM and returns the response to the user. The app file directory looks like the following:
```
my-app/
|-- langgraph.json
|-- my_project/
| |-- __init__.py
| |-- agents.py # code for your graph
|-- pyproject.toml
```
where the graph is defined in `agents.py`.
### No rebuild
The most common way to deploy your Agent Server is to reference a compiled graph instance that's defined at the top level of your file. An example is below:
```python theme={null}
# my_project/agents.py
from langgraph.graph import StateGraph, MessagesState, START
async def model(state: MessagesState):
return {"messages": [{"role": "assistant", "content": "Hi, there!"}]}
graph_workflow = StateGraph(MessagesState)
graph_workflow.add_node("model", model)
graph_workflow.add_edge(START, "model")
agent = graph_workflow.compile()
```
To make the server aware of your graph, you need to specify a path to the variable that contains the [`CompiledStateGraph`](https://reference.langchain.com/python/langgraph/graphs/#langgraph.graph.state.CompiledStateGraph) instance in your LangGraph API configuration (`langgraph.json`), e.g.:
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"dependencies": ["."],
"graphs": {
"chat_agent": "my_project.agents:agent",
}
}
```
### Rebuild
To rebuild your graph on each new run, provide a **factory function** that returns (or yields) a graph. The factory can optionally accept a `ServerRuntime` parameter or a `RunnableConfig`. The server inspects your function's type annotations to determine which arguments to inject, so make sure to include the correct type hints. The server's queue workers will call your factory function any time they need to process a run. The function will also be called for certain other endpoints to update state, read state, or to fetch assistant schemas. The `ServerRuntime` tells you which context triggered the call.
`ServerRuntime` is in beta and may change in future releases.
#### Simple factory
The simplest form is a plain `async def` that returns a compiled graph:
```python theme={null}
from langchain_openai import ChatOpenAI
from langgraph.graph import START, StateGraph
from langchain_core.runnables import RunnableConfig
from langgraph_sdk.runtime import ServerRuntime
from my_agent.utils.state import AgentState
model = ChatOpenAI(model="gpt-5.2")
def make_graph_for_user(user_id: str):
"""Build a graph customized per user."""
graph_workflow = StateGraph(AgentState)
async def call_model(state):
return {"messages": [await model.ainvoke(state["messages"])]}
graph_workflow.add_node("agent", call_model)
graph_workflow.add_edge(START, "agent")
return graph_workflow.compile()
async def make_graph(config: RunnableConfig, runtime: ServerRuntime):
user = runtime.ensure_user()
return make_graph_for_user(user.identity)
```
#### Context manager factory
If you need to set up and tear down resources (database connections, load MCP tools, etc.), use an async context manager. Use `runtime.execution_runtime` to check whether the graph is being called for actual execution or just for introspection (schemas, visualization):
```python theme={null}
import contextlib
from langchain_openai import ChatOpenAI
from langgraph.graph import START, StateGraph
from langchain_core.runnables import RunnableConfig
from langgraph_sdk.runtime import ServerRuntime
from my_agent.utils.state import AgentState
model = ChatOpenAI(model="gpt-5.2")
def make_agent_graph(tools: list):
"""Make a simple LLM agent."""
graph_workflow = StateGraph(AgentState)
bound = model.bind_tools(tools)
async def call_model(state):
return {"messages": [await bound.ainvoke(state["messages"])]}
graph_workflow.add_node("agent", call_model)
graph_workflow.add_edge(START, "agent")
return graph_workflow.compile()
@contextlib.asynccontextmanager
async def make_graph(runtime: ServerRuntime):
if ert := runtime.execution_runtime:
# Only set up expensive resources during actual execution.
# Introspection calls (get_schema, get_graph, ...) skip this.
mcp_tools = await connect_mcp(ert.ensure_user()) # your setup logic
yield make_agent_graph(tools=mcp_tools)
await disconnect_mcp() # your teardown logic
else:
# For schema/state reads, return a graph with the same
# topology but no expensive resource setup.
yield make_agent_graph(tools=[])
```
Finally, specify the path to your factory in `langgraph.json`:
```json theme={null}
{
"$schema": "https://langgra.ph/schema.json",
"dependencies": ["."],
"graphs": {
"chat_agent": "my_project.agents:make_graph",
}
}
```
## ServerRuntime reference
Your factory function receives a `ServerRuntime` instance with the following attributes:
| Attribute | Type | Description |
| ---------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `access_context` | `str` | Why the factory was called: `"threads.create_run"`, `"threads.update"`, `"threads.read"`, or `"assistants.read"`. |
| `user` | `BaseUser \| None` | The authenticated user, or `None` if no [custom auth](/langsmith/custom-auth) is configured. |
| `store` | `BaseStore` | The store instance for persistence and memory. |
**Methods:**
| Method | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ensure_user()` | Returns the authenticated user. Raises `PermissionError` if no user is provided. |
| `execution_runtime` | Returns the execution runtime when `access_context` is `"threads.create_run"`, or `None` otherwise. Use this to conditionally set up expensive resources only during execution. |
### Access contexts
The server calls your factory in several contexts beyond just executing runs. In all contexts, the returned graph should have the **same topology** (nodes, edges, state schema). A mismatched topology in write contexts (`threads.create_run`, `threads.update`) can cause incorrect state updates. In read contexts (`threads.read`, `assistants.read`), a mismatch affects reported pending tasks, schemas, and visualizations but won't corrupt data. Use `execution_runtime` to conditionally set up expensive resources without changing the graph structure.
| Context | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------- |
| `threads.create_run` | Full graph execution. `execution_runtime` is available. |
| `threads.update` | State update via `aupdate_state`. Does not execute node functions, but it can change the pending tasks. |
| `threads.read` | State reads via `aget_state` / `aget_state_history`. |
| `assistants.read` | Schema and graph introspection for visualization, MCP, A2A, etc. |
See more info on LangGraph API configuration file [here](/langsmith/cli#configuration-file).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/graph-rebuild.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith docs
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/home
**LangSmith is a framework-agnostic platform for developing, debugging, and deploying AI agents and LLM applications.**
It helps you trace requests, evaluate outputs, test prompts, and manage deployments in one place. LangSmith works with any agent stack, whether you’re using an existing framework or building your own. Prototype locally, then move to production with integrated monitoring and evaluation to build more reliable AI agents.
## Get started
Sign up at [smith.langchain.com](https://smith.langchain.com) (no credit card required).
You can log in with **Google**, **GitHub**, or **email**.
Go to your [Settings page](https://smith.langchain.com/settings) → **API Keys** → **Create API Key**.
Copy the key and save it securely.
LangSmith works with many frameworks and providers including OpenAI, Anthropic, CrewAI, Vercel AI SDK, Pydantic AI, and more.
Browse [available integrations](/langsmith/integrations) to connect your stack.
Once your account and API key are ready, choose a quickstart to begin building with LangSmith:
Gain visibility into every step your application takes to debug faster and improve reliability.
Measure and track quality over time to ensure your AI applications are consistent and trustworthy.
Deploy your agents as Agent Servers, ready to scale in production.
### More ways to build
Use LangSmith in managed cloud, in a self-hosted environment, or hybrid to match your infrastructure and compliance needs.
Use a visual interface to design, test, and refine applications end-to-end.
Iterate on prompts with built-in versioning and collaboration to ship improvements faster.
Design and deploy AI agents visually with a no-code interface—perfect for rapid prototyping and getting started without writing code.
LangSmith meets the highest standards of data security and privacy with HIPAA, SOC 2 Type 2, and GDPR compliance. For more information, see the [Trust Center](https://trust.langchain.com/).
## Workflow
LangSmith combines observability, evaluation, deployment, and platform setup in one integrated workflow—from local development to production.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/home.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Time travel using the server API
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/human-in-the-loop-time-travel
LangGraph provides the [**time travel**](/oss/python/langgraph/use-time-travel) functionality to resume execution from a prior checkpoint, either replaying the same state or modifying it to explore alternatives. In all cases, resuming past execution produces a new fork in the history.
To time travel using the LangSmith Deployment API (via the LangGraph SDK):
1. **Run the graph** with initial inputs using [LangGraph SDK](/langsmith/langgraph-python-sdk)'s [client.runs.wait](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.client.RunsClient.wait) or [client.runs.stream](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.client.RunsClient.stream) APIs.
2. **Identify a checkpoint in an existing thread**: Use [client.threads.get\_history](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.client.ThreadsClient.get_history) method to retrieve the execution history for a specific `thread_id` and locate the desired `checkpoint_id`.
Alternatively, set a [breakpoint](/oss/python/langgraph/interrupts) before the node(s) where you want execution to pause. You can then find the most recent checkpoint recorded up to that breakpoint.
3. **(Optional) modify the graph state**: Use the [client.threads.update\_state](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.client.ThreadsClient.update_state) method to modify the graph’s state at the checkpoint and resume execution from alternative state.
4. **Resume execution from the checkpoint**: Use the [client.runs.wait](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.client.RunsClient.wait) or [client.runs.stream](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.client.RunsClient.stream) APIs with an input of `None` and the appropriate `thread_id` and `checkpoint_id`.
## Use time travel in a workflow
```python theme={null}
from typing_extensions import TypedDict, NotRequired
from langgraph.graph import StateGraph, START, END
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import InMemorySaver
class State(TypedDict):
topic: NotRequired[str]
joke: NotRequired[str]
model = init_chat_model(
"claude-sonnet-4-5-20250929",
temperature=0,
)
def generate_topic(state: State):
"""LLM call to generate a topic for the joke"""
msg = model.invoke("Give me a funny topic for a joke")
return {"topic": msg.content}
def write_joke(state: State):
"""LLM call to write a joke based on the topic"""
msg = model.invoke(f"Write a short joke about {state['topic']}")
return {"joke": msg.content}
# Build workflow
builder = StateGraph(State)
# Add nodes
builder.add_node("generate_topic", generate_topic)
builder.add_node("write_joke", write_joke)
# Add edges to connect nodes
builder.add_edge(START, "generate_topic")
builder.add_edge("generate_topic", "write_joke")
# Compile
graph = builder.compile()
```
### 1. Run the graph
```python theme={null}
from langgraph_sdk import get_client
client = get_client(url=)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
# create a thread
thread = await client.threads.create()
thread_id = thread["thread_id"]
# Run the graph
result = await client.runs.wait(
thread_id,
assistant_id,
input={}
)
```
```js theme={null}
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: });
// Using the graph deployed with the name "agent"
const assistantID = "agent";
// create a thread
const thread = await client.threads.create();
const threadID = thread["thread_id"];
// Run the graph
const result = await client.runs.wait(
threadID,
assistantID,
{ input: {}}
);
```
Create a thread:
```bash theme={null}
curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Run the graph:
```bash theme={null}
curl --request POST \
--url /threads//runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {}
}"
```
### 2. Identify a checkpoint
```python theme={null}
# The states are returned in reverse chronological order.
states = await client.threads.get_history(thread_id)
selected_state = states[1]
print(selected_state)
```
```js theme={null}
// The states are returned in reverse chronological order.
const states = await client.threads.getHistory(threadID);
const selectedState = states[1];
console.log(selectedState);
```
```bash theme={null}
curl --request GET \
--url /threads//history \
--header 'Content-Type: application/json'
```
### 3. Update the state
[`update_state`](https://reference.langchain.com/python/langgraph/graphs/#langgraph.graph.state.CompiledStateGraph.update_state) will create a new checkpoint. The new checkpoint will be associated with the same thread, but a new checkpoint ID.
```python {highlight={4}} theme={null}
new_config = await client.threads.update_state(
thread_id,
{"topic": "chickens"},
checkpoint_id=selected_state["checkpoint_id"]
)
print(new_config)
```
```js theme={null}
const newConfig = await client.threads.updateState(
threadID,
{
values: { "topic": "chickens" },
checkpointId: selectedState["checkpoint_id"]
}
);
console.log(newConfig);
```
```bash theme={null}
curl --request POST \
--url /threads//state \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"checkpoint_id\": ,
\"values\": {\"topic\": \"chickens\"}
}"
```
### 4. Resume execution from the checkpoint
```python {highlight={4,5}} theme={null}
await client.runs.wait(
thread_id,
assistant_id,
input=None,
checkpoint_id=new_config["checkpoint_id"]
)
```
```javascript {highlight={5,6}} theme={null}
await client.runs.wait(
threadID,
assistantID,
{
input: null,
checkpointId: newConfig["checkpoint_id"]
}
);
```
```bash theme={null}
curl --request POST \
--url /threads//runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"checkpoint_id\":
}"
```
## Learn more
* [**LangGraph time travel guide**](/oss/python/langgraph/use-time-travel): learn more about using time travel in LangGraph.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/human-in-the-loop-time-travel.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Hybrid
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/hybrid
**Important**
The hybrid option requires an [Enterprise](https://langchain.com/pricing) plan.
The **hybrid** model splits LangSmith infrastructure between LangChain's cloud and yours:
* **Control plane** (LangSmith UI, APIs, and orchestration) runs in LangChain's cloud, managed by LangChain.
* **Data plane** (your Agent Servers and agent workloads) runs in your cloud, managed by you.
This combines the convenience of a managed interface with the flexibility of running workloads in your own environment.
Learn more about the [control plane](/langsmith/control-plane), [data plane](/langsmith/data-plane), and [Agent Server](/langsmith/agent-server) architecture concepts.
| Component | Responsibilities | Where it runs | Who manages it |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -------------- |
| Control plane |
UI for creating deployments and revisions
APIs for managing deployments
Observability data storage
| LangChain's cloud | LangChain |
| Data plane |
Operator/listener to reconcile deployments
Agent Servers (agents/graphs)
Backing services (Postgres, Redis, etc.)
| Your cloud | You |
When running LangSmith in a hybrid model, you authenticate with a [LangSmith API key](/langsmith/create-account-api-key).
### Workflow
1. Use the `langgraph-cli` or [Studio](/langsmith/studio) to test your graph locally.
2. Build a Docker image using the `langgraph build` command.
3. Deploy your Agent Server from the [control plane UI](/langsmith/control-plane#control-plane-ui).
Supported Compute Platforms: [Kubernetes](https://kubernetes.io/).
For setup, refer to the [Hybrid setup guide](/langsmith/deploy-hybrid).
### Architecture
### Compute platforms
* **Kubernetes**: Hybrid supports running the data plane on any Kubernetes cluster.
For setup in Kubernetes, refer to the [Hybrid setup guide](/langsmith/deploy-hybrid)
### Egress to LangSmith and the control plane
In the hybrid deployment model, your self-hosted data plane will send network requests to the control plane to poll for changes that need to be implemented in the data plane. Traces from data plane deployments also get sent to the LangSmith instance integrated with the control plane. This traffic to the control plane is encrypted, over HTTPS. The data plane authenticates with the control plane with a LangSmith API key.
In order to enable this egress, you may need to update internal firewall rules or cloud resources (such as Security Groups) to [allow certain IP addresses](/langsmith/cloud#ingress-into-langchain-saas).
AWS/Azure PrivateLink or GCP Private Service Connect is currently not supported. This traffic will go over the internet.
## Listeners
In the hybrid option, one or more ["listener" applications](/langsmith/data-plane#”listener”-application) can run depending on how your LangSmith workspaces and Kubernetes clusters are organized.
### Kubernetes cluster organization
* One or more listeners can run in a Kubernetes cluster.
* A listener can deploy into one or more namespaces in that cluster.
* Multiple listeners cannot deploy to the same namespace.
* Cluster owners are responsible for planning listener layout and Agent Server deployments.
### LangSmith workspace organization
* A workspace can be associated with one or more listeners.
* A listener can only be associated with one workspace. LangSmith workspace to listener is a one-to-many relationship.
* A workspace can only deploy to Kubernetes clusters where all of its listeners are deployed.
## Use cases
Here are some common listener configurations (not strict requirements):
### Each LangSmith workspace → separate Kubernetes cluster
* Cluster `alpha` runs workspace `A`
* Cluster `beta` runs workspace `B`
### One cluster, one namespace per workspace
* Cluster `alpha`, namespace `1` runs workspace `A`
* Cluster `alpha`, namespace `2` runs workspace `B`
### Separate clusters, with shared “dev” cluster
* Cluster `alpha` runs workspace `A`
* Cluster `beta` runs workspace `B`
* Cluster `dev` runs workspaces `A` and `B`
* Both workspaces have two listeners; cluster `dev` has two listener deployments
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/hybrid.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Improve LLM-as-judge evaluators using human feedback
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/improve-judge-evaluator-feedback
Before working through this page, it might be helpful to read the following:
* [Evaluation concepts](/langsmith/evaluation-concepts#evaluators)
* [Creating LLM-as-a-judge evaluators](/langsmith/llm-as-judge)
Reliable [*LLM-as-a-judge evaluators*](/langsmith/evaluation-concepts#llm-as-judge) are critical for making informed decisions about your AI applications (e.g., prompt, model, architecture changes). Defining the evaluator prompt correctly can be difficult, but it directly affects the trustworthiness of your evaluations.
This guide describes how to align your LLM-as-a-judge evaluator using human feedback to improve your evaluator's quality and help you build reliable AI applications.
## How it works
LangSmith's **Align Evaluator** feature has a series of steps that help you align your LLM-as-a-judge evaluator with human expert feedback. You can use this feature to align evaluators that run on a dataset for [offline evaluations](/langsmith/evaluation-concepts#offline-evaluation) or for [online evaluations](/langsmith/evaluation-concepts#online-evaluation). In either case, the steps are similar:
1. **Select experiments or runs** that contain outputs from your application.
2. Add the selected experiments or runs to an **annotation queue** where a human expert can label the data.
3. **Test your LLM-as-a-judge evaluator prompt** against the labeled examples. Check the cases where your evaluator result is not aligned with the labeled data. This indicates areas where your evaluator prompt needs improvement.
4. **Refine and repeat** to improve evaluator alignment. Update your LLM-as-a-judge evaluator prompt and test again.
## Prerequisites
You'll need the following before starting this guide for [offline evaluations](#offline-evaluations) or [online evaluations](#online-evaluations):
### Offline evaluations
* A [dataset](/langsmith/evaluation-concepts#datasets) with at least one [experiment](/langsmith/evaluation-concepts#experiment).
* You'll need to upload or create datasets via the [SDK](/langsmith/manage-datasets-programmatically#create-a-dataset) or the [UI](/langsmith/manage-datasets-in-application#set-up-your-dataset) and run an experiment via the [SDK](/langsmith/evaluate-llm-application#run-the-evaluation) or the [Playground](/langsmith/run-evaluation-from-prompt-playground#5-run-your-evaluation).
### Online evaluations
* An application that’s already sending traces to LangSmith.
* Configure this with one of the [tracing integrations](/langsmith/observability-concepts#integrations) to start.
## Getting started
You can enter the alignment flow for both new and existing evaluators in datasets and tracing projects.
| | Dataset Evaluators | Tracing Project Evaluators |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Create an aligned evaluator from scratch** | 1. **Datasets & Experiments** and select your dataset 2. Click **+ Evaluator** > **Create from labeled data** 3. Enter a descriptive feedback key name (e.g. `correctness`, `hallucination`) | 1. **Projects** and select your project 2. Click **+ New** > **Evaluator** > **Create from labeled data** 3. Enter a descriptive feedback‑key name (e.g. `correctness`, `hallucination`) |
| **Align an existing evaluator** | 1. **Datasets & Experiments** > select your dataset > **Evaluators** tab 2. In the **Align Evaluator with experiment data** box, click **Select Experiments** | 1. **Projects** > select your project > **Evaluators** tab 2. In the **Align Evaluator with experiment data** box, click **Select Experiments** |
## 1. Select experiments or runs
Select one or more experiments (or runs) to send for human labeling. This will add runs to an [annotation queue](/langsmith/evaluation-concepts#annotation-queues).
To add any new experiments/runs to an existing annotation queue, head to the **Evaluators** tab, select the evaluator you are aligning and click **Add to Queue.**
Datasets should be representative of inputs and outputs you expect to see in production.
While you don’t need to cover every possible scenario, it’s important to include examples across the full range of expected use cases. For example, if you're building a sports bot that answers questions about baseball, basketball, and football, your dataset should include at least one labeled example from each sport.
## 2. Label examples
Label examples in the annotation queue by adding a feedback score. Once you've labeled an example, click **Add to Reference Dataset**.
If you have a large number of examples in your experiments, you don't need to label every example to get started. We recommend starting with at least 20 examples, you can always add more later. We recommend that the examples that you label are diverse (balanced in both 0 and 1 labels) to ensure that you're building a well rounded evaluator prompt.
## 3. Test your evaluator prompt against the labeled examples
Once you have labeled examples, the next step is iterating on your evaluator prompt to mimic the labeled data as well as possible. This iteration is done in the **Evaluator Playground**.
To go to the evaluator playground: Click the **View evaluator** button on the top right of the evaluator queue. This will take you to the detail page of the evaluator you are aligning. Click the **Evaluator Playground** button to access the playground.
In the evaluator playground you can create or edit your evaluator prompt and click **Start Alignment** to run it over the set of labeled examples that you created in Step 2. After running your evaluator, you'll see how its generated scores compare to your human labels. The alignment score is the percentage of examples where the evaluator's judgment matches that of the human expert.
## 4. Repeat to improve evaluator alignment
Iterate by updating your prompt and testing again to improve evaluator alignment.
Updates to your evaluator prompt are **not saved by default**. We recommend saving your evaluator prompt regularly, and especially after you see your alignment score improve.
The evaluator playground will show the alignment score for the most recently saved version of your evaluator prompt for comparison when you're iterating on your prompt.
Improving the alignment score of your evaluator isn't an exact science but there are a few strategies that are helpful in increasing the alignment score.
### Tips for improving evaluator alignment
**1. Investigate misaligned examples**
Digging into misaligned examples and trying to group them into common failure modes is a great first step for improving your evaluator alignment.
Once you have identified the common failure modes, add instructions to your evaluator prompt so the LLM knows about them. For example, you could explain that "MFA stands for "multi-factor authentication" if you notice it not understanding that specific acronym. Or you could tell it that "a good response will always contain at least 3 potential hotels to book" if it is confused on what good/bad means in your evaluator's context.
**2. Inspect the reasoning behind the LLM score**
To understand why the LLM scored an example the way it did, you can enable reasoning for your LLM-as-a-judge evaluator. Reasoning is helpful to understand the LLM's thought process and can help you identify common failure modes to incorporate into your evaluator prompt as well..
In order to see the reasoning in the evaluator playground, hover over the LLM score.
This will show the reasoning behind the LLM's score in the evaluator playground.
**3. Add more labeled examples and validate performance**
To avoid overfitting to the labeled examples, it's important to add more labeled examples and test performance, especially if you started off with a small number of examples.
## Video guide
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/improve-judge-evaluator-feedback.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Dynamic few shot example selection
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/index-datasets-for-dynamic-few-shot-example-selection
The dynamic few-shot feature in datasets is deprecated and will be removed on February 28th, 2026.
Configure your datasets so that you can search for few shot examples based on an incoming request.
## Pre-conditions
1. Your dataset must use the KV store data type (we do not currently support chat model or LLM type datasets)
2. You must have an input schema defined for your dataset. See our docs on setting up schema validation [in our UI](/langsmith/manage-datasets-in-application#dataset-schema-validation) for details.
3. You must be on a paid team plan (e.g. Plus plan)
4. You must be on LangSmith cloud
## Index your dataset for few shot search
Navigate to the datasets UI, and click the new `Few-Shot search` tab. Hit the `Start sync` button, which will create a new index on your dataset to make it searchable.
By default, we sync to the latest version of your dataset. That means when new examples are added to your dataset, they will automatically be added to your index. This process runs every few minutes, so there should be a very short delay for indexing new examples. You can see whether your index is up to date under `Few-shot index` on the lefthand side of the screen in the next section.
## Test search quality in the few shot playground
Now that you have turned on indexing for your dataset, you will see the new few shot playground.
You can type in a sample input, and check which results would be returned by our search API.
Each result will have a score and a link to the example in the dataset. The scoring system works such that 0 is a completely random result, and higher scores are better. Results will be sorted in descending order according to score.
Search uses a BM25-like algorithm for keyword based similarity scores. The actual score is subject to change as we improve the search algorithm, so we recommend not relying on the scores themselves, as their meaning may evolve over time. They are simply used for convenience in vibe-testing outputs in the playground.
## Adding few shot search to your application
Click the `Get Code Snippet` button in the previous diagram, you'll be taken to a screen that has code snippets from our LangSmith SDK in different languages.
For code samples on using few shot search in LangChain python applications, please see our [how-to guide in the LangChain docs](https://python.langchain.com/v0.2/docs/how_to/example_selectors_langsmith/).
### Code snippets
Please ensure you are using the python SDK with version >= 1.101 or the typescript SDK with version >= 1.43
For copy and paste convenience, you can find the similar code snippets to the ones shown in the screenshot above here:
```python Python (Async) theme={null}
import langsmith as ls
# Copy this value from LangSmith UI
dataset_id = "1c5e9c95-dfd4-4dc5-a4b8-df7ea921c913"
async with ls.AsyncClient() as client:
examples = await client.similar_examples(
{"question": "knock knock"}, dataset_id=dataset_id, limit=1
)
print(examples[0].outputs) # {"output": "Few shots'll do the trick."}
```
```python Python theme={null}
from langsmith import Client
client = Client()
# Copy this value from LangSmith UI
dataset_id = "1c5e9c95-dfd4-4dc5-a4b8-df7ea921c913"
examples = client.similar_examples(
{"question": "knock knock"}, dataset_id=dataset_id, limit=1
)
print(examples[0].outputs)
# {"output": "Few shots'll do the trick."}
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
const client = new Client();
// Copy this value from LangSmith UI
const dataset_id = "1c5e9c95-dfd4-4dc5-a4b8-df7ea921c913";
const examples = await client.similarExamples({question: "knock knock"}, dataset_id, 1);
console.log(examples[0].outputs);
// {output: "Few shots'll do the trick."}
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/index-datasets-for-dynamic-few-shot-example-selection.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Discover errors and usage patterns with the Insights Agent
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/insights
The Insights Agent automatically analyzes your traces to detect usage patterns, common agent behaviors and failure modes — without requiring you to manually review thousands of traces.
Insights uses hierarchical categorization to make sense of your data and highlight actionable trends.
Insights is available for LangSmith Plus and Enterprise [plans](https://www.langchain.com/pricing) and is only available for LangSmith SaaS deployments.
## Prerequisites
* An OpenAI API key (generate one [on the OpenAI platform](https://platform.openai.com/account/api-keys)) or an Anthropic API key (generate one [on the Anthropic console](https://console.anthropic.com/settings/keys))
* Permissions to create rules in LangSmith (required to generate new Insights Reports)
* Permissions to view tracing projects LangSmith (required to view existing Insights Reports)
## Generate your first Insights report
#### From the [LangSmith UI](https://smith.langchain.com)
1. Navigate to **Tracing Projects** in the left-hand menu and select a tracing project.
2. Click **+New** in the top right corner then **New Insights Report** to generate new insights over the project.
3. Enter a name for your job.
4. Click the icon in the top right of the job creation pane to set your OpenAI (or Anthropic) API key as a [workspace secret](/langsmith/administration-overview#workspaces). If your workspace already has an OpenAI API key set, you can skip this step.
5. Answer the guided questions to focus your Insights Report on what you want to learn about your agent, then click **Run job**.
Toggle to Manual mode to try [prebuilt configs](#using-a-prebuilt-config) for common use cases or [build your own](#building-a-config-from-scratch).
This will kick off a background Insights Report. Reports can take up to 30 minutes to complete.
#### From the [LangSmith SDK](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client)
You can generate Insights Reports over data stored outside LangSmith using the Python SDK. This allows you to analyze chat histories from your production systems, logs, or other sources.
When you call `generate_insights()`, the SDK will:
1. Upload your chat histories as traces to a new LangSmith project
2. Generate an Insights Report over those uploaded traces
3. Return a link to your results in the LangSmith UI
```python Python theme={null}
import os
from langsmith import Client
client = Client()
chat_histories = [
[
{"role": "user", "content": "how are you"},
{"role": "assistant", "content": "good!"},
],
[
{"role": "user", "content": "do you like art"},
{"role": "assistant", "content": "only Tarkovsky"},
],
]
report = client.generate_insights(
chat_histories=chat_histories,
name="Customer Support Topics - March 2024",
instructions="What are the main topics and questions users are asking about?",
openai_api_key=os.environ["OPENAI_API_KEY"], # optional if already set as workspace secret
)
# client.poll_insights(report=report)
```
Generating insights over 1,000 threads typically costs \$1.00-\$2.00 with OpenAI models and \$3.00-\$4.00 with current Anthropic models. The cost scales with the number of threads sampled and the size of each thread.
## Understand the results
Once your job has completed, you can navigate to the **Insights** tab where you'll see a table of Insights Report. Each Report contains insights generated over a specific sample of traces from the tracing project.
Click into your job to see traces organized into a set of auto-generated categories.
You can drill down through categories and subcategories to view the underlying traces, feedback, and run statistics.
### Executive summary
At the top of each report, you'll find an executive summary that surfaces the most important patterns discovered in your traces. This includes:
* Key findings with percentages showing how often each pattern appears.
* Clickable references (e.g., #1, #2, #3) to traces the agent identified as exceptionally relevant to your question.
### Top-level categories
Your traces are automatically grouped into top-level categories that represent the broadest patterns in your data.
The distribution bars show how frequently each pattern occurs, making it easy to spot behaviors that happen more or less than expected.
Each category has a brief description and displays aggregated metrics over the traces it contains, including:
* Typical trace stats (like error rates, latency, cost)
* Feedback scores from your evaluators
* [Attributes](#attributes) extracted as part of the job
### Subcategories
Clicking on any category shows a breakdown into subcategories, which gives you a more granular understanding of interaction patterns in that category of traces.
In the [Chat Langchain](https://chat.langchain.com) example pictured above, under "Data & Retrieval" there are subcategories like "Vector Stores" and "Data Ingestion".
### Individual traces
You can view the traces assigned to each category or subcategory by clicking through to see the traces table. From there, you can click into any trace to see the full conversation details.
## Configure a job
You can create an Insights Report three ways. Start with the auto-generated flow to spin up a baseline, then iterate with saved or manual configs as you refine.
### Autogenerating a config
1. Open **New Insights** and make sure the **Auto** toggle is active.
2. Answer the natural-language questions about your agent's purpose, what you want to learn, and how traces are structured. Insights will translate your answers into a draft config (job name, summary prompt, attributes, and sampling defaults).
3. Choose a provider, then click **Generate config** to preview or **Run job** to launch immediately.
**Providing useful context**
For best results, write a sentence or two for each prompt that gives the agent the context it needs—what you're trying to learn, which signals or fields matter most, and anything you already know isn't useful. The clearer you are about what your agent does and how its traces are structured, the more the Insights Agent can group examples in a way that's specific, actionable, and aligned with how you reason about your data.
**Describing your traces**
Explain how your data is organized—are these single runs or multi-turn conversations? Which inputs and outputs contain the key information? This helps the Insights Agent generate summary prompts and attributes that focus on what matters. You can also directly specify variables from the [summary prompt](#summary-prompt) section if needed.
### Choose a model provider
You can select either OpenAI or Anthropic models to power the agent. You must have the corresponding [workspace secret](/langsmith/administration-overview#workspaces) set for whichever provider you choose (`OPENAI_API_KEY` or `ANTHROPIC_API_KEY`).
Note that using current Anthropic models costs \~3x as much as using OpenAI models.
### Using a prebuilt config
Use the **Saved configurations** dropdown to load presets for common jobs like **Usage Patterns** or **Error Analysis**. Run them directly for a fast start, or adjust filters, prompts, and providers before saving your customized version. To learn more about what you can customize, read the section below.
### Building a config from scratch
Building your own config helps when you need more control—for example, predefining categories you want your data to be grouped into or targeting traces that match specific feedback scores and filters.
#### Select traces
* **Sample size**: The maximum number of traces to analyze. Currently capped at 1,000
* **Time range**: Traces are sampled from this time range
* **Filters**: Additional trace filters. As you adjust filters, you'll see how many traces match your criteria
#### Categories
By default, top-level categories are automatically generated bottom-up from the underlying traces.
In some instances, you know specific categories you're interested in upfront and want the job to bucket traces into those predefined categories.
The **Categories** section of the config lets you do this by enumerating the names and descriptions of the top-level categories you want to be used.
Subcategories are still auto-generated by the algorithm within the predefined top-level categories.
#### Summary prompt
The first step of the job is to create a brief summary of every trace — it is these summaries that are then categorized.
Extracting the right information in the summary is essential for getting useful categories.
The prompt used to generate these summaries can be edited.
The two things to think about when editing the prompt are:
* Summarization instructions: Any information that isn't in the trace summary won't affect the categories that get generated, so make sure to provide clear instructions on what information is important to extract from each trace.
* Trace content: Use mustache formatting to specify which parts of each trace are passed to the summarizer. Large traces with lots of inputs and outputs can be expensive and noisy. Reducing the prompt to only include the most relevant parts of the trace can improve your results.
The Insights Agent analyzes [threads](https://docs.langchain.com/langsmith/threads) - groups of related traces that represent multi-turn conversations. You must specify what parts of the thread to send to the summarizer using at least one of these template variables:
| Variable | Best for | Example |
| -------- | ----------------------------------------------------------------------- | -------------------------------------------------- |
| `run.*` | Access data from the most recent root run (i.e. final turn) in a thread | `{{run.inputs}}` `{{run.outputs}}` `{{run.error}}` |
You can also access nested fields using dot notation. For example, the prompt `"Summarize this: {{run.inputs.foo.bar}}"` will include only the "bar" value within the "foo" value of the last run's inputs.
#### Attributes
Along with a summary, you can define additional categorical, numerical, and boolean attributes to be extracted from each trace.
These attributes will influence the categorization step — traces with similar attribute values will tend to be categorized together.
You can also see aggregations of these attributes per category.
As an example, you might want to extract the attribute `user_satisfied: boolean` from each trace to steer the algorithm towards categories that split up positive and negative user experiences, and to see the average user satisfaction per category.
#### Filter attributes
You can use the `filter_by` parameter on boolean attributes to pre-filter traces before generating insights. When enabled, only traces where the attribute evaluates to `true` are included in the analysis.
This is useful when you want to focus your Insights Report on a specific subset of traces—for example, only analyzing errors, only examining English-language conversations, or only including traces that meet certain quality criteria.
**How it works:**
* Add `"filter_by": true` to any boolean attribute when creating a config for the Insights Agent
* The LLM evaluates each trace against the attribute description during summarization
* Traces where the attribute is `false` or missing are excluded before insights are generated
## Schedule Insights Reports
Schedule Insights reports to run automatically on a recurring basis. When creating or editing a configuration, use the **Schedule** section to choose:
* **Daily** - Runs every day at 8:00 UTC
* **Weekly on Monday** - Runs every Monday at 8:00 UTC
* **Custom** - Enter your own cron expression (in UTC)
Each scheduled run generates a new report using your saved configuration. Time ranges are computed dynamically—for example, "last 24 hours" always analyzes the most recent 24-hour window at execution time.
## Save your config
You can optionally save configs for future reuse using the 'save as' button.
This is especially useful if you want to compare Insights Reports over time to identify changes in user and agent behavior.
Select from previously saved configs in the dropdown in the top-left corner of the pane when creating a new Insights Report.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/insights.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Enable Insights
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/insights-self-hosted
[Insights](/langsmith/insights) provides AI-powered analysis of your traces and application data within LangSmith. This page explains how to enable Insights on a [self-hosted LangSmith instance](/langsmith/self-hosted).
Self-hosted LangSmith is an add-on to the Enterprise plan. For more details, refer to [Pricing](https://www.langchain.com/pricing). [Contact our sales team](https://www.langchain.com/contact-sales) if you want to get a license key to trial LangSmith in your environment.
## Prerequisites
Before enabling Insights, you must complete the following setup steps:
1. Install the base LangSmith platform:
* [Install on Kubernetes](/langsmith/kubernetes).
* [Install on Docker](/langsmith/docker).
2. [Enable LangSmith Deployment](/langsmith/deploy-self-hosted-full-platform) (agent deployment capabilities).
## Components
Insights consists of two main components:
* `agentBootstrap`: Job that deploys the LangSmith Deployment (agent) needed for Insights.
* `insightsAgent`: The main agent that will handle insights generation.
## Enable Insights
To enable Insights, add the following to your `values.yaml`:
```yaml theme={null}
backend:
agentBootstrap:
enabled: true
config:
insights:
enabled: true
encryptionKey: ""
```
### Generate an encryption key
Insights requires a Fernet encryption key to securely store secrets. Generate one using Python:
```bash theme={null}
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
```
You can store the encryption key in a predefined Kubernetes secret using the `insights_encryption_key` parameter. See [Use an existing secret](/langsmith/self-host-using-an-existing-secret#parameters) for details.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/insights-self-hosted.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Integrations
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/integrations
[LangSmith](https://smith.langchain.com/) provides integrations with for a growing set of popular [LLM providers](#llm-providers) and [agent frameworks](#agent-frameworks) as well as [Deep Agents](/oss/python/deepagents/overview), [LangChain](/oss/python/langchain/overview), and [LangGraph](/oss/python/langgraph/overview). For setup and usage, refer to the guides listed on this page.
## LLM providers
**Using Langchain?** LangChain provides a unified interface to 100+ LLM providers, which allows you to switch between models by setting environment variables. [Initialize a model](/oss/python/langchain/models#initialize-a-model) and LangSmith will automatically trace your application.
## Agent frameworks
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/integrations.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Interrupt concurrent
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/interrupt-concurrent
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](/langsmith/double-texting).
The guide covers the `interrupt` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option does not delete the first run, but rather keeps it in the database but sets its status to `interrupted`. Below is a quick example of using the `interrupt` option.
## Setup
First, we will define a quick helper function for printing out JS and CURL model outputs (you can skip this if using Python):
```js theme={null}
function prettyPrint(m) {
const padded = " " + m['type'] + " ";
const sepLen = Math.floor((80 - padded.length) / 2);
const sep = "=".repeat(sepLen);
const secondSep = sep + (padded.length % 2 ? "=" : "");
console.log(`${sep}${padded}${secondSep}`);
console.log("\n\n");
console.log(m.content);
}
```
```bash theme={null}
# PLACE THIS IN A FILE CALLED pretty_print.sh
pretty_print() {
local type="$1"
local content="$2"
local padded=" $type "
local total_width=80
local sep_len=$(( (total_width - ${#padded}) / 2 ))
local sep=$(printf '=%.0s' $(eval "echo {1.."${sep_len}"}"))
local second_sep=$sep
if (( (total_width - ${#padded}) % 2 )); then
second_sep="${second_sep}="
fi
echo "${sep}${padded}${second_sep}"
echo
echo "$content"
}
```
Now, let's import our required packages and instantiate our client, assistant, and thread.
```python theme={null}
import asyncio
from langchain_core.messages import convert_to_messages
from langgraph_sdk import get_client
client = get_client(url=)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
thread = await client.threads.create()
```
```js theme={null}
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
const thread = await client.threads.create();
```
```bash theme={null}
curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}'
```
## Create runs
Now we can start our two runs and join the second one until it has completed:
```python theme={null}
# the first run will be interrupted
interrupted_run = await client.runs.create(
thread["thread_id"],
assistant_id,
input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]},
)
# sleep a bit to get partial outputs from the first run
await asyncio.sleep(2)
run = await client.runs.create(
thread["thread_id"],
assistant_id,
input={"messages": [{"role": "user", "content": "what's the weather in nyc?"}]},
multitask_strategy="interrupt",
)
# wait until the second run completes
await client.runs.join(thread["thread_id"], run["run_id"])
```
```js theme={null}
// the first run will be interrupted
let interruptedRun = await client.runs.create(
thread["thread_id"],
assistantId,
{ input: { messages: [{ role: "human", content: "what's the weather in sf?" }] } }
);
// sleep a bit to get partial outputs from the first run
await new Promise(resolve => setTimeout(resolve, 2000));
let run = await client.runs.create(
thread["thread_id"],
assistantId,
{
input: { messages: [{ role: "human", content: "what's the weather in nyc?" }] },
multitaskStrategy: "interrupt"
}
);
// wait until the second run completes
await client.runs.join(thread["thread_id"], run["run_id"]);
```
```bash theme={null}
curl --request POST \
--url >/threads//runs \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in sf?\"}]},
}" && sleep 2 && curl --request POST \
--url >/threads//runs \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in nyc?\"}]},
\"multitask_strategy\": \"interrupt\"
}" && curl --request GET \
--url /threads//runs//join
```
## View run results
We can see that the thread has partial data from the first run + data from the second run
```python theme={null}
state = await client.threads.get_state(thread["thread_id"])
for m in convert_to_messages(state["values"]["messages"]):
m.pretty_print()
```
```js theme={null}
const state = await client.threads.getState(thread["thread_id"]);
for (const m of state['values']['messages']) {
prettyPrint(m);
}
```
```bash theme={null}
source pretty_print.sh && curl --request GET \
--url /threads//state | \
jq -c '.values.messages[]' | while read -r element; do
type=$(echo "$element" | jq -r '.type')
content=$(echo "$element" | jq -r '.content | if type == "array" then tostring else . end')
pretty_print "$type" "$content"
done
```
Output:
```
================================ Human Message =================================
what's the weather in sf?
================================== Ai Message ==================================
[{'id': 'toolu_01MjNtVJwEcpujRGrf3x6Pih', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
Tool Calls:
tavily_search_results_json (toolu_01MjNtVJwEcpujRGrf3x6Pih)
Call ID: toolu_01MjNtVJwEcpujRGrf3x6Pih
Args:
query: weather in san francisco
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://www.wunderground.com/hourly/us/ca/san-francisco/KCASANFR2002/date/2024-6-18", "content": "High 64F. Winds W at 10 to 20 mph. A few clouds from time to time. Low 49F. Winds W at 10 to 20 mph. Temp. San Francisco Weather Forecasts. Weather Underground provides local & long-range weather ..."}]
================================ Human Message =================================
what's the weather in nyc?
================================== Ai Message ==================================
[{'id': 'toolu_01KtE1m1ifPLQAx4fQLyZL9Q', 'input': {'query': 'weather in new york city'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
Tool Calls:
tavily_search_results_json (toolu_01KtE1m1ifPLQAx4fQLyZL9Q)
Call ID: toolu_01KtE1m1ifPLQAx4fQLyZL9Q
Args:
query: weather in new york city
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://www.accuweather.com/en/us/new-york/10021/june-weather/349727", "content": "Get the monthly weather forecast for New York, NY, including daily high/low, historical averages, to help you plan ahead."}]
================================== Ai Message ==================================
The search results provide weather forecasts and information for New York City. Based on the top result from AccuWeather, here are some key details about the weather in NYC:
* This is a monthly weather forecast for New York City for the month of June.
* It includes daily high and low temperatures to help plan ahead.
* Historical averages for June in NYC are also provided as a reference point.
* More detailed daily or hourly forecasts with precipitation chances, humidity, wind, etc. can be found by visiting the AccuWeather page.
So in summary, the search provides a convenient overview of the expected weather conditions in New York City over the next month to give you an idea of what to prepare for if traveling or making plans there. Let me know if you need any other details!
```
Verify that the original, interrupted run was interrupted
```python theme={null}
print((await client.runs.get(thread["thread_id"], interrupted_run["run_id"]))["status"])
```
```js theme={null}
console.log((await client.runs.get(thread['thread_id'], interruptedRun["run_id"]))["status"])
```
Output:
```
'interrupted'
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/interrupt-concurrent.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Self-host LangSmith on Kubernetes
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/kubernetes
Self-hosting LangSmith is an add-on to the Enterprise Plan designed for our largest, most security-conscious customers. See our [pricing page](https://www.langchain.com/pricing) for more detail, and [contact our sales team](https://www.langchain.com/contact-sales) if you want to get a license key to trial LangSmith in your environment.
This page describes how to set up **LangSmith** (observability, tracing, and evaluation) in a Kubernetes cluster. You'll use Helm to install LangSmith and its dependencies.
After completing this page, you'll have:
* **LangSmith UI and APIs**: for [observability](/langsmith/observability), tracing, and [evaluation](/langsmith/evaluation).
* **Backend services**: (queue, playground, ACE).
* **Datastores**: (PostgreSQL, Redis, ClickHouse, optional blob storage).
For [agent deployment](/langsmith/deployments): To add deployment capabilities, complete this guide first, then follow [Enable LangSmith Deployment](/langsmith/deploy-self-hosted-full-platform).
LangChain has successfully tested LangSmith on the following Kubernetes distributions:
* Google Kubernetes Engine (GKE)
* Amazon Elastic Kubernetes Service (EKS): For architecture patterns and best practices, refer to [self-hosting on AWS](/langsmith/aws-self-hosted).
* Azure Kubernetes Service (AKS): For architecture patterns and best practices, refer to [self-hosting on AKS](/langsmith/azure-self-hosted).
* OpenShift (4.14+)
* Minikube and Kind (for development purposes)
LangChain provides Terraform modules to help provision infrastructure for LangSmith. These modules can quickly set up Kubernetes clusters, storage, and networking for your deployment.
Available modules:
* [AWS Terraform modules](https://github.com/langchain-ai/terraform/tree/main/modules/aws)
* [Azure Terraform modules](https://github.com/langchain-ai/terraform/tree/main/modules/azure)
View the [full Terraform repository](https://github.com/langchain-ai/terraform) for documentation and additional resources.
## Prerequisites
Ensure you have the following tools/items ready. Some items are marked optional:
1. LangSmith License Key
1. You can get this from your LangChain representative. [Contact our sales team](https://www.langchain.com/contact-sales) for more information.
2. Api Key Salt
1. This is a secret key that you can generate. It should be a random string of characters.
2. You can generate this using the following command:
```bash theme={null}
openssl rand -base64 32
```
3. JWT Secret (Optional but used for basic auth)
1. This is a secret key that you can generate. It should be a random string of characters.
2. You can generate this using the following command:
```bash theme={null}
openssl rand -base64 32
```
### Databases
LangSmith uses a PostgreSQL database, a Redis cache, and a ClickHouse database to store traces. By default, these services are installed inside your Kubernetes cluster. However, we highly recommend using external databases instead. For PostgreSQL and Redis, the best option is your cloud provider’s managed services.
For more information, refer to the following setup guides for external services:
* [PostgreSQL](/langsmith/self-host-external-postgres)
* [Redis](/langsmith/self-host-external-redis)
* [ClickHouse](/langsmith/self-host-external-clickhouse)
### Kubernetes cluster requirements
1. You will need a working Kubernetes cluster that you can access via `kubectl`. Your cluster should have the following minimum requirements:
1. Recommended: At least 16 vCPUs, 64GB Memory available
* You may need to tune resource requests/limits for all of our different services based off of organization size/usage. Our recommendations can be found [here](/langsmith/self-host-scale).
* We recommend using a cluster autoscaler to handle scaling up/down of nodes based on resource usage.
* We recommend setting up the metrics server so that autoscaling can be turned on.
* If you are running Clickhouse in-cluster, you must have a node with at least 4 vCPUs and 16GB of memory **allocatable** as ClickHouse will request this amount of resources by default.
2. Valid Dynamic PV provisioner or PVs available on your cluster (required only if you are running databases in-cluster)
* To enable persistence, we will try to provision volumes for any database running in-cluster.
* If using PVs in your cluster, we highly recommend setting up backups in a production environment.
* **We strongly encourage using a storage class backed by SSDs for better performance. We recommend 7000 IOPS and 1000 MiB/s throughput.**
* On EKS, you may need to ensure you have the `ebs-csi-driver` installed and configured for dynamic provisioning. Refer to the [EBS CSI Driver documentation](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html) for more information.
You can verify this by running:
```bash theme={null}
kubectl get storageclass
```
The output should show at least one storage class with a provisioner that supports dynamic provisioning. For example:
```bash theme={null}
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
gp2 (default) ebs.csi.eks.amazonaws.com Delete WaitForFirstConsumer true 161d
```
We highly recommend using a storage class that supports volume expansion. This is because traces can potentially require a lot of disk space and your volumes may need to be resized over time.
Refer to the [Kubernetes documentation](https://kubernetes.io/do/langsmith/observability-concepts/storage/storage-classes/) for more information on storage classes.
2. Helm
1. To install `helm` refer to the [Helm documentation](https://helm.sh/docs/intro/install/)
3. Egress to `https://beacon.langchain.com` (if not running in offline mode)
1. LangSmith requires egress to `https://beacon.langchain.com` for license verification and usage reporting. This is required for LangSmith to function properly. You can find more information on egress requirements in the [Egress](/langsmith/self-host-egress) section.
## Configure your Helm charts:
1. Create a new file called `langsmith_config.yaml` with the configuration options from the previous step.
1. There are several configuration options that you can set in the `langsmith_config.yaml` file. You can find more information on specific configuration options in the [Configuration](/langsmith/self-hosted) section.
2. If you are new to Kubernetes or Helm, we’d recommend starting with one of the example configurations in the examples directory of the Helm Chart repository here: [LangSmith helm chart examples](https://github.com/langchain-ai/helm/tree/main/charts/langsmith/examples).
3. You can see a full list of configuration options in the `values.yaml` file in the Helm Chart repository here: [LangSmith Helm Chart](https://github.com/langchain-ai/helm/tree/main/charts/langsmith/values.yaml)
Only override the settings you need in `langsmith_config.yaml`; don’t copy the entire `values.yaml`.
Keeping your config minimal ensures you continue to inherit new defaults and upgrades from the Helm chart.
2. At a minimum, you will need to set the following configuration options (using basic auth):
```yaml theme={null}
config:
langsmithLicenseKey: ""
apiKeySalt: ""
authType: mixed
initialOrgAdminEmail: "admin@langchain.dev" # Change this to your admin email address
basicAuth:
enabled: true
initialOrgAdminPassword: "secure-password" # Must be at least 12 characters long and have at least one lowercase, uppercase, and symbol
jwtSecret: # A random string of characters used to sign JWT tokens for basic auth.
```
You will also need to specify connection details for any external databases you are using.
## Deploying to Kubernetes:
1. Verify that you can connect to your Kubernetes cluster(note: We highly suggest installing into an empty namespace)
1. Run `kubectl get pods`
Output should look something like:
```bash theme={null}
langsmith-eks-2vauP7wf 21:07:46 No resources found in default namespace.
```
If you are using a namespace other than the default namespace, you will need to specify the namespace in the `helm` and `kubectl` commands by using the `-n ` flag.
2. Ensure you have the LangChain Helm repo added. (skip this step if you are using local charts)
```bash theme={null}
helm repo add langchain https://langchain-ai.github.io/helm
```
3. Find the latest version of the chart. You can find the available versions in the [Helm Chart repository](https://github.com/langchain-ai/helm/releases).
* We generally recommend using the latest version.
* You can also run `helm search repo langchain/langsmith --versions` to see the available versions. The output will look something like this:
```
langchain/langsmith 0.13.0 0.13.1 Helm chart to deploy the langsmith application ...
langchain/langsmith 0.12.34 0.12.73 Helm chart to deploy the langsmith application ...
langchain/langsmith 0.12.33 0.12.72 Helm chart to deploy the langsmith application ...
langchain/langsmith 0.12.32 0.12.70 Helm chart to deploy the langsmith application ...
langchain/langsmith 0.12.31 0.12.69 Helm chart to deploy the langsmith application ...
```
4. Run `helm upgrade -i langsmith langchain/langsmith --values langsmith_config.yaml --version -n --wait --debug`
* Replace `` with the namespace you want to deploy LangSmith to.
* Replace `` with the version of LangSmith you want to install from the previous step. Most users should install the latest version available.
Once the `helm install` command runs and finishes successfully, you should see output similar to this:
```
NAME: langsmith
LAST DEPLOYED: Fri Sep 17 21:08:47 2021
NAMESPACE: langsmith
STATUS: deployed
REVISION: 1
TEST SUITE: None
```
This may take a few minutes to complete as it will create several Kubernetes resources and run several jobs to initialize the database and other services.
5. Run `kubectl get pods` Output should now look something like this (note the exact pod names may vary based on the version and configuration you used):
```
langsmith-ace-backend-98fbd468c-x9gjl 1/1 Running 0
langsmith-backend-84999bbcb7-dfhml 1/1 Running 0
langsmith-clickhouse-0 1/1 Running 0
langsmith-frontend-79bdcbccc6-r7pt7 1/1 Running 0
langsmith-ingest-queue-cbb67748-8rl8x 1/1 Running 0
langsmith-platform-backend-586bd9d97c-2g5mv 1/1 Running 0
langsmith-playground-859d44b46c-fjqjh 1/1 Running 0
langsmith-postgres-0 1/1 Running 0
langsmith-queue-7bd6cb8b9b-bmvxm 1/1 Running 0
langsmith-redis-0 1/1 Running 0
```
## Validate your deployment:
1. Run `kubectl get services`
Output should look something like:
```
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
langsmith-ace-backend ClusterIP 172.20.92.210 1987/TCP 1m
langsmith-backend ClusterIP 172.20.156.146 1984/TCP 1m
langsmith-clickhouse ClusterIP 172.20.250.160 8123/TCP,9000/TCP,9363/TCP 1m
langsmith-frontend LoadBalancer 172.20.18.173 80:30879/TCP,443:31364/TCP 1m
langsmith-platform-backend ClusterIP 172.20.95.187 1986/TCP 1m
langsmith-playground ClusterIP 172.20.142.121 1988/TCP 1m
langsmith-postgres ClusterIP 172.20.226.128 5432/TCP 1m
langsmith-redis ClusterIP 172.20.57.248 6379/TCP 1m
```
2. Curl the external ip of the `langsmith-frontend` service:
```bash theme={null}
curl /api/tenants
```
Expected output:
```json theme={null}
[{"id":"00000000-0000-0000-0000-000000000000","has_waitlist_access":true,"created_at":"2023-09-13T18:25:10.488407","display_name":"Personal","config":{"is_personal":true,"max_identities":1},"tenant_handle":"default"}]
```
3. Visit the external ip for the `langsmith-frontend` service on your browser
The LangSmith UI should be visible/operational
## Using LangSmith
Now that LangSmith is running, you can start using it to trace your code. You can find more information on how to use self-hosted LangSmith in the [self-hosted usage guide](/langsmith/self-hosted).
Your LangSmith instance is now running but may not be fully setup yet.
If you used one of the basic configs, you will have a default admin user account created for you. You can log in with the email address and password you specified in the `langsmith_config.yaml` file.
As a next step, it is strongly recommended you work with your infrastructure administrators to:
* Setup DNS for your LangSmith instance to enable easier access
* Configure SSL to ensure in-transit encryption of traces submitted to LangSmith
* Configure LangSmith with [Single Sign-On](/langsmith/self-host-sso) to secure your LangSmith instance
* Connect LangSmith to external Postgres and Redis instances
* Set up [Blob Storage](/langsmith/self-host-blob-storage) for storing large files
Review our [configuration section](/langsmith/self-hosted) for more information on how to configure these options.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/kubernetes.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to evaluate a runnable
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/langchain-runnable
* `langchain`: [Python](https://docs.langchain.com/oss/python/langchain/overview) and [JS/TS](https://docs.langchain.com/oss/javascript/langchain/overview)
* Runnable: [Python](https://reference.langchain.com/python/langchain_core/runnables/) and [JS/TS](https://reference.langchain.com/javascript/classes/_langchain_core.runnables.Runnable.html)
`langchain` [`Runnable`](https://reference.langchain.com/python/langchain_core/runnables/) objects (such as chat models, retrievers, chains, etc.) can be passed directly into `evaluate()` / `aevaluate()`.
## Setup
Let's define a simple chain to evaluate. First, install all the required packages:
```bash Python theme={null}
pip install -U langsmith langchain[openai]
```
```bash TypeScript theme={null}
yarn add langsmith @langchain/openai
```
Now define a chain:
```python Python theme={null}
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
instructions = (
"Please review the user query below and determine if it contains any form "
"of toxic behavior, such as insults, threats, or highly negative comments. "
"Respond with 'Toxic' if it does, and 'Not toxic' if it doesn't."
)
prompt = ChatPromptTemplate(
[("system", instructions), ("user", "{text}")],
)
model = init_chat_model("gpt-4.1")
chain = prompt | model | StrOutputParser()
```
```typescript TypeScript theme={null}
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
const prompt = ChatPromptTemplate.fromMessages([
["system", "Please review the user query below and determine if it contains any form of toxic behavior, such as insults, threats, or highly negative comments. Respond with 'Toxic' if it does, and 'Not toxic' if it doesn't."],
["user", "{text}"]
]);
const chatModel = new ChatOpenAI();
const outputParser = new StringOutputParser();
const chain = prompt.pipe(chatModel).pipe(outputParser);
```
## Evaluate
To evaluate our chain we can pass it directly to the `evaluate()` / `aevaluate()` method. Note that the input variables of the chain must match the keys of the example inputs. In this case, the example inputs should have the form `{"text": "..."}`.
```python Python theme={null}
from langsmith import aevaluate, Client
client = Client()
# Clone a dataset of texts with toxicity labels.
# Each example input has a "text" key and each output has a "label" key.
dataset = client.clone_public_dataset(
"https://smith.langchain.com/public/3d6831e6-1680-4c88-94df-618c8e01fc55/d"
)
def correct(outputs: dict, reference_outputs: dict) -> bool:
# Since our chain outputs a string not a dict, this string
# gets stored under the default "output" key in the outputs dict:
actual = outputs["output"]
expected = reference_outputs["label"]
return actual == expected
results = await aevaluate(
chain,
data=dataset,
evaluators=[correct],
experiment_prefix="gpt-4.1, baseline",
)
```
```typescript TypeScript theme={null}
import { evaluate } from "langsmith/evaluation";
import { Client } from "langsmith";
const langsmith = new Client();
const dataset = await client.clonePublicDataset(
"https://smith.langchain.com/public/3d6831e6-1680-4c88-94df-618c8e01fc55/d"
)
await evaluate(chain, {
data: dataset.name,
evaluators: [correct],
experimentPrefix: "gpt-4.1, baseline",
});
```
The runnable is traced appropriately for each output.
## Related
* [How to evaluate a `langgraph` graph](/langsmith/evaluate-on-intermediate-steps)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/langchain-runnable.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangGraph JS/TS SDK
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/langgraph-js-ts-sdk
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/langgraph-js-ts-sdk.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangGraph Python SDK
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/langgraph-python-sdk
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/langgraph-python-sdk.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Configure your collector for LangSmith telemetry
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/langsmith-collector
The various services in a LangSmith deployment emit telemetry data in the form of logs, metrics, and traces. You may already have telemetry collectors set up in your Kubernetes cluster, or would like to deploy one to monitor your application.
This page describes how to configure an [OTel Collector](https://opentelemetry.io/docs/collector/configuration/) to gather telemetry data from LangSmith. Note that all of the concepts discussed below can be translated to other collectors such as [Fluentd](https://www.fluentd.org/) or [FluentBit](https://fluentbit.io/).
**This section is only applicable for Kubernetes deployments.**
# Receivers
## Logs
This is an example for a ***Sidecar*** collector to read logs from its own pod, excluding logs from non domain-specific containers. A Sidecar configuration is useful here because we require access to every container's filesystem. A DaemonSet can also be used.
```yaml theme={null}
filelog:
exclude:
- "**/otc-container/*.log"
include:
- /var/log/pods/${POD_NAMESPACE}_${POD_NAME}_${POD_UID}/*/*.log
include_file_name: false
include_file_path: true
operators:
- id: container-parser
type: container
retry_on_failure:
enabled: true
start_at: end
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_UID
valueFrom:
fieldRef:
fieldPath: metadata.uid
volumes:
- name: varlogpods
hostPath:
path: /var/log/pods
volumeMounts:
- name: varlogpods
mountPath: /var/log/pods
readOnly: true
```
**This configuration requires 'get', 'list', and 'watch' permissions on pods in the given namespace.**
## Metrics
Metrics can be scraped using the Prometheus endpoints. A single instance ***Gateway*** collector can be be used to avoid duplication of queries when fetching metrics. The following config scrapes all of the default named LangSmith services:
```yaml theme={null}
prometheus:
config:
scrape_configs:
- job_name: langsmith-services
metrics_path: /metrics
scrape_interval: 15s
# Only scrape endpoints in the LangSmith namespace
kubernetes_sd_configs:
- role: endpoints
namespaces:
names: []
relabel_configs:
# Only scrape services with the name langsmith-.*
- source_labels: [__meta_kubernetes_service_name]
regex: "langsmith-.*"
action: keep
# Only scrape ports with the following names
- source_labels: [__meta_kubernetes_endpoint_port_name]
regex: "(backend|platform|playground|redis-metrics|postgres-metrics|metrics)"
action: keep
# Promote useful metadata into regular labels
- source_labels: [__meta_kubernetes_service_name]
target_label: k8s_service
- source_labels: [__meta_kubernetes_pod_name]
target_label: k8s_pod
# Replace the default "host:port" as Prom's instance label
- source_labels: [__address__]
target_label: instance
```
**This configuration requires 'get', 'list', and 'watch' permissions on pods, services and endpoints in the given namespace.**
### Traces
For traces, you need to enable the OTLP receiver. The following configuration can be used to listen to HTTP traces on port 4318, and GRPC on port 4317:
```yaml theme={null}
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
```
## Processors
### Recommended OTEL processors
The following processors are recommended when using the OTel collector:
* [Batch Processor](https://github.com/open-telemetry/opentelemetry-collector/blob/main/processor/batchprocessor/README.md): Groups the data into batches before sending to exporters.
* [Memory Limiter](https://github.com/open-telemetry/opentelemetry-collector/blob/main/processor/memorylimiterprocessor/README.md): Prevents the collector from using too much memory and crashing. When the soft limit is crossed, the collector stops accepting new data.
* [Kubernetes Attributes Processor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/k8sattributesprocessor): Adds Kubernetes metadata such as pod name into the telemetry data.
## Exporters
Exporters just need to point to an external endpoint of your liking. The following configuration allows you to configure a separate endpoint for logs, metrics and traces:
```yaml theme={null}
otlphttp/logs:
endpoint:
otlphttp/metrics:
endpoint:
otlphttp/traces:
endpoint:
```
**The OTel Collector also supports exporting directly to a [Datadog](https://docs.datadoghq.com/opentelemetry/setup/collector_exporter) endpoint.**
# Example collector configuration: Logs sidecar
```yaml theme={null}
mode: sidecar
image: otel/opentelemetry-collector-contrib
config:
receivers:
filelog:
exclude:
- "**/otc-container/*.log"
include:
- /var/log/pods/${POD_NAMESPACE}_${POD_NAME}_${POD_UID}/*/*.log
include_file_name: false
include_file_path: true
operators:
- id: container-parser
type: container
retry_on_failure:
enabled: true
start_at: end
processors:
batch:
send_batch_size: 8192
timeout: 10s
memory_limiter:
check_interval: 1m
limit_percentage: 90
spike_limit_percentage: 80
exporters:
otlphttp/logs:
endpoint:
service:
pipelines:
logs/langsmith:
receivers: [filelog]
processors: [batch, memory_limiter]
exporters: [otlphttp/logs]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_UID
valueFrom:
fieldRef:
fieldPath: metadata.uid
volumes:
- name: varlogpods
hostPath:
path: /var/log/pods
volumeMounts:
- name: varlogpods
mountPath: /var/log/pods
readOnly: true
```
# Example collector configuration: Metrics and traces Gateway
```yaml theme={null}
mode: deployment
image: otel/opentelemetry-collector-contrib
config:
receivers:
prometheus:
config:
scrape_configs:
- job_name: langsmith-services
metrics_path: /metrics
scrape_interval: 15s
# Only scrape endpoints in the LangSmith namespace
kubernetes_sd_configs:
- role: endpoints
namespaces:
names: []
relabel_configs:
# Only scrape services with the name langsmith-.*
- source_labels: [__meta_kubernetes_service_name]
regex: "langsmith-.*"
action: keep
# Only scrape ports with the following names
- source_labels: [__meta_kubernetes_endpoint_port_name]
regex: "(backend|platform|playground|redis-metrics|postgres-metrics|metrics)"
action: keep
# Promote useful metadata into regular labels
- source_labels: [__meta_kubernetes_service_name]
target_label: k8s_service
- source_labels: [__meta_kubernetes_pod_name]
target_label: k8s_pod
# Replace the default "host:port" as Prom's instance label
- source_labels: [__address__]
target_label: instance
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
send_batch_size: 8192
timeout: 10s
memory_limiter:
check_interval: 1m
limit_percentage: 90
spike_limit_percentage: 80
exporters:
otlphttp/metrics:
endpoint:
otlphttp/traces:
endpoint:
service:
pipelines:
metrics/langsmith:
receivers: [prometheus]
processors: [batch, memory_limiter]
exporters: [otlphttp/metrics]
traces/langsmith:
receivers: [otlp]
processors: [batch, memory_limiter]
exporters: [otlphttp/traces]
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/langsmith-collector.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith Fetch
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/langsmith-fetch
LangSmith Fetch is a command-line interface (CLI) tool for retrieving trace data ([runs](/langsmith/observability-concepts#runs), [traces](/langsmith/observability-concepts#traces), and [threads](/langsmith/observability-concepts#threads)) from your LangSmith projects. It allows you to use LangSmith’s tracing and debugging features directly in your terminal and development workflows.
You can use LangSmith Fetch for the following use cases:
* Immediate debugging: Fetch the most recent trace of a failed or unexpected deployment run with a single command.
* Bulk export for analysis: Export large numbers of traces or entire conversation threads to JSON files for offline analysis, building [evaluation](/langsmith/evaluation-concepts) datasets, or [regression tests](/langsmith/evaluation-types#regression-tests).
* Terminal-based workflows: Integrate trace data into your existing tools; for example, piping output to Unix utilities like jq, or feeding traces into an AI coding assistant for automated analysis.
## Installation
```bash theme={null}
pip install langsmith-fetch
```
## Setup
1. Set your [LangSmith API key](/langsmith/create-account-api-key):
```bash theme={null}
export LANGSMITH_API_KEY=lsv2_...
```
2. Set your project name:
```bash theme={null}
export LANGSMITH_PROJECT=your-project-name
```
The CLI will automatically fetch traces or threads in `LANGSMITH_PROJECT`. Replace `your-project-name` with the name of your LangSmith project (if it doesn't exist, it will be created automatically on first use).
To find your project UUID, refer to [Find project and trace IDs](#find-project-and-trace-ids).
When you set the `LANGSMITH_PROJECT` environment variable, `langsmith-fetch` automatically looks up the project UUID and saves both to `~/.langsmith-cli/config.yaml`. By default, you do not need to include the project UUID with `langsmith-fetch`. However, you can override the configured project by specifying [a different project UUID](#override-the-configured-tracing-project) via the `--project-uuid` CLI flag.
To fetch a thread, `langsmith-fetch` **requires** a project UUID. Therefore, ensure that the `LANGSMITH_PROJECT` environment variable is set, or specify the `--project-uuid ` manually when you run [`langsmith-fetch thread `](#fetch-a-trace-or-thread).
3. (Optional) Set your [LangSmith endpoint](/langsmith/env-var#langsmith_endpoint) if you're using [self-hosted](/langsmith/self-hosted) LangSmith:
```bash theme={null}
export LANGSMITH_ENDPOINT=your-langsmith-api-url
```
### Use with a coding agent
After you've installed and set up `langsmith-fetch`, use your coding agent to ask questions like the following:
```
Use langsmith-fetch to analyze the last 3 threads from my LangSmith project for potential improvements
```
Many agents will use the `langsmith-fetch --help` command to understand how to use the CLI and complete your request.
## Find project and trace IDs
In most cases, you won’t need to find IDs manually (the CLI uses your [environment's project name](#setup) and latest traces by default). However, if you want to fetch a specific item by ID, you can find the IDs in the [LangSmith UI](https://smith.langchain.com):
* **Project UUID**: Each project has a unique ID (UUID). You can find it in the project's URL or by hovering over **ID** next to the project's name. This UUID can be used with the `--project-uuid` flag on CLI commands
* **Trace ID**: Every trace (single execution) has an ID. In the **Runs** view, click on a specific run to see its Trace ID (copyable from the trace details panel). You can use `langsmith-fetch trace ` to retrieve that exact trace if you have the ID.
## Usage
After installation and setup, you can use the `langsmith-fetch` command to retrieve traces or threads. The general usage is:
```bash theme={null}
langsmith-fetch COMMAND [ARGUMENTS] [OPTIONS]
```
LangSmith Fetch provides the following commands to fetch either single items or in bulk:
| Command | Fetches | Output location |
| --------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `trace ` | A specific trace by ID | Prints to stdout (or to a file with `--file`) |
| `thread ` | A specific thread by ID | Prints to stdout (or to a file with `--file`) |
| `traces [directory]` | Recent traces from the project (multiple) | Saves each trace as a JSON file in the given directory, or prints to stdout if no directory is provided. **Tip:** Using a directory is recommended for [bulk exports](#fetch-multiple). |
| `threads [directory]` | Recent threads from the project (multiple) | Saves each thread as a JSON file in the given directory, or prints to stdout if no directory is provided. |
Traces are fetched chronologically with most recent first.
### Options
The commands support additional flags to filter and format the output:
| Option / Flag | Applies to | Description | Default |
| --------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| `-n, --limit ` | `traces`, `threads` | Maximum number of traces/threads to fetch. Use this to limit how many items you retrieve (e.g., the last 5 traces). | **1** (if not specified) |
| `--last-n-minutes ` | `traces`, `threads` | Only fetch items from the last N minutes. This is useful to get recent data (e.g.,`--last-n-minutes 30` for the past half hour). | *(no time filter)* |
| `--since ` | `traces`, `threads` | Only fetch items since a specific time. Provide an ISO 8601 timestamp (e.g.,`2025-12-01T00:00:00Z`) to get data after that time. | *(no time filter)* |
| `--project-uuid ` | `trace, thread, traces, threads` | Manually specify the project by UUID to [override the `LANGSMITH_PROJECT` env setting](#override-the-configured-tracing-project). Use this if you want to fetch from a different project without changing your env var. | From env/config |
| `--filename-pattern ` | `traces`, `threads` | Pattern for output filenames when saving multiple files. You can use placeholders like `{trace_id}`, `{thread_id}`, `{index}`. | `{trace_id}.json` or `{thread_id}.json` |
| `--format ` | **All commands** | Output format: `pretty`, `json`, or `raw`. (Refer to [Output formats](#output-formats) for details.) | `pretty` |
| `--file ` | `trace`, `thread` | Save the fetched trace/thread to a file instead of printing it. | *(stdout)* |
| `--include-metadata` | `traces` (bulk fetch) | Include run metadata in the output (such as tokens used, execution time, status, costs). This will add a `"metadata"` section to each trace’s JSON. | *Off by default* |
| `--include-feedback` | `traces` (bulk fetch) | Include any feedback entries attached to the runs. Enabling this will make an extra API call for each trace to fetch feedback data. | *Off by default* |
| `--max-concurrent ` | `traces`, `threads` | Maximum concurrent fetch requests. Tune this if you are fetching a large number of items; increasing it may speed up retrieval but 5–10 is recommended to avoid API overload. | **5** |
| `--no-progress` | `traces`, `threads` | Disable the progress bar output. By default a progress indicator is shown when fetching multiple items; use this flag to hide it (useful for non-interactive scripts). | Progress bar on |
### Output formats
The `--format` option controls how the fetched data is displayed:
* `pretty` (default): A human-readable view with rich text formatting for easy inspection in the terminal. This format is great for quick debugging of a single trace or thread.
By default:
```bash theme={null}
langsmith-fetch trace
```
Explicitly specify the format:
```bash theme={null}
langsmith-fetch trace --format pretty
```
* `json`: Well-formatted JSON output with syntax highlighting. Use this if you want to examine the raw data structure or pipe it into JSON processing tools.
```bash theme={null}
langsmith-fetch trace --format json
```
* `raw`: Compact JSON with no extra whitespace. This is useful for piping the output to other programs (e.g., using `jq` or saving directly) without extra formatting.
```bash theme={null}
langsmith-fetch trace --format raw | jq '.[] | select(.role=="user")'
```
### Fetch a trace or thread
You can fetch a single thread or trace with the ID. The command will output to the terminal by default:
```bash theme={null}
langsmith-fetch trace
```
To fetch a thread, `langsmith-fetch thread` also requires a project UUID. Ensure that the [`LANGSMITH_PROJECT` environment variable](#setup) is configured, or use the `--project-uuid` flag to define a thread's project:
```bash theme={null}
langsmith-fetch thread --project-uuid
```
You can optionally redirect the thread or trace data to a file using the `--file` option.
### Fetch multiple
For bulk fetches of traces or threads, we recommend specifying a target directory path. Each fetched trace or thread will be saved as a separate JSON file in that folder, making it easy to browse or process them later.
You can specify a destination directory for the bulk commands (`traces`/`threads`). For example, the following command will save the 10 most recent traces as JSON files in the `my-traces-data` directory:
```bash theme={null}
langsmith-fetch traces ./my-traces-data --limit 10
```
```bash theme={null}
langsmith-fetch threads ./my-thread-data --limit 10
```
If you omit the directory and `--limit`, the tool will output the results of the most recent, single trace to your terminal.
When sending to a directory, files will be named in the following way:
* Default: Files named by trace ID (e.g., `3b0b15fe-1e3a-4aef-afa8-48df15879cfe.json`).
* Custom pattern: Use `--filename-pattern` with placeholders:
* `{trace_id}`: Trace ID (default: `{trace_id}.json`).
* `{index}` or `{idx}`: Sequential number starting from 1.
* Format specs supported: `{index:03d}` for zero-padded numbers.
### Include metadata and feedback
You can include [run metadata](/langsmith/observability-concepts#metadata) and any [feedback](/langsmith/observability-concepts#feedback) associated with the trace:
```bash theme={null}
langsmith-fetch traces --limit 1 --include-metadata --include-feedback
```
```
RUN METADATA
============================================================
Status: success
Start Time: 2025-12-12T18:05:47.558274
End Time: 2025-12-12T18:05:48.811072
Duration: 1252ms
Token Usage:
Prompt: 15
Completion: 88
Total: 103
Costs:
Total: $0.00014
Prompt: $0.00001
Completion: $0.00013
Custom Metadata:
LANGSMITH_PROJECT: weather-demo
LANGSMITH_TRACING: true
ls_run_depth: 0
Feedback Stats:
correctness: {'n': 1, 'avg': 1.0, 'stdev': 0.0, 'errors': 0, 'show_feedback_arrow': False, 'comments': [''], 'sources':
['{"type":"app","metadata":null,"user_id":"d5ee8d42-a274-4f32-9c35-b765287fe5ec","ls_user_id":"ac375f5f-0da0-44c1-82a2-0ecfd6ecac27"}'],
'session_min_score': 1.0, 'session_max_score': 1.0, 'values': {}, 'contains_thread_feedback': False}
note: {'n': 0, 'avg': None, 'stdev': None, 'errors': 0, 'show_feedback_arrow': False, 'comments': ['The answer should be more specific on rainfall
totals.'], 'sources':
['{"type":"app","metadata":null,"user_id":"d5ee8d42-a274-4f32-9c35-b765287fe5ec","ls_user_id":"ac375f5f-0da0-44c1-82a2-0ecfd6ecac27"}'],
'session_min_score': None, 'session_max_score': None, 'values': {}, 'contains_thread_feedback': False}
...
```
### Override the configured tracing project
To fetch traces from a different project than the one configured with `LANGSMITH_PROJECT`, use `--project-uuid`:
```bash theme={null}
langsmith-fetch traces --project-uuid --limit 3
```
Running this command will just fetch traces from that project, it will not modify the [LangSmith project](#setup) already configured in `~/.langsmith-cli/config.yaml`.
You may also need to fetch a particular trace in a different project to the configured `LANGSMITH_PROJECT`. You can do this by including a `trace-id` and `project-id` with `langsmith-fetch`:
```bash theme={null}
langsmith-fetch trace --project-uuid
```
### Export to files
You can fetch traces or full threads and export to a file:
```bash theme={null}
langsmith-fetch threads ./my_threads --since 2025-12-01T00:00:00Z
```
This command retrieves all threads that have occurred since December 1, 2025, saving each conversation as a JSON file under `./my_threads`. This is useful for exporting chat transcripts or building regression tests on multi-turn conversations. You could also use `--limit` with threads to fetch a specific number of recent threads, and `--last-n-minutes` works here as well.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/langsmith-fetch.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith-managed ClickHouse
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/langsmith-managed-clickhouse
Please read the [LangSmith architectural overview](/langsmith/self-hosted) and [guide on connecting to external ClickHouse](/langsmith/self-host-external-clickhouse) before proceeding with this guide.
LangSmith uses ClickHouse as the primary storage engine for **traces** and **feedback**. For easier management and scaling, it is recommended to connect a self-hosted LangSmith instance to an external ClickHouse instance. LangSmith-managed ClickHouse is an option that allows you to use a fully managed ClickHouse instance that is monitored and maintained by the LangSmith team.
## Architecture overview
The architecture of using LangSmith-managed ClickHouse with your self-hosted LangSmith instance is similar to using a fully self-hosted ClickHouse instance, with a few key differences:
* You will need to set up a private network connection between your LangSmith instance and the LangSmith-managed ClickHouse instance. This is to ensure that your data is secure and that you can connect to the ClickHouse instance from your self-hosted LangSmith instance.
* With this option, sensitive information (inputs and outputs) of your traces will be stored in cloud object storage (S3 or GCS) within your cloud instead of ClickHouse to ensure that sensitive information doesn't leave your VPC. For more details on where particular data fields are stored, refer to [Data storage](#data-storage).
* The LangSmith team will monitor your ClickHouse instance and ensure that it is running smoothly. This allows us to track metrics like run-ingestion delay and query performance.
The overall architecture looks like this:
## Requirements
* **You must use a supported blob storage option.** Read the [blob storage guide](/langsmith/self-host-blob-storage) for more information.
* To use private endpoints, ensure that your VPC is in a ClickHouse Cloud supported [region](https://clickhouse.com/docs/en/cloud/reference/supported-regions). Otherwise, you will need to use a public endpoint we will secure with firewall rules. Your VPC will need to have a NAT gateway to allow us to allowlist your traffic.
* You must have a VPC that can connect to the LangSmith-managed ClickHouse service. You will need to work with our team to set up the necessary networking.
* You must have a LangSmith self-hosted instance running. You can use our managed ClickHouse service with both [Kubernetes](/langsmith/kubernetes) and [Docker](/langsmith/docker) installations.
## Data storage
ClickHouse stores **runs** and **feedback** data, specifically:
* All feedback data fields.
* Some run data fields.
For a list of fields, refer to [Stored run data fields](#stored-run-data-fields) and [Stored feedback data fields](#stored-feedback-data-fields).
LangChain defines sensitive application data as `inputs`, `outputs`, `errors`, `manifests`, `extras`, and `events` of a run, since these fields may contain LLM prompts and completions. With LangSmith-managed ClickHouse, these sensitive fields are stored in cloud object storage (S3 or GCS) within your cloud, while the rest of the run data is stored in ClickHouse, ensuring sensitive information never leaves your VPC.
### Stored feedback data fields
Because all feedback data is stored in ClickHouse, do not send sensitive information in feedback (scores and annotations/comments) or in any other run fields that are mentioned in [Stored run data fields](#stored-run-data-fields).
Using a LangSmith-managed ClickHouse setup, **all feedback data fields are stored in ClickHouse**:
| Field Name | Type | Description |
| -------------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `id` | UUID | Unique identifier for the record itself |
| `created_at` | datetime | Timestamp when the record was created |
| `modified_at` | datetime | Timestamp when the record was last modified |
| `session_id` | UUID | Unique identifier for the experiment or tracing project the run was a part of |
| `run_id` | UUID | Unique identifier for a specific run within a session |
| `key` | string | A key describing the criteria of the feedback, e.g. `'correctness'` |
| `score` | number | Numerical score associated with the feedback key |
| `value` | string | Reserved for storing a value associated with the score. Useful for categorical feedback. |
| `comment` | string | Any comment or annotation associated with the record. This can be a justification for the score given. |
| `correction` | object | Reserved for storing correction details, if any |
| `feedback_source` | object | Object containing information about the feedback source |
| `feedback_source.type` | string | The type of source where the feedback originated, e.g. `'api'`, `'app'`, `'evaluator'` |
| `feedback_source.metadata` | object | Reserved for additional metadata, currently |
| `feedback_source.user_id` | UUID | Unique identifier for the user providing feedback |
This [reference doc](/langsmith/feedback-data-format) explains the stored feedback format, which is the LangSmith's way of representing evaluation scores and annotations on runs.
### Stored run data fields
Run data fields are split between the managed ClickHouse database and your cloud object storage (e.g., S3 or GCS).
For run fields stored in object storage, only a reference or pointer is kept in ClickHouse. For example, `inputs` and `outputs` content are offloaded to S3/GCS, with the ClickHouse record storing corresponding S3 URLs in the `inputs_s3_urls` and `outputs_s3_urls` fields.
The table details each run field and where it is stored:
| Field | Storage Location |
| ------------------------------ | ------------------ |
| `id` | ClickHouse |
| `name` | ClickHouse |
| `inputs` | **Object Storage** |
| `run_type` | ClickHouse |
| `start_time` | ClickHouse |
| `end_time` | ClickHouse |
| `extra` | **Object Storage** |
| `error` | **Object Storage** |
| `outputs` | **Object Storage** |
| `events` | **Object Storage** |
| `tags` | ClickHouse |
| `trace_id` | ClickHouse |
| `dotted_order` | ClickHouse |
| `status` | ClickHouse |
| `child_run_ids` | ClickHouse |
| `direct_child_run_ids` | ClickHouse |
| `parent_run_ids` | ClickHouse |
| `feedback_stats` | ClickHouse |
| `reference_example_id` | ClickHouse |
| `total_tokens` | ClickHouse |
| `prompt_tokens` | ClickHouse |
| `completion_tokens` | ClickHouse |
| `total_cost` | ClickHouse |
| `prompt_cost` | ClickHouse |
| `completion_cost` | ClickHouse |
| `first_token_time` | ClickHouse |
| `session_id` | ClickHouse |
| `in_dataset` | ClickHouse |
| `parent_run_id` | ClickHouse |
| `execution_order` (deprecated) | ClickHouse |
| `serialized` | ClickHouse |
| `manifest_id` (deprecated) | ClickHouse |
| `manifest_s3_id` | ClickHouse |
| `inputs_s3_urls` | ClickHouse |
| `outputs_s3_urls` | ClickHouse |
| `price_model_id` | ClickHouse |
| `app_path` | ClickHouse |
| `last_queued_at` | ClickHouse |
| `share_token` | ClickHouse |
This [reference doc](/langsmith/run-data-format) explains the format of stored runs (spans), which are the building blocks of traces.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/langsmith-managed-clickhouse.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to define an LLM-as-a-judge evaluator
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/llm-as-judge
LLM applications can be challenging to evaluate since they often generate conversational text with no single correct answer.
This guide shows you how to define an [LLM-as-a-judge evaluator](/langsmith/evaluation-concepts#llm-as-judge) for [offline evaluation](/langsmith/evaluation-concepts#offline-evaluation) using the [LangSmith UI](https://smith.langchain.com).
To run evaluations in real-time on your production traces, refer to [setting up online evaluations](/langsmith/online-evaluations-llm-as-judge).
## Step 1. Create the evaluator
1. In the [LangSmith UI](https://smith.langchain.com), create an evaluator from from the playground or from a dataset: select the **+ Evaluator** button.
2. Select the **Create from scratch** option from the dropdown. Alternatively, you may start by selecting a pre-built evaluator and editing it.
### Pre-built evaluators
Pre-built evaluators are a useful starting point when setting up evaluations. The LangSmith UI supports the following pre-built evaluators:
* **Hallucination**: Detect factually incorrect outputs. Requires a reference output.
* **Correctness**: Check semantic similarity to a reference.
* **Conciseness**: Evaluate whether an answer is a concise response to a question.
* **Code checker**: Verify correctness of code answers.
You can configure these evaluators::
* When running an evaluation using the [playground](/langsmith/observability-concepts#prompt-playground)
* As part of a dataset to [automatically run evaluations on experiments](/langsmith/bind-evaluator-to-dataset)
* When running an [online evaluation](/langsmith/online-evaluations-llm-as-judge)
### Customize your LLM-as-a-judge evaluator
Add specific instructions for your LLM-as-a-judge evaluator prompt and configure which parts of the input/output/reference output should be passed to the evaluator.
## Step 2. Configure the evaluator
### Prompt
Create a new prompt, or choose an existing prompt from the [prompt hub](/langsmith/prompt-engineering-quickstart).
* **Create your own prompt**: Create a custom prompt inline.
* **Pull a prompt from the prompt hub**: Use the **Select a prompt** dropdown to select from an existing prompt. You can't edit these prompts directly within the prompt editor, but you can view the prompt and the schema it uses. To make changes, edit the prompt in the playground and commit the version, and then pull in your new prompt in the evaluator.
### Model
Select the desired model from the provided options.
### Mapping variables
Use variable mapping to indicate the variables that are passed into your evaluator prompt from your run or example. To aid with variable mapping, an example (or run) is provided for reference. Click on the variables in your prompt and use the dropdown to map them to the relevant parts of the input, output, or reference output.
To add prompt variables type the variable with double curly brackets `{{prompt_var}}` if using mustache formatting (the default) or single curly brackets `{prompt_var}` if using f-string formatting.
You may remove variables as needed. For example if you are evaluating a metric such as conciseness, you typically don't need a reference output so you may remove that variable.
### Preview
Previewing the prompt will show you of what the formatted prompt will look like using the reference run and dataset example shown on the right.
### Improve your evaluator with few-shot examples
To better align the LLM-as-a-judge evaluator to human preferences, LangSmith allows you to collect [human corrections](/langsmith/create-few-shot-evaluators#make-corrections) on evaluator scores. With this selection enabled, corrections are then inserted automatically as few-shot examples into your prompt.
Learn [how to set up few-shot examples and make corrections](/langsmith/create-few-shot-evaluators).
### Feedback configuration
Feedback configuration is the scoring criteria that your LLM-as-a-judge evaluator will use. Think of this as the rubric that your evaluator will grade based on. Scores will be added as [feedback](/langsmith/observability-concepts#feedback) to a run or example. Defining feedback for your evaluator:
1. **Name the feedback key**: This is the name that will appear when viewing evaluation results. Names should be unique across experiments.
2. **Add a description**: Describe what the feedback represents.
3. **Choose a feedback type**:
* **Boolean**: True/false feedback.
* **Categorical**: Select from predefined categories.
* **Continuous**: Numerical scoring within a specified range.
Behind the scenes, feedback configuration is added as [structured output](/oss/python/langchain/structured-output) to the LLM-as-a-judge prompt. If you're using an existing prompt from the hub, you must add an output schema to the prompt before configuring an evaluator to use it. Each top-level key in the output schema will be treated as a separate piece of feedback.
## Step 3. Save the evaluator
Once you are finished configuring, save your changes.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/llm-as-judge.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to define an LLM-as-a-judge evaluator
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/llm-as-judge-sdk
LLM applications can be challenging to evaluate since they often generate conversational text with no single correct answer.
This guide shows you how to define an [LLM-as-a-judge evaluator](/langsmith/evaluation-concepts#llm-as-judge) for [offline evaluation](/langsmith/evaluation-concepts#offline-evaluation) using the [LangSmith SDK](https://reference.langchain.com/python/langsmith/observability/sdk).
For a quick start, use [pre-built evaluators](/langsmith/prebuilt-evaluators), which provide ready-to-use LLM-as-a-judge evaluators.
## Create your own LLM-as-a-judge evaluator
For complete control of evaluator logic, create your own LLM-as-a-judge evaluator and run it using the LangSmith SDK ([Python](https://docs.smith.langchain.com/reference/python/reference) / [TypeScript](https://docs.smith.langchain.com/reference/js)).
Requires `langsmith>=0.2.0`
An LLM-as-a-judge evaluator consists of three key components:
1. **Evaluator function**: A function that receives the example inputs and application outputs, then uses an LLM to score the quality. The function should return a boolean, number, string, or dictionary with score information.
2. **Target function**: Your application logic being evaluated (wrapped with [`@traceable`](https://reference.langchain.com/python/langsmith/observability/sdk/run_helpers/#langsmith.run_helpers.traceable) for observability).
3. **Dataset and evaluation**: A dataset of test examples and the `evaluate()` function that runs your target function on each example and applies your evaluators.
### Example
```python theme={null}
from langsmith import evaluate, traceable, wrappers, Client
from openai import OpenAI
from pydantic import BaseModel
# Wrap the OpenAI client to automatically trace all LLM calls
oai_client = wrappers.wrap_openai(OpenAI())
# 1. Define your evaluator function
# This function receives the inputs and outputs from each test example
def valid_reasoning(inputs: dict, outputs: dict) -> bool:
"""Use an LLM to judge if the reasoning and the answer are consistent."""
# Define the evaluation criteria
instructions = """
Given the following question, answer, and reasoning, determine if the reasoning
for the answer is logically valid and consistent with the question and the answer."""
# Use structured output to get a boolean score
class Response(BaseModel):
reasoning_is_valid: bool
# Construct the prompt with the actual inputs and outputs
msg = f"Question: {inputs['question']}\nAnswer: {outputs['answer']}\nReasoning: {outputs['reasoning']}"
# Call the LLM to judge the output
response = oai_client.beta.chat.completions.parse(
model="gpt-4o",
messages=[{"role": "system", "content": instructions}, {"role": "user", "content": msg}],
response_format=Response
)
# Return the boolean score
return response.choices[0].message.parsed.reasoning_is_valid
# 2. Define your target function (the application being evaluated)
# The @traceable decorator logs traces to LangSmith for debugging
@traceable
def dummy_app(inputs: dict) -> dict:
return {"answer": "hmm i'm not sure", "reasoning": "i didn't understand the question"}
# 3. Create a dataset with test examples
ls_client = Client()
dataset = ls_client.create_dataset("big questions")
examples = [
{"inputs": {"question": "how will the universe end"}},
{"inputs": {"question": "are we alone"}},
]
ls_client.create_examples(dataset_id=dataset.id, examples=examples)
# 4. Run the evaluation
# This runs dummy_app on each example and applies the valid_reasoning evaluator
results = evaluate(
dummy_app, # Your application function
data=dataset, # Dataset to evaluate on
evaluators=[valid_reasoning] # List of evaluator functions
)
```
For more information on how to write a custom evaluator, refer to [How to define a code evaluator (SDK)](/langsmith/code-evaluator-sdk).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/llm-as-judge-sdk.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to run an evaluation locally (Python only)
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/local
Sometimes it is helpful to run an evaluation locally without uploading any results to LangSmith. For example, if you're quickly iterating on a prompt and want to smoke test it on a few examples, or if you're validating that your target and evaluator functions are defined correctly, you may not want to record these evaluations.
You can do this by using the LangSmith Python SDK and passing `upload_results=False` to `evaluate()` / `aevaluate()`.
This will run you application and evaluators exactly as it always does and return the same output, but nothing will be recorded to LangSmith. This includes not just the experiment results but also the application and evaluator traces.
If you want to upload results to LangSmith but also need to process them in your script (for quality gates, custom aggregations, etc.), refer to [Read experiment results locally](/langsmith/read-local-experiment-results).
## Example
Let's take a look at an example:
Requires `langsmith>=0.2.0`. Example also uses `pandas`.
```python theme={null}
from langsmith import Client
# 1. Create and/or select your dataset
ls_client = Client()
dataset = ls_client.clone_public_dataset(
"https://smith.langchain.com/public/a63525f9-bdf2-4512-83e3-077dc9417f96/d"
)
# 2. Define an evaluator
def is_concise(outputs: dict, reference_outputs: dict) -> bool:
return len(outputs["answer"]) < (3 * len(reference_outputs["answer"]))
# 3. Define the interface to your app
def chatbot(inputs: dict) -> dict:
return {"answer": inputs["question"] + " is a good question. I don't know the answer."}
# 4. Run an evaluation
experiment = ls_client.evaluate(
chatbot,
data=dataset,
evaluators=[is_concise],
experiment_prefix="my-first-experiment",
# 'upload_results' is the relevant arg.
upload_results=False
)
# 5. Analyze results locally
results = list(experiment)
# Check if 'is_concise' returned False.
failed = [r for r in results if not r["evaluation_results"]["results"][0].score]
# Explore the failed inputs and outputs.
for r in failed:
print(r["example"].inputs)
print(r["run"].outputs)
# Explore the results as a Pandas DataFrame.
# Must have 'pandas' installed.
df = experiment.to_pandas()
df[["inputs.question", "outputs.answer", "reference.answer", "feedback.is_concise"]]
```
```python theme={null}
{'question': 'What is the largest mammal?'}
{'answer': "What is the largest mammal? is a good question. I don't know the answer."}
{'question': 'What do mammals and birds have in common?'}
{'answer': "What do mammals and birds have in common? is a good question. I don't know the answer."}
```
| | inputs.question | outputs.answer | reference.answer | feedback.is\_concise |
| - | ----------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------- | -------------------- |
| 0 | What is the largest mammal? | What is the largest mammal? is a good question. I don't know the answer. | The blue whale | False |
| 1 | What do mammals and birds have in common? | What do mammals and birds have in common? is a good question. I don't know the answer. | They are both warm-blooded | False |
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/local.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Local development & testing
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/local-dev-testing
This is a conceptual guide for developing and testing [Agent Server](/langsmith/agent-server) applications locally. Learn about the local development tools available, when to use each one, and how they fit into your workflow from initial development through to [deploying](/langsmith/deployments) Agent Server in production.
The [LangGraph CLI](/langsmith/cli) provides two commands for local development, each optimized for different stages of your workflow:
* [`langgraph dev`](#langgraph-dev): A lightweight development server for rapid iteration.
* [`langgraph up`](#langgraph-up): A production-like testing environment for validation.
| Feature | `langgraph dev` | `langgraph up` |
| --------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **Docker required** | No | Yes |
| **Installation** | `pip install langgraph-cli[inmem]` | `pip install langgraph-cli` |
| **Primary use case** | Rapid development & testing | Production-like validation |
| **State persistence** | In-memory & pickled to local directory | PostgreSQL |
| **Hot reloading** | Yes (default) | Optional (`--watch` flag) |
| **Default port** | `2024` | `8123` |
| **Resource usage** | Lightweight | Heavier (build and run separate docker containers for the server, PostgreSQL, and Redis) |
| **IDE Debugging** | Built-in [DAP](https://microsoft.github.io/debug-adapter-protocol/) support | Regular container debugging |
| **Custom auth** | Yes | Yes (with license key) |
For full reference details, refer to the [LangGraph CLI reference](/langsmith/cli) page.
## Development
Here's the typical workflow when building applications:
```mermaid theme={null}
flowchart LR
A["Develop langgraph dev"] --> B["Test Locally langgraph dev"] --> C["Validate langgraph up"] --> D["Deploy via UI or API"]
style A fill:#1976d2,stroke:#0d47a1,stroke-width:2px,color:#fff
style B fill:#1976d2,stroke:#0d47a1,stroke-width:2px,color:#fff
style C fill:#f57c00,stroke:#e65100,stroke-width:2px,color:#fff
style D fill:#388e3c,stroke:#1b5e20,stroke-width:2px,color:#fff
```
| Stage | Tool | Purpose |
| -------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| **Develop & Test Locally** | [`langgraph dev`](/langsmith/cli#dev) | Write and iterate on your graph with hot reloading |
| **Validate** | [`langgraph up`](/langsmith/cli#up) | Test production-like behavior with full stack |
| **Deploy** | [LangSmith UI](/langsmith/deployment-quickstart) or [Control Plane API](/langsmith/api-ref-control-plane) | Deploy to production with confidence |
### Recommended workflow
1. **Daily development**: Use `langgraph dev` for rapid iteration.
2. **Periodic validation**: Test major changes with `langgraph up`.
3. **Pre-deployment check**: Run `langgraph up --recreate` for a fresh build.
4. **Deploy**: Push to production via the [LangSmith UI](/langsmith/deployment-quickstart) or [Control Plane API](/langsmith/api-ref-control-plane).
## `langgraph dev`
The [`langgraph dev`](/langsmith/cli#dev) command runs a lightweight server directly in your environment, designed for speed and convenience during active development. The key features include:
* **No Docker required**: Runs directly in your environment.
* **Hot reloading**: Automatically reloads when you change code.
* **Fast startup**: Ready in seconds.
* **Built-in [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) support**: Attach your IDE debugger to the server for line-level breakpoints & debugging.
* **Local storage**: State persisted to local directory.
The `dev` server is tested with the same integration test suite as production to ensure its behavior is the same during development while using minimal resources.
### Get started
```bash theme={null}
# Install with inmem extra
pip install -U "langgraph-cli[inmem]"
# Start dev server
langgraph dev
```
Your server starts at `http://localhost:2024` with hot reloading enabled by default.
### Use cases
Use `langgraph dev` as your primary development tool for:
* **Daily feature development**: Make changes to your code and the server automatically reloads. Test immediately without rebuilding containers—perfect for fast iteration cycles.
* **Quick prototyping and experiments**: Spin up a server in seconds to test ideas without Docker setup overhead.
* **Environments without Docker**: In CI/CD pipelines or lightweight VMs where Docker isn't available:
```bash theme={null}
langgraph dev --no-browser
```
* **Debugger attachment**: Use `--debug-port` to attach your IDE debugger for step-through debugging during development.
## `langgraph up`
The [`langgraph up`](/langsmith/cli#up) command orchestrates a full Docker-based stack that mirrors production infrastructure, helping catch deployment issues before production. The key features include:
* **Verify build & dependencies**: Tests your build process and dependencies.
* **Isolated networking**: Realistic container networking.
* **Production validation**: Verifies deployment readiness.
### Get started
```bash theme={null}
# Ensure Docker is running
docker ps
# Start production-like stack
langgraph up
```
Your server starts at `http://localhost:8123` with full persistent storage.
### Use cases
Use `langgraph up` for validation and production-readiness testing:
* **Pre-deployment validation**: Before deploying to production, you can run a final check with a fresh build to ensure your dependencies are all correctly specified.
```bash theme={null}
langgraph up --recreate
```
This catches issues related to dependency resolution in containers and any other build process problems.
* **Major feature validation**: After implementing significant changes, test with the full production stack periodically to ensure everything works in a containerized environment.
* **Docker troubleshooting**: When debugging container-specific issues, networking problems, or environment variable configurations that only appear in production.
## Pre-deployment checklist
Before deploying an application, verify the following with `langgraph up`:
* All [dependencies](/langsmith/setup-app-requirements-txt) install correctly in the container.
* Application starts without errors.
* Graph executes successfully.
* All [environment variables](/langsmith/env-var) work correctly.
* [Authentication/authorization](/langsmith/cli#adding-custom-authentication) works as expected.
## Dependencies configuration
Both `langgraph dev` and `langgraph up` read your application's [dependencies](/langsmith/application-structure#dependencies) from your [configuration files](/langsmith/application-structure#configuration-file), but they run in different environments:
* **`langgraph dev`** runs your code directly in your local environment (Python or Node.js) without Docker.
* **`langgraph up`** builds a Docker container and runs your code inside that isolated container.
Properly configuring your dependencies ensures both commands work correctly and that what you test locally matches what gets deployed to production.
### `langgraph.json` file
The `dependencies` field tells the [CLI](/langsmith/cli) **where** to find your application code. The `dependencies` field can point to:
* **A directory with package config** (containing `pyproject.toml`, `setup.py`, `requirements.txt`, or `package.json`)
* **A specific subdirectory**: `"dependencies": ["./my_agent"]`
* **A specific package**: `"dependencies": ["my-package==1.0.0"]` (Python) or `"dependencies": ["my-package@1.0.0"]` (JavaScript)
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"my_agent": "./my_agent/agent.py:graph"
},
"env": "./.env"
}
```
```json theme={null}
{
"dependencies": ["."],
"graphs": {
"my_agent": "./my_agent/agent.js:graph"
},
"env": "./.env"
}
```
### Package dependency files
These files define **what** packages your application needs:
**pyproject.toml example:**
```toml theme={null}
[project]
name = "my-agent"
version = "0.1.0"
dependencies = [
"langchain-openai",
"langchain-anthropic",
"langgraph",
]
```
**requirements.txt example:**
```
langchain-openai
langchain-anthropic
langgraph
```
**package.json example:**
```json theme={null}
{
"name": "my-agent",
"version": "1.0.0",
"dependencies": {
"@langchain/openai": "^0.3.0",
"@langchain/anthropic": "^0.3.0",
"@langchain/langgraph": "^0.2.0"
}
}
```
### Dependency resolution process
When you run [`langgraph up`](/langsmith/cli#up), the CLI follows these steps to install your application's dependencies:
1. [`langgraph.json`](/langsmith/application-structure#configuration-file) tells the CLI **where** to look for your application code. The `dependencies: ["."]` field points to the current directory.
2. **Find package configuration**: The CLI looks in that directory for a package configuration file ([`pyproject.toml`](/langsmith/setup-pyproject), [`requirements.txt`](/langsmith/setup-app-requirements-txt), or [`package.json`](/langsmith/setup-javascript)).
3. **Read dependencies list**: The CLI reads the list of packages from the configuration file.
4. **Install packages**: The CLI installs all the packages using the appropriate package manager for your language (`uv` or `pip` for Python, `npm` for JavaScript).
This two-file approach separates concerns: `langgraph.json` handles application structure and location, while the package configuration file handles language-specific package dependencies.
For more information on the installer, refer to [CLI configuration file](/langsmith/cli#configuration-file).
### Troubleshooting
If you encounter issues with dependency installation, try switching to `pip`:
```json theme={null}
{
"dependencies": ["."],
"pip_installer": "pip"
}
```
Then rebuild:
```bash theme={null}
langgraph up --recreate
```
## Debug your local Docker setup
Production deployment might succeed even when `langgraph up` fails on your local machine. This happens because production uses managed infrastructure while `langgraph up` runs the full stack locally on your computer.
The following are common local environment issues that don't affect production.
### Docker configuration issues
`langgraph up` requires Docker locally:
```bash theme={null}
# Check if Docker is running
docker ps
```
[Cloud deployments](/langsmith/cloud) don't use your local Docker.
**Solution**: Install Docker, or use `langgraph dev` for local testing.
### Port conflicts
`langgraph up` uses ports `8123`, `5432`, and `6379` that might be occupied:
```bash theme={null}
# Check for conflicts
lsof -i :8123 # API server
lsof -i :5432 # PostgreSQL
lsof -i :6379 # Redis
```
**Solution**: Stop conflicting services or use the [`--port`](/langsmith/cli#options) flag.
### Resource constraints
`langgraph up` requires more RAM and disk for:
* PostgreSQL container
* Redis container
* API server container
**Solution**: Free up resources or use `langgraph dev`.
### Network configuration
VPN connections, firewall rules, or corporate proxy settings can affect local Docker networking.
**Solution**: Test with `langgraph dev` or temporarily disable VPN/firewall to isolate the issue.
## Related resources
* [CLI Reference](/langsmith/cli): Detailed documentation for all CLI commands
* [Application Structure](/langsmith/application-structure): How to structure your LangGraph application
* [Local Development](/langsmith/local-server): Getting started with local development
* [Troubleshooting](/langsmith/troubleshooting-studio): Common issues and solutions
* [Setting up with pyproject.toml](/langsmith/setup-pyproject): Configure Python dependencies
* [Setting up with requirements.txt](/langsmith/setup-app-requirements-txt): Alternative dependency configuration
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/local-dev-testing.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Run a LangGraph app locally
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/local-server
This quickstart shows you how to set up a LangGraph application locally for testing and development.
## Prerequisites
Before you begin, ensure you have an API key for [LangSmith](https://smith.langchain.com/settings) (free to sign up).
## 1. Install the LangGraph CLI
```shell theme={null}
# Python >= 3.11 is required.
pip install -U "langgraph-cli[inmem]"
```
```shell theme={null}
npx @langchain/langgraph-cli
```
## 2. Create a LangGraph app
Create a new app from the [`new-langgraph-project-python` template](https://github.com/langchain-ai/new-langgraph-project) or [`new-langgraph-project-js` template](https://github.com/langchain-ai/new-langgraphjs-project). This template demonstrates a single-node application you can extend with your own logic.
```shell theme={null}
langgraph new path/to/your/app --template new-langgraph-project-python
```
```shell theme={null}
langgraph new path/to/your/app --template new-langgraph-project-js
```
**Additional templates**
If you use [`langgraph new`](/langsmith/cli) without specifying a template, you will be presented with an interactive menu that will allow you to choose from a list of available templates.
## 3. Install dependencies
In the root of your new LangGraph app, install the dependencies in `edit` mode so your local changes are used by the server:
```shell theme={null}
cd path/to/your/app
pip install -e .
```
```shell theme={null}
cd path/to/your/app
yarn install
```
## 4. Create a `.env` file
You will find a [`.env.example`](/langsmith/application-structure#configuration-file) in the root of your new LangGraph app. Create a `.env` file in the root of your new LangGraph app and copy the contents of the `.env.example` file into it, filling in the necessary API keys:
```bash theme={null}
LANGSMITH_API_KEY=lsv2...
```
## 5. Launch Agent Server
Start the Agent Server locally:
```shell theme={null}
langgraph dev
```
```shell theme={null}
npx @langchain/langgraph-cli dev
```
Sample output:
```
> Ready!
>
> - API: [http://localhost:2024](http://localhost:2024/)
>
> - Docs: http://localhost:2024/docs
>
> - Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
```
The [`langgraph dev`](/langsmith/cli) command starts [Agent Server](/langsmith/agent-server) in an in-memory mode. This mode is suitable for development and testing purposes.
For production use, deploy Agent Server with a persistent storage backend. For more information, refer to the LangSmith [platform options](/langsmith/platform-setup).
To understand when to use `langgraph dev` vs `langgraph up`, see the [Local development & testing guide](/langsmith/local-dev-testing).
## 6. Test the API
1. Install the LangGraph Python SDK:
```shell theme={null}
pip install langgraph-sdk
```
2. Send a message to the assistant (threadless run):
```python theme={null}
from langgraph_sdk import get_client
import asyncio
client = get_client(url="http://localhost:2024")
async def main():
async for chunk in client.runs.stream(
None, # Threadless run
"agent", # Name of assistant. Defined in langgraph.json.
input={
"messages": [{
"role": "human",
"content": "What is LangGraph?",
}],
},
):
print(f"Receiving new event of type: {chunk.event}...")
print(chunk.data)
print("\n\n")
asyncio.run(main())
```
1. Install the LangGraph Python SDK:
```shell theme={null}
pip install langgraph-sdk
```
2. Send a message to the assistant (threadless run):
```python theme={null}
from langgraph_sdk import get_sync_client
client = get_sync_client(url="http://localhost:2024")
for chunk in client.runs.stream(
None, # Threadless run
"agent", # Name of assistant. Defined in langgraph.json.
input={
"messages": [{
"role": "human",
"content": "What is LangGraph?",
}],
},
stream_mode="messages-tuple",
):
print(f"Receiving new event of type: {chunk.event}...")
print(chunk.data)
print("\n\n")
```
1. Install the LangGraph JS SDK:
```shell theme={null}
npm install @langchain/langgraph-sdk
```
2. Send a message to the assistant (threadless run):
```js theme={null}
const { Client } = await import("@langchain/langgraph-sdk");
// only set the apiUrl if you changed the default port when calling langgraph dev
const client = new Client({ apiUrl: "http://localhost:2024"});
const streamResponse = client.runs.stream(
null, // Threadless run
"agent", // Assistant ID
{
input: {
"messages": [
{ "role": "user", "content": "What is LangGraph?"}
]
},
streamMode: "messages-tuple",
}
);
for await (const chunk of streamResponse) {
console.log(`Receiving new event of type: ${chunk.event}...`);
console.log(JSON.stringify(chunk.data));
console.log("\n\n");
}
```
```bash theme={null}
curl -s --request POST \
--url "http://localhost:2024/runs/stream" \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {
\"messages\": [
{
\"role\": \"human\",
\"content\": \"What is LangGraph?\"
}
]
},
\"stream_mode\": \"messages-tuple\"
}"
```
## Next steps
Now that you have a LangGraph app running locally, you're ready to deploy it:
**Choose a hosting option for LangSmith:**
* [**Cloud**](/langsmith/cloud): Fastest setup, fully managed (recommended).
* [**Hybrid**](/langsmith/hybrid): Data plane in your cloud, control plane managed by LangChain.
* [**Self-hosted**](/langsmith/self-hosted): Full control in your infrastructure.
For more details, refer to the [Platform setup comparison](/langsmith/platform-setup).
**Then deploy your app:**
* [Deploy to Cloud quickstart](/langsmith/deployment-quickstart): Quick setup guide.
* [Full Cloud setup guide](/langsmith/deploy-to-cloud): Comprehensive deployment documentation.
**Explore features:**
* **[Studio](/langsmith/studio)**: Visualize, interact with, and debug your application with the Studio UI. Try the [Studio quickstart](/langsmith/quick-start-studio).
* **API References**: [LangSmith Deployment API](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref/), [Python SDK](/langsmith/langgraph-python-sdk), [JS/TS SDK](/langsmith/langgraph-js-ts-sdk)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/local-server.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Log LLM calls
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/log-llm-trace
This guide will cover how to log LLM calls to LangSmith when you are using a custom model or a custom input/output format. To make the most of LangSmith's LLM trace processing, you should log your LLM traces in one of the specified formats.
LangSmith offers the following benefits for LLM traces:
* Rich, structured rendering of message lists
* Token and cost tracking per LLM call, per trace and across traces over time
If you don't log your LLM traces in the suggested formats, you will still be able to log the data to LangSmith, but it may not be processed or rendered in expected ways.
If you are using LangChain OSS to call language models or LangSmith wrappers ([OpenAI](/langsmith/trace-openai), [Anthropic](/langsmith/trace-anthropic)), these approaches will automatically log traces in the correct format.
The examples on this page use the `traceable` decorator/wrapper to log the model run (which is the recommended approach for Python and JS/TS). However, the same idea applies if you are using the [RunTree](/langsmith/annotate-code#use-the-runtree-api) or [API](https://api.smith.langchain.com/redoc) directly.
## Messages format
When tracing a custom model or a custom input/output format, it must either follow the LangChain format, OpenAI completions format or Anthropic messages format. For more details, refer to the [OpenAI Chat Completions](https://platform.openai.com/docs/api-reference/chat/create) or [Anthropic Messages](https://platform.claude.com/docs/en/api/messages) documentation. The LangChain format is:
A list of messages containing the content of the conversation.
Identifies the message type. One of: system | reasoning | user | assistant | tool
Content of the message. List of typed dictionaries.
One of: text | image | file | audio | video | tool\_call | server\_tool\_call | server\_tool\_result.
Text content.
List of annotations for the text
Additional provider-specific data.
Text content.
Additional provider-specific data.
URL pointing to the image location.
Base64-encoded image data.
Reference ID to an externally stored image (e.g., in a provider’s file system or in a bucket).
Image [MIME type](https://www.iana.org/assignments/media-types/media-types.xhtml#image) (e.g., `image/jpeg`, `image/png`).
URL pointing to the file.
Base64-encoded file data.
Reference ID to an externally stored file (e.g., in a provider’s file system or in a bucket).
File [MIME type](https://www.iana.org/assignments/media-types/media-types.xhtml#image) (e.g., `application/pdf`).
URL pointing to the audio file.
Base64-encoded audio data.
Reference ID to an externally stored audio file (e.g., in a provider’s file system or in a bucket).
Audio [MIME type](https://www.iana.org/assignments/media-types/media-types.xhtml#image) (e.g., `audio/mpeg`, `audio/wav`).
URL pointing to the video file.
Base64-encoded video data.
Reference ID to an externally stored video file (e.g., in a provider’s file system or in a bucket).
Video [MIME type](https://www.iana.org/assignments/media-types/media-types.xhtml#image) (e.g., `video/mp4`, `video/webm`).
Arguments to pass to the tool.
Unique identifier for this tool call.
Unique identifier for this tool call.
The name of the tool to be called.
Arguments to pass to the tool.
Identifier of the corresponding server tool call.
Unique identifier for this tool call.
Execution status of the server-side tool. One of: success | error.
Output of the executed tool.
Must match the id of a prior assistant message’s tool\_calls\[i] entry. Only valid when role is tool.
Use this field to send token counts and/or costs with your model's output. See [this guide](/langsmith/log-llm-trace#provide-token-and-cost-information) for more details.
### Examples
```python Text and reasoning theme={null}
inputs = {
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Hi, can you tell me the capital of France?"
}
]
}
]
}
outputs = {
"messages": [
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "The capital of France is Paris."
},
{
"type": "reasoning",
"text": "The user is asking about..."
}
]
}
]
}
```
```python Tool calls theme={null}
input = {
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What's the weather in San Francisco?"
}
]
}
]
}
outputs = {
"messages": [
{
"role": "assistant",
"content": [{"type": "tool_call", "name": "get_weather", "args": {"city": "San Francisco"}, "id": "call_1"}],
},
{
"role": "tool",
"tool_call_id": "call_1",
"content": [
{
"type": "text",
"text": "{\"temperature\": \"18°C\", \"condition\": \"Sunny\"}"
}
]
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "The weather in San Francisco is 18°C and sunny."
}
]
}
]
}
```
```python Multimodal theme={null}
inputs = {
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What breed is this dog?"
},
{
"type": "image",
"url": "https://fastly.picsum.photos/id/237/200/300.jpg?hmac=TmmQSbShHz9CdQm0NkEjx1Dyh_Y984R9LpNrpvH2D_U",
# alternative to a url, you can provide a base64 encoded image
# "base64": "",
"mime_type": "image/jpeg",
}
]
}
]
}
outputs = {
"messages": [
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "This looks like a Black Labrador."
}
]
}
]
}
```
```python Server-side tool calls theme={null}
input = {
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What is the price of AAPL?"
}
]
}
]
}
output = {
"messages": [
{
"role": "assistant",
"content": [
{
"type": "server_tool_call",
"name": "web_search",
"args": {
"query": "price of AAPL",
"type": "search"
},
"id": "call_1"
},
{
"type": "server_tool_result",
"tool_call_id": "call_1",
"status": "success"
},
{
"type": "text",
"text": "The price of AAPL is $150.00"
}
]
}
]
}
```
## Converting custom I/O formats into LangSmith compatible formats
If you're using a custom input or output format, you can convert it to a LangSmith compatible format using `process_inputs`/`processInputs` and `process_outputs`/`processOutputs` functions on the [`@traceable` decorator](https://docs.smith.langchain.com/reference/python/run_helpers/langsmith.run_helpers.traceable) (Python) or [`traceable` function](https://docs.smith.langchain.com/reference/js/functions/traceable.traceable) (TS).
`process_inputs`/`processInputs` and `process_outputs`/`processOutputs` accept functions that allow you to transform the inputs and outputs of a specific trace before they are logged to LangSmith. They have access to the trace's inputs and outputs, and can return a new dictionary with the processed data.
Here's a boilerplate example of how to use `process_inputs` and `process_outputs` to convert a custom I/O format into a LangSmith compatible format:
```python Python theme={null}
class OriginalInputs(BaseModel):
"""Your app's custom request shape"""
class OriginalOutputs(BaseModel):
"""Your app's custom response shape."""
class LangSmithInputs(BaseModel):
"""The input format LangSmith expects."""
class LangSmithOutputs(BaseModel):
"""The output format LangSmith expects."""
def process_inputs(inputs: dict) -> dict:
"""Dict -> OriginalInputs -> LangSmithInputs -> dict"""
def process_outputs(output: Any) -> dict:
"""OriginalOutputs -> LangSmithOutputs -> dict"""
@traceable(run_type="llm", process_inputs=process_inputs, process_outputs=process_outputs)
def chat_model(inputs: dict) -> dict:
"""
Your app's model call. Keeps your custom I/O shape.
The decorators call process_* to log LangSmith-compatible format.
"""
```
## Identifying a custom model in traces
When using a custom model, it is recommended to also provide the following `metadata` fields to identify the model when viewing traces and when filtering.
* `ls_provider`: The provider of the model, e.g. "openai", "anthropic", etc.
* `ls_model_name`: The name of the model, e.g. "gpt-4.1-mini", "claude-3-opus-20240229", etc.
```python Python theme={null}
from langsmith import traceable
inputs = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "I'd like to book a table for two."},
]
output = {
"choices": [
{
"message": {
"role": "assistant",
"content": "Sure, what time would you like to book the table for?"
}
}
]
}
@traceable(
run_type="llm",
metadata={"ls_provider": "my_provider", "ls_model_name": "my_model"}
)
def chat_model(messages: list):
return output
chat_model(inputs)
```
```typescript TypeScript theme={null}
import { traceable } from "langsmith/traceable";
const messages = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "I'd like to book a table for two." }
];
const output = {
choices: [
{
message: {
role: "assistant",
content: "Sure, what time would you like to book the table for?",
},
},
],
usage_metadata: {
input_tokens: 27,
output_tokens: 13,
total_tokens: 40,
},
};
// Can also use one of:
// const output = {
// message: {
// role: "assistant",
// content: "Sure, what time would you like to book the table for?"
// }
// };
//
// const output = {
// role: "assistant",
// content: "Sure, what time would you like to book the table for?"
// };
//
// const output = ["assistant", "Sure, what time would you like to book the table for?"];
const chatModel = traceable(
async ({ messages }: { messages: { role: string; content: string }[] }) => {
return output;
},
{
run_type: "llm",
name: "chat_model",
metadata: {
ls_provider: "my_provider",
ls_model_name: "my_model"
}
}
);
await chatModel({ messages });
```
This code will log the following trace:
If you implement a custom streaming chat\_model, you can "reduce" the outputs into the same format as the non-streaming version. This is currently only supported in Python.
```python theme={null}
def _reduce_chunks(chunks: list):
all_text = "".join([chunk["choices"][0]["message"]["content"] for chunk in chunks])
return {"choices": [{"message": {"content": all_text, "role": "assistant"}}]}
@traceable(
run_type="llm",
reduce_fn=_reduce_chunks,
metadata={"ls_provider": "my_provider", "ls_model_name": "my_model"}
)
def my_streaming_chat_model(messages: list):
for chunk in ["Hello, " + messages[1]["content"]]:
yield {
"choices": [
{
"message": {
"content": chunk,
"role": "assistant",
}
}
]
}
list(
my_streaming_chat_model(
[
{"role": "system", "content": "You are a helpful assistant. Please greet the user."},
{"role": "user", "content": "polly the parrot"},
],
)
)
```
If `ls_model_name` is not present in `extra.metadata`, other fields might be used from the `extra.metadata` for estimating token counts. The following fields are used in the order of precedence:
1. `metadata.ls_model_name`
2. `inputs.model`
3. `inputs.model_name`
To learn more about how to use the `metadata` fields, refer to the [Add metadata and tags](/langsmith/add-metadata-tags) guide.
## Provide token and cost information
LangSmith calculates costs derived from token counts and model prices automatically. Learn about [how to provide tokens and/or costs in a run](/langsmith/cost-tracking#cost-tracking) and [viewing costs in the LangSmith UI](/langsmith/cost-tracking#viewing-costs-in-the-langsmith-ui).
## Time-to-first-token
If you are using `traceable` or one of our SDK wrappers, LangSmith will automatically populate time-to-first-token for streaming LLM runs.
However, if you are using the `RunTree` API directly, you will need to add a `new_token` event to the run tree in order to properly populate time-to-first-token.
Here's an example:
```python Python theme={null}
from langsmith.run_trees import RunTree
run_tree = RunTree(
name="CustomChatModel",
run_type="llm",
inputs={ ... }
)
run_tree.post()
llm_stream = ...
first_token = None
for token in llm_stream:
if first_token is None:
first_token = token
run_tree.add_event({
"name": "new_token"
})
run_tree.end(outputs={ ... })
run_tree.patch()
```
```typescript TypeScript theme={null}
import { RunTree } from "langsmith";
const runTree = new RunTree({
name: "CustomChatModel",
run_type: "llm",
inputs: { ... },
});
await runTree.postRun();
const llmStream = ...;
let firstToken;
for (const token of llmStream) {
if (firstToken == null) {
firstToken = token;
runTree.addEvent({ name: "new_token" });
}
}
await runTree.end({
outputs: { ... },
});
await runTree.patchRun();
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/log-llm-trace.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Log multimodal traces
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/log-multimodal-traces
LangSmith supports logging and rendering images as part of traces. This is currently supported for multimodal LLM runs.
In order to log images, use `wrap_openai`/ `wrapOpenAI` in Python or TypeScript respectively and pass an image URL or base64 encoded image as part of the input.
```python Python theme={null}
from openai import OpenAI
from langsmith.wrappers import wrap_openai
client = wrap_openai(OpenAI())
response = client.chat.completions.create(
model="gpt-4-turbo",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{
"type": "image_url",
"image_url": {
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg",
},
},
],
}
],
)
print(response.choices[0])
```
```typescript TypeScript theme={null}
import OpenAI from "openai";
import { wrapOpenAI } from "langsmith/wrappers";
// Wrap the OpenAI client to automatically log traces
const wrappedClient = wrapOpenAI(new OpenAI());
const response = await wrappedClient.chat.completions.create({
model: "gpt-4-turbo",
messages: [
{
role: "user",
content: [
{ type: "text", text: "What's in this image?" },
{
type: "image_url",
image_url: {
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg",
},
},
],
},
],
});
console.log(response.choices[0]);
```
The image will be rendered as part of the trace in the LangSmith UI.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/log-multimodal-traces.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Log retriever traces
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/log-retriever-trace
Nothing will break if you don't log retriever traces in the correct format and data will still be logged. However, the data will not be rendered in a way that is specific to retriever steps.
Many LLM applications require looking up documents from vector databases, knowledge graphs, or other types of indexes. Retriever traces are a way to log the documents that are retrieved by the retriever. LangSmith provides special rendering for retrieval steps in traces to make it easier to understand and diagnose retrieval issues. In order for retrieval steps to be rendered correctly, a few small steps need to be taken.
1. Annotate the retriever step with `run_type="retriever"`.
2. Return a list of Python dictionaries or TypeScript objects from the retriever step. Each dictionary should contain the following keys:
* `page_content`: The text of the document.
* `type`: This should always be "Document".
* `metadata`: A python dictionary or TypeScript object containing metadata about the document. This metadata will be displayed in the trace.
The following code snippets show how to log a retrieval steps in Python and TypeScript.
```python Python theme={null}
from langsmith import traceable
def _convert_docs(results):
return [
{
"page_content": r,
"type": "Document",
"metadata": {"foo": "bar"}
}
for r in results
]
@traceable(run_type="retriever")
def retrieve_docs(query):
# Foo retriever returning hardcoded dummy documents.
# In production, this could be a real vector datatabase or other document index.
contents = ["Document contents 1", "Document contents 2", "Document contents 3"]
return _convert_docs(contents)
retrieve_docs("User query")
```
```typescript TypeScript theme={null}
import { traceable } from "langsmith/traceable";
interface Document {
page_content: string;
type: string;
metadata: { foo: string };
}
function convertDocs(results: string[]): Document[] {
return results.map((r) => ({
page_content: r,
type: "Document",
metadata: { foo: "bar" }
}));
}
const retrieveDocs = traceable((query: string): Document[] => {
// Foo retriever returning hardcoded dummy documents.
// In production, this could be a real vector database or other document index.
const contents = ["Document contents 1", "Document contents 2", "Document contents 3"];
return convertDocs(contents);
},{
name: "retrieveDocs",
run_type: "retriever"
} // Configuration for traceable
);
await retrieveDocs("User query");
```
The following image shows how a retriever step is rendered in a trace. The contents along with the metadata are displayed with each document.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/log-retriever-trace.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Log traces to a specific project
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/log-traces-to-project
You can change the destination project of your traces both statically through environment variables and dynamically at runtime.
## Set the destination project statically
As mentioned in the [Tracing Concepts](/langsmith/observability-concepts#projects) section, LangSmith uses the concept of a `Project` to group traces. If left unspecified, the project is set to `default`. You can set the `LANGSMITH_PROJECT` environment variable to configure a custom project name for an entire application run. This should be done before executing your application.
```
export LANGSMITH_PROJECT=my-custom-project
```
The `LANGSMITH_PROJECT` flag is only supported in JS SDK versions >= 0.2.16, use `LANGCHAIN_PROJECT` instead if you are using an older version.
If the project specified does not exist, it will be created automatically when the first trace is ingested.
## Set the destination project dynamically
You can also set the project name at program runtime in various ways, depending on how you are [annotating your code for tracing](/langsmith/annotate-code). This is useful when you want to log traces to different projects within the same application.
Setting the project name dynamically using one of the below methods overrides the project name set by the `LANGSMITH_PROJECT` environment variable.
```python Python theme={null}
import openai
from langsmith import traceable
from langsmith.run_trees import RunTree
client = openai.Client()
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
]
# Use the @traceable decorator with the 'project_name' parameter to log traces to LangSmith
# Ensure that the LANGSMITH_TRACING environment variables is set for @traceable to work
@traceable(
run_type="llm",
name="OpenAI Call Decorator",
project_name="My Project"
)
def call_openai(
messages: list[dict], model: str = "gpt-4.1-mini"
) -> str:
return client.chat.completions.create(
model=model,
messages=messages,
).choices[0].message.content
# Call the decorated function
call_openai(messages)
# You can also specify the Project via the project_name parameter
# This will override the project_name specified in the @traceable decorator
call_openai(
messages,
langsmith_extra={"project_name": "My Overridden Project"},
)
# The wrapped OpenAI client accepts all the same langsmith_extra parameters
# as @traceable decorated functions, and logs traces to LangSmith automatically.
# Ensure that the LANGSMITH_TRACING environment variables is set for the wrapper to work.
from langsmith import wrappers
wrapped_client = wrappers.wrap_openai(client)
wrapped_client.chat.completions.create(
model="gpt-4.1-mini",
messages=messages,
langsmith_extra={"project_name": "My Project"},
)
# Alternatively, create a RunTree object
# You can set the project name using the project_name parameter
rt = RunTree(
run_type="llm",
name="OpenAI Call RunTree",
inputs={"messages": messages},
project_name="My Project"
)
chat_completion = client.chat.completions.create(
model="gpt-4.1-mini",
messages=messages,
)
# End and submit the run
rt.end(outputs=chat_completion)
rt.post()
```
```typescript TypeScript theme={null}
import OpenAI from "openai";
import { traceable } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers";
import { RunTree} from "langsmith";
const client = new OpenAI();
const messages = [
{role: "system", content: "You are a helpful assistant."},
{role: "user", content: "Hello!"}
];
const traceableCallOpenAI = traceable(async (messages: {role: string, content: string}[], model: string) => {
const completion = await client.chat.completions.create({
model: model,
messages: messages,
});
return completion.choices[0].message.content;
},{
run_type: "llm",
name: "OpenAI Call Traceable",
project_name: "My Project"
});
// Call the traceable function
await traceableCallOpenAI(messages, "gpt-4.1-mini");
// Create and use a RunTree object
const rt = new RunTree({
run_type: "llm",
name: "OpenAI Call RunTree",
inputs: { messages },
project_name: "My Project"
});
await rt.postRun();
// Execute a chat completion and handle it within RunTree
rt.end({outputs: chatCompletion});
await rt.patchRun();
```
```java Client theme={null}
import com.langchain.smith.otel.OtelConfig;
import com.langchain.smith.otel.OtelSpanCreator;
import com.langchain.smith.otel.OtelTraceExporter;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* Simple example: Send a single OpenTelemetry trace to LangSmith.
*
* Usage:
* export LANGSMITH_API_KEY=your_api_key
* export LANGSMITH_PROJECT=your_project_name # Optional, defaults to "default"
*/
public class OtelLangSmithSimpleExample {
public static void main(String[] args) throws Exception {
// Get API key and project name
String apiKey = System.getenv("LANGSMITH_API_KEY");
if (apiKey == null || apiKey.isEmpty()) {
System.err.println("ERROR: LANGSMITH_API_KEY environment variable is required!");
return;
}
String projectName = System.getenv("LANGSMITH_PROJECT");
if (projectName == null || projectName.isEmpty()) {
projectName = "default";
}
// Configure exporter
Map headers = new HashMap<>();
headers.put("x-api-key", apiKey);
headers.put("Langsmith-Project", projectName);
OtelConfig config = OtelConfig.builder()
.enabled(true)
.endpoint("https://api.smith.langchain.com/otel/v1/traces")
.headers(headers)
.timeout(Duration.ofSeconds(30))
.serviceName("langsmith-java-simple")
.build();
OtelTraceExporter exporter = OtelTraceExporter.fromConfig(config);
Tracer tracer = exporter.getTracer();
// Create a simple span
Span span = OtelSpanCreator.createLlmSpan(
tracer, "simple.llm.call", "openai", "gpt-4", projectName, null);
try {
OtelSpanCreator.setInput(span, "Hello, world!");
Thread.sleep(100); // Simulate processing
OtelSpanCreator.setOutput(span, "Hello! How can I help you?");
OtelSpanCreator.setTokenUsage(span, 5, 8);
span.setStatus(StatusCode.OK);
} finally {
span.end();
}
// Flush and shutdown
exporter.flush().join(5, java.util.concurrent.TimeUnit.SECONDS);
exporter.shutdown().join(2, java.util.concurrent.TimeUnit.SECONDS);
System.out.println("✓ Trace sent to LangSmith!");
}
}
```
## Set the destination workspace dynamically
If you need to dynamically route traces to different LangSmith [workspaces](/langsmith/administration-overview#workspaces) based on runtime configuration (e.g., routing different users or tenants to separate workspaces), Python users can use workspace-specific LangSmith clients with `tracing_context`, while TypeScript users can pass a custom client to `traceable` or use `LangChainTracer` with callbacks.
This approach is useful for multi-tenant applications where you want to isolate traces by customer, environment, or team at the workspace level.
### Prerequisites
* A [LangSmith API key](/langsmith/create-account-api-key) with access to multiple workspaces.
* The [workspace IDs](/langsmith/set-up-hierarchy#set-up-a-workspace) for each target workspace.
### Generic cross-workspace tracing
Use this approach for general applications where you want to dynamically route traces to different workspaces based on runtime logic (e.g., customer ID, tenant, or environment).
**Key components:**
1. Initialize separate `Client` instances for each workspace with their respective `workspace_id`.
2. Use `tracing_context` (Python) or pass the workspace-specific `client` to `traceable` (TypeScript) to route traces.
3. Pass workspace configuration through your application's runtime config.
```python Python theme={null}
import os
import contextlib
from langsmith import Client, traceable, tracing_context
# API key with access to multiple workspaces
api_key = os.getenv("LS_CROSS_WORKSPACE_KEY")
# Initialize clients for different workspaces
workspace_a_client = Client(
api_key=api_key,
api_url="https://api.smith.langchain.com",
workspace_id="" # e.g., "abc123..."
)
workspace_b_client = Client(
api_key=api_key,
api_url="https://api.smith.langchain.com",
workspace_id="" # e.g., "def456..."
)
# Example: Route based on customer ID
def get_workspace_client(customer_id: str):
"""Route to appropriate workspace based on customer."""
if customer_id.startswith("premium_"):
return workspace_a_client, "premium-customer-traces"
else:
return workspace_b_client, "standard-customer-traces"
@traceable
def process_request(data: dict, customer_id: str):
"""Process a customer request with workspace-specific tracing."""
# Your business logic here
return {"status": "success", "data": data}
# Use tracing_context to route to the appropriate workspace
def handle_customer_request(customer_id: str, request_data: dict):
client, project_name = get_workspace_client(customer_id)
# Everything within this context will be traced to the selected workspace
with tracing_context(enabled=True, client=client, project_name=project_name):
result = process_request(request_data, customer_id)
return result
# Example usage
handle_customer_request("premium_user_123", {"query": "Hello"})
handle_customer_request("standard_user_456", {"query": "Hi"})
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
import { traceable } from "langsmith/traceable";
// API key with access to multiple workspaces
const apiKey = process.env.LS_CROSS_WORKSPACE_KEY;
// Initialize clients for different workspaces
const workspaceAClient = new Client({
apiKey: apiKey,
apiUrl: "https://api.smith.langchain.com",
workspaceId: "", // e.g., "abc123..."
});
const workspaceBClient = new Client({
apiKey: apiKey,
apiUrl: "https://api.smith.langchain.com",
workspaceId: "", // e.g., "def456..."
});
// Example: Route based on customer ID
function getWorkspaceClient(customerId: string): {
client: Client;
projectName: string;
} {
if (customerId.startsWith("premium_")) {
return {
client: workspaceAClient,
projectName: "premium-customer-traces",
};
} else {
return {
client: workspaceBClient,
projectName: "standard-customer-traces",
};
}
}
// Route traces to the appropriate workspace by passing the client to traceable
async function handleCustomerRequest(
customerId: string,
requestData: Record
) {
const { client, projectName } = getWorkspaceClient(customerId);
// Create a traceable function with the workspace-specific client
const processRequest = traceable(
async (data: Record, customerId: string) => {
// Your business logic here
return { status: "success", data };
},
{
name: "process_request",
client,
project_name: projectName,
}
);
return await processRequest(requestData, customerId);
}
// Example usage
await handleCustomerRequest("premium_user_123", { query: "Hello" });
await handleCustomerRequest("standard_user_456", { query: "Hi" });
```
### Override default workspace for LangSmith deployments
When [deploying agents](/langsmith/deployments) to LangSmith, you can override the default workspace that traces are sent to by using a graph lifespan context manager. This is useful when you want to route traces from a deployed agent to different workspaces based on runtime configuration passed through the `config` parameter.
```python Python theme={null}
import os
import contextlib
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.graph.state import RunnableConfig
from langsmith import Client, tracing_context
# API key with access to multiple workspaces
api_key = os.getenv("LS_CROSS_WORKSPACE_KEY")
# Initialize clients for different workspaces
workspace_a_client = Client(
api_key=api_key,
api_url="https://api.smith.langchain.com",
workspace_id=""
)
workspace_b_client = Client(
api_key=api_key,
api_url="https://api.smith.langchain.com",
workspace_id=""
)
# Define configuration schema for workspace routing
class Configuration(TypedDict):
workspace_id: str
# Define the graph state
class State(TypedDict):
response: str
def greeting(state: State, config: RunnableConfig) -> State:
"""Generate a workspace-specific greeting."""
workspace_id = config.get("configurable", {}).get("workspace_id", "workspace_a")
if workspace_id == "workspace_a":
response = "Hello from Workspace A!"
elif workspace_id == "workspace_b":
response = "Hello from Workspace B!"
else:
response = "Hello from the default workspace!"
return {"response": response}
# Build the base graph
base_graph = (
StateGraph(state_schema=State, config_schema=Configuration)
.add_node("greeting", greeting)
.set_entry_point("greeting")
.set_finish_point("greeting")
.compile()
)
@contextlib.asynccontextmanager
async def graph(config):
"""Dynamically route traces to different workspaces based on configuration."""
# Extract workspace_id from the configuration
workspace_id = config.get("configurable", {}).get("workspace_id", "workspace_a")
# Route to the appropriate workspace
if workspace_id == "workspace_a":
client = workspace_a_client
project_name = "production-traces"
elif workspace_id == "workspace_b":
client = workspace_b_client
project_name = "development-traces"
else:
client = workspace_a_client
project_name = "default-traces"
# Apply the tracing context for the selected workspace
with tracing_context(enabled=True, client=client, project_name=project_name):
yield base_graph
# Usage: Invoke with different workspace configurations
# await graph({"configurable": {"workspace_id": "workspace_a"}})
# await graph({"configurable": {"workspace_id": "workspace_b"}})
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
import { LangChainTracer } from "@langchain/core/tracers/tracer_langchain";
import { StateGraph, Annotation } from "@langchain/langgraph";
// API key with access to multiple workspaces
const apiKey = process.env.LS_CROSS_WORKSPACE_KEY;
// Initialize clients for different workspaces
const workspaceAClient = new Client({
apiKey: apiKey,
apiUrl: "https://api.smith.langchain.com",
workspaceId: "", // e.g., "abc123..."
});
const workspaceBClient = new Client({
apiKey: apiKey,
apiUrl: "https://api.smith.langchain.com",
workspaceId: "", // e.g., "def456..."
});
// Define the graph state
const StateAnnotation = Annotation.Root({
response: Annotation(),
});
async function greeting(state: typeof StateAnnotation.State, config: any) {
const workspaceId = config?.configurable?.workspace_id || "workspace_a";
let response: string;
if (workspaceId === "workspace_a") {
response = "Hello from Workspace A!";
} else if (workspaceId === "workspace_b") {
response = "Hello from Workspace B!";
} else {
response = "Hello from the default workspace!";
}
return { response };
}
// Build the base graph
const baseGraph = new StateGraph(StateAnnotation)
.addNode("greeting", greeting)
.addEdge("__start__", "greeting")
.addEdge("greeting", "__end__")
.compile();
// Helper to get workspace-specific client and project
function getWorkspaceConfig(workspaceId: string): {
client: Client;
projectName: string;
} {
if (workspaceId === "workspace_a") {
return { client: workspaceAClient, projectName: "production-traces" };
} else if (workspaceId === "workspace_b") {
return { client: workspaceBClient, projectName: "development-traces" };
}
return { client: workspaceAClient, projectName: "default-traces" };
}
// Invoke the graph with workspace-specific tracing
async function invokeWithWorkspaceTracing(
workspaceId: string,
input: typeof StateAnnotation.State
) {
const { client, projectName } = getWorkspaceConfig(workspaceId);
// Create a LangChainTracer with the workspace-specific client
const tracer = new LangChainTracer({
client,
projectName,
});
// Invoke the graph with the tracer attached via callbacks
// All traces will be routed to the selected workspace
return await baseGraph.invoke(input, {
configurable: { workspace_id: workspaceId },
callbacks: [tracer],
});
}
// Example usage
await invokeWithWorkspaceTracing("workspace_a", { response: "" });
await invokeWithWorkspaceTracing("workspace_b", { response: "" });
```
### Key points
* **Generic cross-workspace tracing**: Use `tracing_context` (Python) or pass a workspace-specific `client` to `traceable` (TypeScript) to dynamically route traces to different workspaces.
* **LangGraph cross-workspace tracing**: For [LangGraph applications](/oss/python/langgraph/overview), use `LangChainTracer` with the workspace-specific client and attach it via the `callbacks` parameter.
* **LangSmith deployment override**: Use a graph lifespan context manager (Python) to override the default deployment workspace based on runtime configuration.
* Each `Client` instance maintains its own connection to a specific workspace via the `workspaceId` parameter.
* You can customize both the workspace and project name for each route.
* This pattern works with any LangSmith-compatible tracing (LangChain, OpenAI, custom functions, etc.).
When deploying with cross-workspace tracing, ensure your API key has the necessary permissions for all target workspaces. For LangSmith deployments, you must add an API key with cross-workspace access to your environment variables (e.g., `LS_CROSS_WORKSPACE_KEY`) to override the default service key generated by your deployment.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/log-traces-to-project.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Metadata parameters reference
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/ls-metadata-parameters
When you trace LLM calls with LangSmith, you often want to [track costs](/langsmith/cost-tracking), compare model configurations, and analyze performance across different providers. LangSmith's native integrations (like [LangChain](/langsmith/trace-with-langchain) or the [OpenAI](/langsmith/trace-openai)/[Anthropic](/langsmith/trace-anthropic) wrappers) handle this automatically, but custom model wrappers and self-hosted models require a standardized way to provide this information. LangSmith uses `ls_` metadata parameters for this purpose.
These metadata parameters (all prefixed with `ls_`) let you pass model configuration and identification information through the standard `metadata` field. Once set, LangSmith can automatically calculate costs, display model information in the UI, and enable filtering and analytics across your traces.
Use `ls_` metadata parameters to:
* **Enable automatic cost tracking** for custom or self-hosted models by identifying the provider and model name.
* **Track model configuration** like temperature, max tokens, and other parameters for experiment comparison.
* **Filter and analyze traces** by provider or configuration settings
* **Improve debugging** by recording exactly which model settings were used for each run.
## Basic usage example
The most common use case is enabling cost tracking for custom model wrappers. To do this, you need to provide two key pieces of information: the provider name (`ls_provider`) and the model name (`ls_model_name`). These work together to match against LangSmith's pricing database.
```python Python theme={null}
from langsmith import traceable
@traceable(
run_type="llm",
metadata={
"ls_provider": "my_provider",
"ls_model_name": "my_custom_model"
}
)
def my_custom_llm(prompt: str):
return call_custom_api(prompt)
```
```typescript TypeScript theme={null}
import { traceable } from "langsmith/traceable";
const myCustomLlm = traceable(
async (prompt: string) => {
return callCustomApi(prompt);
},
{
run_type: "llm",
metadata: {
ls_provider: "my_provider",
ls_model_name: "my_custom_model"
}
}
);
```
This minimal setup tells LangSmith what model you're using, enabling automatic cost calculation if the model exists in the pricing database or if you've [configured custom pricing](/langsmith/cost-tracking#set-up-model-pricing).
For more comprehensive tracking, you can include additional configuration parameters. This is especially useful when [running experiments](/langsmith/evaluation-quickstart) or comparing different model settings:
```python Python theme={null}
@traceable(
run_type="llm",
metadata={
"ls_provider": "openai",
"ls_model_name": "gpt-4.1",
"ls_temperature": 0.7,
"ls_max_tokens": 4096,
"ls_stop": ["END"],
"ls_invocation_params": {
"top_p": 0.9,
"frequency_penalty": 0.5
}
}
)
def my_configured_llm(messages: list):
return call_llm(messages)
```
```typescript TypeScript theme={null}
const myConfiguredLlm = traceable(
async (messages: Array) => {
return callLlm(messages);
},
{
run_type: "llm",
metadata: {
ls_provider: "openai",
ls_model_name: "gpt-4.1",
ls_temperature: 0.7,
ls_max_tokens: 4096,
ls_stop: ["END"],
ls_invocation_params: {
top_p: 0.9,
frequency_penalty: 0.5
}
}
}
);
```
With this setup, you can later filter traces by temperature, compare runs with different max token settings, or analyze which configuration parameters produce the best results. All these parameters are optional except for the `ls_provider` and `ls_model_name` pair needed for cost tracking.
## All parameters
### User-configurable parameters
| Parameter | Type | Required | Description |
| ----------------------------------------------- | ---------- | -------- | ----------------------------------- |
| [`ls_provider`](#ls-provider) | `string` | Yes\* | LLM provider name for cost tracking |
| [`ls_model_name`](#ls-model-name) | `string` | Yes\* | Model identifier for cost tracking |
| [`ls_temperature`](#ls-temperature) | `number` | No | Temperature parameter used |
| [`ls_max_tokens`](#ls-max-tokens) | `number` | No | Maximum tokens parameter used |
| [`ls_stop`](#ls-stop) | `string[]` | No | Stop sequences used |
| [`ls_invocation_params`](#ls-invocation-params) | `object` | No | Additional invocation parameters |
\* `ls_provider` and `ls_model_name` must be provided together for cost tracking
### System-generated parameters
| Parameter | Type | Description |
| ------------------------------- | --------- | ---------------------------------------------------------------------- |
| [`ls_run_depth`](#ls-run-depth) | `integer` | Depth in trace tree (0=root, 1=child, etc.) - automatically calculated |
| [`ls_method`](#ls-method) | `string` | Tracing method used (e.g., "traceable") - set by SDK |
### Experiment parameters
| Parameter | Type | Description |
| --------------------------------------- | --------------- | ----------------------------------------------------------------------- |
| [`ls_example_*`](#ls-example-) | `any` | Example metadata prefixed with `ls_example_` - added during experiments |
| [`ls_experiment_id`](#ls-experiment-id) | `string` (UUID) | Unique experiment identifier - added during experiments |
## Parameter details
### `ls_provider`
* **Type:** `string`
* **Required:** Yes (with [`ls_model_name`](#ls-model-name))
**What it does:**
Identifies the LLM provider. Combined with `ls_model_name`, enables automatic cost calculation by matching against [LangSmith's model pricing database](https://smith.langchain.com/settings/workspaces/models).
**Common values:**
* `"openai"`
* `"anthropic"`
* `"azure"`
* `"bedrock"`
* `"google_vertexai"`
* `"google_genai"`
* `"fireworks"`
* `"mistral"`
* `"groq"`
* Or, any custom string
**When to use:**
When you want [automatic cost tracking](/langsmith/cost-tracking) for custom model wrappers or self-hosted models.
**Example:**
```python theme={null}
@traceable(
run_type="llm",
metadata={
"ls_provider": "openai",
"ls_model_name": "gpt-4.1"
}
)
def my_llm_call(prompt: str):
return call_api(prompt)
```
**Relationships:**
* **Requires** [`ls_model_name`](#ls-model-name) for cost tracking to work.
* Works with token usage data to calculate costs.
### `ls_model_name`
* **Type:** `string`
* **Required:** Yes (with `ls_provider`)
**What it does:**
Identifies the specific model. Combined with `ls_provider`, matches against pricing database for automatic cost calculation.
**Common values:**
* OpenAI: `"gpt-4.1"`, `"gpt-4.1-mini"`, `"gpt-3.5-turbo"`
* Anthropic: `"claude-3-5-sonnet-20241022"`, `"claude-3-opus-20240229"`
* Custom: Any model identifier
**When to use:**
When you want automatic [cost tracking](/langsmith/cost-tracking) and model identification in the [UI](https://smith.langchain.com).
**Example:**
```python theme={null}
@traceable(
run_type="llm",
metadata={
"ls_provider": "anthropic",
"ls_model_name": "claude-3-5-sonnet-20241022"
}
)
def my_claude_call(messages: list):
return call_claude(messages)
```
**Relationships:**
* **Requires** [`ls_provider`](#ls-provider) for cost tracking to work.
* Works with token usage data to calculate costs.
### `ls_temperature`
* **Type:** `number` (nullable)
* **Required:** No
**What it does:**
Records the temperature setting used. This is for tracking only—does not affect LangSmith behavior.
**When to use:**
When you want to track model configuration for experiments or debugging.
**Example:**
```python theme={null}
metadata={
"ls_provider": "openai",
"ls_model_name": "gpt-4.1",
"ls_temperature": 0.7
}
```
**Relationships:**
* Independent; just for tracking.
* Useful alongside other config parameters for experiment comparison.
### `ls_max_tokens`
* **Type:** `number` (nullable)
* **Required:** No
**What it does:**
Records the maximum tokens setting used. This is for tracking only—does not affect LangSmith behavior.
**When to use:**
When you want to track model configuration for experiments or debugging.
**Example:**
```python theme={null}
metadata={
"ls_provider": "openai",
"ls_model_name": "gpt-4.1",
"ls_max_tokens": 4096
}
```
**Relationships:**
* Independent; just for tracking.
* Useful for cost analysis when combined with actual token usage.
### `ls_stop`
* **Type:** `string[]` (nullable)
* **Required:** No
**What it does:**
Records stop sequences used. This is for tracking only—does not affect LangSmith behavior.
**When to use:**
When you want to track model configuration for experiments or debugging.
**Example:**
```python theme={null}
metadata={
"ls_provider": "openai",
"ls_model_name": "gpt-4.1",
"ls_stop": ["END", "STOP", "\n\n"]
}
```
**Relationships:**
* Independent; just for tracking.
### `ls_invocation_params`
* **Type:** `object` (any key-value pairs)
* **Required:** No
**What it does:**
Stores additional model parameters that don't fit the specific `ls_` parameters. Can include provider-specific settings.
**Common parameters:**
`top_p`, `frequency_penalty`, `presence_penalty`, `top_k`, `seed`, or any custom parameters
**When to use:**
When you need to track additional configuration beyond the standard parameters.
**Example:**
```python theme={null}
metadata={
"ls_provider": "openai",
"ls_model_name": "gpt-4.1",
"ls_invocation_params": {
"top_p": 0.9,
"frequency_penalty": 0.5,
"presence_penalty": 0.3,
"seed": 12345
}
}
```
**Relationships:**
* Independent; stores arbitrary configuration.
### `ls_run_depth`
* **Type:** `integer`
* **Set by:** LangSmith backend (automatic)
* **Cannot be overridden**
**What it does:**
Indicates depth in the trace tree:
* `0` = Root run (top-level)
* `1` = Direct child
* `2` = Grandchild
* etc.
**When it's used:**
Automatically calculated during trace ingestion. Used for filtering (e.g., "show only root runs") and UI visualization.
**Example query:**
```
metadata_key = 'ls_run_depth' AND metadata_value = 0
```
**Relationships:**
* Determined by trace parent-child structure.
* Cannot be set manually.
### `ls_method`
* **Type:** `string`
* **Set by:** SDK (automatic)
**What it does:**
Indicates which SDK method created the trace (commonly `"traceable"` for `@traceable` decorator).
**When it's used:**
Automatically set by the tracing SDK. Used for debugging and analytics.
**Relationships:**
* Set by SDK based on how trace was created.
* Cannot be set manually.
### `ls_example_*`
* **Type:** Any (depends on example metadata)
* **Pattern:** `ls_example_{original_key}`
* **Set by:** LangSmith experiments system (automatic)
**What it does:**
When running [experiments on datasets](/langsmith/evaluation-quickstart), metadata from the example is automatically prefixed with `ls_example_` and added to the trace.
**Special parameter:**
* `ls_example_dataset_split`: Dataset split (e.g., "train", "test", "validation")
**When it's used:**
During dataset experiments. Allows filtering/grouping by example characteristics.
**Example:**
If example has metadata `{"category": "technical", "difficulty": "hard"}`, trace gets:
```json theme={null}
{
"metadata": {
"ls_example_category": "technical",
"ls_example_difficulty": "hard",
"ls_example_dataset_split": "test"
}
}
```
**Relationships:**
* Automatically derived from example metadata.
* Cannot be set manually on traces.
### `ls_experiment_id`
* **Type:** `string` (UUID)
* **Set by:** LangSmith experiments system (automatic)
**What it does:**
Unique identifier for an experiment run.
**When it's used:**
Automatically added when running [experiments/evaluations on datasets](/langsmith/evaluation-quickstart). Used to group all runs from the same experiment.
**Relationships:**
* Links runs to specific experiments.
* Cannot be set manually.
## Parameter relationships
### Cost tracking dependencies
For LangSmith to automatically calculate costs, several parameters must work together. Here's what's required:
**Primary requirement:** [`ls_provider`](#ls-provider) + [`ls_model_name`](#ls-model-name)
* Both should be present for automatic cost calculation.
* If [`ls_model_name`](#ls-model-name) is missing, system will fall back to checking [`ls_invocation_params`](#ls-invocation-params) for model name.
* [`ls_provider`](#ls-provider) must match a provider in the [pricing database](https://smith.langchain.com/settings/workspaces/models) (or use custom pricing).
**Additional requirements:**
* Run must have `run_type="llm"` (or [arbitrary cost tracking](/langsmith/cost-tracking#tracking-costs-for-arbitrary-runs) must be enabled).
* [Token usage data](/langsmith/log-llm-trace#provide-token-and-cost-information) must be present in the trace (prompt\_tokens, completion\_tokens).
* Model must exist in pricing database or have [custom pricing configured](/langsmith/cost-tracking#set-up-model-pricing).
**Fallback behavior:**
If [`ls_model_name`](#ls-model-name) is not in metadata, the system checks [`ls_invocation_params`](#ls-invocation-params) for model identifiers like `"model"` before giving up on cost tracking.
### Configuration tracking group
These parameters help you track model settings but don't affect LangSmith's core functionality:
**Optional, work independently:** [`ls_temperature`](#ls-temperature), [`ls_max_tokens`](#ls-max-tokens), [`ls_stop`](#ls-stop)
* These are for tracking/display.
* Do not affect LangSmith behavior or cost calculation.
* Useful for experiment comparison and debugging.
### Invocation params special case
The `ls_invocation_params` parameter has a dual role as both a tracking field and a fallback mechanism:
**[`ls_invocation_params`](#ls-invocation-params)**; partially independent with fallback role:
* Primarily stores arbitrary configuration for tracking.
* **Can serve as fallback** for cost tracking if [`ls_model_name`](#ls-model-name) is missing.
* Does not directly affect cost calculation when [`ls_model_name`](#ls-model-name) is present.
### System parameters
These parameters are automatically generated by LangSmith and cannot be manually set:
**Cannot be user-set:** [`ls_run_depth`](#ls-run-depth), [`ls_method`](#ls-method), [`ls_example_*`](#ls-example-), [`ls_experiment_id`](#ls-experiment-id)
* Automatically set by system.
* Used for filtering, analytics, and system tracking.
## Filter traces by metadata parameters
Once you've added `ls_` metadata parameters to your traces, you can use them to filter and search traces programmatically via the [API](https://api.smith.langchain.com/redoc#tag/run/operation/query_runs_api_v1_runs_query_post) or interactively in the [LangSmith UI](https://smith.langchain.com). This lets you narrow down traces by model, provider, configuration settings, or trace depth.
### Use the API
Use the [`Client`](https://docs.smith.langchain.com/reference/python/client/langsmith.client.Client) class with the [`list_runs()`](https://docs.smith.langchain.com/reference/python/client/langsmith.client.Client#langsmith.client.Client.list_runs) method (Python) or [`listRuns()`](https://docs.smith.langchain.com/reference/js/classes/client.Client#listruns) method (TypeScript) to query traces based on metadata values. The [filter syntax](/langsmith/trace-query-syntax) supports equality checks, comparisons, and logical operators.
```python Python theme={null}
from langsmith import Client
client = Client()
# Filter runs by provider
runs = client.list_runs(
project_name="my-app",
filter='metadata_key = "ls_provider" AND metadata_value = "openai"'
)
# Filter by specific model
runs = client.list_runs(
project_name="my-app",
filter='metadata_key = "ls_model_name" AND metadata_value = "gpt-4.1"'
)
# Filter root runs only (top-level traces)
runs = client.list_runs(
project_name="my-app",
filter='metadata_key = "ls_run_depth" AND metadata_value = 0'
)
# Filter by temperature threshold
runs = client.list_runs(
project_name="my-app",
filter='metadata_key = "ls_temperature" AND metadata_value > 0.5'
)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
const client = new Client();
// Filter runs by provider
const runsByProvider: any[] = [];
for await (const run of client.listRuns({
projectName: "my-app",
filter: 'metadata_key = "ls_provider" AND metadata_value = "openai"'
})) {
runsByProvider.push(run);
}
// Filter by specific model
const runsByModel: any[] = [];
for await (const run of client.listRuns({
projectName: "my-app",
filter: 'metadata_key = "ls_model_name" AND metadata_value = "gpt-4.1"'
})) {
runsByModel.push(run);
}
// Filter root runs only (top-level traces)
const rootRuns: any[] = [];
for await (const run of client.listRuns({
projectName: "my-app",
filter: 'metadata_key = "ls_run_depth" AND metadata_value = 0'
})) {
rootRuns.push(run);
}
// Filter by temperature threshold
const highTempRuns: any[] = [];
for await (const run of client.listRuns({
projectName: "my-app",
filter: 'metadata_key = "ls_temperature" AND metadata_value > 0.5'
})) {
highTempRuns.push(run);
}
```
These examples show common filtering patterns:
* **Filter by provider or model** to analyze usage patterns or costs for specific models
* **Filter by run depth** to get only root traces (depth 0) or child runs at specific nesting levels
* **Filter by configuration** to compare experiments with different temperature, max tokens, or other settings
### Use the UI
In the [LangSmith UI](https://smith.langchain.com), use the filter/search bar with the [filter syntax](/langsmith/trace-query-syntax):
```
metadata_key = 'ls_provider' AND metadata_value = 'openai'
metadata_key = 'ls_model_name' AND metadata_value = 'gpt-4.1'
metadata_key = 'ls_run_depth' AND metadata_value = 0
```
## Related resources
* [Cost tracking guide](/langsmith/cost-tracking): Learn how to track and analyze LLM costs in LangSmith.
* [Log LLM traces](/langsmith/log-llm-trace): Format requirements for logging LLM calls with proper token tracking.
* [Trace query syntax](/langsmith/trace-query-syntax): Complete reference for filtering and searching traces.
* [Evaluation quickstart](/langsmith/evaluation-quickstart): Run experiments on datasets to compare model configurations.
* [Add metadata and tags](/langsmith/add-metadata-tags): General guide to adding metadata to traces.
* [Filter traces in application](/langsmith/filter-traces-in-application): Programmatically filter traces in your code.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/ls-metadata-parameters.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Manage datasets
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/manage-datasets
LangSmith provides tools for managing and working with your [*datasets*](/langsmith/evaluation-concepts#datasets). This page describes dataset operations including:
* [Versioning datasets](#version-a-dataset) to track changes over time.
* [Filtering](#evaluate-on-a-filtered-view-of-a-dataset) and [splitting](#evaluate-on-a-dataset-split) datasets for evaluation.
* [Sharing datasets](#share-a-dataset) publicly.
* [Exporting datasets](#export-a-dataset) in various formats.
You'll also learn how to [export filtered traces](#export-filtered-traces-from-experiment-to-dataset) from [experiments](/langsmith/evaluation-concepts#experiment) back to datasets for further analysis and iteration.
## Version a dataset
In LangSmith, datasets are versioned. This means that every time you add, update, or delete examples in your dataset, a new version of the dataset is created.
### Create a new version of a dataset
Any time you add, update, or delete examples in your dataset, a new [version](/langsmith/evaluation-concepts#versions) of your dataset is created. This allows you to track changes to your dataset over time and understand how your dataset has evolved.
By default, the version is defined by the timestamp of the change. When you click on a particular version of a dataset (by timestamp) in the **Examples** tab, you will find the state of the dataset at that point in time.
Note that examples are read-only when viewing a past version of the dataset. You will also see the operations that were between this version of the dataset and the latest version of the dataset.
By default, the latest version of the dataset is shown in the **Examples** tab and experiments from all versions are shown in the **Tests** tab.
In the **Tests** tab, you will find the results of tests run on the dataset at different versions.
### Tag a version
You can also tag versions of your dataset to give them a more human-readable name, which can be useful for marking important milestones in your dataset's history.
For example, you might tag a version of your dataset as "prod" and use it to run tests against your LLM pipeline.
You can tag a version of your dataset in the UI by clicking on **+ Tag this version** in the **Examples** tab.
You can also tag versions of your dataset using the SDK. Here's an example of how to tag a version of a dataset using the [Python SDK](https://docs.smith.langchain.com/reference/python/reference):
```python theme={null}
from langsmith import Client
from datetime import datetime
client = Client()
initial_time = datetime(2024, 1, 1, 0, 0, 0) # The timestamp of the version you want to tag
# You can tag a specific dataset version with a semantic name, like "prod"
client.update_dataset_tag(
dataset_name=toxic_dataset_name, as_of=initial_time, tag="prod"
)
```
To run an evaluation on a particular tagged version of a dataset, refer to the [Evaluate on a specific dataset version section](#evaluate-on-specific-dataset-version).
## Evaluate on a specific dataset version
You may find it helpful to refer to the following content before you read this section:
* [Version a dataset](#version-a-dataset).
* [Fetching examples](/langsmith/manage-datasets-programmatically#fetch-examples).
### Use `list_examples`
You can use `evaluate` / `aevaluate` to pass in an iterable of examples to evaluate on a particular version of a dataset. Use `list_examples` / `listExamples` to fetch examples from a particular version tag using `as_of` / `asOf` and pass that into the `data` argument.
```python Python theme={null}
from langsmith import Client
ls_client = Client()
# Assumes actual outputs have a 'class' key.
# Assumes example outputs have a 'label' key.
def correct(outputs: dict, reference_outputs: dict) -> bool:
return outputs["class"] == reference_outputs["label"]
results = ls_client.evaluate(
lambda inputs: {"class": "Not toxic"},
# Pass in filtered data here:
data=ls_client.list_examples(
dataset_name="Toxic Queries",
as_of="latest", # specify version here
),
evaluators=[correct],
)
```
```typescript TypeScript theme={null}
import { evaluate } from "langsmith/evaluation";
await evaluate((inputs) => labelText(inputs["input"]), {
data: langsmith.listExamples({
datasetName: datasetName,
asOf: "latest",
}),
evaluators: [correctLabel],
});
```
```java Java theme={null}
import com.langchain.smith.models.examples.ExampleListParams;
ExampleListParams listParams = ExampleListParams.builder()
.datasetId(datasetId)
.asOf("latest")
var examples = client.examples().list(listParams);
```
Learn more about how to fetch views of a dataset on the [Create and manage datasets programmatically](/langsmith/manage-datasets-programmatically#fetch-datasets) page.
## Evaluate on a split / filtered view of a dataset
You may find it helpful to refer to the following content before you read this section:
* [Fetching examples](/langsmith/manage-datasets-programmatically#fetch-examples).
* [Creating and managing dataset splits](/langsmith/manage-datasets-in-application#create-and-manage-dataset-splits).
### Evaluate on a filtered view of a dataset
You can use the `list_examples` / `listExamples` method to [fetch](/langsmith/manage-datasets-programmatically#fetch-examples) a subset of examples from a dataset to evaluate on.
One common workflow is to fetch examples that have a certain metadata key-value pair.
```python Python theme={null}
from langsmith import evaluate
results = evaluate(
lambda inputs: label_text(inputs["text"]),
data=client.list_examples(dataset_name=dataset_name, metadata={"desired_key": "desired_value"}),
evaluators=[correct_label],
experiment_prefix="Toxic Queries",
)
```
```typescript TypeScript theme={null}
import { evaluate } from "langsmith/evaluation";
await evaluate((inputs) => labelText(inputs["input"]), {
data: langsmith.listExamples({
datasetName: datasetName,
metadata: {"desired_key": "desired_value"},
}),
evaluators: [correctLabel],
experimentPrefix: "Toxic Queries",
});
```
```java Java theme={null}
import com.langchain.smith.models.examples.ExampleListParams;
ExampleListParams listParams = ExampleListParams.builder()
.datasetId(datasetId)
.metadata("{\"desired_key\":\"desired_value\"}")
.build();
var examples = client.examples().list(listParams);
```
For more filtering capabilities, refer to this [how-to guide](/langsmith/manage-datasets-programmatically#list-examples-by-structured-filter).
### Evaluate on a dataset split
You can use the `list_examples` / `listExamples` method to evaluate on one or multiple [splits](/langsmith/evaluation-concepts#splits) of your dataset. The `splits` parameter takes a list of the splits you would like to evaluate.
```python Python theme={null}
from langsmith import evaluate
results = evaluate(
lambda inputs: label_text(inputs["text"]),
data=client.list_examples(dataset_name=dataset_name, splits=["test", "training"]),
evaluators=[correct_label],
experiment_prefix="Toxic Queries",
)
```
```typescript TypeScript theme={null}
import { evaluate } from "langsmith/evaluation";
await evaluate((inputs) => labelText(inputs["input"]), {
data: langsmith.listExamples({
datasetName: datasetName,
splits: ["test", "training"],
}),
evaluators: [correctLabel],
experimentPrefix: "Toxic Queries",
});
```
```java Java theme={null}
import com.langchain.smith.models.examples.ExampleListParams;
import java.util.Arrays;
import java.util.List;
List splits = Arrays.asList("test", "training");
ExampleListParams listParams = ExampleListParams.builder()
.datasetId(datasetId)
.splits(splits)
.build();
var examples = client.examples().list(listParams);
```
For more details on fetching views of a dataset, refer to the guide on [fetching datasets](/langsmith/manage-datasets-programmatically#fetch-datasets).
## Share a dataset
### Share a dataset publicly
Sharing a dataset publicly will make the **dataset examples, experiments and associated runs, and feedback on this dataset accessible to anyone with the link**, even if they don't have a LangSmith account. Make sure you're not sharing sensitive information.
This feature is only available in the cloud-hosted version of LangSmith.
From the **Dataset & Experiments** tab, select a dataset, click **⋮** (top right of the page), click **Share Dataset**. This will open a dialog where you can copy the link to the dataset.
### Unshare a dataset
1. Click on **Unshare** by clicking on **Public** in the upper right hand corner of any publicly shared dataset, then **Unshare** in the dialog.
2. Navigate to your organization's list of publicly shared datasets, by clicking on **Settings** -> **Shared URLs** or [this link](https://smith.langchain.com/settings/shared), then click on **Unshare** next to the dataset you want to unshare.
## Export a dataset
You can export your LangSmith dataset to a CSV, JSONL, or [OpenAI's fine tuning format](https://platform.openai.com/docs/guides/fine-tuning#example-format) from the LangSmith UI.
From the **Dataset & Experiments** tab, select a dataset, click **⋮** (top right of the page), click **Download Dataset**.
## Export filtered traces from experiment to dataset
After running an [offline evaluation](/langsmith/evaluation-concepts#offline-evaluation) in LangSmith, you may want to export [traces](/langsmith/observability-concepts#traces) that met some evaluation criteria to a dataset.
### View experiment traces
To do so, first click on the arrow next to your experiment name. This will direct you to a project that contains the traces generated from your experiment.
From there, you can filter the traces based on your evaluation criteria. In this example, we're filtering for all traces that received an accuracy score greater than 0.5.
After applying the filter on the project, we can multi-select runs to add to the dataset, and click **Add to Dataset**.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/manage-datasets.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Create and manage datasets in the UI
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/manage-datasets-in-application
[*Datasets*](/langsmith/evaluation-concepts#datasets) enable you to perform repeatable evaluations over time using consistent data. Datasets are made up of [*examples*](/langsmith/evaluation-concepts#examples), which store inputs, outputs, and optionally, reference outputs.
This page outlines the various methods for [creating](#create-a-dataset-and-add-examples) and [managing](#manage-a-dataset) datasets in the [LangSmith UI](https://smith.langchain.com).
## Create a dataset and add examples
The following sections explain the different ways you can create a dataset in LangSmith and add examples to it. Depending on your workflow, you can manually curate examples, automatically capture them from tracing, import files, or even generate synthetic data:
* [Manually from a tracing project](#manually-from-a-tracing-project)
* [Automatically from a tracing project](#automatically-from-a-tracing-project)
* [From examples in an annotation queue](#from-examples-in-an-annotation-queue)
* [From the Prompt Playground](#from-the-prompt-playground)
* [Import a dataset from a CSV or JSONL file](#import-a-dataset-from-a-csv-or-jsonl-file)
* [Create a new dataset from the dataset page](#create-a-new-dataset-from-the-datasets-&-experiments-page)
* [Add synthetic examples created by an LLM via the Datasets UI](#add-synthetic-examples-created-by-an-llm)
### Manually from a tracing project
A common pattern for constructing datasets is to convert notable traces from your application into dataset examples. This approach requires that you have [configured tracing to LangSmith](/langsmith/observability-concepts#tracing-configuration).
A technique to build datasets is to filter the most interesting traces, such as traces that were tagged with poor user feedback, and add them to a dataset. For tips on how to filter traces, refer to [Filter traces](/langsmith/filter-traces-in-application) guide.
There are two ways to add data manually from a tracing project to datasets. Navigate to **Tracing Projects** and select a project.
1. Multi-select runs from the runs table. On the **Runs** tab, multi-select runs. At the bottom of the page, click **Add to Dataset**.
2. On the **Runs** tab, select a run from the table. On the individual run details page, select **Add to** -> **Dataset** in the top right corner.
When you select a dataset from the run details page, a modal will pop up letting you know if any [transformations](/langsmith/dataset-transformations) were applied or if schema validation failed. For example, the screenshot below shows a dataset that is using transformations to optimize for collecting LLM runs.
You can then optionally edit the run before adding it to the dataset.
### Automatically from a tracing project
You can use [run rules](/langsmith/rules) to automatically add traces to a dataset based on certain conditions. For example, you could add all traces that are [tagged](/langsmith/observability-concepts#tags) with a specific use case or have a [low feedback score](/langsmith/observability-concepts#feedback).
### From examples in an annotation queue
If you rely on subject matter experts to build meaningful datasets, use [annotation queues](/langsmith/annotation-queues) to provide a streamlined view for reviewers. Human reviewers can optionally modify the inputs/outputs/reference outputs from a trace before it is added to the dataset.
Annotation queues can be optionally configured with a default dataset, though you can add runs to any dataset by using the dataset switcher on the bottom of the screen. Once you select the right dataset, click **Add to Dataset** or hit the hot key `D` to add the run to it.
Any modifications you make to the run in your annotation queue will carry over to the dataset, and all metadata associated with the run will also be copied.
Note you can also set up rules to add runs that meet specific criteria to an annotation queue using [automation rules](/langsmith/rules).
### From the Prompt Playground
On the [**Prompt Playground**](/langsmith/observability-concepts#prompt-playground) page, select **Set up Evaluation**, click **+New** if you're starting a new dataset or select from an existing dataset.
Creating datasets inline in the playground is not supported for datasets that have nested keys. In order to add/edit examples with nested keys, you must edit [from the datasets page](/langsmith/manage-datasets-in-application#from-the-datasets-page).
To edit the examples:
* Use **+Row** to add a new example to the dataset
* Delete an example using the **⋮** dropdown on the right hand side of the table
* If you're creating a reference-free dataset remove the "Reference Output" column using the **x** button in the column. Note: this action is not reversible.
### Import a dataset from a CSV or JSONL file
On the **Datasets & Experiments** page, click **+New Dataset**, then **Import** an existing dataset from CSV or JSONL file.
### Create a new dataset from the datasets & experiments page
1. Navigate to the **Datasets & Experiments** page from the left-hand menu.
2. Click **+ New Dataset**.
3. On the **New Dataset** page, select the **Create from scratch** tab.
4. Add a name and description for the dataset.
5. (Optional) Create a [dataset schema](#create-a-dataset-schema) to validate your dataset.
6. Click **Create**, which will create an empty dataset.
7. To add examples inline, on the dataset's page, go to the **Examples** tab. Click **+ Example**.
8. Define examples in JSON and click **Submit**. For more details on dataset splits, refer to [Create and manage dataset splits](#create-and-manage-dataset-splits).
### Add synthetic examples created by an LLM
If you have existing examples and a [schema](#create-a-dataset-schema) defined on your dataset, when you click **+ Example** there is an option to **Add AI-Generated Examples**. This will use an LLM to create [synthetic](/langsmith/evaluation-concepts#synthetic-data) examples.
In **Generate examples**, do the following:
1. Click **API Key** in the top right of the pane to set your OpenAI API key as a [workspace secret](/langsmith/administration-overview#workspaces). If your workspace already has an OpenAI API key set, you can skip this step.
2. Select few-shot examples: Toggle **Automatic** or **Manual** reference examples. You can select these examples manually from your dataset or use the automatic selection option.
3. Enter the number of synthetic examples you want to generate.
4. Click **Generate**.
5. The examples will appear on the **Select generated examples** page. Choose which examples to add to your dataset, with the option to edit them before finalizing. Click **Save Examples**.
6. Each example will be validated against your specified dataset schema and tagged as **synthetic** in the source metadata.
## Manage a dataset
### Create a dataset schema
LangSmith datasets store arbitrary JSON objects. We recommend (but do not require) that you define a schema for your dataset to ensure that they conform to a specific JSON schema. Dataset schemas are defined with standard [JSON schema](https://json-schema.org/), with the addition of a few [prebuilt types](/langsmith/dataset-json-types) that make it easier to type common primitives like messages and tools.
Certain fields in your schema have a `+ Transformations` option. Transformations are preprocessing steps that, if enabled, update your examples when you add them to the dataset. For example the `convert to OpenAI messages` transformation will convert message-like objects, like LangChain messages, to OpenAI message format.
For the full list of available transformations, see [our reference](/langsmith/dataset-transformations).
If you plan to collect production traces in your dataset from LangChain [ChatModels](https://python.langchain.com/do/langsmith/observability-concepts/chat_models/) or from OpenAI calls using the [LangSmith OpenAI wrapper](/langsmith/annotate-code#wrap-the-openai-client), we offer a prebuilt Chat Model schema that converts messages and tools into industry standard openai formats that can be used downstream with any model for testing. You can also customize the template settings to match your use case.
Please see the [dataset transformations reference](/langsmith/dataset-transformations) for more information.
### Create and manage dataset splits
Dataset splits are divisions of your dataset that you can use to segment your data. For example, it is common in machine learning workflows to split datasets into training, validation, and test sets. This can be useful to prevent overfitting - where a model performs well on the training data but poorly on unseen data. In evaluation workflows, it can be useful to do this when you have a dataset with multiple categories that you may want to evaluate separately; or if you are testing a new use case that you may want to include in your dataset in the future, but want to keep separate for now. Note that the same effect can be achieved manually via metadata - but we expect splits to be used for higher level organization of your dataset to split it into separate groups for evaluation, whereas metadata would be used more for storing information on your examples like tags and information about its origin.
In machine learning, it is best practice to keep your splits separate (each example belongs to exactly one split). However, we allow you to select multiple splits for the same example in LangSmith because it can make sense for some evaluation workflows - for example, if an example falls into multiple categories on which you may want to evaluate your application.
In order to create and manage splits in the app, you can select some examples in your dataset and click "Add to Split". From the resulting popup menu, you can select and unselect splits for the selected examples, or create a new split.
### Edit example metadata
You can add metadata to your examples by clicking on an example and then clicking "Edit" on the top righthand side of the popover. From this page, you can update/delete existing metadata, or add new metadata. You may use this to store information about your examples, such as tags or version info, which you can then [group by](/langsmith/analyze-an-experiment#group-results-by-metadata) when analyzing experiment results or [filter by](/langsmith/manage-datasets-programmatically#list-examples-by-metadata) when you call `list_examples` in the SDK.
### Filter examples
You can filter examples by split, metadata key/value or perform full-text search over examples. These filtering options are available to the top left of the examples table.
* **Filter by split**: Select split > Select a split to filter by
* **Filter by metadata**: Filters > Select "Metadata" from the dropdown > Select the metadata key and value to filter on
* **Full-text search**: Filters > Select "Full Text" from the dropdown > Enter your search criteria
You may add multiple filters, and only examples that satisfy all of the filters will be displayed in the table.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/manage-datasets-in-application.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to create and manage datasets programmatically
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/manage-datasets-programmatically
You can use the Python and TypeScript SDK to manage datasets programmatically. This includes creating, updating, and deleting datasets, as well as adding examples to them.
## Create a dataset
### Create a dataset from list of values
The most flexible way to make a dataset using the client is by creating examples from a list of inputs and optional outputs. Below is an example.
Note that you can add arbitrary metadata to each example, such as a note or a source. The metadata is stored as a dictionary.
If you have many examples to create, consider using the `create_examples`/`createExamples` method to create multiple examples in a single request. If creating a single example, you can use the `create_example`/`createExample` method.
```python Python theme={null}
from langsmith import Client
examples = [
{
"inputs": {"question": "What is the largest mammal?"},
"outputs": {"answer": "The blue whale"},
"metadata": {"source": "Wikipedia"},
},
{
"inputs": {"question": "What do mammals and birds have in common?"},
"outputs": {"answer": "They are both warm-blooded"},
"metadata": {"source": "Wikipedia"},
},
{
"inputs": {"question": "What are reptiles known for?"},
"outputs": {"answer": "Having scales"},
"metadata": {"source": "Wikipedia"},
},
{
"inputs": {"question": "What's the main characteristic of amphibians?"},
"outputs": {"answer": "They live both in water and on land"},
"metadata": {"source": "Wikipedia"},
},
]
client = Client()
dataset_name = "Elementary Animal Questions"
# Storing inputs in a dataset lets us
# run chains and LLMs over a shared set of examples.
dataset = client.create_dataset(
dataset_name=dataset_name, description="Questions and answers about animal phylogenetics.",
)
# Prepare inputs, outputs, and metadata for bulk creation
client.create_examples(
dataset_id=dataset.id,
examples=examples
)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
const client = new Client();
const exampleInputs: [string, string][] = [
["What is the largest mammal?", "The blue whale"],
["What do mammals and birds have in common?", "They are both warm-blooded"],
["What are reptiles known for?", "Having scales"],
[
"What's the main characteristic of amphibians?",
"They live both in water and on land",
],
];
const datasetName = "Elementary Animal Questions";
// Storing inputs in a dataset lets us
// run chains and LLMs over a shared set of examples.
const dataset = await client.createDataset(datasetName, {
description: "Questions and answers about animal phylogenetics",
});
// Prepare inputs, outputs, and metadata for bulk creation
const inputs = exampleInputs.map(([inputPrompt]) => ({ question: inputPrompt }));
const outputs = exampleInputs.map(([, outputAnswer]) => ({ answer: outputAnswer }));
const metadata = exampleInputs.map(() => ({ source: "Wikipedia" }));
// Use the bulk createExamples method
await client.createExamples({
inputs,
outputs,
metadata,
datasetId: dataset.id,
});
```
```java Java theme={null}
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
import com.langchain.smith.core.JsonValue;
import com.langchain.smith.errors.UnexpectedStatusCodeException;
import com.langchain.smith.models.datasets.Dataset;
import com.langchain.smith.models.datasets.DatasetCreateParams;
import com.langchain.smith.models.datasets.DatasetListParams;
import com.langchain.smith.models.examples.bulk.BulkCreateParams;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CreateDatasetExample {
public static void main(String[] args) {
LangsmithClient client = LangsmithOkHttpClient.fromEnv();
List exampleInputs = List.of(
new String[]{"What is the largest mammal?", "The blue whale"},
new String[]{"What do mammals and birds have in common?", "They are both warm-blooded"},
new String[]{"What are reptiles known for?", "Having scales"},
new String[]{"What's the main characteristic of amphibians?", "They live both in water and on land"}
);
String datasetName = "Elementary Animal Questions";
Dataset dataset;
try {
dataset = client.datasets().create(
DatasetCreateParams.builder()
.name(datasetName)
.description("Questions and answers about animal phylogenetics")
.build()
);
} catch (UnexpectedStatusCodeException e) {
// Dataset already exists, get it
if (e.statusCode() == 409) {
DatasetListParams listParams = DatasetListParams.builder()
.name(datasetName)
.build();
dataset = client.datasets().list(listParams).items().get(0);
} else {
throw e;
}
}
// Prepare inputs, outputs, and metadata for bulk creation
List
### Create a dataset from traces
To create datasets from the runs (spans) of your traces, you can use the same approach. For **many** more examples of how to fetch and filter runs, see the [export traces](/langsmith/export-traces) guide. Below is an example:
```python Python theme={null}
from langsmith import Client
client = Client()
dataset_name = "Example Dataset"
# Filter runs to add to the dataset
runs = client.list_runs(
project_name="my_project",
is_root=True,
error=False,
)
dataset = client.create_dataset(dataset_name, description="An example dataset")
# Prepare inputs and outputs for bulk creation
examples = [{"inputs": run.inputs, "outputs": run.outputs} for run in runs]
# Use the bulk create_examples method
client.create_examples(
dataset_id=dataset.id,
examples=examples
)
```
```typescript TypeScript theme={null}
import { Client, Run } from "langsmith";
const client = new Client();
const datasetName = "Example Dataset";
// Filter runs to add to the dataset
const runs: Run[] = [];
for await (const run of client.listRuns({
projectName: "my_project",
isRoot: 1,
error: false,
})) {
runs.push(run);
}
const dataset = await client.createDataset(datasetName, {
description: "An example dataset",
dataType: "kv",
});
// Prepare inputs and outputs for bulk creation
const inputs = runs.map(run => run.inputs);
const outputs = runs.map(run => run.outputs ?? {});
// Use the bulk createExamples method
await client.createExamples({
inputs,
outputs,
datasetId: dataset.id,
});
```
```java Java theme={null}
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
import com.langchain.smith.core.JsonValue;
import com.langchain.smith.models.datasets.Dataset;
import com.langchain.smith.models.datasets.DatasetCreateParams;
import com.langchain.smith.models.examples.bulk.BulkCreateParams;
import com.langchain.smith.models.runs.RunQueryParams;
import com.langchain.smith.models.runs.RunQueryResponse;
import java.util.ArrayList;
import java.util.List;
public class CreateDatasetExample {
public static void main(String[] args) {
LangsmithClient client = LangsmithOkHttpClient.fromEnv();
String projectId = System.getenv("LANGSMITH_PROJECT_ID");
String datasetName = "Example Dataset";
List allRuns = new ArrayList<>();
String cursor = null;
try {
do {
RunQueryParams.Builder paramsBuilder = RunQueryParams.builder()
.addSession(projectId)
.isRoot(true)
.error(false)
.limit(10L);
if (cursor != null) {
paramsBuilder.cursor(cursor);
}
RunQueryResponse response = client.runs().query(paramsBuilder.build());
allRuns.addAll(response.runs());
// Get cursor for next page
try {
Map cursorProps = response.cursors()._additionalProperties();
if (cursorProps != null && cursorProps.containsKey("next")) {
JsonValue nextValue = cursorProps.get("next");
if (nextValue != null && !nextValue.isNull() && !nextValue.isMissing()) {
cursor = nextValue.asString().orElse(null);
} else {
cursor = null;
}
} else {
cursor = null;
}
} catch (Exception e) {
cursor = null;
}
if (response.runs().size() < 50) {
cursor = null;
}
} while (cursor != null && !cursor.isEmpty());
} catch (Exception e) {
System.err.println("Error querying runs: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
System.out.println("Total runs found: " + allRuns.size());
// Create dataset
Dataset dataset = client.datasets().create(
DatasetCreateParams.builder()
.name(datasetName)
.description("An example dataset")
.build()
);
// Prepare inputs and outputs for bulk creation
BulkCreateParams.Builder bulkParamsBuilder = BulkCreateParams.builder();
int examplesWithData = 0;
for (RunQueryResponse.Run run : allRuns) {
if (run.inputs().isPresent() && run.outputs().isPresent()) {
// Get the additional properties maps which contain the actual data
Map inputsMap = run.inputs().get()._additionalProperties();
Map outputsMap = run.outputs().get()._additionalProperties();
bulkParamsBuilder.addBody(
BulkCreateParams.Body.builder()
.datasetId(dataset.id())
.inputs(JsonValue.from(inputsMap))
.outputs(JsonValue.from(outputsMap))
.build()
);
examplesWithData++;
}
}
System.out.println("Prepared " + examplesWithData + " examples from " + allRuns.size() + " runs");
if (examplesWithData == 0) {
System.err.println("No runs have both inputs and outputs. Cannot create examples.");
System.exit(1);
}
client.examples().bulk().create(bulkParamsBuilder.build());
System.out.println("Created " + examplesWithData + " examples in dataset");
}
}
```
### Create a dataset from a CSV file
In this section, we will demonstrate how you can create a dataset by uploading a CSV file.
First, ensure your CSV file is properly formatted with columns that represent your input and output keys. These keys will be utilized to map your data properly during the upload. You can specify an optional name and description for your dataset. Otherwise, the file name will be used as the dataset name and no description will be provided.
```python Python theme={null}
from langsmith import Client
import os
client = Client()
csv_file = 'path/to/your/csvfile.csv'
input_keys = ['column1', 'column2'] # replace with your input column names
output_keys = ['output1', 'output2'] # replace with your output column names
dataset = client.upload_csv(
csv_file=csv_file,
input_keys=input_keys,
output_keys=output_keys,
name="My CSV Dataset",
description="Dataset created from a CSV file",
data_type="kv"
)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
const client = new Client();
const csvFile = 'path/to/your/csvfile.csv';
const inputKeys = ['column1', 'column2']; // replace with your input column names
const outputKeys = ['output1', 'output2']; // replace with your output column names
const dataset = await client.uploadCsv({
csvFile: csvFile,
fileName: "My CSV Dataset",
inputKeys: inputKeys,
outputKeys: outputKeys,
description: "Dataset created from a CSV file",
dataType: "kv"
});
```
```java Java theme={null}
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
import com.langchain.smith.models.datasets.Dataset;
import com.langchain.smith.models.datasets.DatasetUploadParams;
import com.langchain.smith.models.datasets.DataType;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
LangsmithClient client = LangsmithOkHttpClient.fromEnv();
Path csvFile = Paths.get("path/to/your/csvfile.csv");
List inputKeys = List.of("column1", "column2");
List outputKeys = List.of("output1", "output2");
Dataset dataset = client.datasets().upload(
DatasetUploadParams.builder()
.file(csvFile)
.inputKeys(inputKeys)
.outputKeys(outputKeys)
.name("My CSV Dataset")
.description("Dataset created from a CSV file")
.dataType(DataType.KV)
.build()
);
```
### Create a dataset from pandas DataFrame (Python only)
The python client offers an additional convenience method to upload a dataset from a pandas dataframe.
```python theme={null}
from langsmith import Client
import os
import pandas as pd
client = Client()
df = pd.read_parquet('path/to/your/myfile.parquet')
input_keys = ['column1', 'column2'] # replace with your input column names
output_keys = ['output1', 'output2'] # replace with your output column names
dataset = client.upload_dataframe(
df=df,
input_keys=input_keys,
output_keys=output_keys,
name="My Parquet Dataset",
description="Dataset created from a parquet file",
data_type="kv" # The default
)
```
## Fetch datasets
You can programmatically fetch datasets from LangSmith using the `list_datasets`/`listDatasets` method in the Python and TypeScript SDKs. Below are some common calls.
Initialize the client before running the below code snippets.
```python Python theme={null}
from langsmith import Client
client = Client()
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
const client = new Client();
```
```java Java theme={null}
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
LangsmithClient client = LangsmithOkHttpClient.fromEnv();
```
### Query all datasets
```python Python theme={null}
datasets = client.list_datasets()
```
```typescript TypeScript theme={null}
const datasets = await client.listDatasets();
```
```java Java theme={null}
import com.langchain.smith.models.datasets.DatasetListParams;
DatasetListParams listParams = DatasetListParams.builder().build();
var datasets = client.datasets().list(listParams);
```
### List datasets by name
If you want to search by the exact name, you can do the following:
```python Python theme={null}
datasets = client.list_datasets(dataset_name="My Test Dataset 1")
```
```typescript TypeScript theme={null}
const datasets = await client.listDatasets({
datasetName: "My Test Dataset 1"
});
```
```java Java theme={null}
import com.langchain.smith.models.datasets.DatasetListParams;
DatasetListParams listParams = DatasetListParams.builder()
.name("My Test Dataset 1")
.build();
var datasets = client.datasets().list(listParams);
```
If you want to do a case-invariant substring search, try the following:
```python Python theme={null}
datasets = client.list_datasets(dataset_name_contains="some substring")
```
```typescript TypeScript theme={null}
const datasets = await client.listDatasets({
datasetNameContains: "some substring"
});
```
```java Java theme={null}
import com.langchain.smith.models.datasets.DatasetListParams;
DatasetListParams listParams = DatasetListParams.builder()
.nameContains("some substring")
.build();
var datasets = client.datasets().list(listParams);
```
### List datasets by type
You can filter datasets by type:
```python Python theme={null}
datasets = client.list_datasets(data_type="kv")
```
```typescript TypeScript theme={null}
const datasets = await client.listDatasets({
dataType: "kv"
});
```
```java Java theme={null}
import com.langchain.smith.models.datasets.DatasetListParams;
DatasetListParams listParams = DatasetListParams.builder()
.datatype(DataType.of("kv"))
.build();
var datasets = client.datasets().list(listParams);
```
## Fetch examples
You can programmatically fetch examples from LangSmith using the `list_examples`/`listExamples` method in the Python and TypeScript SDKs. Below are some common calls.
Initialize the client before running the below code snippets.
```python Python theme={null}
from langsmith import Client
client = Client()
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
const client = new Client();
```
```java Java theme={null}
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
LangsmithClient client = LangsmithOkHttpClient.fromEnv();
```
### List all examples for a dataset
You can filter by dataset ID:
```python Python theme={null}
examples = client.list_examples(dataset_id="c9ace0d8-a82c-4b6c-13d2-83401d68e9ab")
```
```typescript TypeScript theme={null}
const examples = await client.listExamples({
datasetId: "c9ace0d8-a82c-4b6c-13d2-83401d68e9ab"
});
```
```java Java theme={null}
import com.langchain.smith.models.examples.ExampleListParams;
ExampleListParams listParams = ExampleListParams.builder()
.dataset("c9ace0d8-a82c-4b6c-13d2-83401d68e9ab")
.build();
var examples = client.examples().list(listParams);
```
Or you can filter by dataset name (this must exactly match the dataset name you want to query)
```python Python theme={null}
examples = client.list_examples(dataset_name="My Test Dataset")
```
```typescript TypeScript theme={null}
const examples = await client.listExamples({
datasetName: "My test Dataset"
});
```
### List examples by id
You can also list multiple examples all by ID.
```python Python theme={null}
example_ids = [
'734fc6a0-c187-4266-9721-90b7a025751a',
'd6b4c1b9-6160-4d63-9b61-b034c585074f',
'4d31df4e-f9c3-4a6e-8b6c-65701c2fed13',
]
examples = client.list_examples(example_ids=example_ids)
```
```typescript TypeScript theme={null}
const exampleIds = [
"734fc6a0-c187-4266-9721-90b7a025751a",
"d6b4c1b9-6160-4d63-9b61-b034c585074f",
"4d31df4e-f9c3-4a6e-8b6c-65701c2fed13",
];
const examples = await client.listExamples({
exampleIds: exampleIds
});
```
```java Java theme={null}
import com.langchain.smith.models.examples.ExampleListParams;
import java.util.List;
List exampleIds = List.of(
"734fc6a0-c187-4266-9721-90b7a025751a",
"d6b4c1b9-6160-4d63-9b61-b034c585074f",
"4d31df4e-f9c3-4a6e-8b6c-65701c2fed13"
);
ExampleListParams listParams = ExampleListParams.builder()
.id(exampleIds)
.build();
var examples = client.examples().list(listParams);
```
### List examples by metadata
You can also filter examples by metadata. Below is an example querying for examples with a specific metadata key-value pair. Under the hood, we check to see if the example's metadata contains the key-value pair(s) you specify.
For example, if you have an example with metadata `{"foo": "bar", "baz": "qux"}`, both `{foo: bar}` and `{baz: qux}` would match, as would `{foo: bar, baz: qux}`.
```python Python theme={null}
examples = client.list_examples(dataset_name=dataset_name, metadata={"foo": "bar"})
```
```typescript TypeScript theme={null}
const examples = await client.listExamples({
datasetName: datasetName,
metadata: {foo: "bar"}
});
```
```java Java theme={null}
import com.langchain.smith.models.examples.ExampleListParams;
ExampleListParams listParams = ExampleListParams.builder()
.datasetId(datasetId)
.metadata("{\"foo\":\"bar\"}")
.build();
var examples = client.examples().list(listParams);
```
### List examples by structured filter
Similar to how you can use the structured filter query language to [fetch runs](/langsmith/export-traces#use-filter-query-language), you can use it to fetch examples.
This is currently only available in v0.1.83 and later of the Python SDK and v0.1.35 and later of the TypeScript SDK.
Additionally, the structured filter query language is only supported for `metadata` fields.
You can use the `has` operator to fetch examples with metadata fields that contain specific key/value pairs and the `exists` operator to fetch examples with metadata fields that contain a specific key. Additionally, you can chain multiple filters together using the `and` operator and negate a filter using the `not` operator.
```python Python theme={null}
examples = client.list_examples(
dataset_name=dataset_name,
filter='and(not(has(metadata, \'{"foo": "bar"}\')), exists(metadata, "tenant_id"))'
)
```
```typescript TypeScript theme={null}
const examples = await client.listExamples({
datasetName: datasetName,
filter: 'and(not(has(metadata, \'{"foo": "bar"}\')), exists(metadata, "tenant_id"))'
});
```
```java Java theme={null}
import com.langchain.smith.models.examples.ExampleListParams;
String filter = "and(not(has(metadata, '{\"foo\": \"bar\"}')), exists(metadata, \"tenant_id\"))";
ExampleListParams listParams = ExampleListParams.builder()
.datasetId(datasetId)
.filter(filter)
.build();
var examples = client.examples().list(listParams);
```
## Update examples
### Update single example
You can programmatically update examples from LangSmith using the `update_example`/`updateExample` method in the Python and TypeScript SDKs. Below is an example.
```python Python theme={null}
client.update_example(
example_id=example.id,
inputs={"input": "updated input"},
outputs={"output": "updated output"},
metadata={"foo": "bar"},
split="train"
)
```
```typescript TypeScript theme={null}
await client.updateExample(example.id, {
inputs: { input: "updated input" },
outputs: { output: "updated output" },
metadata: { "foo": "bar" },
split: "train",
});
```
```java Java theme={null}
import com.langchain.smith.core.JsonValue;
import com.langchain.smith.models.examples.ExampleUpdateParams;
// Create Inputs using the builder
ExampleUpdateParams.Inputs inputsObj = ExampleUpdateParams.Inputs.builder()
.putAdditionalProperty("input", JsonValue.from("updated input"))
.build();
// Create Outputs using the builder
ExampleUpdateParams.Outputs outputsObj = ExampleUpdateParams.Outputs.builder()
.putAdditionalProperty("output", JsonValue.from("updated output"))
.build();
// Create Metadata using the builder
ExampleUpdateParams.Metadata metadataObj = ExampleUpdateParams.Metadata.builder()
.putAdditionalProperty("foo", JsonValue.from("bar"))
.build();
ExampleUpdateParams updateParams = ExampleUpdateParams.builder()
.inputs(inputsObj)
.outputs(outputsObj)
.metadata(metadataObj)
.split("train")
.build();
ExampleUpdateResponse updateResponse = client.examples().update(example.id(), updateParams);
```
### Bulk update examples
You can also programmatically update multiple examples in a single request with the `update_examples`/`updateExamples` method in the Python and TypeScript SDKs. Below is an example.
```python Python theme={null}
client.update_examples(
example_ids=[example.id, example_2.id],
inputs=[{"input": "updated input 1"}, {"input": "updated input 2"}],
outputs=[
{"output": "updated output 1"},
{"output": "updated output 2"},
],
metadata=[{"foo": "baz"}, {"foo": "qux"}],
splits=[["training", "foo"], "training"] # Splits can be arrays or standalone strings
)
```
```typescript TypeScript theme={null}
await client.updateExamples([
{
id: example.id,
inputs: { input: "updated input 1" },
outputs: { output: "updated output 1" },
metadata: { foo: "baz" },
split: ["training", "foo"] // Splits can be arrays or standalone strings
},
{
id: example2.id,
inputs: { input: "updated input 2" },
outputs: { output: "updated output 2" },
metadata: { foo: "qux" },
split: "training"
},
]);
```
```java Java theme={null}
Map inputs1 = Map.of("question", "What is the capital of France?")
Map outputs1 = Map.of("answer", "The capital of France is Paris.");
Map metadata1 = Map.of(
"source", "Wikipedia",
"difficulty", "easy"
);
Map inputs2 = Map.of("question", "What is 2 + 2?");
Map outputs2 = Map.of("answer", "The answer is 4.");
Map metadata2 = Map.of(
"source", "Math textbook",
"difficulty", "easy");
BulkPatchAllParams.Builder bulkParamsBuilder = BulkPatchAllParams.builder();
bulkParamsBuilder.addBody(
BulkPatchAllParams.Body.builder()
.id(example1.id())
.inputs(buildInputs(inputs1))
.outputs(buildOutputs(outputs1))
.metadata(buildMetadata(metadata1))
.splitOfStrings(Arrays.asList("training", "validation"))
.build()
);
bulkParamsBuilder.addBody(
BulkPatchAllParams.Body.builder()
.id(example2.id())
.inputs(buildInputs(inputs2))
.outputs(buildOutputs(outputs2))
.metadata(buildMetadata(metadata2))
.split("test")
.build()
);
client.examples().bulk().patchAll(bulkParamsBuilder.build());
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/manage-datasets-programmatically.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Manage your organization using the API
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/manage-organization-by-api
LangSmith's API supports programmatic access via API key to all of the actions available in the UI, with only a few exceptions that are noted in [User-only endpoints](#user-only-endpoints).
Before diving into this content, it might be helpful to read the following:
* [Conceptual guide on organizations and workspaces](/langsmith/administration-overview)
* [Organization setup how-to guild](/langsmith/set-up-hierarchy#set-up-an-organization)
There are a few limitations that will be lifted soon:
* The LangSmith SDKs do not support these organization management actions yet.
* Organization-scoped [service keys](/langsmith/administration-overview#service-keys) with Organization Admin permission may be used for these actions.
Use the `X-Tenant-Id` header to specify which workspace to target. If the header is not present, operations will default to the workspace the API key was initially created in if it is not organization-scoped.
**If `X-Tenant-Id` is not specified when accessing workspace-scoped resources with an organization-scoped API key, the request will fail with `403 Forbidden`.**
Some commonly-used endpoints and use cases are listed below. For a complete list of available endpoints, see the [API docs](https://api.smith.langchain.com/redoc). **The `X-Organization-Id` header should be present on all requests, and `X-Tenant-Id` header should be present on requests that are scoped to a particular workspace.**
## Workspaces
* [List workspaces](https://api.smith.langchain.com/redoc#tag/workspaces/operation/list_workspaces_api_v1_workspaces_get)
* [Create workspace](https://api.smith.langchain.com/redoc#tag/workspaces/operation/create_workspace_api_v1_workspaces_post)
* [Update workspace name](https://api.smith.langchain.com/redoc#tag/workspaces/operation/patch_workspace_api_v1_workspaces__workspace_id__patch)
## User management
### RBAC
* [List roles](https://api.smith.langchain.com/redoc#tag/orgs/operation/list_organization_roles_api_v1_orgs_current_roles_get)
* [List permissions](https://api.smith.langchain.com/redoc#tag/orgs/operation/update_organization_roles_api_v1_orgs_current_roles__role_id__patch)
* [Create role](https://api.smith.langchain.com/redoc#tag/orgs/operation/create_organization_roles_api_v1_orgs_current_roles_post)
* [Update role](https://api.smith.langchain.com/redoc#tag/orgs/operation/update_organization_roles_api_v1_orgs_current_roles__role_id__patch)
### Membership management
`List roles` under [RBAC](#rbac) should be used for retrieving role IDs of these operations. `List [organization|workspace] members` endpoints (below) response `"id"`s should be used as `identity_id` in these operations.
Organization level:
* [List active organization members](https://api.smith.langchain.com/redoc#tag/orgs/operation/get_current_active_org_members_api_v1_orgs_current_members_active_get)
* [List pending organization members](https://api.smith.langchain.com/redoc#tag/orgs/operation/get_current_pending_org_members_api_v1_orgs_current_members_pending_get)
* [Invite a user to the organization and one or more workspaces](https://api.smith.langchain.com/redoc#tag/orgs/operation/add_members_to_current_org_batch_api_v1_orgs_current_members_batch_post). This should be used when the user is not already a member in the organization.
* [Update a user's organization role](https://api.smith.langchain.com/redoc#tag/workspaces/operation/add_member_to_current_workspace_api_v1_workspaces_current_members_post)
* [Remove someone from the organization](https://api.smith.langchain.com/redoc#tag/orgs/operation/remove_member_from_current_org_api_v1_orgs_current_members__identity_id__delete)
Workspace level:
* [List workspace members](https://api.smith.langchain.com/redoc#tag/workspaces/operation/get_current_workspace_members_api_v1_workspaces_current_members_get)
* [Add a member to a workspace that is already part of the organization](https://api.smith.langchain.com/redoc#tag/workspaces/operation/add_member_to_current_workspace_api_v1_workspaces_current_members_post)
* [Update a user's workspace role](https://api.smith.langchain.com/redoc#tag/workspaces/operation/add_member_to_current_workspace_api_v1_workspaces_current_members_post)
* [Remove someone from a workspace](https://api.smith.langchain.com/redoc#tag/workspaces/operation/delete_current_workspace_member_api_v1_workspaces_current_members__identity_id__delete)
These params should be omitted: `read_only` (deprecated), `password` and `full_name` ([basic auth](/langsmith/authentication-methods) only)
## API keys
* [Create a service key](https://api.smith.langchain.com/redoc#tag/api-key/operation/generate_api_key_api_v1_api_key_post)
* [Delete a service key](https://api.smith.langchain.com/redoc#tag/api-key/operation/delete_api_key_api_v1_api_key__api_key_id__delete)
## Security settings
Organization Admin permissions are required to make these changes.
"Shared resources" in this context refer to [public prompts](/langsmith/create-a-prompt#save-your-prompt), [shared runs](/langsmith/share-trace), and [shared datasets](/langsmith/manage-datasets#share-a-dataset).
Updating these settings affects **all resources in the organization**.
You can update these settings under the **Settings > Shared** tab for a workspace, or via API:
* [Update organization sharing settings](https://api.smith.langchain.com/redoc#tag/orgs/operation/update_current_organization_info_api_v1_orgs_current_info_patch)
* use `unshare_all` to unshare **ALL** shared resources in the organization - use `disable_public_sharing` to prevent future sharing of resources
These settings are only editable via API:
* [Disable/enable PAT creation](https://api.smith.langchain.com/redoc#tag/orgs/operation/update_current_organization_info_api_v1_orgs_current_info_patch) (for self-hosted, available in Helm chart version 0.11.25+)
* Use `pat_creation_disabled` to disable PAT creation for the entire organization.
* See the [admin guide](/langsmith/administration-overview#organization-roles) for information about the Organization Viewer role, which cannot create PATs.
* For self-hosted deployments, you can also [globally disable PAT creation](/langsmith/self-host-user-management#disabling-personal-access-token-creation) across all organizations using an environment variable.
## User-only endpoints
These endpoints are user-scoped and require a logged-in user's JWT, so they should only be executed through the UI.
* `/api-key/current` endpoints: these are related a user's PATs
* `/sso/email-verification/send` (Cloud-only): this endpoint is related to [SAML SSO](/langsmith/user-management)
## Sample code
The sample code below goes through a few common workflows related to organization management. Make sure to make necessary replacements wherever `` is in the code.
```python theme={null}
import os
import requests
def main():
api_key = os.environ["LANGSMITH_API_KEY"]
# LANGSMITH_ORGANIZATION_ID is not a standard environment variable in the SDK, just used for this example
organization_id = os.environ["LANGSMITH_ORGANIZATION_ID"]
base_url = os.environ.get("LANGSMITH_ENDPOINT") # or "https://api.smith.langchain.com". Update appropriately for self-hosted installations or the EU region
headers = {
"Content-Type": "application/json",
"X-API-Key": api_key,
"X-Organization-Id": organization_id,
}
session = requests.Session()
session.headers.update(headers)
workspaces_path = f"{base_url}/api/v1/workspaces"
orgs_path = f"{base_url}/api/v1/orgs/current"
api_keys_path = f"{base_url}/api/v1/api-key"
# Create a workspace
workspace_res = session.post(workspaces_path, json={"display_name": "My Workspace"})
workspace_res.raise_for_status()
workspace = workspace_res.json()
workspace_id = workspace["id"]
new_workspace_headers = {
"X-Tenant-Id": workspace_id,
}
# Grab roles - this includes both organization and workspace roles
roles_res = session.get(f"{orgs_path}/roles")
roles_res.raise_for_status()
roles = roles_res.json()
# system org roles are 'Organization Admin', 'Organization User'
# system workspace roles are 'Admin', 'Editor', 'Viewer'
org_roles_by_name = {role["display_name"]: role for role in roles if role["access_scope"] == "organization"}
ws_roles_by_name = {role["display_name"]: role for role in roles if role["access_scope"] == "workspace"}
# Invite a user to the org and the new workspace, as an Editor.
# workspace_role_id is only allowed if RBAC is enabled (an enterprise feature).
new_user_email = ""
new_user_res = session.post(
f"{orgs_path}/members",
json={
"email": new_user_email,
"role_id": org_roles_by_name["Organization User"]["id"],
"workspace_ids": [workspace_id],
"workspace_role_id": ws_roles_by_name["Editor"]["id"],
},
)
new_user_res.raise_for_status()
# Add a user that already exists in the org to the new workspace, as a Viewer.
# workspace_role_id is only allowed if RBAC is enabled (an enterprise feature).
existing_user_email = ""
org_members_res = session.get(f"{orgs_path}/members")
org_members_res.raise_for_status()
org_members = org_members_res.json()
existing_org_member = next(
(member for member in org_members["members"] if member["email"] == existing_user_email), None
)
existing_user_res = session.post(
f"{workspaces_path}/current/members",
json={
"user_id": existing_org_member["user_id"],
"workspace_ids": [workspace_id],
"workspace_role_id": ws_roles_by_name["Viewer"]["id"],
},
headers=new_workspace_headers,
)
existing_user_res.raise_for_status()
# List all members of the workspace
members_res = session.get(f"{workspaces_path}/current/members", headers=new_workspace_headers)
members_res.raise_for_status()
members = members_res.json()
workspace_member = next(
(member for member in members["members"] if member["email"] == existing_user_email), None
)
# Update the user's workspace role to Admin (enterprise-only)
existing_user_id = workspace_member["id"]
update_res = session.patch(
f"{workspaces_path}/current/members/{existing_user_id}",
json={"role_id": ws_roles_by_name["Admin"]["id"]},
headers=new_workspace_headers,
)
update_res.raise_for_status()
# Update the user's organization role to Organization Admin
update_res = session.patch(
f"{orgs_path}/members/{existing_org_member['id']}",
json={"role_id": org_roles_by_name["Organization Admin"]["id"]},
)
update_res.raise_for_status()
# Create a new Service key
api_key_res = session.post(
api_keys_path,
json={"description": "my key"},
headers=new_workspace_headers,
)
api_key_res.raise_for_status()
api_key_json = api_key_res.json()
api_key = api_key_json["key"]
if __name__ == "__main__":
main()
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/manage-organization-by-api.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Manage prompts
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/manage-prompts
LangSmith provides several tools to help you manage your [*prompts*](/langsmith/prompt-engineering-concepts) effectively. This page describes the following features:
* [Commit tags](#commit-tags) for version control and environment management.
* [Webhook triggers](#trigger-a-webhook-on-prompt-commit) for automating workflows when prompts are updated.
* [Public prompt hub](#public-prompt-hub) for discovering and using community-created prompts.
## Commit tags
[*Commit tags*](/langsmith/prompt-engineering-concepts#tags) are labels that reference a specific [*commit*](/langsmith/prompt-engineering-concepts#commits) in your prompt's version history. They help you mark significant versions and control which versions run in different environments. By referencing tags rather than commit IDs in your code, you can update which version is being used without modifying the code itself.
Each tag references exactly one commit, though you can reassign a tag to point to a different commit.
**Not to be confused with resource tags**: Commit tags are specific to prompt versioning and reference individual commits in a prompt's history. [Resource tags](/langsmith/set-up-resource-tags) are key-value pairs used to organize workspace resources like projects, datasets, and prompts. While both can use similar naming conventions (like `prod` or `staging`), commit tags control **which version** of a prompt runs, while resource tags help you **organize and filter** resources across your workspace.
### Create a tag
To create a tag, navigate to the **Commits** tab for a prompt. Click on the tag icon next to the commit you want to tag. Click **New Tag** and enter a name for the tag.
### Move a tag
To point a tag to a different commit, click on the tag icon next to the destination commit, and select the tag you want to move. This will automatically update the tag to point to the new commit.
### Delete a tag
To delete a tag, click on the delete icon next to the tag you want to delete. This will delete the tag altogether and it will no longer be associated with any commit.
### Use tags in code
Tags provide a stable way to reference specific versions of your prompts in code. Instead of using commit hashes directly, you can reference tags that can be updated without changing your code.
Here is an example of pulling a prompt by tag in Python:
```python theme={null}
prompt = client.pull_prompt("joke-generator:prod")
# If prod tag points to commit a1b2c3d4, this is equivalent to:
prompt = client.pull_prompt("joke-generator:a1b2c3d4")
```
For more information on how to use prompts in code, refer to [Managing prompts programmatically](/langsmith/manage-prompts-programmatically).
## Trigger a webhook on prompt commit
You can configure a webhook to be triggered whenever a commit is made to a prompt.
Some common use cases of this include:
* Triggering a CI/CD pipeline when prompts are updated.
* Synchronizing prompts with a GitHub repository.
* Notifying team members about prompt modifications.
### Configure a webhook
Navigate to the **Prompts** section in the left-hand sidebar or from the application homepage. In the top right corner, click on the `+ Webhook` button.
Add a webhook URL and any required headers.
You can only configure one webhook per workspace. If you want to configure multiple per workspace or set up a different webhook for each prompt, let us know in the [LangChain Forum](https://forum.langchain.com/).
To test out your webhook, click the **Send test notification** button. This will send a test notification to the webhook URL you provided with a sample payload.
The sample payload is a JSON object with the following fields:
* `prompt_id`: The ID of the prompt that was committed.
* `prompt_name`: The name of the prompt that was committed.
* `commit_hash`: The commit hash of the prompt.
* `created_at`: The date of the commit.
* `created_by`: The author of the commit.
* `manifest`: The manifest of the prompt.
### Trigger the webhook
Commit to a prompt to trigger the webhook you've configured.
#### Use the Playground
If you do this in the Playground, you'll be prompted to deselect the webhooks you'd like to avoid triggering.
#### Using the API
If you commit via the API, you can specify to skip triggering the webhook by setting the `skip_webhooks` parameter to `true` or to an array of webhook ids to ignore. Refer to the [API docs](https://api.smith.langchain.com/redoc#tag/commits/operation/create_commit_api_v1_commits__owner___repo__post) for more information.
## Public prompt hub
LangSmith's public prompt hub is a collection of prompts that have been created by the LangChain community that you can use for reference.
Note that prompts are user-generated and unverified. LangChain does not review or endorse public prompts, use these at your own risk. Use of Prompt Hub is subject to our [Terms of Service](https://www.langchain.com/terms-of-service).
Navigate to the **Prompts** section of the left-hand sidebar and click on **Browse all Public Prompts in the LangChain Hub**.
Here you'll find all of the publicly listed prompts in the LangChain Hub. You can search for prompts by name, handle, use cases, descriptions, or models. You can fork prompts to your personal organization, view the prompt's details, and run the prompt in the Playground. You can [pull any public prompt into your code](/langsmith/manage-prompts-programmatically) using the SDK.
To view prompts tied to your workspace, visit the **Prompts** tab in the sidebar.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/manage-prompts.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Manage prompts programmatically
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/manage-prompts-programmatically
You can use the LangSmith Python and TypeScript SDK to manage prompts programmatically.
Previously this functionality lived in the `langchainhub` package which is now deprecated. All functionality going forward will live in the `langsmith` package.
## Install packages
In Python, you can directly use the LangSmith SDK (*recommended, full functionality*) or you can use through the LangChain package (limited to pushing and pulling prompts).
In TypeScript, you must use the LangChain npm package for pulling prompts (it also allows pushing). For all other functionality, use the LangSmith package.
```bash pip theme={null}
pip install -U langsmith # version >= 0.1.99
```
```bash uv theme={null}
uv add langsmith # version >= 0.1.99
```
```bash TypeScript theme={null}
yarn add langsmith langchain // langsmith version >= 0.1.99 and langchain version >= 0.2.14
```
## Configure environment variables
If you already have `LANGSMITH_API_KEY` set to your current workspace's api key from LangSmith, you can skip this step.
Otherwise, get an API key for your workspace by navigating to `Settings > API Keys > Create API Key` in LangSmith.
Set your environment variable.
```bash theme={null}
export LANGSMITH_API_KEY="lsv2_..."
```
What we refer to as "prompts" used to be called "repos", so any references to "repo" in the code are referring to a prompt.
## Push a prompt
To create a new prompt or update an existing prompt, you can use the `push prompt` method.
```python Python theme={null}
from langsmith import Client
from langchain_core.prompts import ChatPromptTemplate
client = Client()
prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}")
url = client.push_prompt("joke-generator", object=prompt)
# url is a link to the prompt in the UI
print(url)
```
```python LangChain (Python) theme={null}
from langchain_classic import hub as prompts
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}")
url = prompts.push("joke-generator", prompt)
# url is a link to the prompt in the UI
print(url)
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
import { ChatPromptTemplate } from "@langchain/core/prompts";
const prompt = ChatPromptTemplate.fromTemplate("tell me a joke about {topic}");
const url = hub.push("joke-generator", {
object: prompt,
});
// url is a link to the prompt in the UI
console.log(url);
```
```java Java theme={null}
import com.langchain.smith.models.prompts.PromptPushParams;
import com.langchain.smith.models.prompts.Prompt;
Prompt prompt = Prompt.builder()
.name("joke-generator")
.object(prompt)
.build();
var url = client.prompts().push(prompt);
```
You can also push a prompt as a RunnableSequence of a prompt and a model. This is useful for storing the model configuration you want to use with this prompt. The provider must be supported by the LangSmith playground. (see settings here: [Supported Providers](https://langsmith.com/playground))
```python Python theme={null}
from langsmith import Client
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
client = Client()
model = ChatOpenAI(model="gpt-4.1-mini")
prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}")
chain = prompt | model
client.push_prompt("joke-generator-with-model", object=chain)
```
```python LangChain (Python) theme={null}
from langchain_classic import hub as prompts
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4.1-mini")
prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}")
chain = prompt | model
url = prompts.push("joke-generator-with-model", chain)
# url is a link to the prompt in the UI
print(url)
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({ model: "gpt-4.1-mini" });
const prompt = ChatPromptTemplate.fromTemplate("tell me a joke about {topic}");
const chain = prompt.pipe(model);
await hub.push("joke-generator-with-model", {
object: chain,
});
```
## Pull a prompt
To pull a prompt, you can use the `pull prompt` method, which returns a the prompt as a langchain `PromptTemplate`.
To pull a **private prompt** you do not need to specify the owner handle (though you can, if you have one set).
To pull a **public prompt** from the LangChain Hub, you need to specify the handle of the prompt's author.
```python Python theme={null}
from langsmith import Client
from langchain_openai import ChatOpenAI
client = Client()
prompt = client.pull_prompt("joke-generator")
model = ChatOpenAI(model="gpt-4.1-mini")
chain = prompt | model
chain.invoke({"topic": "cats"})
```
```python LangChain (Python) theme={null}
from langchain_classic import hub as prompts
from langchain_openai import ChatOpenAI
prompt = prompts.pull("joke-generator")
model = ChatOpenAI(model="gpt-4.1-mini")
chain = prompt | model
chain.invoke({"topic": "cats"})
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
import { ChatOpenAI } from "@langchain/openai";
const prompt = await hub.pull("joke-generator");
const model = new ChatOpenAI({ model: "gpt-4.1-mini" });
const chain = prompt.pipe(model);
await chain.invoke({"topic": "cats"});
```
```java Java theme={null}
RepoListPage jokePrompts = client.repos().list(
RepoListParams.builder()
.query("joke")
.isPublic(RepoListParams.IsPublic.FALSE)
.build()
);
```
Similar to pushing a prompt, you can also pull a prompt as a RunnableSequence of a prompt and a model. Just specify include\_model when pulling the prompt. If the stored prompt includes a model, it will be returned as a RunnableSequence. Make sure you have the proper environment variables set for the model you are using.
```python Python theme={null}
from langsmith import Client
client = Client()
chain = client.pull_prompt("joke-generator-with-model", include_model=True)
chain.invoke({"topic": "cats"})
```
```python LangChain (Python) theme={null}
from langchain_classic import hub as prompts
chain = prompts.pull("joke-generator-with-model", include_model=True)
chain.invoke({"topic": "cats"})
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
import { Runnable } from "@langchain/core/runnables";
const chain = await hub.pull("joke-generator-with-model", { includeModel: true });
await chain.invoke({"topic": "cats"});
```
When pulling a prompt, you can also specify a specific commit hash or [commit tag](/langsmith/manage-prompts#commit-tags) to pull a specific version of the prompt.
```python Python theme={null}
prompt = client.pull_prompt("joke-generator:12344e88")
```
```python LangChain (Python) theme={null}
prompt = prompts.pull("joke-generator:12344e88")
```
```typescript TypeScript theme={null}
const prompt = await hub.pull("joke-generator:12344e88")
```
To pull a public prompt from the LangChain Hub, you need to specify the handle of the prompt's author.
```python Python theme={null}
prompt = client.pull_prompt("efriis/my-first-prompt")
```
```python LangChain (Python) theme={null}
prompt = prompts.pull("efriis/my-first-prompt")
```
```typescript TypeScript theme={null}
const prompt = await hub.pull("efriis/my-first-prompt")
```
For pulling prompts, if you are using Node.js or an environment that supports dynamic imports, we recommend using the `langchain/hub/node` entrypoint, as it handles deserialization of models associated with your prompt configuration automatically.
If you are in a non-Node environment, "includeModel" is not supported for non-OpenAI models and you should use the base `langchain/hub` entrypoint.
## Prompt caching
The LangSmith SDK includes built-in in-memory caching for prompts. When enabled, LangSmith will cache pulled prompts in memory, reducing latency and API calls for frequently used prompts. The cache uses a global singleton instance that is shared across all clients and persists for the lifetime of the process. It implements a stale-while-revalidate pattern, ensuring your application always gets a fast response while keeping prompts up-to-date in the background.
**Requirements:**
* Python SDK: `langsmith >= 0.7.0`
* TypeScript SDK: `langsmith >= 0.5.0`
### Default behavior
Caching is **enabled by default**. When enabled, the default settings are:
| Setting | Default | Description |
| -------------------------- | --------------- | ----------------------------------------------------------------------- |
| `max_size` | 100 | Maximum number of prompts to cache |
| `ttl_seconds` | 300 (5 minutes) | Time before a cached prompt is considered stale |
| `refresh_interval_seconds` | 60 | How often to check for stale prompts and refresh them in the background |
When refreshing, the global cache will use the last client that requested a given prompt to fetch new data.
### Using the cache
By default, all clients use the global prompt cache. No configuration is needed:
```python Python theme={null}
from langsmith import Client
# Obtain a reference to the global cache just for logging metrics
from langsmith.prompt_cache import prompt_cache_singleton
# Caching is enabled by default using the global singleton
client = Client()
# First pull - fetches from API and caches
prompt = client.pull_prompt("joke-generator")
# Subsequent pulls - returns cached version instantly
prompt = client.pull_prompt("joke-generator")
# Check cache metrics
print(f"Cache hits: {prompt_cache_singleton.metrics.hits}")
print(f"Cache misses: {prompt_cache_singleton.metrics.misses}")
print(f"Hit rate: {prompt_cache_singleton.metrics.hit_rate:.1%}")
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
// Obtain a reference to the global cache just for logging metrics
import { promptCacheSingleton } from "langsmith";
// Caching is enabled by default
// First pull - fetches from API and caches
const prompt = await hub.pull("joke-generator");
// Subsequent pulls - returns cached version instantly
const prompt2 = await hub.pull("joke-generator");
// Check cache metrics
console.log(`Cache hits: ${promptCacheSingleton.metrics.hits}`);
console.log(`Cache misses: ${promptCacheSingleton.metrics.misses}`);
console.log(`Hit rate: ${(promptCacheSingleton.hitRate * 100).toFixed(1)}%`);
```
### Configuring the global cache
You can configure the global prompt cache that all clients use by default. This is useful when you want to customize caching behavior across your entire application:
```python Python theme={null}
from langsmith import Client
from langsmith.prompt_cache import (
configure_global_prompt_cache,
prompt_cache_singleton,
)
# Configure global cache before creating any clients
configure_global_prompt_cache(
max_size=200, # Cache up to 200 prompts
ttl_seconds=7200, # Consider prompts stale after 2 hours
refresh_interval_seconds=600, # Check for stale prompts every 10 minutes
)
# All clients will use these settings
client1 = Client()
client2 = Client()
# Both clients share the same global cache with your custom settings
prompt1 = client1.pull_prompt("prompt-1")
prompt2 = client2.pull_prompt("prompt-2")
# Check global cache metrics
print(f"Global cache hits: {prompt_cache_singleton.metrics.hits}")
print(f"Global cache misses: {prompt_cache_singleton.metrics.misses}")
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
import {
configureGlobalPromptCache,
promptCacheSingleton,
} from "langsmith";
// Configure global cache before pulling prompts
configureGlobalPromptCache({
maxSize: 200, // Cache up to 200 prompts
ttlSeconds: 7200, // Consider prompts stale after 2 hours
refreshIntervalSeconds: 600, // Check for stale prompts every 10 minutes
});
// All hub.pull calls will use these settings
const prompt1 = await hub.pull("prompt-1");
const prompt2 = await hub.pull("prompt-2");
// Check global cache metrics
console.log(`Global cache hits: ${promptCacheSingleton.metrics.hits}`);
console.log(`Global cache misses: ${promptCacheSingleton.metrics.misses}`);
```
### Disabling the cache
To disable caching for a specific client, pass `disable_prompt_cache=True`. You can also configure a max size of zero globally:
```python Python theme={null}
from langsmith import Client
# Disable caching for this client
client = Client(disable_prompt_cache=True)
# Every pull will fetch from the API
prompt = client.pull_prompt("joke-generator")
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
import { configureGlobalPromptCache } from "langsmith";
// Disable caching globally
configureGlobalPromptCache({ maxSize: 0 });
// Every pull will fetch from the API
const prompt = await hub.pull("joke-generator");
```
### Skipping the cache
To bypass the cache and fetch a fresh prompt from the API for an individual request, use the `skip_cache` parameter:
```python Python theme={null}
# Force a fresh fetch, ignoring any cached version
prompt = client.pull_prompt("joke-generator", skip_cache=True)
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
// Force a fresh fetch, ignoring any cached version
const prompt = await hub.pull("joke-generator", { skipCache: true });
```
This is useful when you need to ensure you have the latest version of a prompt, such as after making changes in the LangSmith UI.
### Offline mode
For environments with limited or no network connectivity, you can pre-populate the cache and use it offline. Set `ttl_seconds` to `None` (Python) or `null` (TypeScript) to prevent cache entries from expiring and disable background refresh.
**Step 1: Export your prompts to a cache file (while online)**
```python Python theme={null}
from langsmith import Client
from langsmith.prompt_cache import prompt_cache_singleton
# Create client (caching is enabled by default)
client = Client()
# Pull the prompts you need
client.pull_prompt("prompt-1")
client.pull_prompt("prompt-2")
client.pull_prompt("prompt-3")
# Export cache to a file
prompt_cache_singleton.dump("prompts_cache.json")
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
import { promptCacheSingleton } from "langsmith";
// Caching is enabled by default
// Pull the prompts you need
await hub.pull("prompt-1");
await hub.pull("prompt-2");
await hub.pull("prompt-3");
// Export cache to a file
promptCacheSingleton.dump("prompts_cache.json");
```
**Step 2: Load the cache file in your offline environment**
```python Python theme={null}
from langsmith import Client
from langsmith.prompt_cache import (
configure_global_prompt_cache,
prompt_cache_singleton,
)
# Configure cache with infinite TTL (never expire, no background refresh)
configure_global_prompt_cache(ttl_seconds=None)
# Load the cache file
prompt_cache_singleton.load("prompts_cache.json")
# Create client (uses the loaded cache)
client = Client()
# Uses cached version without any API calls
prompt = client.pull_prompt("prompt-1")
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
import {
configureGlobalPromptCache,
promptCacheSingleton,
} from "langsmith";
// Configure cache with infinite TTL (never expire, no background refresh)
configureGlobalPromptCache({ ttlSeconds: null });
// Load the cache file
promptCacheSingleton.load("prompts_cache.json");
// Uses cached version without any API calls
const prompt = await hub.pull("prompt-1");
```
### Cache operations
The cache supports several operations for managing cached prompts:
```python Python theme={null}
from langsmith import Client
from langsmith.prompt_cache import prompt_cache_singleton
client = Client()
# Invalidate a specific prompt from cache
prompt_cache_singleton.invalidate("joke-generator:latest")
# Clear all cached prompts
prompt_cache_singleton.clear()
# Reset metrics
prompt_cache_singleton.reset_metrics()
# Check if cache is running background refresh
# (only runs if ttl_seconds is not None)
if prompt_cache_singleton._refresh_thread is not None:
print("Background refresh is active")
```
```typescript TypeScript theme={null}
import { promptCacheSingleton } from "langsmith";
// Invalidate a specific prompt from cache
promptCacheSingleton.invalidate("joke-generator:latest");
// Clear all cached prompts
promptCacheSingleton.clear();
// Reset metrics
promptCacheSingleton.resetMetrics();
```
### Cleanup
You can manually call `stop()` to stop the background refresh task:
```python Python theme={null}
prompt_cache_singleton.stop()
```
```typescript TypeScript theme={null}
promptCacheSingleton.stop();
```
The background refresh task is only started when you first set a value in the cache, and only if `ttl_seconds` is not `None`. If `ttl_seconds` is `None` (offline mode), no background task is created.
## Use a prompt without LangChain
If you want to store your prompts in LangSmith but use them directly with a model provider's API, you can use our conversion methods. These convert your prompt into the payload required for the OpenAI or Anthropic API.
These conversion methods rely on logic from within LangChain integration packages, and you will need to install the appropriate package as a dependency in addition to your official SDK of choice. Here are some examples:
### OpenAI
```bash Python theme={null}
pip install -U langchain_openai
```
```bash TypeScript theme={null}
yarn add @langchain/openai @langchain/core // @langchain/openai version >= 0.3.2
```
```python Python theme={null}
from openai import OpenAI
from langsmith.client import Client, convert_prompt_to_openai_format
# langsmith client
client = Client()
# openai client
oai_client = OpenAI()
# pull prompt and invoke to populate the variables
prompt = client.pull_prompt("joke-generator")
prompt_value = prompt.invoke({"topic": "cats"})
openai_payload = convert_prompt_to_openai_format(prompt_value)
openai_response = oai_client.chat.completions.create(**openai_payload)
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
import { convertPromptToOpenAI } from "@langchain/openai";
import OpenAI from "openai";
const prompt = await hub.pull("jacob/joke-generator");
const formattedPrompt = await prompt.invoke({
topic: "cats",
});
const { messages } = convertPromptToOpenAI(formattedPrompt);
const openAIClient = new OpenAI();
const openAIResponse = await openAIClient.chat.completions.create({
model: "gpt-4.1-mini",
messages,
});
```
### Anthropic
```bash Python theme={null}
pip install -U langchain_anthropic
```
```bash TypeScript theme={null}
yarn add @langchain/anthropic @langchain/core // @langchain/anthropic version >= 0.3.3
```
```python Python theme={null}
from anthropic import Anthropic
from langsmith.client import Client, convert_prompt_to_anthropic_format
# langsmith client
client = Client()
# anthropic client
anthropic_client = Anthropic()
# pull prompt and invoke to populate the variables
prompt = client.pull_prompt("joke-generator")
prompt_value = prompt.invoke({"topic": "cats"})
anthropic_payload = convert_prompt_to_anthropic_format(prompt_value)
anthropic_response = anthropic_client.messages.create(**anthropic_payload)
```
```typescript TypeScript theme={null}
import * as hub from "langchain/hub";
import { convertPromptToAnthropic } from "@langchain/anthropic";
import Anthropic from "@anthropic-ai/sdk";
const prompt = await hub.pull("jacob/joke-generator");
const formattedPrompt = await prompt.invoke({
topic: "cats",
});
const { messages, system } = convertPromptToAnthropic(formattedPrompt);
const anthropicClient = new Anthropic();
const anthropicResponse = await anthropicClient.messages.create({
model: "claude-haiku-4-5-20251001",
system,
messages,
max_tokens: 1024,
stream: false,
});
```
## List, delete, and like prompts
You can also list, delete, and like/unlike prompts using the `list prompts`, `delete prompt`, `like prompt` and `unlike prompt` methods. See the [LangSmith SDK client](https://github.com/langchain-ai/langsmith-sdk) for extensive documentation on these methods.
```python Python theme={null}
# List all prompts in my workspace
prompts = client.list_prompts()
# List my private prompts that include "joke"
prompts = client.list_prompts(query="joke", is_public=False)
# Delete a prompt
client.delete_prompt("joke-generator")
# Like a prompt
client.like_prompt("efriis/my-first-prompt")
# Unlike a prompt
client.unlike_prompt("efriis/my-first-prompt")
```
```typescript TypeScript theme={null}
// List all prompts in my workspace
import Client from "langsmith";
const client = new Client({ apiKey: "lsv2_..." });
const prompts = client.listPrompts();
for await (const prompt of prompts) {
console.log(prompt);
}
// List my private prompts that include "joke"
const private_joke_prompts = client.listPrompts({ query: "joke", isPublic: false});
// Delete a prompt
client.deletePrompt("joke-generator");
// Like a prompt
client.likePrompt("efriis/my-first-prompt");
// Unlike a prompt
client.unlikePrompt("efriis/my-first-prompt");
```
```java Java theme={null}
// List all prompts in my workspace
RepoListPage prompts = client.repos().list();
for (RepoWithLookups prompt : prompts.repos()) {
System.out.println(prompt.repoHandle());
}
// List my private prompts that include "joke"
RepoListPage jokePrompts = client.repos().list(
RepoListParams.builder()
.query("joke")
.isPublic(RepoListParams.IsPublic.FALSE)
.build()
);
// Delete a prompt
String promptId = "joke-generator";
String[] parts = promptId.split("/", 2);
String owner = parts.length > 1 ? parts[0] : "-";
String repo = parts.length > 1 ? parts[1] : promptId;
client.repos().delete(
RepoDeleteParams.builder()
.owner(owner)
.repo(repo)
.build()
);
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/manage-prompts-programmatically.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Configure prompt settings
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/managing-model-configurations
The LangSmith [playground](/langsmith/prompt-engineering-concepts#prompt-playground) enables you to control various settings for your prompts. The **Prompt Settings** window contains:
* [Model configuration](#model-configurations)
* [Tool settings](#tool-settings)
* [Prompt formatting](#prompt-formatting)
To access **Prompt Settings**:
1. Navigate to the **Playground** in the left sidebar.
2. Under the **Prompts** heading select the gear icon next to the model name, which will launch the **Prompt Settings** window.
## Model configurations
Model configurations define the parameters your prompt runs against. In the LangSmith Playground, you can save and manage these configurations, which allows you to reuse your preferred settings across prompts and sessions. For details on specific settings, refer to your model provider’s documentation (for example, [Anthropic](https://platform.claude.com/docs/en/api/messages), [OpenAI](https://platform.openai.com/docs/api-reference/responses/create)).
### Create saved configurations
1. In the **Model Configurations** tab, adjust the model configuration as needed—you can select a [saved configuration to edit](#edit-configurations).
2. Click the **Save As** button in the top bar.
3. Enter a name and optional description for your configuration and confirm.
4. Now that you've saved the configuration, anyone in your organization's [workspace](/langsmith/administration-overview#workspaces) can access it. All saved configurations are available in the **Model Configuration** dropdown.
5. Once you have created a saved configuration, you can set it as your default, so any new prompt you create will automatically use this configuration. To set a configuration as your default, click the **Set as default** icon next to the model name in the dropdown.
### Edit configurations
1. To rename a saved configuration, or update the description, select the configuration name or description and make the necessary changes.
2. Update the current configuration's parameters as needed and click the **Save** button at the top.
### Delete configurations
1. Select the configuration you want to remove.
2. Click the trash icon to delete it.
### Extra parameters
The **Extra Parameters** field allows you to pass additional model parameters that aren't directly supported in the LangSmith interface. This is particularly useful in two scenarios:
1. When model providers release new parameters that haven't yet been integrated into the LangSmith interface. You can specify these parameters in JSON format to use them right away. For example:
```json theme={null}
{
"reasoning_effort": "medium"
}
```
2. When troubleshooting parameter-related errors in the playground, such as:
```
TypeError: AsyncCompletions.create() got an unexpected keyword argument 'max_concurrency'
```
If you receive an error about unnecessary parameters (which is more common when using [LangChain JS](/oss/python/langchain/overview) for run tracing), you can use this field to remove the extra parameters.
## Tool settings
[*Tools*](/langsmith/prompt-engineering-concepts#tools) enable your LLM to perform tasks like searching the web, looking up information, and so on. In the **Tools Settings** tab, you can manage the ways your LLM uses and accesses the tools you have defined in your prompt, including:
* **Parallel Tool Calls**: Calling multiple tools in parallel when appropriate. This allows the model to gather information from different sources simultaneously. (Dependent on model support for parallel execution.)
* **Tool Choice**: Select the tools that the model can access. For more details, refer to [Use tools in a prompt](/langsmith/use-tools).
## Prompt formatting
The **Prompt Format** tab allows you to specify:
* The **Prompt type**. For details on chat and completion prompts, refer to [Prompt engineering](/langsmith/prompt-engineering-concepts#chat-vs-completion) concepts.
* The **Template format**. For details on prompt templating and using variables, refer to [F-string vs. mustache](/langsmith/prompt-engineering-concepts##f-string-vs-mustache).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/managing-model-configurations.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Prevent logging of sensitive data in traces
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/mask-inputs-outputs
When working with LangSmith traces, you may need to prevent sensitive information from being logged to maintain privacy and comply with security requirements. LangSmith provides multiple approaches to protect your data before it's sent to the backend:
* [Completely hide inputs and outputs](#hide-inputs-and-outputs) using environment variables or [Client](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client) configuration.
* [Hide metadata](#hide-metadata) to remove or transform run metadata.
* [Apply rule-based masking](#rule-based-masking-of-inputs-and-outputs) with regex patterns or anonymization libraries to selectively redact sensitive information.
* [Process inputs and outputs for individual functions](#processing-inputs-&-outputs-for-a-single-function) with function-level customization.
* [Use third-party anonymizers](#examples) like Microsoft Presidio and Amazon Comprehend for advanced PII detection.
If your compliance or privacy requirements mandate that certain operations should never be traced at all (for example, clients with zero-retention policies), consider using [conditional tracing](/langsmith/conditional-tracing) to disable tracing selectively for specific requests instead of masking data.
## Hide inputs and outputs
If you want to completely hide the inputs and outputs of your traces, you can set the following environment variables when running your application:
```bash theme={null}
LANGSMITH_HIDE_INPUTS=true
LANGSMITH_HIDE_OUTPUTS=true
```
This works for both the LangSmith SDK (Python and TypeScript) and LangChain.
You can also customize and override this behavior for a given [Client](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client) instance. This can be done by setting the `hide_inputs` and `hide_outputs` parameters on the [Client](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client) object (`hideInputs` and `hideOutputs` in TypeScript).
The following example returns an empty object for both `hide_inputs` and `hide_outputs`, but you can customize this to your needs:
```python Python theme={null}
import openai
from langsmith import Client
from langsmith.wrappers import wrap_openai
openai_client = wrap_openai(openai.Client())
langsmith_client = Client(
hide_inputs=lambda inputs: {}, hide_outputs=lambda outputs: {}
)
# The trace produced will have its metadata present, but the inputs will be hidden
openai_client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
],
langsmith_extra={"client": langsmith_client},
)
# The trace produced will not have hidden inputs and outputs
openai_client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
],
)
```
```typescript TypeScript theme={null}
import OpenAI from "openai";
import { Client } from "langsmith";
import { wrapOpenAI } from "langsmith/wrappers";
const langsmithClient = new Client({
hideInputs: (inputs) => ({}),
hideOutputs: (outputs) => ({}),
});
// The trace produced will have its metadata present, but the inputs will be hidden
const filteredOAIClient = wrapOpenAI(new OpenAI(), {
client: langsmithClient,
});
await filteredOAIClient.chat.completions.create({
model: "gpt-4.1-mini",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello!" },
],
});
const openaiClient = wrapOpenAI(new OpenAI());
// The trace produced will not have hidden inputs and outputs
await openaiClient.chat.completions.create({
model: "gpt-4.1-mini",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello!" },
],
});
```
## Hide metadata
The `hide_metadata` parameter allows you to control whether run metadata is hidden or transformed when tracing with the LangSmith Python SDK. Metadata is passed with the `extra` parameter when creating runs (e.g., `extra={"metadata": {...}}`). `hide_metadata` is useful for removing sensitive information, complying with privacy requirements, or reducing the amount of data sent to LangSmith. You can configure metadata hiding in two ways:
* Using the SDK:
```python theme={null}
from langsmith import Client
client = Client(hide_metadata=True)
```
* Using environment variables:
```bash theme={null}
export LANGSMITH_HIDE_METADATA=true
```
The `hide_metadata` parameter accepts three types of values:
* `True`: Completely removes all metadata (sends an empty dictionary).
* `False` or `None`: Preserves metadata as-is (default behavior).
* `Callable`: A custom function that transforms the metadata dictionary.
When set, this parameter affects the `metadata` field in the `extra` parameter for all runs created or updated by the [Client](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client), including runs created through the `@traceable` decorator or LangChain integrations.
### Hide all metadata
Set `hide_metadata=True` to remove all metadata completely from runs sent to LangSmith:
```python theme={null}
from langsmith import Client
# Hide all metadata completely
client = Client(hide_metadata=True)
# Now when you create runs, metadata will be empty
client.create_run(
"my_run",
inputs={"question": "What is 2+2?"},
run_type="llm",
extra={"metadata": {"user_id": "123", "session": "abc"}}
)
# The metadata sent to LangSmith will be {} instead of the provided metadata
```
### Custom transformation
Use a callable function to selectively filter, redact, or modify metadata before it's sent to LangSmith:
```python theme={null}
# Remove sensitive keys
def hide_sensitive_metadata(metadata: dict) -> dict:
return {k: v for k, v in metadata.items() if not k.startswith("_private")}
client = Client(hide_metadata=hide_sensitive_metadata)
# Redact specific values
def redact_emails(metadata: dict) -> dict:
import re
result = {}
for k, v in metadata.items():
if isinstance(v, str) and "@" in v:
result[k] = "[REDACTED_EMAIL]"
else:
result[k] = v
return result
client = Client(hide_metadata=redact_emails)
# Add transformation marker
def add_marker(metadata: dict) -> dict:
return {**metadata, "transformed": True}
client = Client(hide_metadata=add_marker)
```
## Rule-based masking of inputs and outputs
This feature is available in the following LangSmith SDK versions:
* Python: 0.1.81 and above
* TypeScript: 0.1.33 and above
To mask specific data in inputs and outputs, you can use the `create_anonymizer` / `createAnonymizer` function and pass the newly created anonymizer when instantiating the [Client](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client). The anonymizer can be either constructed from a list of regex patterns and the replacement values or from a function that accepts and returns a string value.
The anonymizer will be skipped for inputs if `LANGSMITH_HIDE_INPUTS = true`. Same applies for outputs if `LANGSMITH_HIDE_OUTPUTS = true`.
However, if inputs or outputs are to be sent to [Client](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client), the `anonymizer` method will take precedence over functions found in `hide_inputs` and `hide_outputs`. By default, the `create_anonymizer` will only look at maximum of 10 nesting levels deep, which can be configured via the `max_depth` parameter.
```python Python theme={null}
from langsmith.anonymizer import create_anonymizer
from langsmith import Client, traceable
import re
# create anonymizer from list of regex patterns and replacement values
anonymizer = create_anonymizer([
{ "pattern": r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}", "replace": "" },
{ "pattern": r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", "replace": "" }
])
# or create anonymizer from a function
email_pattern = re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}")
uuid_pattern = re.compile(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
anonymizer = create_anonymizer(
lambda text: email_pattern.sub("", uuid_pattern.sub("", text))
)
client = Client(anonymizer=anonymizer)
@traceable(client=client)
def main(inputs: dict) -> dict:
...
```
```typescript TypeScript theme={null}
import { createAnonymizer } from "langsmith/anonymizer"
import { traceable } from "langsmith/traceable"
import { Client } from "langsmith"
// create anonymizer from list of regex patterns and replacement values
const anonymizer = createAnonymizer([
{ pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}/g, replace: "" },
{ pattern: /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g, replace: "" }
])
// or create anonymizer from a function
const anonymizer = createAnonymizer((value) => value.replace("...", ""))
const client = new Client({ anonymizer })
const main = traceable(async (inputs: any) => {
// ...
}, { client })
```
Please note, that using the anonymizer might incur a performance hit with complex regular expressions or large payloads, as the anonymizer serializes the payload to JSON before processing.
Improving the performance of `anonymizer` API is on our roadmap! If you are encountering performance issues, please contact support via [support.langchain.com](https://support.langchain.com).
Older versions of LangSmith SDKs can use the `hide_inputs` and `hide_outputs` parameters to achieve the same effect. You can also use these parameters to process the inputs and outputs more efficiently.
```python Python theme={null}
import re
from langsmith import Client, traceable
# Define the regex patterns for email addresses and UUIDs
EMAIL_REGEX = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}"
UUID_REGEX = r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
def replace_sensitive_data(data, depth=10):
if depth == 0:
return data
if isinstance(data, dict):
return {k: replace_sensitive_data(v, depth-1) for k, v in data.items()}
elif isinstance(data, list):
return [replace_sensitive_data(item, depth-1) for item in data]
elif isinstance(data, str):
data = re.sub(EMAIL_REGEX, "", data)
data = re.sub(UUID_REGEX, "", data)
return data
else:
return data
client = Client(
hide_inputs=lambda inputs: replace_sensitive_data(inputs),
hide_outputs=lambda outputs: replace_sensitive_data(outputs)
)
inputs = {"role": "user", "content": "Hello! My email is user@example.com and my ID is 123e4567-e89b-12d3-a456-426614174000."}
outputs = {"role": "assistant", "content": "Hi! I've noted your email as user@example.com and your ID as 123e4567-e89b-12d3-a456-426614174000."}
@traceable(client=client)
def child(inputs: dict) -> dict:
return outputs
@traceable(client=client)
def parent(inputs: dict) -> dict:
child_outputs = child(inputs)
return child_outputs
parent(inputs)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
import { traceable } from "langsmith/traceable";
// Define the regex patterns for email addresses and UUIDs
const EMAIL_REGEX = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}/g;
const UUID_REGEX = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g;
function replaceSensitiveData(data: any, depth: number = 10): any {
if (depth === 0) return data;
if (typeof data === "object" && !Array.isArray(data)) {
const result: Record = {};
for (const [key, value] of Object.entries(data)) {
result[key] = replaceSensitiveData(value, depth - 1);
}
return result;
} else if (Array.isArray(data)) {
return data.map(item => replaceSensitiveData(item, depth - 1));
} else if (typeof data === "string") {
return data.replace(EMAIL_REGEX, "").replace(UUID_REGEX, "");
} else {
return data;
}
}
const langsmithClient = new Client({
hideInputs: (inputs) => replaceSensitiveData(inputs),
hideOutputs: (outputs) => replaceSensitiveData(outputs)
});
const inputs = {
role: "user",
content: "Hello! My email is user@example.com and my ID is 123e4567-e89b-12d3-a456-426614174000."
};
const outputs = {
role: "assistant",
content: "Hi! I've noted your email as and your ID as ."
};
const child = traceable(async (inputs: any) => {
return outputs;
}, { name: "child", client: langsmithClient });
const parent = traceable(async (inputs: any) => {
const childOutputs = await child(inputs);
return childOutputs;
}, { name: "parent", client: langsmithClient });
await parent(inputs);
```
## Processing inputs and outputs for a single function
The `process_outputs` parameter is available in LangSmith SDK version 0.1.98 and above for Python.
In addition to [Client](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client)-level input and output processing, LangSmith provides function-level processing through the `process_inputs` and `process_outputs` parameters of the `@traceable` decorator.
These parameters accept functions that allow you to transform the inputs and outputs of a specific function before they are logged to LangSmith. This is useful for reducing payload size, removing sensitive information, or customizing how an object should be serialized and represented in LangSmith for a particular function.
Here's an example of how to use `process_inputs` and `process_outputs`:
```python theme={null}
from langsmith import traceable
def process_inputs(inputs: dict) -> dict:
# inputs is a dictionary where keys are argument names and values are the provided arguments
# Return a new dictionary with processed inputs
return {
"processed_key": inputs.get("my_cool_key", "default"),
"length": len(inputs.get("my_cool_key", ""))
}
def process_outputs(output: Any) -> dict:
# output is the direct return value of the function
# Transform the output into a dictionary
# In this case, "output" will be an integer
return {"processed_output": str(output)}
@traceable(process_inputs=process_inputs, process_outputs=process_outputs)
def my_function(my_cool_key: str) -> int:
# Function implementation
return len(my_cool_key)
result = my_function("example")
```
In this example, `process_inputs` creates a new dictionary with processed input data, and `process_outputs` transforms the output into a specific format before logging to LangSmith.
It's recommended to avoid mutating the source objects in the processor functions. Instead, create and return new objects with the processed data.
For asynchronous functions, the usage is similar:
```python theme={null}
@traceable(process_inputs=process_inputs, process_outputs=process_outputs)
async def async_function(key: str) -> int:
# Async implementation
return len(key)
```
These function-level processors take precedence over [Client](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client)-level processors (`hide_inputs` and `hide_outputs`) when both are defined.
## Examples
You can combine rule-based masking with various anonymizers to scrub sensitive information from inputs and outputs. The following examples will cover working with regex, Microsoft Presidio, and Amazon Comprehend.
### Regex
The implementation below is not exhaustive and may miss some formats or edge cases. Test any implementation thoroughly before using it in production.
You can use regex to mask inputs and outputs before they are sent to LangSmith. The implementation below masks email addresses, phone numbers, full names, credit card numbers, and SSNs.
```python theme={null}
import re
import openai
from langsmith import Client
from langsmith.wrappers import wrap_openai
# Define regex patterns for various PII
SSN_PATTERN = re.compile(r'\b\d{3}-\d{2}-\d{4}\b')
CREDIT_CARD_PATTERN = re.compile(r'\b(?:\d[ -]*?){13,16}\b')
EMAIL_PATTERN = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b')
PHONE_PATTERN = re.compile(r'\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b')
FULL_NAME_PATTERN = re.compile(r'\b([A-Z][a-z]*\s[A-Z][a-z]*)\b')
def regex_anonymize(text):
"""
Anonymize sensitive information in the text using regex patterns.
Args:
text (str): The input text to be anonymized.
Returns:
str: The anonymized text.
"""
# Replace sensitive information with placeholders
text = SSN_PATTERN.sub('[REDACTED SSN]', text)
text = CREDIT_CARD_PATTERN.sub('[REDACTED CREDIT CARD]', text)
text = EMAIL_PATTERN.sub('[REDACTED EMAIL]', text)
text = PHONE_PATTERN.sub('[REDACTED PHONE]', text)
text = FULL_NAME_PATTERN.sub('[REDACTED NAME]', text)
return text
def recursive_anonymize(data, depth=10):
"""
Recursively traverse the data structure and anonymize sensitive information.
Args:
data (any): The input data to be anonymized.
depth (int): The current recursion depth to prevent excessive recursion.
Returns:
any: The anonymized data.
"""
if depth == 0:
return data
if isinstance(data, dict):
anonymized_dict = {}
for k, v in data.items():
anonymized_value = recursive_anonymize(v, depth - 1)
anonymized_dict[k] = anonymized_value
return anonymized_dict
elif isinstance(data, list):
anonymized_list = []
for item in data:
anonymized_item = recursive_anonymize(item, depth - 1)
anonymized_list.append(anonymized_item)
return anonymized_list
elif isinstance(data, str):
anonymized_data = regex_anonymize(data)
return anonymized_data
else:
return data
openai_client = wrap_openai(openai.Client())
# Initialize the LangSmith [Client](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client) with the anonymization functions
langsmith_client = Client(
hide_inputs=recursive_anonymize, hide_outputs=recursive_anonymize
)
# The trace produced will have its metadata present, but the inputs and outputs will be anonymized
response_with_anonymization = openai_client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "My name is John Doe, my SSN is 123-45-6789, my credit card number is 4111 1111 1111 1111, my email is john.doe@example.com, and my phone number is (123) 456-7890."},
],
langsmith_extra={"client": langsmith_client},
)
# The trace produced will not have anonymized inputs and outputs
response_without_anonymization = openai_client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "My name is John Doe, my SSN is 123-45-6789, my credit card number is 4111 1111 1111 1111, my email is john.doe@example.com, and my phone number is (123) 456-7890."},
],
)
```
The anonymized run will look like this in LangSmith:
The non-anonymized run will look like this in LangSmith:
### Microsoft Presidio
The implementation below provides a general example of how to anonymize sensitive information in messages exchanged between a user and an LLM. It is not exhaustive and does not account for all cases. Test any implementation thoroughly before using it in production.
Microsoft Presidio is a data protection and de-identification SDK. The implementation below uses Presidio to anonymize inputs and outputs before they are sent to LangSmith. For up to date information, please refer to Presidio's [official documentation](https://microsoft.github.io/presidio/).
To use Presidio and its spaCy model, install the following:
```bash pip theme={null}
pip install presidio-analyzer
pip install presidio-anonymizer
python -m spacy download en_core_web_lg
```
```bash uv theme={null}
uv add presidio-analyzer
uv add presidio-anonymizer
python -m spacy download en_core_web_lg
```
Also, install OpenAI:
```bash pip theme={null}
pip install openai
```
```bash uv theme={null}
uv add openai
```
```python theme={null}
import openai
from langsmith import Client
from langsmith.wrappers import wrap_openai
from presidio_anonymizer import AnonymizerEngine
from presidio_analyzer import AnalyzerEngine
anonymizer = AnonymizerEngine()
analyzer = AnalyzerEngine()
def presidio_anonymize(data):
"""
Anonymize sensitive information sent by the user or returned by the model.
Args:
data (any): The data to be anonymized.
Returns:
any: The anonymized data.
"""
message_list = (
data.get('messages') or [data.get('choices', [{}])[0].get('message')]
)
if not message_list or not all(isinstance(msg, dict) and msg for msg in message_list):
return data
for message in message_list:
content = message.get('content', '')
if not content.strip():
print("Empty content detected. Skipping anonymization.")
continue
results = analyzer.analyze(
text=content,
entities=["PERSON", "PHONE_NUMBER", "EMAIL_ADDRESS", "US_SSN"],
language='en'
)
anonymized_result = anonymizer.anonymize(
text=content,
analyzer_results=results
)
message['content'] = anonymized_result.text
return data
openai_client = wrap_openai(openai.Client())
# initialize the langsmith [Client](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client) with the anonymization functions
langsmith_client = Client(
hide_inputs=presidio_anonymize, hide_outputs=presidio_anonymize
)
# The trace produced will have its metadata present, but the inputs and outputs will be anonymized
response_with_anonymization = openai_client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "My name is Slim Shady, call me at 313-666-7440 or email me at real.slim.shady@gmail.com"},
],
langsmith_extra={"client": langsmith_client},
)
# The trace produced will not have anonymized inputs and outputs
response_without_anonymization = openai_client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "My name is Slim Shady, call me at 313-666-7440 or email me at real.slim.shady@gmail.com"},
],
)
```
The anonymized run will look like this in LangSmith:
The non-anonymized run will look like this in LangSmith:
### Amazon Comprehend
The implementation below provides a general example of how to anonymize sensitive information in messages exchanged between a user and an LLM. It is not exhaustive and does not account for all cases. Test any implementation thoroughly before using it in production.
Comprehend is a natural language processing service that can detect personally identifiable information. The implementation below uses Comprehend to anonymize inputs and outputs before they are sent to LangSmith. For up to date information, please refer to Comprehend's [official documentation](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_DetectPiiEntities.html).
To use Comprehend, install [boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html):
```bash pip theme={null}
pip install boto3
```
```bash uv theme={null}
uv add boto3
```
Also, install OpenAI:
```bash pip theme={null}
pip install openai
```
```bash uv theme={null}
uv add openai
```
You will need to set up credentials in AWS and authenticate using the AWS CLI. Follow the instructions [here](https://docs.aws.amazon.com/comprehend/latest/dg/setting-up.html).
```python theme={null}
import openai
import boto3
from langsmith import Client
from langsmith.wrappers import wrap_openai
comprehend = boto3.client('comprehend', region_name='us-east-1')
def redact_pii_entities(text, entities):
"""
Redact PII entities in the text based on the detected entities.
Args:
text (str): The original text containing PII.
entities (list): A list of detected PII entities.
Returns:
str: The text with PII entities redacted.
"""
sorted_entities = sorted(entities, key=lambda x: x['BeginOffset'], reverse=True)
redacted_text = text
for entity in sorted_entities:
begin = entity['BeginOffset']
end = entity['EndOffset']
entity_type = entity['Type']
# Define the redaction placeholder based on entity type
placeholder = f"[{entity_type}]"
# Replace the PII in the text with the placeholder
redacted_text = redacted_text[:begin] + placeholder + redacted_text[end:]
return redacted_text
def detect_pii(text):
"""
Detect PII entities in the given text using AWS Comprehend.
Args:
text (str): The text to analyze.
Returns:
list: A list of detected PII entities.
"""
try:
response = comprehend.detect_pii_entities(
Text=text,
LanguageCode='en',
)
entities = response.get('Entities', [])
return entities
except Exception as e:
print(f"Error detecting PII: {e}")
return []
def comprehend_anonymize(data):
"""
Anonymize sensitive information sent by the user or returned by the model.
Args:
data (any): The input data to be anonymized.
Returns:
any: The anonymized data.
"""
message_list = (
data.get('messages') or [data.get('choices', [{}])[0].get('message')]
)
if not message_list or not all(isinstance(msg, dict) and msg for msg in message_list):
return data
for message in message_list:
content = message.get('content', '')
if not content.strip():
print("Empty content detected. Skipping anonymization.")
continue
entities = detect_pii(content)
if entities:
anonymized_text = redact_pii_entities(content, entities)
message['content'] = anonymized_text
else:
print("No PII detected. Content remains unchanged.")
return data
openai_client = wrap_openai(openai.Client())
# initialize the langsmith [Client](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client) with the anonymization functions
langsmith_client = Client(
hide_inputs=comprehend_anonymize, hide_outputs=comprehend_anonymize
)
# The trace produced will have its metadata present, but the inputs and outputs will be anonymized
response_with_anonymization = openai_client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "My name is Slim Shady, call me at 313-666-7440 or email me at real.slim.shady@gmail.com"},
],
langsmith_extra={"client": langsmith_client},
)
# The trace produced will not have anonymized inputs and outputs
response_without_anonymization = openai_client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "My name is Slim Shady, call me at 313-666-7440 or email me at real.slim.shady@gmail.com"},
],
)
```
The anonymized run will look like this in LangSmith:
The non-anonymized run will look like this in LangSmith:
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/mask-inputs-outputs.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to return categorical vs numerical metrics
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/metric-type
LangSmith supports both categorical and numerical metrics, and you can return either when writing a custom evaluator.
For an evaluator result to be logged as a numerical metric, it must returned as:
* (Python only) an `int`, `float`, or `bool`
* a dict of the form `{"key": "metric_name", "score": int | float | bool}`
For an evaluator result to be logged as a categorical metric, it must be returned as:
* (Python only) a `str`
* a dict of the form `{"key": "metric_name", "value": str | int | float | bool}`
Here are some examples:
* Python: Requires `langsmith>=0.2.0`
* TypeScript: Support for multiple scores is available in `langsmith@0.1.32` and higher
```python Python theme={null}
def numerical_metric(inputs: dict, outputs: dict, reference_outputs: dict) -> float:
# Evaluation logic...
return 0.8
# Equivalently
# return {"score": 0.8}
# Or
# return {"key": "numerical_metric", "score": 0.8}
def categorical_metric(inputs: dict, outputs: dict, reference_outputs: dict) -> str:
# Evaluation logic...
return "english"
# Equivalently
# return {"key": "categorical_metric", "score": "english"}
# Or
# return {"score": "english"}
```
```typescript TypeScript theme={null}
import type { Run, Example } from "langsmith/schemas";
function numericalMetric(run: Run, example: Example) {
// Your evaluation logic here
return { key: "numerical_metric", score: 0.8};
}
function categoricalMetric(run: Run, example: Example) {
// Your evaluation logic here
return { key: "categorical_metric", value: "english"};
}
```
## Related
* [Return multiple metrics in one evaluator](/langsmith/multiple-scores)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/metric-type.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Monorepo support
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/monorepo-support
LangSmith supports deploying agents from monorepo setups where your agent code may depend on shared packages located elsewhere in the repository. This guide shows how to structure your monorepo and configure your `langgraph.json` file to work with shared dependencies.
## Repository structure
For complete working examples, see:
* [Python monorepo example](https://github.com/langchain-ai/python-langraph-monorepo-example)
* [JS monorepo example](https://github.com/langchain-ai/js-langgraph-monorepo-example)
```plaintext Python theme={null}
my-monorepo/
├── shared-utils/ # Shared Python package
│ ├── __init__.py
│ ├── common.py
│ └── pyproject.toml # Or setup.py
├── agents/
│ └── customer-support/ # Agent directory
│ ├── agent/
│ │ ├── __init__.py
│ │ └── graph.py
│ ├── langgraph.json # Config file in agent directory
│ ├── .env
│ └── pyproject.toml # Agent dependencies
└── other-service/
└── ...
```
```plaintext JS theme={null}
my-monorepo/
├── package.json # Root package.json with workspaces
├── shared-utils/ # Shared TypeScript package
│ ├── package.json
│ ├── src/
│ │ └── index.ts
│ └── tsconfig.json
├── agents/
│ └── customer-support/ # Agent directory
│ ├── src/
│ │ └── agent.ts
│ ├── langgraph.json # Config file in agent directory
│ ├── package.json # Agent dependencies
│ ├── .env
│ └── tsconfig.json
└── other-service/
└── ...
```
## LangGraph.json configuration
Place the langgraph.json file in your agent’s directory (not in the monorepo root). Ensure the file follows the required structure:
```json Python theme={null}
{
"dependencies": [
".", # Current agent package
"../../shared-utils" # Relative path to shared package
],
"graphs": {
"customer_support": "./agent/graph.py:graph"
},
"env": ".env"
}
```
```json JS theme={null}
{
"node_version": "20",
"graphs": {
"customer_support": "./src/agent.ts:graph"
},
"env": ".env"
}
```
The Python implementation automatically handles packages in parent directories by:
* Detecting relative paths that start with `"."`.
* Adding parent directories to the Docker build context as needed.
* Supporting both real packages (with `pyproject.toml`/`setup.py`) and simple Python modules.
For JavaScript monorepos:
* Shared workspace dependencies are resolved automatically by your package manager.
* Your `package.json` should reference shared packages using workspace syntax.
Example `package.json` in the agent directory:
```json theme={null}
{
"name": "customer-support-agent",
"dependencies": {
"@company/shared-utils": "workspace:*",
"@langchain/langgraph": "^0.2.0"
}
}
```
## Building the application
Run `langgraph build`:
```bash Python theme={null}
cd agents/customer-support
langgraph build -t my-customer-support-agent
```
```bash JS theme={null}
# Run from the root of the monorepo
langgraph build -t my-customer-support-agent -c agents/customer-support/langgraph.json --build-command "yarn run turbo build" --install-command "yarn install"
```
The Python build process:
1. Automatically detects relative dependency paths.
2. Copies shared packages into the Docker build context.
3. Installs all dependencies in the correct order.
4. No special flags or commands required.
The JavaScript build process:
1. Uses the directory you called `langgraph build` from (the monorepo root in this case) as the build context.
2. Automatically detects your package manager (yarn, npm, pnpm, bun)
3. Runs the appropriate install command.
* If you have one or both of a custom build/install command it will run from the directory you called `langgraph build` from.
* Otherwise, it will run from the directory where the `langgraph.json` file is located.
4. Optionally runs a custom build command from the directory where the `langgraph.json` file is located (only if you pass the `--build-command` flag).
## Tips and best practices
1. **Keep agent configs in agent directories**: Place `langgraph.json` files in the specific agent directories, not at the monorepo root. This allows you to support multiple agents in the same monorepo, without having to deploy them all in the same LangSmith deployment.
2. **Use relative paths for Python**: For Python monorepos, use relative paths like `"../../shared-package"` in the `dependencies` array.
3. **Leverage workspace features for JS**: For JavaScript/TypeScript, use your package manager's workspace features to manage dependencies between packages.
4. **Test locally first**: Always test your build locally before deploying to ensure all dependencies are correctly resolved.
5. **Environment variables**: Keep environment files (`.env`) in your agent directories for environment-specific configuration.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/monorepo-support.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to simulate multi-turn interactions
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/multi-turn-simulation
* [Multi-turn interactions](/langsmith/evaluation-concepts#multi-turn-interactions)
* [Evaluators](/langsmith/evaluation-concepts#evaluators)
* [LLM-as-judge](/langsmith/evaluation-concepts#llm-as-judge)
* [OpenEvals](https://github.com/langchain-ai/openevals)
AI applications with conversational interfaces, like chatbots, operate over multiple interactions with a user, also called conversation *turns*. When evaluating the performance of such applications, core concepts such as [building a dataset](/langsmith/evaluation-concepts#datasets) and defining [evaluators](/langsmith/evaluation-concepts#evaluators) and metrics to judge your app outputs remain useful. However, you may also find it useful to run a *simulation* between your app and a user, then evaluate this dynamically created trajectory.
Some advantages of doing this are:
* Ease of getting started vs. an evaluation over a full dataset of pre-existing trajectories
* End-to-end coverage from an initial query until a successful or unsuccessful resolution
* The ability to detect repetitive behavior or context loss over several iterations of your app
The downside is that because you are broadening your evaluation surface area to contain multiple turns, there is less consistency than evaluating a single output from your app given a static input from a dataset.
This guide will show you how to simulate multi-turn interactions and evaluate them using the open-source [`openevals`](https://github.com/langchain-ai/openevals) package, which contains prebuilt evaluators and other convenient resources for evaluating your AI apps. It will also use OpenAI models, though you can use other providers as well.
## Setup
First, ensure you have the required dependencies installed:
```bash Python theme={null}
pip install -U langsmith openevals
```
```bash TypeScript theme={null}
npm install langsmith openevals
```
If you are using `yarn` as your package manager, you will also need to manually install `@langchain/core` as a peer dependency of `openevals`. This is not required for LangSmith evals in general.
And set up your environment variables:
```bash theme={null}
export LANGSMITH_TRACING="true"
export LANGSMITH_API_KEY=""
export OPENAI_API_KEY=""
```
## Running a simulation
There are two primary components you'll need to get started:
* `app`: Your application, or a function wrapping it. Must accept a single chat message (dict with "role" and "content" keys) as an input arg and a `thread_id` as a kwarg. Should accept other kwargs as more may be added in future releases. Returns a chat message as output with at least role and content keys.
* `user`: The simulated user. In this guide, we will use an imported prebuilt function named `create_llm_simulated_user` which uses an LLM to generate user responses, though you can [create your own too](https://github.com/langchain-ai/openevals?tab=readme-ov-file#custom-simulated-users).
The simulator in `openevals` passes a single chat message to your `app` from the `user` for each turn. Therefore you should statefully track the current history internally based on `thread_id` if needed.
Here's an example that simulates a multi-turn customer support interaction. This guide uses a simple chat app that wraps a single call to the OpenAI chat completions API, however this is where you would call your application or agent. In this example, our simulated user is playing the role of a particularly aggressive customer:
```python Python theme={null}
from openevals.simulators import run_multiturn_simulation, create_llm_simulated_user
from openevals.types import ChatCompletionMessage
from langsmith.wrappers import wrap_openai
from openai import OpenAI
# Wrap OpenAI client for tracing
client = wrap_openai(OpenAI())
history = {}
# Your application logic
def app(inputs: ChatCompletionMessage, *, thread_id: str, **kwargs):
if thread_id not in history:
history[thread_id] = []
history[thread_id].append(inputs)
# inputs is a message object with role and content
res = client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{
"role": "system",
"content": "You are a patient and understanding customer service agent.",
},
] + history[thread_id],
)
response_message = res.choices[0].message
history[thread_id].append(response_message)
return response_message
user = create_llm_simulated_user(
system="You are an aggressive and hostile customer who wants a refund for their car.",
model="openai:gpt-4.1-mini",
)
# Run the simulation directly with the new function
simulator_result = run_multiturn_simulation(
app=app,
user=user,
max_turns=5,
)
print(simulator_result)
```
```typescript TypeScript theme={null}
import { OpenAI } from "openai";
import { wrapOpenAI } from "langsmith/wrappers/openai";
import {
createLLMSimulatedUser,
runMultiturnSimulation,
type ChatCompletionMessage,
} from "openevals";
// Wrap OpenAI client for tracing
const client = wrapOpenAI(new OpenAI());
const history = {};
// Your application logic
const app = async ({ inputs, threadId }: { inputs: ChatCompletionMessage, threadId: string }) => {
if (history[threadId] === undefined) {
history[threadId] = [];
}
history[threadId].push(inputs);
const res = await client.chat.completions.create({
model: "gpt-4.1-mini",
messages: [
{
role: "system",
content:
"You are a patient and understanding customer service agent.",
},
inputs,
],
});
const responseMessage = res.choices[0].message;
history[threadId].push(responseMessage);
return res.choices[0].message;
};
const user = createLLMSimulatedUser({
system: "You are an aggressive and hostile customer who wants a refund for their car.",
model: "openai:gpt-4.1-mini",
});
const result = await runMultiturnSimulation({
app,
user,
maxTurns: 5,
});
console.log(result);
```
The response looks like this:
```json theme={null}
{
"trajectory": [
{
"role": "user",
"content": "This piece of junk car is a complete disaster! I demand a full refund immediately. How dare you sell me such a worthless vehicle!",
"id": "chatcmpl-BUpXa07LaM7wXbyaNnng1Gtn5Dsbh"
},
{
"role": "assistant",
"content": "I'm really sorry to hear about your experience and understand how frustrating this must be. I'd like to help resolve this issue as smoothly as possible. Could you please provide some details about the problem with the vehicle? Once I have more information, I'll do my best to assist you with a solution, whether it's a refund or other options. Thank you for your patience.",
"refusal": null,
"annotations": [],
"id": "d7520f6a-7cf8-46f8-abe4-7df04f134482"
},
"...",
{
"role": "assistant",
"content": "I truly understand your frustration and sincerely apologize for the inconvenience you've experienced.\n\nPlease allow me a moment to review your case, and I will do everything I can to expedite your refund. Your patience is greatly appreciated, and I am committed to resolving this matter to your satisfaction.",
"refusal": null,
"annotations": [],
"id": "a0536d4f-9353-4cfa-84df-51c8d29e076d"
}
]
}
```
The simulation first generates an initial query from the simulated `user`, then passes response chat messages back and forth until it reaches `max_turns` (you can alternatively pass a `stopping_condition` that takes the current trajectory and returns `True` or `False` - [see the OpenEvals README for more information](https://github.com/langchain-ai/openevals?tab=readme-ov-file#multiturn-simulation)). The return value is the final list of chat messages that make up the converation's **trajectory**.
There are several ways to configure the simulated user, such as having it return fixed responses for the first turns of your simulation, as well as the simulation as a whole. For full details, check out [the OpenEvals README](https://github.com/langchain-ai/openevals?tab=readme-ov-file#multiturn-simulation).
The final trace will look something [like this](https://smith.langchain.com/public/648ca37d-1c4d-4f7b-9b6a-89e35dc5d4f0/r) with responses from your `app` and `user` interleaved:
Congrats! You just ran your first multi-turn simulation. Next, we'll cover how to run it in a LangSmith experiment.
## Running in LangSmith experiments
You can use the results of multi-turn simulations as part of a LangSmith experiment to track performance and progress over time. For these sections, it helps to be familiar with at least one of LangSmith's [`pytest`](/langsmith/pytest) (Python-only), [`Vitest`/`Jest`](/langsmith/vitest-jest) (JS only), or [`evaluate`](/langsmith/evaluate-llm-application) runners.
### Using `pytest` or `Vitest/Jest`
See the following guides to learn how to set up evals using LangSmith's integrations with test frameworks:
* [`pytest`](https://docs.smith.langchain.com/langsmith/pytest)
* [`Vitest` or `Jest`](https://docs.smith.langchain.com/langsmith/vitest-jest)
If you are using one of the [LangSmith test framework integrations](/langsmith/pytest), you can pass in an array of OpenEvals evaluators as a `trajectory_evaluators` param when running the simulation. These evaluators will run at the end of the simulation, taking the final list of chat messages as an `outputs` kwarg. Your passed `trajectory_evaluator` must therefore accept this kwarg.
Here's an example:
```python Python theme={null}
from openevals.simulators import run_multiturn_simulation, create_llm_simulated_user
from openevals.llm import create_llm_as_judge
from openevals.types import ChatCompletionMessage
from langsmith import testing as t
from langsmith.wrappers import wrap_openai
from openai import OpenAI
import pytest
@pytest.mark.langsmith
def test_multiturn_message_with_openai():
inputs = {"role": "user", "content": "I want a refund for my car!"}
t.log_inputs(inputs)
# Wrap OpenAI client for tracing
client = wrap_openai(OpenAI())
history = {}
def app(inputs: ChatCompletionMessage, *, thread_id: str):
if thread_id not in history:
history[thread_id] = []
history[thread_id] = history[thread_id] + [inputs]
res = client.chat.completions.create(
model="gpt-4.1-nano",
messages=[
{
"role": "system",
"content": "You are a patient and understanding customer service agent.",
}
]
+ history[thread_id],
)
response = res.choices[0].message
history[thread_id].append(response)
return response
user = create_llm_simulated_user(
system="You are a nice customer who wants a refund for their car.",
model="openai:gpt-4.1-nano",
fixed_responses=[
inputs,
],
)
trajectory_evaluator = create_llm_as_judge(
model="openai:o3-mini",
prompt="Based on the below conversation, was the user satisfied?\n{outputs}",
feedback_key="satisfaction",
)
res = run_multiturn_simulation(
app=app,
user=user,
trajectory_evaluators=[trajectory_evaluator],
max_turns=5,
)
t.log_outputs(res)
# Optionally, assert that the evaluator scored the interaction as satisfactory.
# This will cause the overall test case to fail if "score" is False.
assert res["evaluator_results"][0]["score"]
```
```typescript TypeScript theme={null}
import { OpenAI } from "openai";
import { wrapOpenAI } from "langsmith/wrappers/openai";
import * as ls from "langsmith/vitest";
import { expect } from "vitest";
// import * as ls from "langsmith/jest";
// import { expect } from "@jest/globals";
import {
createLLMSimulatedUser,
runMultiturnSimulation,
createLLMAsJudge,
type ChatCompletionMessage,
} from "openevals";
const client = wrapOpenAI(new OpenAI());
ls.describe("Multiturn demo", () => {
ls.test(
"Should have a satisfactory interaction with a nice user",
{
inputs: {
messages: [{ role: "user" as const, content: "I want a refund for my car!" }],
},
},
async ({ inputs }) => {
const history = {};
// Create a custom app function
const app = async (
{ inputs, threadId }: { inputs: ChatCompletionMessage, threadId: string }
) => {
if (history[threadId] === undefined) {
history[threadId] = [];
}
history[threadId].push(inputs);
const res = await client.chat.completions.create({
model: "gpt-4.1-nano",
messages: [
{
role: "system",
content:
"You are a patient and understanding customer service agent",
},
inputs,
],
});
const responseMessage = res.choices[0].message;
history[threadId].push(responseMessage);
return responseMessage;
};
const user = createLLMSimulatedUser({
system:
"You are a nice customer who wants a refund for their car.",
model: "openai:gpt-4.1-nano",
fixedResponses: inputs.messages,
});
const trajectoryEvaluator = createLLMAsJudge({
model: "openai:o3-mini",
prompt:
"Based on the below conversation, was the user satisfied?\n{outputs}",
feedbackKey: "satisfaction",
});
const result = await runMultiturnSimulation({
app,
user,
trajectoryEvaluators: [trajectoryEvaluator],
maxTurns: 5,
});
ls.logOutputs(result);
// Optionally, assert that the evaluator scored the interaction as satisfactory.
// This will cause the overall test case to fail if "score" is false.
expect(result.evaluatorResults[0].score).toBe(true);
}
);
});
```
LangSmith will automatically detect and log the feedback returned from the passed `trajectory_evaluators`, adding it to the experiment. Note also that the test case uses the `fixed_responses` param on the simulated user to start the conversation with a specific input, which you can log and make part of your stored dataset.
You may also find it convenient to have the simulated user's system prompt be part of your logged dataset.
### Using `evaluate`
You can also use the [`evaluate`](/langsmith/evaluate-llm-application) runner to evaluate simulated multi-turn interactions. This will be a little bit different from the `pytest`/`Vitest`/`Jest` example in the following ways:
* The simulation should be part of your `target` function, and your target function should return the final trajectory.
* This will make the trajectory the `outputs` that LangSmith will pass to your evaluators.
* Instead of using the `trajectory_evaluators` param, you should pass your evaluators as a param into the `evaluate()` method.
* You will need an existing dataset of inputs and (optionally) reference trajectories.
Here's an example:
```python Python theme={null}
from openevals.simulators import run_multiturn_simulation, create_llm_simulated_user
from openevals.llm import create_llm_as_judge
from openevals.types import ChatCompletionMessage
from langsmith.wrappers import wrap_openai
from langsmith import Client
from openai import OpenAI
ls_client = Client()
examples = [
{
"inputs": {
"messages": [{ "role": "user", "content": "I want a refund for my car!" }]
},
},
]
dataset = ls_client.create_dataset(dataset_name="multiturn-starter")
ls_client.create_examples(
dataset_id=dataset.id,
examples=examples,
)
trajectory_evaluator = create_llm_as_judge(
model="openai:o3-mini",
prompt="Based on the below conversation, was the user satisfied?\n{outputs}",
feedback_key="satisfaction",
)
def target(inputs: dict):
# Wrap OpenAI client for tracing
client = wrap_openai(OpenAI())
history = {}
def app(next_message: ChatCompletionMessage, *, thread_id: str):
if thread_id not in history:
history[thread_id] = []
history[thread_id] = history[thread_id] + [next_message]
res = client.chat.completions.create(
model="gpt-4.1-nano",
messages=[
{
"role": "system",
"content": "You are a patient and understanding customer service agent.",
}
]
+ history[thread_id],
)
response = res.choices[0].message
history[thread_id].append(response)
return response
user = create_llm_simulated_user(
system="You are a nice customer who wants a refund for their car.",
model="openai:gpt-4.1-nano",
fixed_responses=inputs["messages"],
)
res = run_multiturn_simulation(
app=app,
user=user,
max_turns=5,
)
return res["trajectory"]
results = ls_client.evaluate(
target,
data=dataset.name,
evaluators=[trajectory_evaluator],
)
```
```typescript TypeScript theme={null}
import { OpenAI } from "openai";
import { Client } from "langsmith";
import { wrapOpenAI } from "langsmith/wrappers/openai";
import { evaluate } from "langsmith/evaluation";
import {
createLLMSimulatedUser,
runMultiturnSimulation,
createLLMAsJudge,
type ChatCompletionMessage,
} from "openevals";
const lsClient = new Client();
const inputs = {
messages: [
{
role: "user",
content: "I want a refund for my car!",
},
],
};
const datasetName = "Multiturn";
const dataset = await lsClient.createDataset(datasetName);
await lsClient.createExamples([{ inputs, dataset_id: dataset.id }]);
const trajectoryEvaluator = createLLMAsJudge({
model: "openai:o3-mini",
prompt:
"Based on the below conversation, was the user satisfied?\n{outputs}",
feedbackKey: "satisfaction",
});
const client = wrapOpenAI(new OpenAI());
const target = async (inputs: { messages: ChatCompletionMessage[]}) => {
const history = {};
// Create a custom app function
const app = async (
{ inputs: nextMessage, threadId }: { inputs: ChatCompletionMessage, threadId: string }
) => {
if (history[threadId] === undefined) {
history[threadId] = [];
}
history[threadId].push(nextMessage);
const res = await client.chat.completions.create({
model: "gpt-4.1-nano",
messages: [
{
role: "system",
content:
"You are a patient and understanding customer service agent",
},
nextMessage,
],
});
const responseMessage = res.choices[0].message;
history[threadId].push(responseMessage);
return responseMessage;
};
const user = createLLMSimulatedUser({
system:
"You are a nice customer who wants a refund for their car.",
model: "openai:gpt-4.1-nano",
fixedResponses: inputs.messages,
});
const result = await runMultiturnSimulation({
app,
user,
maxTurns: 5,
});
return result.trajectory;
};
await evaluate(target, {
data: datasetName,
evaluators: [trajectoryEvaluator],
});
```
## Modifying the simulated user persona
The above examples run using the same simulated user persona for all input examples, defined by the `system` parameter passed into `create_llm_simulated_user`. If you would like to use a different persona for specific items in your dataset, you can update your dataset examples to also contain an extra field with the desired `system` prompt, then pass that field in when creating your simulated user like this:
```python Python theme={null}
from openevals.simulators import run_multiturn_simulation, create_llm_simulated_user
from openevals.llm import create_llm_as_judge
from openevals.types import ChatCompletionMessage
from langsmith.wrappers import wrap_openai
from langsmith import Client
from openai import OpenAI
ls_client = Client()
examples = [
{
"inputs": {
"messages": [{ "role": "user", "content": "I want a refund for my car!" }],
"simulated_user_prompt": "You are an angry and belligerent customer who wants a refund for their car."
},
},
{
"inputs": {
"messages": [{ "role": "user", "content": "Please give me a refund for my car." }],
"simulated_user_prompt": "You are a nice customer who wants a refund for their car.",
},
}
]
dataset = ls_client.create_dataset(dataset_name="multiturn-with-personas")
ls_client.create_examples(
dataset_id=dataset.id,
examples=examples,
)
trajectory_evaluator = create_llm_as_judge(
model="openai:o3-mini",
prompt="Based on the below conversation, was the user satisfied?\n{outputs}",
feedback_key="satisfaction",
)
def target(inputs: dict):
# Wrap OpenAI client for tracing
client = wrap_openai(OpenAI())
history = {}
def app(next_message: ChatCompletionMessage, *, thread_id: str):
if thread_id not in history:
history[thread_id] = []
history[thread_id] = history[thread_id] + [next_message]
res = client.chat.completions.create(
model="gpt-4.1-nano",
messages=[
{
"role": "system",
"content": "You are a patient and understanding customer service agent.",
}
]
+ history[thread_id],
)
response = res.choices[0].message
history[thread_id].append(response)
return response
user = create_llm_simulated_user(
system=inputs["simulated_user_prompt"],
model="openai:gpt-4.1-nano",
fixed_responses=inputs["messages"],
)
res = run_multiturn_simulation(
app=app,
user=user,
max_turns=5,
)
return res["trajectory"]
results = ls_client.evaluate(
target,
data=dataset.name,
evaluators=[trajectory_evaluator],
)
```
```typescript TypeScript theme={null}
import { OpenAI } from "openai";
import { Client } from "langsmith";
import { wrapOpenAI } from "langsmith/wrappers/openai";
import { evaluate } from "langsmith/evaluation";
import {
createLLMSimulatedUser,
runMultiturnSimulation,
createLLMAsJudge,
type ChatCompletionMessage,
} from "openevals";
const lsClient = new Client();
const datasetName = "Multiturn with personas";
const dataset = await lsClient.createDataset(datasetName);
const examples = [{
inputs: {
messages: [
{
role: "user",
content: "I want a refund for my car!",
},
],
simulated_user_prompt: "You are an angry and belligerent customer who wants a refund for their car.",
},
dataset_id: dataset.id,
}, {
inputs: {
messages: [
{
role: "user",
content: "Please give me a refund for my car."
}
],
simulated_user_prompt: "You are a nice customer who wants a refund for their car.",
},
dataset_id: dataset.id,
}];
await lsClient.createExamples(examples);
const trajectoryEvaluator = createLLMAsJudge({
model: "openai:o3-mini",
prompt:
"Based on the below conversation, was the user satisfied?\n{outputs}",
feedbackKey: "satisfaction",
});
const client = wrapOpenAI(new OpenAI());
const target = async (inputs: {
messages: ChatCompletionMessage[],
simulated_user_prompt: string,
}) => {
const history = {};
// Create a custom app function
const app = async (
{ inputs: nextMessage, threadId }: { inputs: ChatCompletionMessage, threadId: string }
) => {
if (history[threadId] === undefined) {
history[threadId] = [];
}
history[threadId].push(nextMessage);
const res = await client.chat.completions.create({
model: "gpt-4.1-nano",
messages: [
{
role: "system",
content:
"You are a patient and understanding customer service agent",
},
nextMessage,
],
});
const responseMessage = res.choices[0].message;
history[threadId].push(responseMessage);
return responseMessage;
};
const user = createLLMSimulatedUser({
system: inputs.simulated_user_prompt,
model: "openai:gpt-4.1-nano",
fixedResponses: inputs.messages,
});
const result = await runMultiturnSimulation({
app,
user,
maxTurns: 5,
});
return result.trajectory;
};
await evaluate(target, {
data: datasetName,
evaluators: [trajectoryEvaluator],
});
```
## Next steps
You've just seen some techniques for simulating multi-turn interactions and running them in LangSmith evals.
Here are some topics you might want to explore next:
* [Trace multiturn conversations across different traces](/langsmith/threads)
* [Use multiple messages in the playground UI](/langsmith/multiple-messages)
* [Return multiple metrics in one evaluator](/langsmith/multiple-scores)
You can also explore the [OpenEvals readme](https://github.com/langchain-ai/openevals) for more on prebuilt evaluators.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/multi-turn-simulation.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Include multimodal content in a prompt
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/multimodal-content
Some applications are based around multimodal content, like a chatbot that can answer questions about a PDF or image. In these cases, you'll want to include multimodal content in your prompt and test the model's ability to answer questions about the content.
The LangSmith Playground supports two methods for incorporating multimodal content in your prompts:
1. Inline content: Embed static files (images, PDFs, audio) directly in your prompt. This is ideal when you want to consistently include the same multimodal content across all uses of the prompt. For example, you might include a reference image that helps ground the model's responses.
2. Template variables: Create dynamic placeholders for attachments that can be populated with different content each time. This approach offers more flexibility, allowing you to:
* Test how the model handles different inputs
* Create reusable prompts that work with varying content
Not all models support multimodal content. Before using multimodal features in the playground, make sure your selected model supports the file types you want to use.
## Inline content
Click the file icon in the message where you want to add multimodal content. Under the `Upload content` tab, you can upload a file and include it inline in the prompt.
## Template variables
Click the file icon in the message where you want to add multimodal content. Under the `Template variables` tab, you can create a template variable for a specific attachment type. Currently, only images, PDFs, and audio files (.wav, .mp3) are supported.
## Populate the template variable
Once you've added a template variable, you can provide content for it using the panel on the right side of the screen. Simply click the `+` button to upload or select content that will be used to populate the template variable.
## Run an evaluation
After testing out your prompt manually, you can [run an evaluation](/langsmith/evaluate-with-attachments?mode=ui) to see how the prompt performs over a golden dataset of examples.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/multimodal-content.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Test multi-turn conversations
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/multiple-messages
This how-to guide walks you through the various ways you can set up the playground for multi-turn conversations, which will allow you to test different tool configurations and system prompts against longer threads of messages.
## From an existing run
First, ensure you have properly [traced](/langsmith/observability) a multi-turn conversation, and then navigate to your tracing project. Once you get to your tracing project simply open the run, select the LLM call, and open it in the playground as follows:
You can then edit the system prompt, tweak the tools and/or output schema and observe how the output of the multi-turn conversation changes.
## From a dataset
Before starting, make sure you have [set up your dataset](/langsmith/manage-datasets-in-application). Since you want to evaluate multi-turn conversations, make sure there is a key in your inputs that contains a list of messages.
Once you have created your dataset, head to the playground and [load your dataset](/langsmith/manage-datasets-in-application#from-the-prompt-playground) to evaluate.
Then, add a messages list variable to your prompt, making sure to name it the same as the key in your inputs that contains the list of messages:
When you run your prompt, the messages from each example will be added as a list in place of the 'Messages List' variable.
## Manually
There are two ways to manually create multi-turn conversations. The first way is by simply appending messages to the prompt:
This is helpful for quick iteration, but is rigid since the multi-turn conversation is hardcoded. Instead, if you want your prompt to work with any multi-turn conversation you can add a 'Messages List' variable and add your multi-turn conversation there:
This allows you to just tweak the system prompt or the tools, while allowing any multi-turn conversation to take the place of the `Messages List` variable, allowing you to reuse this prompt across various runs.
## Next steps
Now that you know how to set up the playground for multi-turn interactions, you can either manually inspect and judge the outputs, or you can [add evaluators](/langsmith/code-evaluator-ui) to classify results.
You can also read [these how-to guides](/langsmith/create-a-prompt) to learn more about how to use the playground to run evaluations.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/multiple-messages.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to return multiple scores in one evaluator
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/multiple-scores
Sometimes it is useful for a custom evaluator or summary evaluator to return multiple metrics. For example, if you have multiple metrics being generated by an LLM judge, you can save time and money by making a single LLM call that generates multiple metrics instead of making multiple LLM calls.
To return multiple scores using the Python SDK, simply return a list of dictionaries/objects of the following form:
```python theme={null}
[
# 'key' is the metric name
# 'score' is the value of a numerical metric
{"key": string, "score": number},
# 'value' is the value of a categorical metric
{"key": string, "value": string},
... # You may log as many as you wish
]
```
To do so with the JS/TS SDK, return an object with a 'results' key and then a list of the above form
```typescript theme={null}
{results: [{ key: string, score: number }, ...]};
```
Each of these dictionaries can contain any or all of the [feedback fields](/langsmith/feedback-data-format); check out the linked document for more information.
Example:
* Python: Requires `langsmith>=0.2.0`
* TypeScript: Support for multiple scores is available in `langsmith@0.1.32` and higher
```python Python theme={null}
def multiple_scores(outputs: dict, reference_outputs: dict) -> list[dict]:
# Replace with real evaluation logic.
precision = 0.8
recall = 0.9
f1 = 0.85
return [
{"key": "precision", "score": precision},
{"key": "recall", "score": recall},
{"key": "f1", "score": f1},
]
```
```typescript TypeScript theme={null}
import type { Run, Example } from "langsmith/schemas";
function multipleScores(rootRun: Run, example: Example) {
// Your evaluation logic here
return {
results: [
{ key: "precision", score: 0.8 },
{ key: "recall", score: 0.9 },
{ key: "f1", score: 0.85 },
],
};
}
```
Rows from the resulting experiment will display each of the scores.
## Related
* [Return categorical vs numerical metrics](/langsmith/metric-type)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/multiple-scores.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Troubleshoot trace nesting
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/nest-traces
When tracing with the LangSmith SDK, LangGraph, and LangChain, tracing should automatically propagate the correct context so that code executed within a parent trace will be rendered in the expected location in the UI.
If you see a child run go to a separate trace (and appear on the top level), it may be caused by one of the following known "edge cases".
## Python
The following outlines common causes for "split" traces when building with python.
### Context propagation using asyncio
When using async calls (especially with streaming) in Python versions \< 3.11, you may encounter issues with trace nesting. This is because Python's `asyncio` only [added full support for passing context](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task) in version 3.11.
#### Why
LangChain and LangSmith SDK use [contextvars](https://docs.python.org/3/library/contextvars.html) to propagate tracing information implicitly. In Python 3.11 and above, this works seamlessly. However, in earlier versions (3.8, 3.9, 3.10), `asyncio` tasks lack proper `contextvar` support, which can lead to disconnected traces.
#### To resolve
1. **Upgrade Python Version (Recommended)** If possible, upgrade to Python 3.11 or later for automatic context propagation.
2. **Manual Context Propagation** If upgrading isn't an option, you'll need to manually propagate the tracing context. The method varies depending on your setup:
a) **Using LangGraph or LangChain** Pass the parent `config` to the child call:
```python theme={null}
import asyncio
from langchain_core.runnables import RunnableConfig, RunnableLambda
@RunnableLambda
async def my_child_runnable(
inputs: str,
# The config arg (present in parent_runnable below) is optional
):
yield "A"
yield "response"
@RunnableLambda
async def parent_runnable(inputs: str, config: RunnableConfig):
async for chunk in my_child_runnable.astream(inputs, config):
yield chunk
async def main():
return [val async for val in parent_runnable.astream("call")]
asyncio.run(main())
```
b) **Using LangSmith Directly** Pass the run tree directly:
```python theme={null}
import asyncio
import langsmith as ls
@ls.traceable
async def my_child_function(inputs: str):
yield "A"
yield "response"
@ls.traceable
async def parent_function(
inputs: str,
# The run tree can be auto-populated by the decorator
run_tree: ls.RunTree,
):
async for chunk in my_child_function(inputs, langsmith_extra={"parent": run_tree}):
yield chunk
async def main():
return [val async for val in parent_function("call")]
asyncio.run(main())
```
c) **Combining Decorated Code with LangGraph/LangChain** Use a combination of techniques for manual handoff:
```python theme={null}
import asyncio
import langsmith as ls
from langchain_core.runnables import RunnableConfig, RunnableLambda
@RunnableLambda
async def my_child_runnable(inputs: str):
yield "A"
yield "response"
@ls.traceable
async def my_child_function(inputs: str, run_tree: ls.RunTree):
with ls.tracing_context(parent=run_tree):
async for chunk in my_child_runnable.astream(inputs):
yield chunk
@RunnableLambda
async def parent_runnable(inputs: str, config: RunnableConfig):
# @traceable decorated functions can directly accept a RunnableConfig when passed in via "config"
async for chunk in my_child_function(inputs, langsmith_extra={"config": config}):
yield chunk
@ls.traceable
async def parent_function(inputs: str, run_tree: ls.RunTree):
# You can set the tracing context manually
with ls.tracing_context(parent=run_tree):
async for chunk in parent_runnable.astream(inputs):
yield chunk
async def main():
return [val async for val in parent_function("call")]
asyncio.run(main())
```
### Context propagation using threading
It's common to start tracing and want to apply some parallelism on child tasks all within a single trace. Python's stdlib `ThreadPoolExecutor` by default breaks tracing.
#### Why
Python's contextvars start empty within new threads. Here are two approaches to handle maintain trace contiguity:
#### To resolve
1. **Using LangSmith's ContextThreadPoolExecutor**
LangSmith provides a `ContextThreadPoolExecutor` that automatically handles context propagation:
```python theme={null}
from langsmith.utils import ContextThreadPoolExecutor
from langsmith import traceable
@traceable
def outer_func():
with ContextThreadPoolExecutor() as executor:
inputs = [1, 2]
r = list(executor.map(inner_func, inputs))
@traceable
def inner_func(x):
print(x)
outer_func()
```
2. **Manually providing the parent run tree**
Alternatively, you can manually pass the parent run tree to the inner function:
```python theme={null}
from langsmith import traceable, get_current_run_tree
from concurrent.futures import ThreadPoolExecutor
@traceable
def outer_func():
rt = get_current_run_tree()
with ThreadPoolExecutor() as executor:
r = list(
executor.map(
lambda x: inner_func(x, langsmith_extra={"parent": rt}), [1, 2]
)
)
@traceable
def inner_func(x):
print(x)
outer_func()
```
In this approach, we use `get_current_run_tree()` to obtain the current run tree and pass it to the inner function using the `langsmith_extra` parameter.
Both methods ensure that the inner function calls are correctly aggregated under the initial trace stack, even when executed in separate threads.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/nest-traces.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith Observability
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/observability
The following sections help you set up and use tracing, monitoring, and observability features:
LangSmith works with many frameworks and providers. Browse [available integrations](/langsmith/integrations) to connect your stack including OpenAI, Anthropic, CrewAI, Vercel AI SDK, Pydantic AI, and more.
Configure tracing with basic options, framework integrations, or advanced settings for full control.
Access and manage traces via UI or API with filtering, exporting, sharing, and comparison tools.
Create dashboards and set alerts to track performance and get notified when issues arise.
Use rules, webhooks, and online evaluations to streamline observability workflows.
Gather and manage annotations on outputs using queues and inline annotation.
Follow a step-by-step tutorial to trace a Retrieval-Augmented Generation application from start to finish.
For terminology definitions and core concepts, refer to [Observability concepts](/langsmith/observability-concepts).
Use **[Polly](/langsmith/polly)**, LangSmith's AI assistant, to analyze traces and get AI-powered insights into your application's performance.
To set up a LangSmith instance, visit the [Platform setup section](/langsmith/platform-setup) to choose between cloud, hybrid, or self-hosted. All options include observability, evaluation, prompt engineering, and deployment.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/observability.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Observability concepts
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/observability-concepts
This page covers key concepts that are important to understand when logging traces to LangSmith.
A [*trace*](#traces) records the sequence of steps your application takes—from receiving an input, through intermediate processing, to producing a final output. Each step within a trace is represented by a [*run*](#runs). Multiple traces are grouped together within a [*project*](#projects), and traces from multi-turn conversations can be linked together as a [*thread*](#threads).
The following diagram displays these concepts in the context of a simple RAG app, which retrieves documents from an index and generates an answer.
## Runs
A *run* is a span representing a single unit of work or operation within your LLM application. This could be anything from a single call to an LLM or chain, to a prompt formatting call, to a runnable lambda invocation. If you are familiar with [OpenTelemetry](https://opentelemetry.io/), you can think of a run as a span.
## Traces
A *trace* is a collection of runs for a single operation. For example, if you have a user request that triggers a chain, and that chain makes a call to an LLM, then to an output parser, and so on, all of these runs would be part of the same trace. If you are familiar with [OpenTelemetry](https://opentelemetry.io/), you can think of a LangSmith trace as a collection of spans. Runs are bound to a trace by a unique trace ID.
## Threads
A *thread* is a sequence of traces representing a single conversation. Many LLM applications have a chatbot-like interface in which the user and the LLM application engage in a multi-turn conversation. Each turn in the conversation is represented as its own trace, but these traces are linked together by being part of the same thread. The most recent trace in a thread is the latest message exchange.
To group traces into threads, you pass a special metadata key (`session_id`, `thread_id`, or `conversation_id`) with a unique identifier value that links the traces together.
[Learn how to configure threads](/langsmith/threads).
Use **[Polly](/langsmith/polly)** to analyze traces, runs, and threads. Polly helps you understand agent performance, debug issues, and gain insights from conversation threads without manually digging through data.
## Projects
A *project* is a collection of traces. You can think of a project as a container for all the traces that are related to a single application or service. You can have multiple projects, and each project can have multiple traces.
For more details on project setup and traces, refer to [Log traces to a project](/langsmith/log-traces-to-project).
## Feedback
*Feedback* allows you to score an individual run based on certain criteria. Each feedback entry consists of a feedback tag and feedback score, and is bound to a run by a unique run ID. Feedback can be continuous or discrete (categorical), and you can reuse feedback tags across different runs within an organization.
You can collect feedback on runs in a number of ways:
1. [Sent up along with a trace](/langsmith/attach-user-feedback) from the LLM application.
2. Generated by a user in the app [inline](/langsmith/annotate-traces-inline) or in an [annotation queue](/langsmith/annotation-queues).
3. Generated by an automatic evaluator during [offline evaluation](/langsmith/evaluate-llm-application).
4. Generated by an [online evaluator](/langsmith/online-evaluations-llm-as-judge).
To learn more about how feedback is stored in the application, refer to the [Feedback data format guide](/langsmith/feedback-data-format).
## Tags
*Tags* are collections of strings that can be attached to runs. You can use tags to do the following in the LangSmith UI:
* Categorize runs for easier search.
* Filter runs.
* Group runs together for analysis.
[Learn how to attach tags to your traces](/langsmith/add-metadata-tags).
## Metadata
*Metadata* is a collection of key-value pairs that you can attach to runs. You can use metadata to store additional information about a run, such as the version of the application that generated the run, the environment in which the run was generated, or any other information that you want to associate with a run. Similarly to tags, you can use metadata to filter runs in the LangSmith UI or group runs together for analysis.
[Learn how to add metadata to your traces](/langsmith/add-metadata-tags).
## Data storage and retention
For traces ingested on or after Wednesday, May 22, 2024, LangSmith (SaaS) retains trace data for a maximum of 400 days past the date and time the trace was inserted into the LangSmith trace database.
After 400 days, the traces are permanently deleted from LangSmith, with a limited amount of metadata retained for the purpose of showing accurate statistics, such as historic usage and cost.
For more information on data retention tiers, pricing, and auto-upgrade scenarios, refer to [Usage and billing: Data retention](/langsmith/administration-overview#data-retention).
If you wish to keep tracing data longer than the data retention period, you can add it to a dataset. A [dataset](/langsmith/manage-datasets) allows you to store the trace inputs and outputs (e.g., as a key-value dataset), and will persist indefinitely, even after the trace gets deleted.
## Deleting traces from LangSmith
If you need to remove a trace from LangSmith before its expiration date, you can do so by deleting the project that contains it.
You can delete a project with one of the following ways:
* In the [LangSmith UI](https://smith.langchain.com), select the **Delete** option on the project's overflow menu.
* With the [`delete_tracer_sessions`](https://api.smith.langchain.com/redoc#tag/tracer-sessions/operation/delete_tracer_session_api_v1_sessions__session_id__delete) API endpoint
* With the `delete_project()` ([Python](https://reference.langchain.com/python/langsmith/observability/sdk/)) or `deleteProject()` ([JS/TS](https://reference.langchain.com/javascript/modules/langsmith.html)) in the LangSmith SDK.
LangSmith does not support self-service deletion of individual traces.
If you have a need to delete a single trace (or set of traces) from LangSmith project before its expiration date, the account owner should contact support via [LangSmith Support](https://support.langchain.com) with the organization ID and trace IDs.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/observability-concepts.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Trace an LLM application tutorial
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/observability-llm-tutorial
In this tutorial, we'll build an LLM application that uses retrieval augmented generation (RAG) to answer questions about a dataset. We'll add observability to the application at each stage of development, from prototyping to production.
```python Python theme={null}
from openai import OpenAI
openai_client = OpenAI()
# This is the retriever we will use in RAG
# This is mocked out, but it could be anything we want
def retriever(query: str):
results = ["Harrison worked at Kensho"]
return results
# This is the end-to-end RAG chain.
# It does a retrieval step then calls OpenAI
def rag(question):
docs = retriever(question)
system_message = """Answer the users question using only the provided information below:
{docs}""".format(docs="\n".join(docs))
return openai_client.chat.completions.create(
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": question},
],
model="gpt-4.1-mini",
)
```
```typescript TypeScript theme={null}
import { OpenAI } from "openai";
const openAIClient = new OpenAI();
// This is the retriever we will use in RAG
// This is mocked out, but it could be anything we want
async function retriever(query: string) {
return ["This is a document"];
}
// This is the end-to-end RAG chain.
// It does a retrieval step then calls OpenAI
async function rag(question: string) {
const docs = await retriever(question);
const systemMessage =
"Answer the users question using only the provided information below:\n\n" +
docs.join("\n");
return await openAIClient.chat.completions.create({
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: question },
],
model: "gpt-4.1-mini",
});
}
```
## Prototyping
Having observability set up from the start can help you iterate **much** more quickly than you would otherwise be able to. It allows you to have great visibility into your application as you are rapidly iterating on the prompt, or changing the data and models you are using. In this section we'll walk through how to set up observability so you can have maximal observability as you are prototyping.
### Set up your environment
First, create an API key by navigating to the [settings page](https://smith.langchain.com/settings).
Next, install the LangSmith SDK:
```bash Python SDK theme={null}
pip install langsmith
```
```bash TypeScript SDK theme={null}
npm install langsmith
```
Finally, set up the appropriate environment variables. This will log traces to the `default` project (though you can easily change that).
```bash theme={null}
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=
export LANGSMITH_WORKSPACE_ID=
export LANGSMITH_PROJECT=default
```
To send traces to a specific project, use the [`LANGSMITH_PROJECT` environment variable](/langsmith/log-traces-to-project). If this is not set, LangSmith will create a default tracing project automatically on trace ingestion.
You may see these variables referenced as `LANGCHAIN_*` in other places. These are all equivalent, however the best practice is to use `LANGSMITH_TRACING`, `LANGSMITH_API_KEY`, `LANGSMITH_PROJECT`.
The `LANGSMITH_PROJECT` flag is only supported in JS SDK versions >= 0.2.16, use `LANGCHAIN_PROJECT` instead if you are using an older version.
### Trace your LLM calls
The first thing you might want to trace is all your OpenAI calls. After all, this is where the LLM is actually being called, so it is the most important part! We've tried to make this as easy as possible with LangSmith by introducing a dead-simple OpenAI wrapper. All you have to do is modify your code to look something like:
```python Python theme={null}
from openai import OpenAI
from langsmith.wrappers import wrap_openai
openai_client = wrap_openai(OpenAI())
# This is the retriever we will use in RAG
# This is mocked out, but it could be anything we want
def retriever(query: str):
results = ["Harrison worked at Kensho"]
return results
# This is the end-to-end RAG chain.
# It does a retrieval step then calls OpenAI
def rag(question):
docs = retriever(question)
system_message = """Answer the users question using only the provided information below:
{docs}""".format(docs="\n".join(docs))
return openai_client.chat.completions.create(
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": question},
],
model="gpt-4.1-mini",
)
```
```typescript TypeScript theme={null}
import { OpenAI } from "openai";
import { wrapOpenAI } from "langsmith/wrappers";
const openAIClient = wrapOpenAI(new OpenAI());
// This is the retriever we will use in RAG
// This is mocked out, but it could be anything we want
async function retriever(query: string) {
return ["This is a document"];
}
// This is the end-to-end RAG chain.
// It does a retrieval step then calls OpenAI
async function rag(question: string) {
const docs = await retriever(question);
const systemMessage =
"Answer the users question using only the provided information below:\n\n" +
docs.join("\n");
return await openAIClient.chat.completions.create({
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: question },
],
model: "gpt-4.1-mini",
});
}
```
Notice how we import `from langsmith.wrappers import wrap_openai` and use it to wrap the OpenAI client (`openai_client = wrap_openai(OpenAI())`).
What happens if you call it in the following way?
```python theme={null}
rag("where did harrison work")
```
This will produce a trace of just the OpenAI call - it should look something like [this](https://smith.langchain.com/public/e7b7d256-10fe-4d49-a8d5-36ca8e5af0d2/r)
### Trace the whole chain
Great - we've traced the LLM call. But it's often very informative to trace more than that. LangSmith is **built** for tracing the entire LLM pipeline - so let's do that! We can do this by modifying the code to now look something like this:
```python Python theme={null}
from openai import OpenAI
from langsmith import traceable
from langsmith.wrappers import wrap_openai
openai_client = wrap_openai(OpenAI())
def retriever(query: str):
results = ["Harrison worked at Kensho"]
return results
@traceable
def rag(question):
docs = retriever(question)
system_message = """Answer the users question using only the provided information below:
{docs}""".format(docs="\n".join(docs))
return openai_client.chat.completions.create(
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": question},
],
model="gpt-4.1-mini",
)
```
```typescript TypeScript theme={null}
import { OpenAI } from "openai";
import { traceable } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers";
const openAIClient = wrapOpenAI(new OpenAI());
async function retriever(query: string) {
return ["This is a document"];
}
const rag = traceable(async function rag(question: string) {
const docs = await retriever(question);
const systemMessage =
"Answer the users question using only the provided information below:\n\n" +
docs.join("\n");
return await openAIClient.chat.completions.create({
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: question },
],
model: "gpt-4.1-mini",
});
});
```
Notice how we import `from langsmith import traceable` and use it decorate the overall function (`@traceable`).
What happens if you call it in the following way?
```python theme={null}
rag("where did harrison work")
```
This will produce a trace of the entire RAG pipeline - it should look something like [this](https://smith.langchain.com/public/8cafba6a-1a6d-4a73-8565-483186f31c29/r)
## Beta testing
The next stage of LLM application development is beta testing your application. This is when you release it to a few initial users. Having good observability set up here is crucial as often you don't know exactly how users will actually use your application, so this allows you get insights into how they do so. This also means that you probably want to make some changes to your tracing set up to better allow for that. This extends the observability you set up in the previous section
### Collecting feedback
A huge part of having good observability during beta testing is collecting feedback. What feedback you collect is often application specific - but at the very least a simple thumbs up/down is a good start. After logging that feedback, you need to be able to easily associate it with the run that caused that. Luckily LangSmith makes it easy to do that.
First, you need to log the feedback from your app. An easy way to do this is to keep track of a run ID for each run, and then use that to log feedback. Keeping track of the run ID would look something like:
```python theme={null}
from langsmith import uuid7
run_id = str(uuid7())
rag(
"where did harrison work",
langsmith_extra={"run_id": run_id}
)
```
Associating feedback with that run would look something like:
```python theme={null}
from langsmith import Client
ls_client = Client()
ls_client.create_feedback(
run_id,
key="user-score",
score=1.0,
)
```
Once the feedback is logged, you can then see it associated with each run by clicking into the `Metadata` tab when inspecting the run. It should look something like [this](https://smith.langchain.com/public/8cafba6a-1a6d-4a73-8565-483186f31c29/r)
You can also query for all runs with positive (or negative) feedback by using the filtering logic in the runs table. You can do this by creating a filter like the following:
### Logging metadata
It is also a good idea to start logging metadata. This allows you to start keep track of different attributes of your app. This is important in allowing you to know what version or variant of your app was used to produce a given result.
For this example, we will log the LLM used. Oftentimes you may be experimenting with different LLMs, so having that information as metadata can be useful for filtering. In order to do that, we can add it as such:
```python theme={null}
from openai import OpenAI
from langsmith import traceable
from langsmith.wrappers import wrap_openai
openai_client = wrap_openai(OpenAI())
@traceable(run_type="retriever")
def retriever(query: str):
results = ["Harrison worked at Kensho"]
return results
@traceable(metadata={"llm": "gpt-4.1-mini"})
def rag(question):
docs = retriever(question)
system_message = """Answer the users question using only the provided information below:
{docs}""".format(docs='\n'.join(docs))
return openai_client.chat.completions.create(messages = [
{"role": "system", "content": system_message},
{"role": "user", "content": question},
], model="gpt-4.1-mini")
```
Notice we added `@traceable(metadata={"llm": "gpt-4.1-mini"})` to the `rag` function.
Keeping track of metadata in this way assumes that it is known ahead of time. This is fine for LLM types, but less desirable for other types of information - like a User ID. In order to log information that, we can pass it in at run time with the run ID.
```python theme={null}
from langsmith import uuid7
run_id = str(uuid7())
rag(
"where did harrison work",
langsmith_extra={"run_id": run_id, "metadata": {"user_id": "harrison"}}
)
```
Now that we've logged these two pieces of metadata, we should be able to see them both show up in the UI [here](https://smith.langchain.com/public/37adf7e5-97aa-42d0-9850-99c0199bddf6/r).
We can filter for these pieces of information by constructing a filter like the following:
## Production
Great - you've used this newfound observability to iterate quickly and gain confidence that your app is performing well. Time to ship it to production! What new observability do you need to add?
First of all, let's note that the same observability you've already added will keep on providing value in production. You will continue to be able to drill down into particular runs.
In production you likely have a LOT more traffic. So you don't really want to be stuck looking at datapoints one at a time. Luckily, LangSmith has a set of tools to help with observability in production.
### Monitoring
If you click on the `Monitor` tab in a project, you will see a series of monitoring charts. Here we track lots of LLM specific statistics - number of traces, feedback, time-to-first-token, etc. You can view these over time across a few different time bins.
### A/B testing
Group-by functionality for A/B testing requires at least 2 different values to exist for a given metadata key.
You can also use this tab to perform a version of A/B Testing. In the previous tutorial we starting tracking a few different metadata attributes - one of which was `llm`. We can group the monitoring charts by ANY metadata attribute, and instantly get grouped charts over time. This allows us to experiment with different LLMs (or prompts, or other) and track their performance over time.
In order to do this, we just need to click on the `Metadata` button at the top. This will give us a drop down of options to choose from to group by:
Once we select this, we will start to see charts grouped by this attribute:
### Drilldown
One of the awesome abilities that LangSmith provides is the ability to easily drilldown into datapoints that you identify as problematic while looking at monitoring charts. In order to do this, you can simply hover over a datapoint in the monitoring chart. When you do this, you will be able to click the datapoint. This will lead you back to the runs table with a filtered view:
## Conclusion
In this tutorial you saw how to set up your LLM application with best-in-class observability. No matter what stage your application is in, you will still benefit from observability.
If you have more in-depth questions about observability, check out the [how-to section](/langsmith/observability-concepts) for guides on topics like testing, prompt management, and more.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/observability-llm-tutorial.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Tracing quickstart
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/observability-quickstart
[*Observability*](/langsmith/observability-concepts) is a critical requirement for applications built with Large Language Models (LLMs). LLMs are non-deterministic, which means that the same prompt can produce different responses. This behavior makes debugging and monitoring more challenging than with traditional software.
LangSmith addresses this by providing end-to-end visibility into how your application handles a request. Each request generates a [*trace*](/langsmith/observability-concepts#traces), which captures the full record of what happened. Within a trace are individual [*runs*](/langsmith/observability-concepts#runs), the specific operations your application performed, such as an LLM call or a retrieval step. Tracing runs allows you to inspect, debug, and validate your application’s behavior.
In this quickstart, you will set up a minimal [*Retrieval Augmented Generation (RAG)*](https://www.mckinsey.com/featured-insights/mckinsey-explainers/what-is-retrieval-augmented-generation-rag) application and add tracing with LangSmith. You will:
1. Configure your environment.
2. Create an application that retrieves context and calls an LLM.
3. Enable tracing to capture both the retrieval step and the LLM call.
4. View the resulting traces in the LangSmith UI.
If you prefer to watch a video on getting started with tracing, refer to the quickstart [Video guide](#video-guide).
## Prerequisites
Before you begin, make sure you have:
* **A LangSmith account**: Sign up or log in at [smith.langchain.com](https://smith.langchain.com).
* **A LangSmith API key**: Follow the [Create an API key](/langsmith/create-account-api-key#create-an-api-key) guide.
* **An OpenAI API key**: Generate this from the [OpenAI dashboard](https://platform.openai.com/account/api-keys).
The example app in this quickstart will use OpenAI as the LLM provider. You can adapt the example for your app's LLM provider.
If you're building an application with [LangChain](https://docs.langchain.com/oss/python/langchain/overview) or [LangGraph](https://docs.langchain.com/oss/python/langgraph/overview), you can enable LangSmith tracing with a single environment variable. Get started by reading the guides for tracing with [LangChain](/langsmith/trace-with-langchain) or tracing with [LangGraph](/langsmith/trace-with-langgraph).
## 1. Create a directory and install dependencies
In your terminal, create a directory for your project and install the dependencies in your environment:
```bash Python theme={null}
mkdir ls-observability-quickstart && cd ls-observability-quickstart
python -m venv .venv && source .venv/bin/activate
python -m pip install --upgrade pip
pip install -U langsmith openai
```
```bash TypeScript theme={null}
mkdir ls-observability-quickstart-ts && cd ls-observability-quickstart-ts
npm init -y
npm install langsmith openai typescript ts-node
npx tsc --init
```
## 2. Set up environment variables
Set the following environment variables:
* `LANGSMITH_TRACING`
* `LANGSMITH_API_KEY`
* `OPENAI_API_KEY` (or your LLM provider's API key)
* (optional) `LANGSMITH_WORKSPACE_ID`: If your LangSmith API key is linked to multiple workspaces, set this variable to specify which workspace to use.
```bash theme={null}
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=""
export OPENAI_API_KEY=""
export LANGSMITH_WORKSPACE_ID=""
```
If you're using Anthropic, use the [Anthropic wrapper](/langsmith/trace-anthropic) to trace your calls. For other providers, use [the traceable wrapper](/langsmith/annotate-code#use-%40traceable-%2F-traceable).
To send traces to a specific project, use the [`LANGSMITH_PROJECT` environment variable](/langsmith/log-traces-to-project). If this is not set, LangSmith will create a default tracing project automatically on trace ingestion.
## 3. Define your application
You can use the example app code outlined in this step to instrument a RAG application. Or, you can use your own application code that includes an LLM call.
This is a minimal RAG app that uses the OpenAI SDK directly without any LangSmith tracing added yet. It has three main parts:
* **Retriever function**: Simulates document retrieval that always returns the same string.
* **OpenAI client**: Instantiates a plain OpenAI client to send a chat completion request.
* **RAG function**: Combines the retrieved documents with the user’s question to form a system prompt, calls the `chat.completions.create()` endpoint with `gpt-4.1-mini`, and returns the assistant’s response.
Add the following code into your app file (e.g., `app.py` or `app.ts`):
```python Python theme={null}
from openai import OpenAI
def retriever(query: str):
# Minimal example retriever
return ["Harrison worked at Kensho"]
# OpenAI client call (no wrapping yet)
client = OpenAI()
def rag(question: str) -> str:
docs = retriever(question)
system_message = (
"Answer the user's question using only the provided information below:\n"
+ "\n".join(docs)
)
# This call is not traced yet
resp = client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": question},
],
)
return resp.choices[0].message.content
if __name__ == "__main__":
print(rag("Where did Harrison work?"))
```
```typescript TypeScript theme={null}
import "dotenv/config";
import OpenAI from "openai";
// Minimal example retriever
function retriever(query: string): string[] {
return ["Harrison worked at Kensho"];
}
// OpenAI client call (no wrapping yet)
const client = new OpenAI();
async function rag(question: string) {
const docs = retriever(question);
const systemMessage =
"Answer the user's question using only the provided information below:\n" +
docs.join("\n");
// This call is not traced yet
const resp = await client.chat.completions.create({
model: "gpt-4.1-mini",
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: question },
],
});
return resp.choices[0].message?.content;
}
(async () => {
console.log(await rag("Where did Harrison work?"));
})();
```
## 4. Trace LLM calls
To start, you’ll trace all your OpenAI calls. LangSmith provides wrappers:
* Python: [`wrap_openai`](https://docs.smith.langchain.com/reference/python/wrappers/langsmith.wrappers._openai.wrap_openai)
* TypeScript: [`wrapOpenAI`](https://docs.smith.langchain.com/reference/js/functions/wrappers_openai.wrapOpenAI)
This snippet wraps the OpenAI client so that every subsequent model call is logged automatically as a traced child run in LangSmith.
1. Include the highlighted lines in your app file:
```python Python highlight={2,7} theme={null}
from openai import OpenAI
from langsmith.wrappers import wrap_openai # traces openai calls
def retriever(query: str):
return ["Harrison worked at Kensho"]
client = wrap_openai(OpenAI()) # log traces by wrapping the model calls
def rag(question: str) -> str:
docs = retriever(question)
system_message = (
"Answer the user's question using only the provided information below:\n"
+ "\n".join(docs)
)
resp = client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": question},
],
)
return resp.choices[0].message.content
if __name__ == "__main__":
print(rag("Where did Harrison work?"))
```
```typescript TypeScript highlight={3,9} theme={null}
import "dotenv/config";
import OpenAI from "openai";
import { wrapOpenAI } from "langsmith/wrappers"; // traces openai calls
function retriever(query: string): string[] {
return ["Harrison worked at Kensho"];
}
const client = wrapOpenAI(new OpenAI()); // log traces by wrapping the model calls
async function rag(question: string) {
const docs = retriever(question);
const systemMessage =
"Answer the user's question using only the provided information below:\n" +
docs.join("\n");
const resp = await client.chat.completions.create({
model: "gpt-4.1-mini",
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: question },
],
});
return resp.choices[0].message?.content;
}
(async () => {
console.log(await rag("Where did Harrison work?"));
})();
```
2. Call your application:
```bash Python theme={null}
python app.py
```
```bash TypeScript theme={null}
npx ts-node app.ts
```
You'll receive the following output:
```
Harrison worked at Kensho.
```
3. In the [LangSmith UI](https://smith.langchain.com), navigate to the **default** Tracing Project for your workspace (or the workspace you specified in [Step 2](#2-set-up-environment-variables)). You'll see the OpenAI call you just instrumented.
## 5. Trace an entire application
You can also use the `traceable` decorator for [Python](https://docs.smith.langchain.com/reference/python/run_helpers/langsmith.run_helpers.traceable) or [TypeScript](https://langsmith-docs-bdk0fivr6-langchain.vercel.app/reference/js/functions/traceable.traceable) to trace your entire application instead of just the LLM calls.
1. Include the highlighted code in your app file:
```python Python highlight={3,10} theme={null}
from openai import OpenAI
from langsmith.wrappers import wrap_openai
from langsmith import traceable
def retriever(query: str):
return ["Harrison worked at Kensho"]
client = wrap_openai(OpenAI()) # keep this to capture the prompt and response from the LLM
@traceable
def rag(question: str) -> str:
docs = retriever(question)
system_message = (
"Answer the user's question using only the provided information below:\n"
+ "\n".join(docs)
)
resp = client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": question},
],
)
return resp.choices[0].message.content
if __name__ == "__main__":
print(rag("Where did Harrison work?"))
```
```typescript TypeScript highlight={3,11} theme={null}
import "dotenv/config";
import OpenAI from "openai";
import { wrapOpenAI, traceable } from "langsmith/wrappers";
function retriever(query: string): string[] {
return ["Harrison worked at Kensho"];
}
const client = wrapOpenAI(new OpenAI()); // keep this to capture the prompt and response from the LLM
const rag = traceable(async (question: string) => {
const docs = retriever(question);
const systemMessage =
"Answer the user's question using only the provided information below:\n" +
docs.join("\n");
const resp = await client.chat.completions.create({
model: "gpt-4.1-mini",
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: question },
],
});
return resp.choices[0].message?.content;
});
(async () => {
console.log(await rag("Where did Harrison work?"));
})();
```
2. Call the application again to create a run:
```bash Python theme={null}
python app.py
```
```bash TypeScript theme={null}
npx ts-node app.ts
```
3. Return to the [LangSmith UI](https://smith.langchain.com), navigate to the **default** Tracing Project for your workspace (or the workspace you specified in [Step 2](#2-set-up-environment-variables)). You'll find a trace of the entire app pipeline with the **rag** step and the **ChatOpenAI** LLM call.
## Next steps
Here are some topics you might want to explore next:
* [Tracing integrations](/langsmith/trace-with-langchain) provide support for various LLM providers and agent frameworks.
* [Filtering traces](/langsmith/filter-traces-in-application) can help you effectively navigate and analyze data in tracing projects that contain a significant amount of data.
* [Trace a RAG application](/langsmith/observability-llm-tutorial) is a full tutorial, which adds observability to an application from development through to production.
* [Sending traces to a specific project](/langsmith/log-traces-to-project) changes the destination project of your traces.
After logging traces, use **[Polly](/langsmith/polly)** to analyze them and get AI-powered insights into your application's performance.
## Video guide
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/observability-quickstart.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Deploy an observability stack for your LangSmith deployment
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/observability-stack
**This section is only applicable for Kubernetes deployments.**
LangSmith applications expose telemetry data that can be sent to the backend of your choice. If you don’t already have an observability stack, or prefer to keep LangSmith telemetry separate from your main application, you can use the LangSmith Observability Helm chart to deploy a basic observability stack.
# Section 1: Prometheus exporters
Use this section if you would like to only deploy metrics exporters for the components in your self hosted deployment, which you can then scrape using your telemetry. If you would like a full observability stack deployed for you, go to the [End-to-End Deployment Section](/langsmith/observability-stack#prerequisites).
The helm chart provides a set of Prometheus exporters to expose metrics from [Redis](https://github.com/prometheus-community/helm-charts/tree/main/charts/prometheus-redis-exporter), [Postgres](https://github.com/prometheus-community/helm-charts/tree/main/charts/prometheus-postgres-exporter), [Nginx](https://github.com/prometheus-community/helm-charts/tree/main/charts/prometheus-nginx-exporter), and [Kube state metrics](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-state-metrics).
1. Create a local file called `langsmith_obs_config.yaml`
2. Copy over the values from this [file](https://github.com/langchain-ai/helm/blob/main/charts/langsmith-observability/examples/metric-exporters-only.yaml) into `langsmith_obs_config.yaml`, making sure to modify the values to match your LangSmith deployment.
3. Find the latest version of the chart by running `helm search repo langchain/langsmith-observability --versions`.
4. Grab the latest version number, and run `helm install langsmith-observability langchain/langsmith-observability --values langsmith_obs_config.yaml --version -n --wait --debug`
This will allow you to scrape metrics at the following service endpoints:
* Postgres: `langsmith-observability-postgres-exporter:9187/metrics`
* Redis: `langsmith-observability-redis-exporter:9121/metrics`
* Nginx: `langsmith-observability-nginx-exporter:9113/metrics`
* KubeStateMetrics: `langsmith-observability-kube-state-metrics:8080/metrics`
You should see the following if the installation went through:
```bash theme={null}
Release "langsmith-observability" has been installed. Happy Helming!NAME: langsmith-observabilityLAST DEPLOYED: Wed Jun 25 11:17:34 2025NAMESPACE: langsmith-observabilitySTATUS: deployedREVISION: 1
```
And if you run `kubectl get pods -n langsmith-observability`, you should see:
```bash theme={null}
langsmith-observability-kube-state-metrics-b58bb8db4-bm4g5 1/1 Running 0 2m22slangsmith-observability-nginx-exporter-6d686d9d4b-5qw9v 1/1 Running 0 2m22slangsmith-observability-postgres-exporter-67d5db5684-tffbm 1/1 Running 0 2m22slangsmith-observability-redis-exporter-846c4d65cb-vbtwd 1/1 Running 0 2m22s
```
# Section 2: Full observability stack
**This is not a production observability stack. Use this to gain quick insight into logs, metrics and traces for your deployment. This is only made to handle a few dozen GB of data per day.**
This section will show you how to deploy the end-to-end observability stack for LangSmith, using the [Helm Chart](https://github.com/langchain-ai/helm/tree/main/charts/langsmith-observability).
This chart is built around the open-source LGTM Stack from Grafana. It consists of:
* [Loki](https://grafana.com/docs/loki/latest/) for logs.
* [Mimir](https://grafana.com/docs/mimir/latest/) for metrics + alerting.
* [Tempo](https://grafana.com/docs/tempo/latest/) for traces.
* [Grafana](https://grafana.com/docs/grafana/latest/) for monitoring UI.
As well as [OpenTelemetry Collectors](https://opentelemetry.io/docs/collector/) for gathering the telemetry data.
## Prerequisites
### 1. Compute resources
The resource requests and limits for each part of the stack can be modified in the helm chart. Here are the current allocations (request/limit):
* Loki: `2vCPU/3vCPU + 2Gi/4Gi`
* Mimir: `1vCPU/2vCPU + 2Gi/4Gi`
* Tempo: `1vCPU/2vCPU + 4Gi/6Gi`
Make sure you have those resources allocated before bringing up the helm chart, or modify the resource values in your helm configuration file.
### 2. Cert-Manager
The helm chart uses the OpenTelemetry Operator to provision collectors. The operator require that you have [cert-manager](https://cert-manager.io/docs/installation/) installed in your Kubernetes cluster.
If you do not have it installed, you can run the following commands:
```bash theme={null}
helm repo add jetstack https://charts.jetstack.iohelm repo updatehelm install cert-manager jetstack/cert-manager -n cert-manager --create-namespace
```
### 3. OpenTelemetry operator
Use the following to install the OpenTelemetry Operator:
```bash theme={null}
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-chartshelm repo updatehelm install opentelemetry-operator open-telemetry/opentelemetry-operator -n
```
## Installation
The following instructions will bring up OTel collectors, the LGTM stack, Grafana and Prometheus exporters.
1. Create a local file called `langsmith_obs_config.yaml`
2. Copy over the values from this [file](https://github.com/langchain-ai/helm/blob/main/charts/langsmith-observability/examples/e2e-stack.yaml) into `langsmith_obs_config.yaml`, making sure to modify the values to match your LangSmith deployment.
3. Find the latest version of the chart by running `helm search repo langchain/langsmith-observability --versions`.
4. Grab the latest version number, and run `helm install langsmith-observability langchain/langsmith-observability --values langsmith_obs_config.yaml --version -n --wait --debug`
**You can selectively collect logs, metrics or traces by modifying the boolean values under `otelCollector` in your config file. You can also selectively bring up each respective piece of the backend (Loki, Mimir, Tempo).**
You should see the following if the install went through:
```bash theme={null}
Release "langsmith-observability" has been installed. Happy Helming!NAME: langsmith-observabilityLAST DEPLOYED: Wed Jun 25 11:17:34 2025NAMESPACE: langsmith-observabilitySTATUS: deployedREVISION: 1
```
And if you run `kubectl get pods -n langsmith-observability`, you should see:
```bash theme={null}
langsmith-observability-collector-gateway-collector-7746fb8pzbg 1/1 Running 0 5m26slangsmith-observability-grafana-7c6fc976f9-cdbvr 1/1 Running 0 2m49slangsmith-observability-kube-state-metrics-b58bb8db4-bm4g5 1/1 Running 0 5m27slangsmith-observability-loki-0 2/2 Running 0 5m27slangsmith-observability-loki-chunks-cache-0 2/2 Running 0 5m27slangsmith-observability-loki-gateway-769fb6fff8-zjsn5 1/1 Running 0 5m27slangsmith-observability-loki-results-cache-0 2/2 Running 0 5m27slangsmith-observability-mimir-0 1/1 Running 0 5m26slangsmith-observability-nginx-exporter-6d686d9d4b-5qw9v 1/1 Running 0 5m27slangsmith-observability-postgres-exporter-67d5db5684-tffbm 1/1 Running 0 5m27slangsmith-observability-redis-exporter-846c4d65cb-vbtwd 1/1 Running 0 5m27slangsmith-observability-tempo-0 1/1 Running 0 5m27sopentelemetry-operator-756dff697-vblbn 2/2 Running 0 12m
```
## Post-Installation
### Enable logs and traces in LangSmith
Once you have installed the observability helm chart, you need to set the following values in your *LangSmith* helm configuration file to enable collection of logs and traces.
```yaml theme={null}
commonPodAnnotations:
# E.g.: "langsmith-observability/langsmith-observability-collector-sidecar"
sidecar.opentelemetry.io/inject: "${LANGSMITH_OBS_NAMESPACE}/${LANGSMITH_OTEL_CRD_NAME}"
observability:
tracing:
enabled: true
# Replace this with the endpoint of your trace collector.
# E.g.: "http://langsmith-observability-collector-gateway-collector.langsmith-observability.svc.cluster.local:4318/v1/traces"
endpoint: "http://${GATEWAY_COLLECTOR_SERVICE_NAME}.${LANGSMITH_OBS_NAMESPACE}.svc.cluster.local:4318/v1/traces"
```
1. To get `${LANGSMITH_OTEL_CRD_NAME}`, you can run `kubectl get opentelemetrycollectors -n ${LANGSMITH_OBS_NAMESPACE}` and select the name of the one with MODE = `sidecar`
2. To get `${GATEWAY_COLLECTOR_SERVICE_NAME}` name, run `kubectl get services -n ${LANGSMITH_OBS_NAMESPACE}` and select the one with Ports 4317/4318 AND a ClusterIP set. It should be something like `langsmith-observability-collector-gateway-collector`
Now run `helm upgrade langsmith langchain/langsmith --values langsmith_config.yaml -n --wait --debug`
Once upgraded, if you run `kubectl get pods -n ` you should see the following (note the 2/2 for sidecar collectors):
```bash theme={null}
langsmith-ace-backend-7dc85f7dff-xjbkj 2/2 Running 0 7m53slangsmith-backend-566b66979c-rgcfh 2/2 Running 1 7m53slangsmith-clickhouse-0 2/2 Running 0 7m49slangsmith-frontend-7cf8549885-vpkns 2/2 Running 0 7m53slangsmith-platform-backend-5d46db7d9d-f6gh7 2/2 Running 0 7m52slangsmith-platform-backend-5d46db7d9d-lrr4d 2/2 Running 1 7m41slangsmith-platform-backend-5d46db7d9d-pcp27 2/2 Running 0 7m28slangsmith-playground-65d4c9699c-h656r 2/2 Running 0 7m52slangsmith-postgres-0 2/2 Running 0 7m51slangsmith-queue-bdcd45bd6-htssd 2/2 Running 0 7m52slangsmith-queue-bdcd45bd6-pwdx4 2/2 Running 0 6m31slangsmith-queue-bdcd45bd6-xqrb8 2/2 Running 0 5m11slangsmith-redis-0 2/2 Running 0 7m51s
```
## Grafana usage
Once everything is installed, do the following: to get your Grafana password:
```bash theme={null}
kubectl get secret langsmith-observability-grafana -n -o jsonpath="{.data.admin-password}" | base64 --decode
```
Then port-forward into the `langsmith-observability-grafana` container at port 3000, and open your browser as `localhost:3000`. Use the username `admin` and the password from the secret above to log into Grafana.
Once in Grafana, you can use the UI to monitor logs, metrics and traces. Grafana also comes pre-packaged with sets of dashboards for monitoring the main components of your deployment.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/observability-stack.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Observability in Studio
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/observability-studio
LangSmith [Studio](/langsmith/studio) provides tools to inspect, debug, and improve your app beyond execution. By working with traces, datasets, and prompts, you can see how your application behaves in detail, measure its performance, and refine its outputs:
* [Iterate on prompts](#iterate-on-prompts): Modify prompts inside graph nodes directly or with the LangSmith playground.
* [Run experiments over a dataset](#run-experiments-over-a-dataset): Execute your assistant over a LangSmith dataset to score and compare results.
* [Debug LangSmith traces](#debug-langsmith-traces): Import traced runs into Studio and optionally clone them into your local agent.
* [Add a node to a dataset](#add-node-to-dataset): Turn parts of thread history into dataset examples for evaluation or further analysis.
## Iterate on prompts
Studio supports the following methods for modifying prompts in your graph:
* [Direct node editing](#direct-node-editing)
* [Playground interface](#playground)
### Direct node editing
Studio allows you to edit prompts used inside individual nodes, directly from the graph interface.
### Graph configuration
Define your [configuration](/oss/python/langgraph/use-graph-api#add-runtime-configuration) to specify prompt fields and their associated nodes using `langgraph_nodes` and `langgraph_type` keys.
#### `langgraph_nodes`
* **Description**: Specifies which nodes of the graph a configuration field is associated with.
* **Value Type**: Array of strings, where each string is the name of a node in your graph.
* **Usage Context**: Include in the `json_schema_extra` dictionary for Pydantic models or the `metadata["json_schema_extra"]` dictionary for dataclasses.
* **Example**:
```python theme={null}
system_prompt: str = Field(
default="You are a helpful AI assistant.",
json_schema_extra={"langgraph_nodes": ["call_model", "other_node"]},
)
```
#### `langgraph_type`
* **Description**: Specifies the type of configuration field, which determines how it's handled in the UI.
* **Value Type**: String
* **Supported Values**:
* `"prompt"`: Indicates the field contains prompt text that should be treated specially in the UI.
* **Usage Context**: Include in the `json_schema_extra` dictionary for Pydantic models or the `metadata["json_schema_extra"]` dictionary for dataclasses.
* **Example**:
```python theme={null}
system_prompt: str = Field(
default="You are a helpful AI assistant.",
json_schema_extra={
"langgraph_nodes": ["call_model"],
"langgraph_type": "prompt",
},
)
```
```python theme={null}
## Using Pydantic
from pydantic import BaseModel, Field
from typing import Annotated, Literal
class Configuration(BaseModel):
"""The configuration for the agent."""
system_prompt: str = Field(
default="You are a helpful AI assistant.",
description="The system prompt to use for the agent's interactions. "
"This prompt sets the context and behavior for the agent.",
json_schema_extra={
"langgraph_nodes": ["call_model"],
"langgraph_type": "prompt",
},
)
model: Annotated[
Literal[
"anthropic/claude-sonnet-4-5-20250929",
"anthropic/claude-haiku-4-5-20251001",
"openai/o1",
"openai/gpt-4.1-mini",
"openai/o1-mini",
"openai/o3-mini",
],
{"__template_metadata__": {"kind": "llm"}},
] = Field(
default="openai/gpt-4.1-mini",
description="The name of the language model to use for the agent's main interactions. "
"Should be in the form: provider/model-name.",
json_schema_extra={"langgraph_nodes": ["call_model"]},
)
## Using Dataclasses
from dataclasses import dataclass, field
@dataclass(kw_only=True)
class Configuration:
"""The configuration for the agent."""
system_prompt: str = field(
default="You are a helpful AI assistant.",
metadata={
"description": "The system prompt to use for the agent's interactions. "
"This prompt sets the context and behavior for the agent.",
"json_schema_extra": {"langgraph_nodes": ["call_model"]},
},
)
model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
default="anthropic/claude-3-5-sonnet-20240620",
metadata={
"description": "The name of the language model to use for the agent's main interactions. "
"Should be in the form: provider/model-name.",
"json_schema_extra": {"langgraph_nodes": ["call_model"]},
},
)
```
#### Editing prompts in the UI
1. Locate the gear icon on nodes with associated configuration fields.
2. Click to open the configuration modal.
3. Edit the values.
4. Save to update the current assistant version or create a new one.
### Playground
The [playground](/langsmith/create-a-prompt) interface allows testing individual LLM calls without running the full graph:
1. Select a thread.
2. Click **View LLM Runs** on a node. This lists all the LLM calls (if any) made inside the node.
3. Select an LLM run to open in the playground.
4. Modify prompts and test different model and tool settings.
5. Copy updated prompts back to your graph.
## Run experiments over a dataset
Studio lets you run [evaluations](/langsmith/evaluation-concepts) by executing your assistant against a predefined LangSmith [dataset](/langsmith/evaluation-concepts#datasets). This allows you to test performance across a variety of inputs, compare outputs to reference answers, and score results with configured [evaluators](/langsmith/evaluation-concepts#evaluators).
This guide shows you how to run a full end-to-end experiment directly from Studio.
### Prerequisites
Before running an experiment, ensure you have the following:
* **A LangSmith dataset**: Your dataset should contain the inputs you want to test and optionally, reference outputs for comparison. The schema for the inputs must match the required input schema for the assistant. For more information on schemas, see the [graph API schema documentation](/oss/python/langgraph/use-graph-api#schema). For more on creating datasets, refer to [How to Manage Datasets](/langsmith/manage-datasets-in-application#set-up-your-dataset).
* **(Optional) Evaluators**: You can attach evaluators (e.g., LLM-as-a-Judge, heuristics, or custom functions) to your dataset in LangSmith. These will run automatically after the graph has processed all inputs.
* **A running application**: The experiment can be run against:
* An application deployed on [LangSmith](/langsmith/deployments).
* A locally running application started via the [langgraph-cli](/langsmith/local-server).
Studio experiments follow the same [data retention](/langsmith/administration-overview#data-retention) rules as other experiments. By default, traces have base tier retention (14 days). However, traces will automatically upgrade to extended tier retention (400 days) if feedback is added to them. Feedback can be added in one of two ways:
* The [dataset has evaluators configured](/langsmith/bind-evaluator-to-dataset).
* [Feedback](/langsmith/observability-concepts#feedback) is manually added to a trace.
This auto-upgrade increases both the retention period and the cost of the trace. For more details, refer to [Data retention auto-upgrades](/langsmith/administration-overview#how-it-works).
### Experiment setup
1. Launch the experiment. Click the **Run experiment** button in the top right corner of the Studio page.
2. Select your dataset. In the modal that appears, select the dataset (or a specific dataset split) to use for the experiment and click **Start**.
3. Monitor the progress. All of the inputs in the dataset will now be run against the active assistant. Monitor the experiment's progress via the badge in the top right corner.
4. You can continue to work in Studio while the experiment runs in the background. Click the arrow icon button at any time to navigate to LangSmith and view the detailed experiment results.
## Debug LangSmith traces
This guide explains how to open LangSmith traces in Studio for interactive investigation and debugging.
### Open deployed threads
1. Open the LangSmith trace, selecting the root run.
2. Click **Run in Studio**.
This will open Studio connected to the associated deployment with the trace's parent thread selected.
### Testing local agents with remote traces
This section explains how to test a local agent against remote traces from LangSmith. This enables you to use production traces as input for local testing, allowing you to debug and verify agent modifications in your development environment.
#### Prerequisites
* A LangSmith traced thread
* A [locally running agent](/langsmith/local-server#local-development-server).
**Local agent requirements**
* langgraph>=0.3.18
* langgraph-api>=0.0.32
* Contains the same set of nodes present in the remote trace
#### Clone thread
1. Open the LangSmith trace, selecting the root run.
2. Click the dropdown next to **Run in Studio**.
3. Enter your local agent's URL.
4. Select **Clone thread locally**.
5. If multiple graphs exist, select the target graph.
A new thread will be created in your local agent with the thread history inferred and copied from the remote thread, and you will be navigated to Studio for your locally running application.
## Add node to dataset
Add [examples](/langsmith/evaluation-concepts#examples) to [LangSmith datasets](/langsmith/manage-datasets) from nodes in the thread log. This is useful to evaluate individual steps of the agent.
1. Select a thread.
2. Click **Add to Dataset**.
3. Select nodes whose input/output you want to add to a dataset.
4. For each selected node, select the target dataset to create the example in. By default a dataset for the specific assistant and node will be selected. If this dataset does not yet exist, it will be created.
5. Edit the example's input/output as needed before adding it to the dataset.
6. Select **Add to dataset** at the bottom of the page to add all selected nodes to their respective datasets.
For more details, refer to [How to evaluate an application's intermediate steps](/langsmith/evaluate-on-intermediate-steps).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/observability-studio.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Set up online code evaluators
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/online-evaluations-code
**Recommended Reading**
Before diving into this content, it might be helpful to read the following:
* Running [online evaluations](/langsmith/evaluation-concepts#online-evaluations)
Online evaluations provide real-time feedback on your production traces. This is useful to continuously monitor the performance of your application—to identify issues, measure improvements, and ensure consistent quality over time.
Code evaluators allow you to write an evaluator in Python or JavaScript directly in LangSmith. Often used for validating structure or statistical properties of your data.
When an online evaluator runs on any run within a trace, the trace will be auto-upgraded to [extended data retention](/langsmith/administration-overview#data-retention-auto-upgrades). This upgrade will impact trace pricing, but ensures that traces meeting your evaluation criteria (typically those most valuable for analysis) are preserved for investigation.
## View online evaluators
Head to the **Tracing Projects** tab and select a tracing project. To view existing online evaluators for that project, click on the **Evaluators** tab.
## Configure online evaluators
#### 1. Navigate to online evaluators
Head to the **Tracing Projects** tab and select a tracing project. Click on **+ New** in the top right corner of the tracing project page, then click on **New Evaluator**. Select **code** evaluator.
#### 2. Name your evaluator
#### 3. Create a filter
For example, you may want to apply specific evaluators based on:
* Runs where a [user left feedback](/langsmith/attach-user-feedback) indicating the response was unsatisfactory.
* Runs that invoke a specific tool call. See [filtering for tool calls](/langsmith/filter-traces-in-application#example-filtering-for-tool-calls) for more information.
* Runs that match a particular piece of metadata (e.g. if you log traces with a `plan_type` and only want to run evaluations on traces from your enterprise customers). See [adding metadata to your traces](/langsmith/add-metadata-tags) for more information.
Filters on evaluators work the same way as when you're filtering traces in a project. For more information on filters, you can refer to [this guide](./filter-traces-in-application).
It's often helpful to inspect runs as you're creating a filter for your evaluator. With the evaluator configuration panel open, you can inspect runs and apply filters to them. Any filters you apply to the runs table will automatically be reflected in filters on your evaluator.
#### 4. (Optional) Configure a sampling rate
Configure a sampling rate to control the percentage of filtered runs that trigger the automation action. For example, to control costs, you may want to set a filter to only apply the evaluator to 10% of traces. In order to do this, you would set the sampling rate to 0.1.
#### 5. (Optional) Apply rule to past runs
Apply rule to past runs by toggling the **Apply to past runs** and entering a "Backfill from" date. This is only possible upon rule creation. Note: the backfill is processed as a background job, so you will not see the results immediately.
In order to track progress of the backfill, you can view logs for your evaluator by heading to the **Evaluators** tab within a tracing project and clicking the Logs button for the evaluator you created. Online evaluator logs are similar to [automation rule logs](./rules#view-logs-for-your-automations).
* Add an evaluator name
* Optionally filter runs that you would like to apply your evaluator on or configure a sampling rate.
* Select **Apply Evaluator**
## Write your evaluation function
**Code evaluators restrictions.**
**Allowed Libraries**: You can import all standard library functions, as well as the following public packages:
```
numpy (v2.2.2): "numpy"
pandas (v1.5.2): "pandas"
jsonschema (v4.21.1): "jsonschema"
scipy (v1.14.1): "scipy"
sklearn (v1.26.4): "scikit-learn"
```
**Network Access**: You cannot access the internet from a code evaluator.
Code evaluators must be written inline. We recommend testing locally before setting up your code evaluator in LangSmith.
In the UI, you will see a panel that lets you write your code inline, with some starter code:
Code evaluators take in one argument:
* A `Run` ([reference](/langsmith/run-data-format)). This represents the sampled run to evaluate.
They return a single value:
* Feedback(s) Dictionary: A dictionary whose keys are the type of feedback you want to return, and values are the score you will give for that feedback key. For example, `{"correctness": 1, "silliness": 0}` would create two types of feedback on the run, one saying it is correct, and the other saying it is not silly.
In the below screenshot, you can see an example of a simple function that validates that each run in the experiment has a known json field:
```python Python theme={null}
import json
def perform_eval(run):
output_to_validate = run['outputs']
is_valid_json = 0
# assert you can serialize/deserialize as json
try:
json.loads(json.dumps(output_to_validate))
except Exception as e:
return { "formatted": False }
# assert output facts exist
if "facts" not in output_to_validate:
return { "formatted": False }
# assert required fields exist
if "years_mentioned" not in output_to_validate["facts"]:
return { "formatted": False }
return {"formatted": True}
```
```javascript JavaScript theme={null}
function perform_eval(run) {
const outputToValidate = run.outputs;
// Assert you can serialize/deserialize as json
try {
JSON.stringify(outputToValidate);
JSON.parse(JSON.stringify(outputToValidate));
} catch (e) {
return { "formatted": false };
}
// Assert output facts exist
if (!("facts" in outputToValidate)) {
return { "formatted": false };
}
// Assert required fields exist
if (!outputToValidate["facts"].hasOwnProperty("years_mentioned")) {
return { "formatted": false };
}
return { "formatted": true };
}
```
## Test and save your evaluation function
Before saving, you can test your evaluator function on a recent run by clicking **Test Code** to make sure that your code executes properly.
Once you **Save**, your online evaluator will run over newly sampled runs (or backfilled ones too if you chose the backfill option).
If you prefer a video tutorial, check out the [Online Evaluations video](https://academy.langchain.com/pages/intro-to-langsmith-preview) from the Introduction to LangSmith Course.
## Video guide
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/online-evaluations-code.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Set up composite online evaluators
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/online-evaluations-composite
**Recommended Reading**
Before diving into this content, it might be helpful to read the following:
* Running [online evaluations](/langsmith/evaluation-concepts#online-evaluation)
* [Composite evaluators](/langsmith/composite-evaluators-ui)
Online evaluations provide real-time feedback on your production traces. This is useful to continuously monitor the performance of your application—to identify issues, measure improvements, and ensure consistent quality over time.
**Composite evaluators** are a way to combine multiple evaluator scores into a single [score](/langsmith/evaluation-concepts#evaluator-outputs). This is useful when you want to evaluate multiple aspects of your application and combine the results into a single result.
When an online evaluator runs on any run within a trace, the trace will be auto-upgraded to [extended data retention](/langsmith/administration-overview#data-retention-auto-upgrades). This upgrade will impact trace pricing, but ensures that traces meeting your evaluation criteria (typically those most valuable for analysis) are preserved for investigation.
## View online evaluators
Head to the **Tracing Projects** tab and select a tracing project. To view existing online evaluators for that project, click on the **Evaluators** tab.
## Configure composite online evaluators
You can create composite evaluators on a [tracing project](/langsmith/observability-concepts#projects) for [online evaluations](/langsmith/evaluation-concepts#online-evaluation). With composite evaluators in the UI, you can compute a weighted average or weighted sum of multiple evaluator scores, with configurable weights.
### 1. Navigate to the tracing project
To start configuring a composite evaluator, navigate to the **Tracing Projects** tab and select a project.
From within a tracing project: **+ New** > **Evaluator** > **Composite score**
### 2. Configure the composite evaluator
1. Name your evaluator.
2. Select an aggregation method, either **Average** or **Sum**.
* **Average**: ∑(weight\*score) / ∑(weight).
* **Sum**: ∑(weight\*score).
3. Add the feedback keys you want to include in the composite score.
4. Add the weights for the feedback keys. By default, the weights are equal for each feedback key. Adjust the weights to increase or decrease the importance of specific feedback keys in the final score.
5. Click **Create** to save the evaluator.
If you need to adjust the weights for the composite scores, they can be updated after the evaluator is created. The resulting scores will be updated for all runs that have the evaluator configured.
### 3. View composite evaluator results
Composite scores are attached to a run as **feedback**, similarly to feedback from a single evaluator.
**On a tracing project**:
* Composite scores appear as feedback on runs.
* [Filter for runs](/langsmith/filter-traces-in-application) with a composite score, or where the composite score meets a certain threshold.
* [Create a chart](/langsmith/dashboards#custom-dashboards) to visualize trends in the composite score over time.
If any of the constituent evaluators are not configured on the run, the composite score will not be calculated for that run.
## Video guide
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/online-evaluations-composite.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Set up LLM-as-a-judge online evaluators
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/online-evaluations-llm-as-judge
[Online evaluations](/langsmith/evaluation-concepts#online-evaluation) provide real-time feedback on your production traces. This is useful to monitor the performance of your application continuously—to identify issues, measure improvements, and ensure consistent quality over time.
**[LLM-as-a-judge](/langsmith/evaluation-concepts#llm-as-judge)** evaluators use an LLM to evaluate traces as a scalable substitute for human-like judgment. This guide covers **run-level** evaluators that evaluate a single run. For evaluating entire conversation threads, see [multi-turn online evaluators](/langsmith/online-evaluations-multi-turn).
When an online evaluator runs on any run within a trace, the trace will be auto-upgraded to [extended data retention](/langsmith/administration-overview#data-retention-auto-upgrades). This upgrade will impact trace pricing, but ensures that traces meeting your evaluation criteria (typically those most valuable for analysis) are preserved for investigation.
## View online evaluators
In the [LangSmith UI](https://smith.langchian.com), head to the **Tracing Projects** tab and select a tracing project. To view existing online evaluators for that project, click on the **Evaluators** tab.
## Configure online evaluators
#### 1. Navigate to online evaluators
Head to the **Tracing Projects** tab and select a tracing project. Click on **+ New** in the top right corner of the tracing project page, then click on **New Evaluator**. Select the evaluator you want to configure.
#### 2. Name your evaluator
#### 3. Create a filter
For example, you may want to apply specific evaluators based on:
* Runs where a [user left feedback](/langsmith/attach-user-feedback) indicating the response was unsatisfactory.
* Runs that invoke a specific tool call. See [filtering for tool calls](/langsmith/filter-traces-in-application#example-filtering-for-tool-calls) for more information.
* Runs that match a particular piece of metadata (e.g. if you log traces with a `plan_type` and only want to run evaluations on traces from your enterprise customers). See [adding metadata to your traces](/langsmith/add-metadata-tags) for more information.
Filters on evaluators work the same way as when you're filtering traces in a project. For more information on filters, you can refer to [this guide](./filter-traces-in-application).
It's often helpful to inspect runs as you're creating a filter for your evaluator. With the evaluator configuration panel open, you can inspect runs and apply filters to them. Any filters you apply to the runs table will automatically be reflected in filters on your evaluator.
#### 4. (Optional) Configure a sampling rate
Configure a sampling rate to control the percentage of filtered runs that trigger the automation action. For example, to control costs, you may want to set a filter to only apply the evaluator to 10% of traces. In order to do this, you would set the sampling rate to 0.1.
#### 5. (Optional) Apply rule to past runs
Apply rule to past runs by toggling the **Apply to past runs** and entering a "Backfill from" date. This is only possible upon rule creation.
The backfill is processed as a background job, so you will not see the results immediately.
In order to track progress of the backfill, you can view logs for your evaluator by heading to the **Evaluators** tab within a tracing project and clicking the Logs button for the evaluator you created. Online evaluator logs are similar to [automation rule logs](./rules#view-logs-for-your-automations).
* Add an evaluator name.
* Optionally filter runs that you would like to apply your evaluator on or configure a sampling rate.
* Select **Apply Evaluator**.
#### 6. Configure the LLM-as-a-judge evaluator
View this guide to configure an [LLM-as-a-judge evaluator](/langsmith/llm-as-judge?mode=ui#pre-built-evaluators-1).
## Video guide
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/online-evaluations-llm-as-judge.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Set up multi-turn online evaluators
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/online-evaluations-multi-turn
Multi-turn online evaluators allow you to evaluate entire conversations between a human and an agent — not just individual exchanges. They measure end-to-end interaction quality across all turns in a thread.
You can use multi-turn evaluations to measure:
1. Semantic Intent: What the user was trying to do.
2. Semantic Outcome: What actually happened, did the task succeed.
3. Trajectory: How the conversation unfolded, including trajectory of tool calls.
Running multi-turn online evals will auto-upgrade each trace within a thread to [extended data retention](/langsmith/administration-overview#data-retention-auto-upgrades). This upgrade will impact trace pricing, but ensures that traces meeting your evaluation criteria (typically those most valuable for analysis) are preserved for investigation.
## Prerequisites
* Your tracing project must be using [threads](/langsmith/threads).
* The top-level inputs and outputs of each trace in a thread must have a `messages` key that contains a list of messages. We support messages in [LangChain](/langsmith/log-llm-trace#messages-format), [OpenAI Chat Completions](https://platform.openai.com/docs/api-reference/chat/create), and [Anthropic Messages](https://platform.claude.com/docs/en/api/messages) formats.
* If the top-level inputs and outputs of each trace only contain the latest message in the conversation, LangSmith will automatically combine messages across turns into a thread.
* If the top-level inputs and outputs of each trace contain the full conversation history, LangSmith will use that directly.
If your traces don't follow the format above, thread level evaluators won't work. You'll need to update how you trace to LangSmith to ensure each trace's top-level inputs and outputs contain a list of `messages`.
Please refer to the [troubleshooting](/langsmith/online-evaluations-multi-turn#troubleshooting) section for more information.
## Configuration
1. Navigate to the **Tracing Projects** tab and select a tracing project.
2. Click **+ New** in the top right corner of the tracing project page > **New Evaluator** > **Evaluate a multi-turn thread**.
3. **Name your evaluator**.
4. **Apply filters or a sampling rate**.
Use filters or sampling to control evaluator cost. For example, evaluate only threads under *N* turns or sample 10% of all threads.
5. **Configure an idle time**.
The first time you configure a thread level evaluator, you'll define the idle time — the amount of time after the last trace in a thread before it's considered complete and ready for evaluation. This value should reflect the expected length of user interactions in your app. It applies across all evaluators in the project.
When first testing your evaluator, use a short idle time so you can see results quickly. Once validated, increase it to match the expected length of user interactions.
6. **Configure your model.**
Select the provider and model you want to use for your evaluator. Threads tend to get long, so you should use a model with a higher context window in order to avoid running into limits. For example, OpenAI's GPT-4.1 mini or Gemini 2.5 Flash are good options as they both have 1M+ token context windows.
7. **Configure your LLM-as-a-judge prompt.**
Define what you want to evaluate. This prompt will be used to evaluate the thread. You can also configure which parts of the `messages` list are passed to the evaluator to control the content it receives:
* All messages: Send the full message list.
* Human and AI pairs: Send only user and assistant messages (excluding system messages, tool calls, etc.).
* First human and last AI: Send only the first user message and the last assistant reply.
8. **Set up your feedback configuration**.
Configure a name for the feedback key, the format for the feedback you want to collect and optionally enable reasoning on the feedback.
We don't recommend using the same feedback key for a thread-level evaluator and a run-level evaluator as it can be hard to distinguish between the two.
8. **Save your evaluator.**
After saving, your evaluator will appear in the **Evaluators** tab. You can test it once the idle time has passed for any new threads created after saving.
## Limits
These are the current limits for multi-turn online evaluators (subject to change). Please reach out if you are running into any of these limits.
* **Runs must be less than one week old**: When a thread becomes idle, only runs within the past 7 days are eligible for evaluation.
* **Maximum of 500 threads evaluated at once**: If you have more than 500 threads marked as idle in a five minute period, we will automatically sample beyond 500.
* **Maximum of 10 multi-turn online evaluators per workspace**
## Troubleshooting
**Checking the status of your evaluator**
You can check when your evaluator was last run by heading to the **Evaluators** tab within a tracing project and clicking the **Logs** button for the evaluator you created to view its run history.
**Inspect the data sent to the evaluator**
Inspect the data sent to the evaluator by heading to the **Evaluators** tab within a tracing project, clicking on the evaluator you created and clicking the **Evaluator traces** tab.
In this tab, you can see the inputs passed into the LLM-as-a-judge evaluator. If your messages are not being passed in correctly, you will see blank values in the inputs. This can happen if your messages are not formatted in one of [the expected formats](/langsmith/online-evaluations-multi-turn#prerequisites).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/online-evaluations-multi-turn.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Document API authentication in OpenAPI
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/openapi-security
This guide shows how to customize the OpenAPI security schema for your LangSmith API documentation. A well-documented security schema helps API consumers understand how to authenticate with your API and even enables automatic client generation. See the [Authentication & Access Control conceptual guide](/langsmith/auth) for more details about LangGraph's authentication system.
**Implementation vs Documentation**
This guide only covers how to document your security requirements in OpenAPI. To implement the actual authentication logic, see [How to add custom authentication](/langsmith/custom-auth).
This guide applies to all LangSmith deployments (Cloud and self-hosted). It does not apply to usage of the LangGraph open source library if you are not using LangSmith.
## Default schema
The default security scheme varies by deployment type:
By default, LangSmith requires a LangSmith API key in the `x-api-key` header:
```yaml theme={null}
components:
securitySchemes:
apiKeyAuth:
type: apiKey
in: header
name: x-api-key
security:
- apiKeyAuth: []
```
When using one of the LangGraph SDK's, this can be inferred from environment variables.
By default, self-hosted deployments have no security scheme. This means they are to be deployed only on a secured network or with authentication. To add custom authentication, see [How to add custom authentication](/langsmith/custom-auth).
## Custom security schema
To customize the security schema in your OpenAPI documentation, add an `openapi` field to your `auth` configuration in `langgraph.json`. Remember that this only updates the API documentation - you must also implement the corresponding authentication logic as shown in [How to add custom authentication](/langsmith/custom-auth).
Note that LangSmith does not provide authentication endpoints - you'll need to handle user authentication in your client application and pass the resulting credentials to the LangGraph API.
```json theme={null}
{
"auth": {
"path": "./auth.py:my_auth", // Implement auth logic here
"openapi": {
"securitySchemes": {
"OAuth2": {
"type": "oauth2",
"flows": {
"implicit": {
"authorizationUrl": "https://your-auth-server.com/oauth/authorize",
"scopes": {
"me": "Read information about the current user",
"threads": "Access to create and manage threads"
}
}
}
}
},
"security": [
{"OAuth2": ["me", "threads"]}
]
}
}
}
```
```json theme={null}
{
"auth": {
"path": "./auth.py:my_auth", // Implement auth logic here
"openapi": {
"securitySchemes": {
"apiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key"
}
},
"security": [
{"apiKeyAuth": []}
]
}
}
}
```
## Testing
After updating your configuration:
1. Deploy your application
2. Visit `/docs` to see the updated OpenAPI documentation
3. Try out the endpoints using credentials from your authentication server (make sure you've implemented the authentication logic first)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/openapi-security.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Optimize a classifier
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/optimize-classifier
This tutorial walks through optimizing a classifier based on user a feedback. Classifiers are great to optimize because its generally pretty simple to collect the desired output, which makes it easy to create few shot examples based on user feedback. That is exactly what we will do in this example.
## The objective
In this example, we will build a bot that classify GitHub issues based on their title. It will take in a title and classify it into one of many different classes. Then, we will start to collect user feedback and use that to shape how this classifier performs.
## Getting started
To get started, we will first set it up so that we send all traces to a specific project. We can do this by setting an environment variable:
```python theme={null}
import os
os.environ["LANGSMITH_PROJECT"] = "classifier"
```
We can then create our initial application. This will be a really simple function that just takes in a GitHub issue title and tries to label it.
```python theme={null}
import openai
from langsmith import traceable, Client
import uuid
client = openai.Client()
available_topics = [
"bug",
"improvement",
"new_feature",
"documentation",
"integration",
]
prompt_template = """Classify the type of the issue as one of {topics}.
Issue: {text}"""
@traceable(
run_type="chain",
name="Classifier",
)
def topic_classifier(
topic: str):
return client.chat.completions.create(
model="gpt-4.1-mini",
temperature=0,
messages=[
{
"role": "user",
"content": prompt_template.format(
topics=','.join(available_topics),
text=topic,
)
}
],
).choices[0].message.content
```
We can then start to interact with it. When interacting with it, we will generate the LangSmith run id ahead of time and pass that into this function. We do this so we can attach feedback later on.
Here's how we can invoke the application:
```python theme={null}
from langsmith import uuid7
run_id = uuid7()
topic_classifier(
"fix bug in LCEL",
langsmith_extra={"run_id": run_id})
```
Here's how we can attach feedback after. We can collect feedback in two forms.
First, we can collect "positive" feedback - this is for examples that the model got right.
```python theme={null}
ls_client = Client()
run_id = uuid7()
topic_classifier(
"fix bug in LCEL",
langsmith_extra={"run_id": run_id})
ls_client.create_feedback(
run_id,
key="user-score",
score=1.0,
)
```
Next, we can focus on collecting feedback that corresponds to a "correction" to the generation. In this example the model will classify it as a bug, whereas I really want this to be classified as documentation.
```python theme={null}
ls_client = Client()
run_id = uuid7()
topic_classifier(
"fix bug in documentation",
langsmith_extra={"run_id": run_id})
ls_client.create_feedback(
run_id,
key="correction",
correction="documentation")
```
## Set up automations
We can now set up automations to move examples with feedback of some form into a dataset. We will set up two automations, one for positive feedback and the other for negative feedback.
The first will take all runs with positive feedback and automatically add them to a dataset. The logic behind this is that any run with positive feedback we can use as a good example in future iterations. Let's create a dataset called `classifier-github-issues` to add this data to.
The second will take all runs with a correction and use a webhook to add them to a dataset. When creating this webhook, we will select the option to "Use Corrections". This option will make it so that when creating a dataset from a run, rather than using the output of the run as the gold-truth output of the datapoint, it will use the correction.
## Update the application
We can now update our code to pull down the dataset we are sending runs to. Once we pull it down, we can create a string with the examples in it. We can then put this string as part of the prompt!
```python theme={null}
### NEW CODE ###
# Initialize the LangSmith Client so we can use to get the dataset
ls_client = Client()
# Create a function that will take in a list of examples and format them into a string
def create_example_string(examples):
final_strings = []
for e in examples:
final_strings.append(f"Input: {e.inputs['topic']}\n> {e.outputs['output']}")
return "\n\n".join(final_strings)
### NEW CODE ###
client = openai.Client()
available_topics = [
"bug",
"improvement",
"new_feature",
"documentation",
"integration",
]
prompt_template = """Classify the type of the issue as one of {topics}.
Here are some examples:
{examples}
Begin!
Issue: {text}
>"""
@traceable(
run_type="chain",
name="Classifier",
)
def topic_classifier(
topic: str):
# We can now pull down the examples from the dataset
# We do this inside the function so it always get the most up-to-date examples,
# But this can be done outside and cached for speed if desired
examples = list(ls_client.list_examples(dataset_name="classifier-github-issues")) # <- New Code
example_string = create_example_string(examples)
return client.chat.completions.create(
model="gpt-4.1-mini",
temperature=0,
messages=[
{
"role": "user",
"content": prompt_template.format(
topics=','.join(available_topics),
text=topic,
examples=example_string,
)
}
],
).choices[0].message.content
```
If now run the application with a similar input as before, we can see that it correctly learns that anything related to docs (even if a bug) should be classified as `documentation`
```python theme={null}
ls_client = Client()
run_id = uuid7()
topic_classifier(
"address bug in documentation",
langsmith_extra={"run_id": run_id})
```
## Semantic search over examples
One additional thing we can do is only use the most semantically similar examples. This is useful when you start to build up a lot of examples.
In order to do this, we can first define an example to find the `k` most similar examples:
```python theme={null}
import numpy as np
def find_similar(examples, topic, k=5):
inputs = [e.inputs['topic'] for e in examples] + [topic]
vectors = client.embeddings.create(input=inputs, model="text-embedding-3-small")
vectors = [e.embedding for e in vectors.data]
vectors = np.array(vectors)
args = np.argsort(-vectors.dot(vectors[-1])[:-1])[:5]
examples = [examples[i] for i in args]
return examples
```
We can then use that in the application
```python theme={null}
ls_client = Client()
def create_example_string(examples):
final_strings = []
for e in examples:
final_strings.append(f"Input: {e.inputs['topic']}\n> {e.outputs['output']}")
return "\n\n".join(final_strings)
client = openai.Client()
available_topics = [
"bug",
"improvement",
"new_feature",
"documentation",
"integration",
]
prompt_template = """Classify the type of the issue as one of {topics}.
Here are some examples:
{examples}
Begin!
Issue: {text}
>"""
@traceable(
run_type="chain",
name="Classifier",
)
def topic_classifier(
topic: str):
examples = list(ls_client.list_examples(dataset_name="classifier-github-issues"))
examples = find_similar(examples, topic)
example_string = create_example_string(examples)
return client.chat.completions.create(
model="gpt-4.1-mini",
temperature=0,
messages=[
{
"role": "user",
"content": prompt_template.format(
topics=','.join(available_topics),
text=topic,
examples=example_string,
)
}
],
).choices[0].message.content
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/optimize-classifier.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Organization and workspace operations reference
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/organization-workspace-operations
This page provides a comprehensive reference table of [workspace](/langsmith/administration-overview#workspaces) and [organization](/langsmith/administration-overview#organizations) operations and which roles can perform them.
The list includes API operations in LangSmith along with:
* Which system roles can perform each operation.
* The specific permission string required.
* Notes about partial access or special cases.
For an overview of LangSmith's RBAC system, role definitions, and permission concepts, refer to [Role-based access control](/langsmith/rbac).
## Contents
| Organization-level operations | Workspace-level operations |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Core management:** • [Organization settings](#organization-settings): Org info and configuration • [Workspaces](#workspaces): Workspace management • [Organization members](#organization-members): Member management • [Roles and permissions](#roles-and-permissions): Custom roles | **Core resources:** • [Projects](#projects): Organize traces and runs • [Runs](#runs): Individual execution traces • [Datasets](#datasets): Test datasets for evaluation • [Examples](#examples): Individual dataset examples • [Experiments](#experiments): Comparative experiments |
| **Security and authentication:** • [SSO and authentication](#sso-and-authentication): Single sign-on setup • [SCIM](#scim): Identity provisioning • [Access policies](#access-policies): Attribute-based access control | **Monitoring and analysis:** • [Rules](#rules): Automated run rules • [Alerts](#alerts): Alert rules for monitoring • [Feedback](#feedback): Scores and labels on outputs • [Annotation Queues](#annotation-queues): Human review queues • [Charts](#charts): Custom visualizations |
| **Billing and accounts:** • [Billing and payments](#billing-and-payments): Subscription management • [API keys](#api-keys): Org-level keys | **Development and configuration:** • [Prompts](#prompts): Prompt templates (LangChain Hub) • [Deployments](#deployments): Deployment configurations • [MCP Servers](#mcp-servers): Model Context Protocol servers |
| **Analytics:** • [Charts and dashboards](#organization-charts-and-dashboards): Org-level visualizations • [Usage and analytics](#usage-and-analytics): Usage tracking and TTL settings | **Workspace management:** • [Workspace settings](#workspace-settings-and-management): Members, settings • [Tags](#tags): Metadata tagging system • [Bulk Exports](#bulk-exports): Data export operations |
**Additional information:**
* [User-level operations](#user-level-operations): Operations for all authenticated users
* [Permission inheritance](#permission-inheritance): How roles inherit across org/workspaces
## Legend
* ✓ **Allowed**: User with this role can perform this action
* ✗ **Not Allowed**: User with this role cannot perform this action
* ⚠ **Partial**: User has limited access (see notes)
## Organization-level operations
Organization-level operations are controlled by organization roles, which are separate from the RBAC feature. Learn more in the [Role-based access control](/langsmith/rbac#organization-roles) guide.
### Organization settings
| Operation | Org Admin | Org Operator | Org User | Org Viewer | Required Permission |
| --------------------------- | :-------: | :----------: | :------: | :--------: | --------------------- |
| View organization info | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| View organization dashboard | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Update organization info | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| View billing info | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| View company info | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Set company info | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
### Workspaces
Organization-level workspace management operations.
| Operation | Org Admin | Org Operator | Org User | Org Viewer | Required Permission |
| ------------------- | :-------: | :----------: | :------: | :--------: | --------------------- |
| List all workspaces | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Create workspace | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
### Organization members
| Operation | Org Admin | Org Operator | Org User | Org Viewer | Required Permission | Notes |
| ------------------------------- | :-------: | :----------: | :------: | :--------: | --------------------- | ------------------------------------------------------ |
| View organization members | ✓ | ✓ | ✓ | ✓ | `organization:read` | |
| View active org members | ✓ | ✓ | ✓ | ✓ | `organization:read` | |
| View pending org members | ✓ | ✓ | ✓ | ✓ | `organization:read` | |
| Invite member to organization | ✓ | ⚠ | ✗ | ✗ | `organization:manage` | Org Operator can only invite Org Users and Org Viewers |
| Invite members (batch) | ✓ | ⚠ | ✗ | ✗ | `organization:manage` | Org Operator can only invite Org Users and Org Viewers |
| Add basic auth members | ✓ | ⚠ | ✗ | ✗ | `organization:manage` | Org Operator can only add Org Users and Org Viewers |
| Remove organization member | ✓ | ⚠ | ✗ | ✗ | `organization:manage` | Org Operator cannot remove Org Admins |
| Update organization member role | ✓ | ⚠ | ✗ | ✗ | `organization:manage` | Org Operator can only modify Org Users and Org Viewers |
| Delete pending org member | ✓ | ⚠ | ✗ | ✗ | `organization:manage` | Org Operator cannot delete pending Org Admin invites |
### Roles and permissions
| Operation | Org Admin | Org Operator | Org User | Org Viewer | Required Permission |
| -------------------------- | :-------: | :----------: | :------: | :--------: | --------------------- |
| List organization roles | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| List available permissions | ✓ | ✓ | ✓ | ✓ | N/A (user-level) |
| Create custom role | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Update custom role | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Delete custom role | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
### SSO and authentication
| Operation | Org Admin | Org Operator | Org User | Org Viewer | Required Permission |
| ---------------------------- | :-------: | :----------: | :------: | :--------: | --------------------- |
| View SSO settings | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Create SSO settings | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Update SSO settings | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Delete SSO settings | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| View login methods | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Update allowed login methods | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Set default SSO provision | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
### SCIM
System for Cross-domain Identity Management for user provisioning.
| Operation | Org Admin | Org Operator | Org User | Org Viewer | Required Permission |
| ----------------- | :-------: | :----------: | :------: | :--------: | --------------------- |
| List SCIM tokens | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Get SCIM token | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Create SCIM token | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Update SCIM token | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Delete SCIM token | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
### Access policies
Attribute-based access control (ABAC) policies for fine-grained permissions.
ABAC is in private beta.
| Operation | Org Admin | Org Operator | Org User | Org Viewer | Required Permission |
| ---------------------------- | :-------: | :----------: | :------: | :--------: | --------------------- |
| List access policies | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Get access policy | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Create access policy | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Delete access policy | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Attach access policy to role | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
### Billing and payments
| Operation | Org Admin | Org Operator | Org User | Org Viewer | Required Permission |
| ------------------------------ | :-------: | :----------: | :------: | :--------: | --------------------- |
| Create Stripe setup intent | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Handle payment method creation | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Change payment plan | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Create Stripe checkout session | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Confirm checkout completion | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Create Stripe account links | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
### API keys
| Operation | Org Admin | Org Operator | Org User | Org Viewer | Required Permission |
| ---------------------------------------------- | :-------: | :----------: | :------: | :--------: | -------------------------------------------------- |
| List org-scoped API keys | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Create org-scoped API key (workspace-scoped)\* | ✓ | ✓ | ⚠ | ✗ | `organization:pats:create` |
| Create org-scoped API key (org-wide)\* | ✓ | ✗ | ✗ | ✗ | `organization:pats:create` + `organization:manage` |
| List personal access tokens | ✓ | ✓ | ✓ | ✗ | `organization:read` |
| Create personal access token | ✓ | ✓ | ✓ | ✗ | `organization:pats:create` |
| Delete personal access token | ✓ | ✓ | ✓ | ✗ | `organization:read` |
\* Organization Operators and Organization Users can create workspace-scoped API keys only for workspaces where they are a Workspace Admin. Org-wide API keys require the Organization Admin role.
### Organization charts and dashboards
| Operation | Org Admin | Org Operator | Org User | Org Viewer | Required Permission |
| ------------------------ | :-------: | :----------: | :------: | :--------: | --------------------- |
| List org charts | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Get org chart by ID | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Create org chart | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Update org chart | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Delete org chart | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Render org chart | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Get org chart section | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Create org chart section | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Update org chart section | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Delete org chart section | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
| Render org chart section | ✓ | ✓ | ✓ | ✓ | `organization:read` |
### Usage and analytics
| Operation | Org Admin | Org Operator | Org User | Org Viewer | Required Permission |
| ----------------------------------------------------------------------------- | :-------: | :----------: | :------: | :-----------------: | --------------------- |
| View organization usage | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| [View granular billable usage](/langsmith/granular-usage) | ✓ | ✓ | ✓ | `organization:read` | |
| [Export granular usage as CSV](/langsmith/granular-usage#export-usage-as-csv) | ✓ | ✓ | ✓ | `organization:read` | |
| View TTL settings | ✓ | ✓ | ✓ | ✓ | `organization:read` |
| Upsert TTL settings | ✓ | ✓ | ✗ | ✗ | `organization:manage` |
## Workspace-level operations
These operations are controlled by [workspace-level roles and permissions](/langsmith/rbac#workspace-roles).
To understand what each role means and their overall capabilities, refer to the [Role-based access control](/langsmith/rbac) guide.
### Projects
Projects organize traces and runs from your LLM applications.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ------------------------------------------------- | :-------------: | :--------------: | :--------------: | -------------------------------- |
| Create a new project | ✓ | ✗ | ✗ | `projects:create` |
| View project list | ✓ | ✓ | ✓ | `projects:read` |
| View project details | ✓ | ✓ | ✓ | `projects:read` |
| View prebuilt dashboard | ✓ | ✓ | ✓ | `projects:read` |
| View project metadata (top K values) | ✓ | ✓ | ✓ | `projects:read` |
| Update project metadata (name, description, tags) | ✓ | ✓ | ✗ | `projects:update` |
| Create filter view | ✓ | ✗ | ✗ | `projects:create` |
| View filter views | ✓ | ✓ | ✓ | `projects:read` |
| View specific filter view | ✓ | ✓ | ✓ | `projects:read` |
| Update filter view | ✓ | ✓ | ✗ | `projects:update` |
| Delete filter view | ✓ | ✗ | ✗ | `projects:delete` |
| Delete a project | ✓ | ✗ | ✗ | `projects:delete` |
| Delete multiple projects | ✓ | ✗ | ✗ | `projects:delete` |
| Get insights jobs (Beta) | ✓ | ✓ | ✓ | `projects:read` |
| Get specific insights job (Beta) | ✓ | ✓ | ✓ | `projects:read` |
| Create insights job (Beta) | ✓ | ✓ | ✓ | `projects:read` + `rules:create` |
| Update insights job (Beta) | ✓ | ✓ | ✗ | `projects:update` |
| Delete insights job (Beta) | ✓ | ✗ | ✗ | `projects:delete` |
| Get insights job configs (Beta) | ✓ | ✓ | ✓ | `rules:read` |
| Create insights job config (Beta) | ✓ | ✓ | ✗ | `rules:create` |
| Auto-generate insights job config (Beta) | ✓ | ✓ | ✗ | `rules:create` |
| Update insights job config (Beta) | ✓ | ✓ | ✗ | `rules:update` |
| Delete insights job config (Beta) | ✓ | ✓ | ✗ | `rules:delete` |
| Get run cluster from insights job (Beta) | ✓ | ✓ | ✓ | `projects:read` |
| Get runs from insights job (Beta) | ✓ | ✓ | ✓ | `projects:read` |
### Runs
Individual execution traces and spans from your LLM applications.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ---------------------------------------------------------------------- | :-------------: | :--------------: | :--------------: | ------------------- |
| Send traces from SDK (includes single run, batch, multipart, and OTEL) | ✓ | ✓ | ✗ | `runs:create` |
| View a specific run | ✓ | ✓ | ✓ | `runs:read` |
| View thread preview | ✓ | ✓ | ✓ | `runs:read` |
| Query/list runs | ✓ | ✓ | ✓ | `runs:read` |
| View run statistics | ✓ | ✓ | ✓ | `runs:read` |
| View grouped run statistics | ✓ | ✓ | ✓ | `runs:read` |
| Group runs by expression | ✓ | ✓ | ✓ | `runs:read` |
| Generate filter query from natural language | ✓ | ✓ | ✓ | `runs:read` |
| Prefetch runs | ✓ | ✓ | ✓ | `runs:read` |
| Update a run (PATCH) | ✓ | ✓ | ✗ | `runs:create` |
| View run sharing state | ✓ | ✓ | ✓ | `runs:read` |
| Share a run publicly | ✓ | ✓ | ✗ | `runs:share` |
| Unshare a run | ✓ | ✓ | ✗ | `runs:share` |
| Delete runs by trace ID or metadata | ✓ | ✗ | ✗ | `runs:delete` |
### Rules
Automated run rules that trigger actions based on run conditions.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ----------------------- | :-------------: | :--------------: | :--------------: | ------------------- |
| List all run rules | ✓ | ✓ | ✓ | `rules:read` |
| Create a run rule | ✓ | ✓ | ✗ | `rules:create` |
| Update a run rule | ✓ | ✓ | ✗ | `rules:update` |
| Delete a run rule | ✓ | ✓ | ✗ | `rules:delete` |
| View rule logs | ✓ | ✓ | ✓ | `rules:read` |
| Get last applied rule | ✓ | ✓ | ✓ | `rules:read` |
| Manually trigger a rule | ✓ | ✓ | ✗ | `rules:update` |
| Trigger multiple rules | ✓ | ✓ | ✗ | `rules:update` |
### Alerts
Alert rules for monitoring run conditions.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ----------------- | :-------------: | :--------------: | :--------------: | ------------------- |
| Create alert rule | ✓ | ✓ | ✓ | `runs:read` |
| Update alert rule | ✓ | ✓ | ✓ | `runs:read` |
| Delete alert rule | ✓ | ✓ | ✓ | `runs:read` |
| Get alert rule | ✓ | ✓ | ✓ | `runs:read` |
| List alert rules | ✓ | ✓ | ✓ | `runs:read` |
| Test alert action | ✓ | ✓ | ✓ | `runs:read` |
### Datasets
Test datasets with examples for evaluation.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| -------------------------------------------- | :-------------: | :--------------: | :--------------: | ---------------------------------------------------- |
| Create a dataset | ✓ | ✓ | ✗ | `datasets:create` |
| List datasets | ✓ | ✓ | ✓ | `datasets:read` |
| View dataset details | ✓ | ✓ | ✓ | `datasets:read` |
| Update dataset metadata | ✓ | ✓ | ✗ | `datasets:update` |
| Delete a dataset | ✓ | ✗ | ✗ | `datasets:delete` |
| Upload CSV dataset | ✓ | ✓ | ✗ | `datasets:create` |
| Clone dataset | ✓ | ✓ | ✗ | `datasets:update` |
| Get dataset version | ✓ | ✓ | ✓ | `datasets:read` |
| Get dataset versions | ✓ | ✓ | ✓ | `datasets:read` |
| Diff dataset versions | ✓ | ✓ | ✓ | `datasets:read` |
| Update dataset version (tags) | ✓ | ✓ | ✗ | `datasets:update` |
| Download dataset (OpenAI format) | ✓ | ✓ | ✓ | `datasets:read` |
| Download dataset (OpenAI fine-tuning format) | ✓ | ✓ | ✓ | `datasets:read` |
| Download dataset (CSV) | ✓ | ✓ | ✓ | `datasets:read` |
| Download dataset (JSONL) | ✓ | ✓ | ✓ | `datasets:read` |
| View dataset sharing state | ✓ | ✓ | ✓ | `datasets:read` |
| Share dataset publicly | ✓ | ✗ | ✗ | `datasets:share` |
| Unshare dataset | ✓ | ✗ | ✗ | `datasets:share` |
| Get index info | ✓ | ✓ | ✓ | `datasets:read` |
| Index dataset | ✓ | ✓ | ✗ | `datasets:update` |
| Sync dataset index | ✓ | ✓ | ✗ | `datasets:update` |
| Remove dataset index | ✓ | ✓ | ✗ | `datasets:update` |
| Search dataset | ✓ | ✓ | ✓ | `datasets:read` |
| Generate synthetic examples | ✓ | ✓ | ✗ | `datasets:update` |
| Get dataset splits | ✓ | ✓ | ✓ | `datasets:read` |
| Update dataset splits | ✓ | ✓ | ✓ | `datasets:read` |
| Run playground experiment (batch) | ✓ | ⚠ | ✗ | `prompts:read` + `datasets:read` + `projects:create` |
| Run playground experiment (stream) | ✓ | ⚠ | ✗ | `prompts:read` + `datasets:read` + `projects:create` |
| Run studio experiment | ✓ | ⚠ | ✗ | `datasets:read` + `projects:create` |
Workspace Editors have partial access because they cannot create projects, which limits their ability to create new experiments.
### Examples
Individual examples within datasets.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ------------------------------- | :-------------: | :--------------: | :--------------: | ------------------- |
| Count examples | ✓ | ✓ | ✓ | `datasets:read` |
| View a specific example | ✓ | ✓ | ✓ | `datasets:read` |
| List examples | ✓ | ✓ | ✓ | `datasets:read` |
| Create a new example | ✓ | ✓ | ✗ | `datasets:update` |
| Create examples (bulk) | ✓ | ✓ | ✗ | `datasets:update` |
| Update a single example | ✓ | ✓ | ✗ | `datasets:update` |
| Update examples (bulk) | ✓ | ✓ | ✗ | `datasets:update` |
| Update examples (multipart) | ✓ | ✓ | ✗ | `datasets:update` |
| Upload examples from CSV | ✓ | ✓ | ✗ | `datasets:update` |
| Upload examples from JSONL | ✓ | ✓ | ✗ | `datasets:update` |
| Delete a single example | ✓ | ✓ | ✗ | `datasets:update` |
| Delete examples (bulk) | ✓ | ✓ | ✗ | `datasets:update` |
| View examples with runs | ✓ | ✓ | ✓ | `datasets:read` |
| View grouped examples with runs | ✓ | ✓ | ✓ | `datasets:read` |
| Validate a single example | ✓ | ✓ | ✓ | `datasets:read` |
| Validate examples (bulk) | ✓ | ✓ | ✓ | `datasets:read` |
### Experiments
Comparative experiments for evaluating LLM outputs.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ------------------------------- | :-------------: | :--------------: | :--------------: | ------------------------------------------------------------------------- |
| View comparative experiments | ✓ | ✓ | ✓ | `projects:read` |
| Create comparative experiment | ✓ | ⚠ | ✗ | `projects:create` |
| Delete comparative experiment | ✓ | ✗ | ✗ | `projects:delete` |
| View examples with runs | ✓ | ✓ | ✓ | `datasets:read` |
| View grouped examples with runs | ✓ | ✓ | ✓ | `datasets:read` |
| View grouped experiments | ✓ | ✓ | ✓ | `datasets:read` |
| View feedback delta | ✓ | ✓ | ✓ | `datasets:read` |
| Upload experiment results | ✓ | ⚠ | ✗ | `datasets:create` + `datasets:update` + `projects:create` + `runs:create` |
| Get experiment view overrides | ✓ | ✓ | ✗ | `datasets:update` |
| Create experiment view override | ✓ | ✓ | ✗ | `datasets:update` |
| Update experiment view override | ✓ | ✓ | ✗ | `datasets:update` |
| Delete experiment view override | ✓ | ✓ | ✗ | `datasets:update` |
Workspace Editors have partial access because they cannot create projects, which limits their ability to create new experiments.
### Feedback
Scores, labels, and corrections on LLM outputs.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| --------------------------------------------- | :-------------: | :--------------: | :--------------: | ------------------- |
| List feedback formulas | ✓ | ✓ | ✓ | `feedback:read` |
| Get feedback formula | ✓ | ✓ | ✓ | `feedback:read` |
| Create feedback formula | ✓ | ✓ | ✗ | `feedback:create` |
| Update feedback formula | ✓ | ✓ | ✗ | `feedback:update` |
| Delete feedback formula | ✓ | ✓ | ✗ | `feedback:delete` |
| View specific feedback | ✓ | ✓ | ✓ | `feedback:read` |
| List feedbacks | ✓ | ✓ | ✓ | `feedback:read` |
| Create feedback | ✓ | ✓ | ✗ | `feedback:create` |
| Eagerly create feedback | ✓ | ✓ | ✗ | `feedback:create` |
| Update feedback | ✓ | ✓ | ✗ | `feedback:update` |
| Delete feedback | ✓ | ✓ | ✗ | `feedback:delete` |
| Batch ingest feedback | ✓ | ✓ | ✗ | `feedback:create` |
| Create feedback ingest token | ✓ | ✓ | ✗ | `feedback:create` |
| List feedback ingest tokens | ✓ | ✓ | ✗ | `feedback:create` |
| Create feedback with token (no auth required) | ✓ | ✓ | ✓ | N/A (token-based) |
| List feedback configs | ✓ | ✓ | ✓ | `feedback:read` |
| Create feedback config | ✓ | ✓ | ✗ | `feedback:create` |
| Update feedback config | ✓ | ✓ | ✗ | `feedback:update` |
### Annotation queues
Human review queues for LLM outputs.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ------------------------------------------- | :-------------: | :--------------: | :--------------: | -------------------------- |
| List annotation queues | ✓ | ✓ | ✓ | `annotation-queues:read` |
| Get annotation queue | ✓ | ✓ | ✓ | `annotation-queues:read` |
| Create annotation queue | ✓ | ✓ | ✗ | `annotation-queues:create` |
| Update annotation queue | ✓ | ✓ | ✗ | `annotation-queues:update` |
| Delete annotation queue | ✓ | ✗ | ✗ | `annotation-queues:delete` |
| Populate annotation queue | ✓ | ✓ | ✗ | `annotation-queues:update` |
| Get runs from queue | ✓ | ✓ | ✓ | `annotation-queues:read` |
| Get run from queue (by index) | ✓ | ✓ | ✓ | `annotation-queues:read` |
| Get queues for run | ✓ | ✓ | ✓ | `annotation-queues:read` |
| Get queue total size | ✓ | ✓ | ✓ | `annotation-queues:read` |
| Get queue total archived | ✓ | ✓ | ✓ | `annotation-queues:read` |
| Get queue size | ✓ | ✓ | ✓ | `annotation-queues:read` |
| Add runs to queue | ✓ | ✓ | ✗ | `annotation-queues:update` |
| Update run in queue | ✓ | ✓ | ✗ | `annotation-queues:update` |
| Delete run from queue | ✓ | ✓ | ✗ | `annotation-queues:update` |
| Delete runs from queue (bulk) | ✓ | ✓ | ✗ | `annotation-queues:update` |
| Create identity annotation queue run status | ✓ | ✓ | ✗ | `annotation-queues:update` |
| Export archived runs | ✓ | ✓ | ✓ | `annotation-queues:read` |
### Prompts
Prompt templates and chains in the LangChain Hub.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ----------------------- | :-------------: | :--------------: | :--------------: | ------------------- |
| List prompt repos | ✓ | ✓ | ✓ | `prompts:read` |
| View prompt repo | ✓ | ✓ | ✓ | `prompts:read` |
| Create prompt repo | ✓ | ✓ | ✗ | `prompts:create` |
| Fork prompt repo | ✓ | ✓ | ✗ | `prompts:create` |
| Update prompt repo | ✓ | ✓ | ✗ | `prompts:update` |
| Delete prompt repo | ✓ | ✓ | ✗ | `prompts:delete` |
| List commits | ✓ | ✓ | ✓ | `prompts:read` |
| View commit | ✓ | ✓ | ✓ | `prompts:read` |
| Push commit | ✓ | ✓ | ✗ | `prompts:update` |
| List repo tags | ✓ | ✓ | ✓ | `prompts:read` |
| Get all tags | ✓ | ✓ | ✓ | `prompts:read` |
| Create tag | ✓ | ✓ | ✗ | `prompts:create` |
| Update tag | ✓ | ✓ | ✗ | `prompts:update` |
| Delete tag | ✓ | ✓ | ✗ | `prompts:delete` |
| View events | ✓ | ✓ | ✓ | `prompts:read` |
| List comments | ✓ | ✓ | ✓ | `prompts:read` |
| Create comment | ✓ | ✓ | ✗ | `prompts:read` |
| Delete comment | ✓ | ✓ | ✗ | `prompts:read` |
| Toggle like | ✓ | ✓ | ✗ | `prompts:read` |
| Optimize prompt | ✓ | ✓ | ✗ | `prompts:update` |
| List optimization jobs | ✓ | ✓ | ✓ | `prompts:read` |
| Create optimization job | ✓ | ✓ | ✗ | `prompts:create` |
| Update optimization job | ✓ | ✓ | ✗ | `prompts:update` |
| Delete optimization job | ✓ | ✓ | ✗ | `prompts:delete` |
| Invoke prompt canvas | ✓ | ✓ | ✗ | `prompts:update` |
| List quick actions | ✓ | ✓ | ✓ | `prompts:read` |
| Create quick action | ✓ | ✓ | ✓ | `prompts:read` |
| Delete quick action | ✓ | ✓ | ✓ | `prompts:read` |
| Update quick action | ✓ | ✓ | ✓ | `prompts:read` |
Some prompt operations support public access for shared prompts.
### Charts
Custom visualizations and dashboards.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ----------------------- | :-------------: | :--------------: | :--------------: | ------------------- |
| List charts | ✓ | ✓ | ✓ | `charts:read` |
| Get chart by ID | ✓ | ✓ | ✓ | `charts:read` |
| Create chart | ✓ | ✓ | ✗ | `charts:create` |
| Update chart | ✓ | ✓ | ✗ | `charts:update` |
| Delete chart | ✓ | ✓ | ✗ | `charts:delete` |
| Render chart | ✓ | ✓ | ✓ | `charts:read` |
| List chart sections | ✓ | ✓ | ✓ | `charts:read` |
| Get chart section by ID | ✓ | ✓ | ✓ | `charts:read` |
| Create chart section | ✓ | ✓ | ✗ | `charts:create` |
| Update chart section | ✓ | ✓ | ✗ | `charts:update` |
| Delete chart section | ✓ | ✓ | ✗ | `charts:delete` |
| Render chart section | ✓ | ✓ | ✓ | `charts:read` |
### Deployments
[LangSmith Deployment](/langsmith/deployments) configurations.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ----------------- | :-------------: | :--------------: | :--------------: | -------------------- |
| Create deployment | ✓ | ✓ | ✗ | `deployments:create` |
| View deployment | ✓ | ✓ | ✓ | `deployments:read` |
| Update deployment | ✓ | ✓ | ✗ | `deployments:update` |
| Delete deployment | ✓ | ✗ | ✗ | `deployments:delete` |
### Workspace settings and management
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ------------------------------------ | :-------------: | :--------------: | :--------------: | ------------------- |
| View workspace info | ✓ | ✓ | ✓ | `workspaces:read` |
| View workspace statistics | ✓ | ✓ | ✓ | `workspaces:read` |
| Update workspace (name, description) | ✓ | ✗ | ✗ | `workspaces:manage` |
| Delete workspace | ✓ | ✗ | ✗ | `workspaces:manage` |
| View workspace members | ✓ | ✓ | ✓ | `workspaces:read` |
| View active workspace members | ✓ | ✓ | ✓ | `workspaces:read` |
| View pending workspace members | ✓ | ✓ | ✓ | `workspaces:read` |
| Add member to workspace | ✓ | ✗ | ✗ | `workspaces:manage` |
| Add members (batch) | ✓ | ✗ | ✗ | `workspaces:manage` |
| Update workspace member role | ✓ | ✗ | ✗ | `workspaces:manage` |
| Remove workspace member | ✓ | ✗ | ✗ | `workspaces:manage` |
| Delete pending workspace member | ✓ | ✗ | ✗ | `workspaces:manage` |
| View usage limits | ✓ | ✓ | ✓ | `workspaces:read` |
| View shared entities | ✓ | ✓ | ✓ | `workspaces:read` |
| Bulk unshare entities | ✓ | ✗ | ✗ | `workspaces:manage` |
### Tags
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ------------------------------- | :-------------: | :--------------: | :--------------: | ------------------- |
| List tag keys | ✓ | ✓ | ✓ | `workspaces:read` |
| Get tag key | ✓ | ✓ | ✓ | `workspaces:read` |
| Create tag key | ✓ | ✗ | ✗ | `workspaces:manage` |
| Update tag key | ✓ | ✗ | ✗ | `workspaces:manage` |
| Delete tag key | ✓ | ✗ | ✗ | `workspaces:manage` |
| List tag values | ✓ | ✓ | ✓ | `workspaces:read` |
| Get tag value | ✓ | ✓ | ✓ | `workspaces:read` |
| Create tag value | ✓ | ✗ | ✗ | `workspaces:manage` |
| Update tag value | ✓ | ✗ | ✗ | `workspaces:manage` |
| Delete tag value | ✓ | ✗ | ✗ | `workspaces:manage` |
| List tags | ✓ | ✓ | ✓ | `workspaces:read` |
| List tags for resource | ✓ | ✓ | ✓ | `workspaces:read` |
| List tags for resources (batch) | ✓ | ✓ | ✓ | `workspaces:read` |
| List taggings | ✓ | ✓ | ✓ | `workspaces:read` |
| Create tagging | ✓ | ✗ | ✗ | `workspaces:manage` |
| Delete tagging | ✓ | ✗ | ✗ | `workspaces:manage` |
### Bulk exports
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ------------------------------ | :-------------: | :--------------: | :--------------: | ------------------- |
| List bulk exports | ✓ | ✓ | ✓ | `workspaces:read` |
| Get bulk export | ✓ | ✓ | ✓ | `workspaces:read` |
| Create bulk export | ✓ | ✗ | ✗ | `workspaces:manage` |
| Cancel bulk export | ✓ | ✗ | ✗ | `workspaces:manage` |
| Get bulk export destinations | ✓ | ✓ | ✓ | `workspaces:read` |
| Get bulk export destination | ✓ | ✓ | ✓ | `workspaces:read` |
| Create bulk export destination | ✓ | ✗ | ✗ | `workspaces:manage` |
| Get filtered export runs | ✓ | ✓ | ✓ | `workspaces:read` |
### MCP servers
Model Context Protocol servers for extended functionality.
| Operation | Workspace Admin | Workspace Editor | Workspace Viewer | Required Permission |
| ----------------- | :-------------: | :--------------: | :--------------: | ------------------- |
| List MCP servers | ✓ | ✓ | ✓ | `workspaces:read` |
| Get MCP server | ✓ | ✓ | ✓ | `workspaces:read` |
| Create MCP server | ✓ | ✓ | ✓ | `workspaces:read` |
| Update MCP server | ✓ | ✓ | ✓ | `workspaces:read` |
| Delete MCP server | ✓ | ✓ | ✓ | `workspaces:read` |
## User-level operations
These operations are available to all authenticated users and don't require specific workspace or organization permissions:
* View own user profile
* Update own user profile
* List organizations for user
* Create new organization
* List pending workspace invites
* Delete pending workspace invite
* Claim pending workspace invite
* List pending organization invites
* Delete pending organization invite
* Claim pending organization invite
## Permission inheritance
### Organization to workspace
* [Organization Admin](/langsmith/rbac#organization-admin) automatically has full permissions in all workspaces.
* [Organization Operator](/langsmith/rbac#organization-operator) only gets workspace access when explicitly added to workspaces with workspace-level roles (or to workspaces they create).
* [Organization User](/langsmith/rbac#organization-user) and [Organization Viewer](/langsmith/rbac#organization-viewer) only get workspace access when explicitly added to workspaces with workspace-level roles.
For detailed role definitions, refer to [Organization roles](/langsmith/rbac#organization-roles) and [Workspace roles](/langsmith/rbac#workspace-roles).
### Workspace role independence
* Users can have different workspace roles in different workspaces.
* A user might be a [Workspace Admin](/langsmith/rbac#workspace-admin) in one workspace and a [Workspace Viewer](/langsmith/rbac#workspace-viewer) in another.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/organization-workspace-operations.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# View server logs for a trace
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/platform-logs
When viewing a trace that was generated by a run in LangSmith, you can access the associated server logs directly from the trace view.
Viewing server logs for a trace only works with the [Cloud SaaS](https://langchain-ai.github.io/langgra/langsmith/observability-concepts/deployment_options/#cloud-saas) and [fully self-hosted](https://langchain-ai.github.io/langgra/langsmith/observability-concepts/deployment_options/#self-hosted-control-plane) deployment options.
## Access server logs from trace view
In the trace view, use the **See Logs** button in the top right corner, next to the **Run in Studio** button.
Clicking this button will take you to the server logs view for the associated deployment in LangSmith.
## Server logs view
The server logs view displays logs from both:
* **Agent Server's own operational logs** - Internal server operations, API calls, and system events
* **User application logs** - Logs written in your graph with:
* Python: Use the `logging` or `structlog` libraries
* JavaScript: Use the re-exported Winston logger from `@langchain/langgraph-sdk/logging`:
```javascript theme={null}
import { getLogger } from "@langchain/langgraph-sdk/logging";
const logger = getLogger();
logger.info("Your log message");
```
## Filtering logs by trace ID
When you navigate from the trace view, the **Filters** box will automatically pre-fill with the Trace ID from the trace you just viewed.
This allows you to quickly filter the logs to see only those related to your specific trace execution.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/platform-logs.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Set up LangSmith
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/platform-setup
This section covers how to host and manage LangSmith infrastructure. You can set up LangSmith for [observability](/langsmith/observability), [evaluation](/langsmith/evaluation), and [prompt engineering](/langsmith/prompt-engineering), or use the full platform experience with [LangSmith Deployment](/langsmith/deployments) to also deploy and manage your applications through the UI.
**Start here if you're setting up or maintaining LangSmith infrastructure.**
If you want to deploy an agent application, the [Deployment section](/langsmith/deployments) covers application structure and deployment configuration.
## Choose how to set up LangSmith
You can deploy LangSmith in one of three modes:
* [**Cloud**](/langsmith/cloud): fully managed by LangChain
* [**Hybrid**](/langsmith/hybrid): LangChain manages the control plane; you host the data plane
* [**Self-hosted**](/langsmith/self-hosted): you manage the full stack within your infrastructure
Fully managed observability, evaluation, prompt engineering, and application deployment. Deploy from GitHub with automated CI/CD.
**(Enterprise)** Observability, evaluation, prompt engineering, and application deployment with your applications running in your infrastructure.
**(Enterprise)** Full control with observability, evaluation, and prompt engineering. Enable the full platform experience with LangSmith Deployment or run standalone servers.
### Comparison
Refer to the following table for a comparison:
| Feature | **Cloud** | **Hybrid** | **Self-Hosted** |
| ---------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------- | ----------------------------------------- |
| **Infrastructure location** | LangChain's cloud | Split: Control plane in LangChain cloud, data plane in your cloud | Your cloud |
| **Who manages updates** | LangChain | LangChain (control plane), You (data plane) | You |
| **Who manages CI/CD for your apps** | LangChain | You | You |
| **Can deploy applications?** | ✅ Yes | ✅ Yes | ✅ Yes (with LangSmith Deployment enabled) |
| **Observability data location** | LangChain cloud | LangChain cloud | Your cloud |
| **[Pricing](https://www.langchain.com/plans)** | Plus tier | Enterprise | Enterprise |
| **Best for** | Quick setup, managed infrastructure | Data residency requirements + managed control plane | Full control, data isolation |
You can [run an Agent Server locally for free](/langsmith/local-server) for testing and development.
### Related
* [Plans](https://langchain.com/pricing)
* [Pricing](https://www.langchain.com/plans)
* [Observability](/langsmith/observability)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/platform-setup.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Model providers
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/playground-model-providers
The LangSmith [prompt playground](/langsmith/prompt-engineering-concepts#prompt-playground) supports a wide range of model providers. You can select a provider, configure your preferred settings, and save these configurations to reuse across multiple prompts.
Use this page for a list of the available providers and their configuration options:
For details on creating and managing model configurations, refer to the [Configure prompt settings](/langsmith/managing-model-configurations) page.
## Amazon Bedrock
Before you use this model, ensure you have [AWS credentials or IAM role](https://docs.aws.amazon.com/bedrock/latest/userguide/security-iam.html).
### Available models
AWS Bedrock provides access to foundation models from multiple providers:
* **Anthropic:** Claude models.
* **Amazon:** Titan models.
* **Cohere:** Command models.
* **Meta:** Llama models.
* **Others:** Additional providers available based on region.
For the current list of available models, refer to the [AWS Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html).
### Configuration parameters
Parameters depend on the underlying model provider:
#### For Anthropic models
Uses Anthropic configuration (see Anthropic section above).
#### For Amazon Titan
| Parameter | Range | Description |
| --------------- | --------- | ----------------------- |
| **Temperature** | 0.0 - 1.0 | Response randomness |
| **Max Tokens** | 1+ | Maximum response length |
| **Top P** | 0.0 - 1.0 | Nucleus sampling |
#### AWS-specific settings
* **Region:** AWS region for model deployment.
* **IAM Role:** Use role-based authentication instead of keys.
### Tool calling
Depends on underlying model:
* **Anthropic models:** `auto`, `any`.
* **Cohere models:** `auto`.
## Anthropic
Before you use this model, ensure you have an [Anthropic API key](https://console.anthropic.com/settings/keys).
### Available models
Anthropic offers three tiers of models across their Claude generations:
* **Opus:** Highest intelligence and capability.
* **Sonnet:** Balanced performance and cost.
* **Haiku:** Fast and cost-effective.
Recent Claude models support extended thinking capabilities for showing reasoning processes.
For the current list of available models, refer to the [Anthropic documentation](https://docs.anthropic.com/claude/docs/models-overview).
### Configuration parameters
| Parameter | Range | Default | Description |
| --------------------- | --------- | -------- | -------------------------------------------------- |
| **Temperature** | 0.0 - 1.0 | Optional | Randomness control (uncheck to use model default) |
| **Max Output Tokens** | 1+ | 1024 | Maximum response length |
| **Top P** | 0.0 - 1.0 | Optional | Nucleus sampling (uncheck for model default) |
| **Top K** | 1+ | Optional | Limits to top K tokens (uncheck for model default) |
Temperature, Top P, and Top K are optional. When unchecked, Claude uses its internal defaults.
#### Extended Thinking
Available on supported Claude models. Enable the model to show reasoning before responding, similar to OpenAI's o-series.
| Parameter | Range | Description |
| ---------------------------- | ------ | --------------------------------------- |
| **Enable Extended Thinking** | Toggle | Show/hide thinking process |
| **Budget Tokens** | 1+ | Max tokens for thinking (default: 1024) |
When enabled, responses include:
1. A "thinking" section with the model's reasoning.
2. The final response.
#### Advanced options
* **Base URL:** Override API endpoint for custom deployments.
### Tool calling
* **Supported Tool Choices:** `auto`, `any` (requires at least one tool).
* **Parallel Execution:** No (sequential only).
## Azure OpenAI
Before you use this model, ensure you have [Azure OpenAI credentials](https://learn.microsoft.com/en-us/azure/ai-services/openai/quickstart) (endpoint + API key).
### Available models
Azure OpenAI provides the same model families as OpenAI:
* **GPT series:** General-purpose chat models.
* **o-series:** Reasoning-focused models.
* **Legacy models:** GPT-3.5 and GPT-4 variants.
Model availability varies by Azure region and requires deployment before use.
For the current list of available models, refer to the [Azure OpenAI documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models).
### Configuration parameters
Azure OpenAI supports the same parameters as OpenAI:
#### Standard parameters
| Parameter | Range | Description |
| --------------------- | ---------- | ------------------------------------------------------------------ |
| **Temperature** | 0.0 - 2.0 | Controls randomness. Lower = more focused, higher = more creative. |
| **Max Output Tokens** | 1+ | Maximum length of the response |
| **Top P** | 0.0 - 1.0 | Nucleus sampling threshold. Alternative to temperature. |
| **Presence Penalty** | -2.0 - 2.0 | Penalize new topics (positive) or encourage them (negative) |
| **Frequency Penalty** | -2.0 - 2.0 | Penalize repetition (positive) or allow it (negative) |
| **Seed** | Integer | For reproducible outputs |
#### Advanced parameters
**Reasoning Effort:** Available on reasoning-optimized models (o-series and newer GPT models).
**Service Tier:** Available on newer models.
**Other parameters:**
* **JSON Mode:** Force valid JSON responses.
* **Parallel Tool Calls:** Execute multiple tools concurrently.
#### Azure-specific features
* **Deployment Management:** Models must be deployed before use.
* **Regional Availability:** Choose Azure regions for data residency.
* **Content Filtering:** Built-in content moderation and safety features.
* **Managed Identity:** Azure AD authentication support.
* **Private Endpoints:** VNet integration for secure access.
### Tool calling
* **Supported Tool Choices:** `auto`, `required`, `none`, or specific tool name.
* **Parallel Execution:** Yes.
## DeepSeek
Before you use this model, ensure you have a [DeepSeek API key](https://platform.deepseek.com/api_keys).
### Available models
DeepSeek offers general-purpose models, reasoning-optimized models (R-series), and coding-specialized models.
For the current list of available models, refer to [DeepSeek's documentation](https://platform.deepseek.com/api-docs/).
### Configuration parameters
| Parameter | Range | Description |
| --------------------- | ---------- | ----------------------- |
| **Temperature** | 0.0 - 2.0 | Response randomness |
| **Max Tokens** | 1+ | Maximum response length |
| **Top P** | 0.0 - 1.0 | Nucleus sampling |
| **Presence Penalty** | -2.0 - 2.0 | |
| **Frequency Penalty** | -2.0 - 2.0 | |
## Fireworks
Before you use this model, ensure you have a [Fireworks API key](https://fireworks.ai/api-keys).
### Available models
Fireworks provides high-speed inference for popular open-source models and fine-tuned variants, including:
* **Llama:** Meta's Llama models in various sizes.
* **Mixtral:** Mistral's mixture-of-experts models.
* **Qwen:** Alibaba's multilingual models.
* **DeepSeek:** DeepSeek models.
* **Other open models:** Gemma, Phi, and more.
For the current list of available models, refer to [Fireworks' model documentation](https://docs.fireworks.ai/models).
### Configuration parameters
| Parameter | Range | Description |
| --------------- | --------- | ----------------------- |
| **Temperature** | 0.0 - 2.0 | Response randomness |
| **Max Tokens** | 1+ | Maximum response length |
| **Top P** | 0.0 - 1.0 | Nucleus sampling |
### Tool calling
* **Supported Tool Choices:** `auto`, `required`, `none`.
* **Parallel Execution:** Yes.
## Google Gemini
Before you use this model, ensure you have a [Google AI API key](https://aistudio.google.com/app/apikey).
### Available models
Google offers Gemini models in multiple tiers (Ultra, Pro, Flash) optimized for different use cases.
For the current list of available models, refer to [Google's Gemini documentation](https://ai.google.dev/models/gemini).
### Configuration parameters
| Parameter | Range | Description |
| --------------------- | --------- | ----------------------- |
| **Temperature** | 0.0 - 2.0 | Response randomness |
| **Max Output Tokens** | 1+ | Maximum response length |
| **Top P** | 0.0 - 1.0 | Nucleus sampling |
| **Top K** | 1+ | Top-k sampling |
### Tool calling
* **Supported Tool Choices:** `auto`, `any`, `none`.
* **Parallel Execution:** No.
## Google Vertex AI
Before you use this model, ensure you have [Google Cloud credentials](https://cloud.google.com/vertex-ai/docs/authentication).
### Available models
Google offers Gemini models in multiple tiers (Ultra, Pro, Flash) optimized for different use cases, plus other models available through Vertex AI.
For the current list of available models, refer to the [Vertex AI documentation](https://cloud.google.com/vertex-ai/docs/generative-ai/models).
### Configuration parameters
| Parameter | Range | Description |
| --------------------- | --------- | ----------------------- |
| **Temperature** | 0.0 - 2.0 | Response randomness |
| **Max Output Tokens** | 1+ | Maximum response length |
| **Top P** | 0.0 - 1.0 | Nucleus sampling |
| **Top K** | 1+ | Top-k sampling |
#### Advanced options
* **Region Selection:** Deploy in specific Google Cloud regions.
* **Safety Settings:** Configure content filtering thresholds.
### Tool calling
* **Supported Tool Choices:** `auto`, `any`, `none`.
* **Parallel Execution:** No.
## Groq
Before you use this model, ensure you have a [Groq API key](https://console.groq.com/keys).
### Available models
Groq provides high-speed inference for popular open-source models including Llama, Mixtral, and Gemma variants.
For the current list of available models, refer to [Groq's model documentation](https://console.groq.com/docs/models).
### Configuration parameters
| Parameter | Range | Description |
| --------------- | --------- | ----------------------- |
| **Temperature** | 0.0 - 2.0 | Response randomness |
| **Max Tokens** | 1+ | Maximum response length |
### Tool calling
* **Supported Tool Choices:** `auto`, `required`, `none`.
* **Parallel Execution:** Yes.
## Mistral AI
Before you use this model, ensure you have a [Mistral AI API key](https://console.mistral.ai/api-keys/).
### Available models
Mistral offers models in multiple tiers (Large, Medium, Small) optimized for different performance and cost requirements.
For the current list of available models, refer to [Mistral's documentation](https://docs.mistral.ai/platform/endpoints/).
### Configuration parameters
| Parameter | Range | Description |
| --------------- | --------- | ----------------------- |
| **Temperature** | 0.0 - 1.0 | Response randomness |
| **Max Tokens** | 1+ | Maximum response length |
| **Top P** | 0.0 - 1.0 | Nucleus sampling |
### Tool calling
* **Supported Tool Choices:** `auto`, `any`, `none`.
* **Parallel Execution:** No.
## OpenAI
Before you use this model, ensure you have an [OpenAI API key](https://platform.openai.com/api-keys) or [Azure OpenAI credentials](https://learn.microsoft.com/en-us/azure/ai-services/openai/quickstart).
### Available models
OpenAI offers several model families with different capabilities and price points:
* **GPT series:** General-purpose chat models with various size/capability tiers.
* **o-series:** Reasoning-focused models optimized for complex problem-solving.
* **Legacy models:** Older GPT-3.5 and GPT-4 variants.
For the current list of available models, refer to the [OpenAI documentation](https://platform.openai.com/docs/models).
### Configuration parameters
Standard:
| Parameter | Range | Description |
| --------------------- | ---------- | ------------------------------------------------------------------ |
| **Temperature** | 0.0 - 2.0 | Controls randomness. Lower = more focused, higher = more creative. |
| **Max Output Tokens** | 1+ | Maximum length of the response |
| **Top P** | 0.0 - 1.0 | Nucleus sampling threshold. Alternative to temperature. |
| **Presence Penalty** | -2.0 - 2.0 | Penalize new topics (positive) or encourage them (negative) |
| **Frequency Penalty** | -2.0 - 2.0 | Penalize repetition (positive) or allow it (negative) |
| **Seed** | Integer | For reproducible outputs |
Advanced:
**Reasoning Effort**: Available on reasoning-optimized models (o-series and newer GPT models).
Controls reasoning depth before responding. Higher effort = better quality for complex tasks, longer latency.
| Value | Description |
| --------- | -------------------------------------------- |
| `none` | Disables reasoning (standard chat behavior) |
| `minimal` | Minimal reasoning |
| `low` | Light reasoning |
| `medium` | Moderate reasoning (default) |
| `high` | Deep reasoning |
| `xhigh` | Extra deep reasoning (if supported by model) |
When reasoning\_effort is active (not `none`), temperature, top\_p, and penalties are automatically disabled.
**Service Tier**: Available on newer models.
Controls request priority and processing allocation.
| Value | Description |
| ---------- | ---------------------------------------------------- |
| `auto` | System decides based on load (default) |
| `default` | Standard processing queue |
| `flex` | Lower cost, variable latency (if supported by model) |
| `priority` | High-priority queue, lower latency, higher cost |
**Other parameters:**
* **JSON Mode:** Force valid JSON responses.
* **Responses API:** Improved streaming (default: enabled).
* **Parallel Tool Calls:** Execute multiple tools concurrently.
### Tool calling
* **Supported Tool Choices:** `auto`, `required`, `none`, or specific tool name
* **Parallel Execution:** Yes
## OpenAI Compatible Endpoint
Authentication varies by endpoint (often API key or none).
### Configuration
**Required:**
* **Base URL:** Your endpoint URL (e.g., `https://your-endpoint.com/v1`).
* **Model Name:** Your model identifier.
Works with any framework or service that implements the OpenAI-compatible API format, including:
* Self-hosted open-source inference servers
* Model routing proxies
* Custom model endpoints
### Configuration parameters
All OpenAI-compatible parameters:
| Parameter | Range | Description |
| --------------------- | ---------- | ----------------------- |
| **Temperature** | 0.0 - 2.0 | Response randomness |
| **Max Tokens** | 1+ | Maximum response length |
| **Top P** | 0.0 - 1.0 | Nucleus sampling |
| **Frequency Penalty** | -2.0 - 2.0 | Reduce repetition |
| **Presence Penalty** | -2.0 - 2.0 | Encourage new topics |
**Advanced:**
* **JSON Mode:** If endpoint supports it.
* **Streaming:** If endpoint supports it.
* **Function Calling:** If endpoint implements OpenAI format.
### Tool calling
* **Supported Tool Choices:** `auto`, `required`, `none` (if endpoint supports).
* **Parallel Execution:** Yes (if endpoint supports).
### Example endpoints
**Local Ollama:**
```
Base URL: http://localhost:11434/v1
Model: llama3.1
```
**vLLM Server:**
```
Base URL: https://your-server.com/v1
Model: mistral-7b-instruct
```
**LiteLLM Proxy:**
```
Base URL: https://litellm.example.com
Model: gpt-4 (routes to configured backend)
```
## XAI
Before you use this model, ensure you have an [xAI API key](https://console.x.ai/).
### Available models
xAI offers Grok models in multiple sizes for different use cases.
For the current list of available models, refer to [xAI's documentation](https://docs.x.ai/docs).
### Configuration parameters
Standard OpenAI-compatible parameters:
| Parameter | Range | Description |
| --------------------- | --------- | -------------------------- |
| **Temperature** | 0.0 - 2.0 | Response randomness |
| **Max Tokens** | 1+ | Maximum response length |
| **Top P** | 0.0 - 1.0 | Nucleus sampling |
| **Presence Penalty** | 0 - 2.0 | Hidden on reasoning models |
| **Frequency Penalty** | 0 - 2.0 | Hidden on reasoning models |
### Tool calling
* **Supported Tool Choices:** OpenAI-compatible.
* **Parallel Execution:** Yes (if supported).
## Common Configuration Across All Providers
### Extra Parameters
All providers support a **JSON editor for extra parameters** not exposed in the UI:
```json theme={null}
{
"logprobs": true,
"top_logprobs": 5,
"custom_parameter": "value"
}
```
**Use cases:**
* Provider-specific beta features
* Advanced parameters not yet in UI
* Custom metadata for tracking
**Limitation:** Cannot override parameters already in the UI (e.g., can't set temperature here if it's set above)
### Rate Limiting
**Requests Per Second (RPS)** - Available for all providers when running over datasets:
* **Range:** 0 - 500 RPS
* **Purpose:** Respect API rate limits, control costs
* **Default:** Varies by provider
Set this when running experiments or evaluations to avoid hitting rate limits.
## Next steps
Learn how to create and manage model configurations in the playground.
Get started building prompts with your chosen model provider.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/playground-model-providers.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith Polly
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/polly
**Polly is in beta.** Your [feedback](https://forum.langchain.com) on Polly is invaluable as the team refines its capabilities.
**LangSmith Polly** is an AI assistant embedded directly in your LangSmith [workspace](/langsmith/administration-overview#workspaces) to help you analyze and understand your application data.
Polly helps you gain insight from your traces, conversation threads, and prompts without having to dig through data manually. By asking natural language questions, you can quickly understand agent performance, debug issues, and analyze user sentiment.
Polly appears in the right-hand bottom corner of the following locations within [LangSmith UI](https://smith.langchain.com):
**Observability & Debugging:**
* [Trace pages](#trace-pages): Analyze individual runs and execution traces.
* [Thread views](#thread-views): Understand conversation threads and user interactions.
**Prompt Engineering:**
* [Prompt Playground](#prompt-playground): Edit and optimize prompts.
* [Prompt Hub pages](#prompt-hub-pages): Explore and understand shared prompts.
**Evaluation & Testing:**
* [Dataset Experiments](#dataset-experiments): Analyze experiment results and compare runs.
* [Dataset Examples](#dataset-examples): Browse and understand dataset structure.
* [Annotation Queues](#annotation-queues): Review runs and make informed annotation decisions.
## Get started
Before you start using Polly, you need to add an API key for the model you're using:
In the [LangSmith UI](https://smith.langchain.com), ensure that your API key is set as a [workspace secret](/langsmith/administration-overview#workspace-secrets).
1. Navigate to **Settings** and then move to the **Secrets** tab.
2. Select **Add secret** and enter the key environment variable (e.g.,`OPENAI_API_KEY` or `ANTHROPIC_API_KEY`) and your API key as the **Value**.
3. Select **Save secret**.
When adding workspace secrets in the LangSmith UI, make sure the secret keys match the environment variable names expected by your model provider.
## Observability
### Trace pages
On an individual [trace](/langsmith/observability-concepts#traces), Polly analyzes the [run](/langsmith/observability-concepts#runs) data and execution trajectory. Polly examines the full trace context, including [run metadata](/langsmith/observability-concepts#metadata), inputs, outputs, intermediate steps, and configuration to help you understand what happened and identify areas for improvement.
**Example questions:**
* "Is there anything that the agent could have done better here?"
* "Why did this run fail?"
* "What took the most time in this trace?"
* "Summarize what happened in this trace"
### Thread views
Under the **Threads** tab, Polly analyzes conversation [threads](/langsmith/observability-concepts#threads) to help you understand user sentiment, conversation outcomes, and interaction patterns. Use Polly to identify user pain points and understand whether issues were resolved.
**Example questions:**
* "Did the user seem frustrated?"
* "What issues is the user experiencing?"
* "Was the user's problem solved?"
* "What was the main topic of this thread?"
## Prompt engineering
### Prompt Playground
In the [Playground](/langsmith/prompt-engineering-concepts#prompt-playground), Polly helps you edit and optimize your [prompts](/langsmith/prompt-engineering-concepts#prompt-in-langsmith). Use automated options like **Optimize prompt**, **Generate a tool**, or **Generate an output schema**, or give Polly custom instructions for editing your prompt.
**Example questions:**
* "Make it respond in Italian"
* "Add more context about the user's role"
* "Make the tone more professional"
* "Simplify the instructions"
### Prompt Hub pages
When viewing a prompt in the [LangSmith Hub](/langsmith/prompt-engineering-concepts#langsmith-hub), Polly helps you understand the prompt's structure, messages, tools, and configuration. This is useful for exploring and learning from shared prompts.
**Example questions:**
* "What does this prompt do?"
* "What tools does this prompt use?"
* "Explain the structure of this prompt"
* "What are the key instructions in this prompt?"
## Evaluation
### Dataset Experiments
On the **Datasets** page under the **Experiments** tab, Polly analyzes experiment results and helps you compare runs across different experiments. Polly can identify patterns, summarize performance, and help you understand which approaches work best.
**Example questions:**
* "Which experiment performed best?"
* "What are the main differences between these runs?"
* "Summarize the results of this experiment"
* "What patterns do you see in the failures?"
### Dataset Examples
On the **Datasets** page under the **Examples** tab, Polly helps you understand your dataset structure, browse examples, and identify data patterns. This is useful for understanding what data you're working with and preparing datasets for experiments.
**Example questions:**
* "What type of data is in this dataset?"
* "Show me examples with errors"
* "What patterns do you see in the inputs?"
* "How many examples are in this dataset?"
### Annotation Queues
In **Annotation Queues**, Polly helps you analyze runs before making annotation decisions. Whether you're reviewing runs individually or comparing them pairwise, Polly provides insights into run behavior, errors, and execution patterns to inform your scoring.
**Example questions:**
* "What went wrong in this run?"
* "Summarize what happened in this run"
* "Compare these two runs"
* "What should I consider when scoring this?"
## What's next
Learn more about the features that Polly helps you explore:
Learn more about tracing and monitoring your LLM applications
Understand how threads work in LangSmith
Create and iterate on prompts in the playground
Evaluate and test your applications systematically
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/polly.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith Polly
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/polly-evaluation
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/polly-evaluation.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith Polly
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/polly-observability
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/polly-observability.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith Polly
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/polly-prompt-engineering
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/polly-prompt-engineering.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to use prebuilt evaluators
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/prebuilt-evaluators
LangSmith integrates with the open-source openevals package to provide a suite of prebuilt evaluators that you can use as starting points for evaluation.
This how-to guide will demonstrate how to set up and run one type of evaluator (LLM-as-a-judge). For a complete list of prebuilt evaluators with usage examples, refer to the [openevals](https://github.com/langchain-ai/openevals) and [agentevals](https://github.com/langchain-ai/agentevals) repos.
## Setup
You'll need to install the `openevals` package to use the pre-built LLM-as-a-judge evaluator.
```bash Python theme={null}
pip install -U openevals
```
```bash TypeScript theme={null}
yarn add openevals @langchain/core
```
You'll also need to set your OpenAI API key as an environment variable, though you can choose different providers too:
```bash theme={null}
export OPENAI_API_KEY="your_openai_api_key"
```
We'll also use LangSmith's [pytest](/langsmith/pytest) integration for Python and [Vitest/Jest](/langsmith/vitest-jest) for TypeScript to run our evals. `openevals` also integrates seamlessly with the [`evaluate`](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._runner.evaluate) method. See the [appropriate guides](/langsmith/pytest) for setup instructions.
## Running an evaluator
The general flow is simple: import the evaluator or factory function from `openevals`, then run it within your test file with inputs, outputs, and reference outputs. LangSmith will automatically log the evaluator's results as feedback.
Note that not all evaluators will require each parameter (the exact match evaluator only requires outputs and reference outputs, for example). Additionally, if your LLM-as-a-judge prompt requires additional variables, passing them in as kwargs will format them into the prompt.
Set up your test file like this:
```python Python theme={null}
import pytest
from langsmith import testing as t
from openevals.llm import create_llm_as_judge
from openevals.prompts import CORRECTNESS_PROMPT
correctness_evaluator = create_llm_as_judge(
prompt=CORRECTNESS_PROMPT,
feedback_key="correctness",
model="openai:o3-mini",
)
# Mock standin for your application
def my_llm_app(inputs: dict) -> str:
return "Doodads have increased in price by 10% in the past year."
@pytest.mark.langsmith
def test_correctness():
inputs = "How much has the price of doodads changed in the past year?"
reference_outputs = "The price of doodads has decreased by 50% in the past year."
outputs = my_llm_app(inputs)
t.log_inputs({"question": inputs})
t.log_outputs({"answer": outputs})
t.log_reference_outputs({"answer": reference_outputs})
correctness_evaluator(
inputs=inputs,
outputs=outputs,
reference_outputs=reference_outputs
)
```
```typescript TypeScript theme={null}
import * as ls from "langsmith/vitest";
// import * as ls from "langsmith/jest";
import { createLLMAsJudge, CORRECTNESS_PROMPT } from "openevals";
const correctnessEvaluator = createLLMAsJudge({
prompt: CORRECTNESS_PROMPT,
feedbackKey: "correctness",
model: "openai:o3-mini",
});
// Mock standin for your application
const myLLMApp = async (_inputs: Record) => {
return "Doodads have increased in price by 10% in the past year.";
};
ls.describe("Correctness", () => {
ls.test("incorrect answer", {
inputs: {
question: "How much has the price of doodads changed in the past year?"
},
referenceOutputs: {
answer: "The price of doodads has decreased by 50% in the past year."
}
}, async ({ inputs, referenceOutputs }) => {
const outputs = await myLLMApp(inputs);
ls.logOutputs({ answer: outputs });
await correctnessEvaluator({
inputs,
outputs,
referenceOutputs,
});
});
});
```
The `feedback_key`/`feedbackKey` parameter will be used as the name of the feedback in your experiment.
Running the eval in your terminal will result in something like the following:
You can also pass prebuilt evaluators directly into the `evaluate` method if you have already created a dataset in LangSmith. If using Python, this requires `langsmith>=0.3.11`:
```python Python theme={null}
from langsmith import Client
from openevals.llm import create_llm_as_judge
from openevals.prompts import CONCISENESS_PROMPT
client = Client()
conciseness_evaluator = create_llm_as_judge(
prompt=CONCISENESS_PROMPT,
feedback_key="conciseness",
model="openai:o3-mini",
)
experiment_results = client.evaluate(
# This is a dummy target function, replace with your actual LLM-based system
lambda inputs: "What color is the sky?",
data="Sample dataset",
evaluators=[
conciseness_evaluator
]
)
```
```typescript TypeScript theme={null}
import { evaluate } from "langsmith/evaluation";
import { createLLMAsJudge, CONCISENESS_PROMPT } from "openevals";
const concisenessEvaluator = createLLMAsJudge({
prompt: CONCISENESS_PROMPT,
feedbackKey: "conciseness",
model: "openai:o3-mini",
});
await evaluate((inputs) => "What color is the sky?", {
data: datasetName,
evaluators: [concisenessEvaluator],
});
```
For a complete list of available evaluators, see the [openevals](https://github.com/langchain-ai/openevals) and [agentevals](https://github.com/langchain-ai/agentevals) repos.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/prebuilt-evaluators.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Frequently Asked Questions
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/pricing-faq
View our [Pricing](https://www.langchain.com/pricing) page for more information on our pricing plans.
## I've been using LangSmith since before pricing took effect for new users. when will pricing go into effect for my account?
If you've been using LangSmith already, your usage will be billable starting in July 2024. At that point if you want to add seats or use more than the monthly allotment of free traces, you will need to add a credit card to LangSmith or contact sales. If you are interested in the Enterprise plan with higher rate limits and special deployment options, you can learn more or make a purchase by [contacting our sales team](https://www.langchain.com/contact-sales).
## Which plan is right for me?
If you're an individual developer, the Developer plan is a great choice for small projects.
For teams that want to collaborate in LangSmith, check out the Plus plan. **If you are an early-stage startup building an AI application**, you may be eligible for our Startup plan with discounted prices and a generous free monthly trace allotment. Please reach out via our [Startup Contact Form](https://airtable.com/app8ZrGLtHAtFVO1o/pagfLAmdTz4ep7TGu/form) for more details.
If you need more advanced administration, authentication and authorization, deployment options, support, or annual invoicing, the Enterprise plan is right for you. Please reach out via our [Sales Contact Form](https://www.langchain.com/contact-sales) for more details.
## What is a seat?
A seat is a distinct user inside your organization. We consider the total number of users (including invited users) to determine the number of seats to bill.
## What is a trace?
A trace is one complete invocation of your application chain or agent, evaluator run, or playground run. Here is an [example](https://smith.langchain.com/public/17c24270-9f74-47e7-b70c-d508afc448fa/r) of a single trace.
## What is a Deployment Run?
A Deployment Run is one end-to-end invocation of a LangGraph agent deployed via LangSmith Deployment. Nodes and subgraphs within a single agent execution are not charged separately. Calls to other LangGraph agents (through RemoteGraph or the LangGraph SDK or the API directly) are charged separately, to the deployment that hosts the agent being called. An interrupt for human-in-the-loop creates a separate Deployment Run when resuming.
Deployment Runs are billed at \$0.005 each. For high-volume usage, please [contact our sales team](https://www.langchain.com/contact-sales) to discuss custom pricing options.
## What is an ingested event?
An ingested event is any distinct, trace-related data sent to LangSmith. This includes:
* Inputs, outputs and metadata sent at the start of a run step within a trace
* Inputs, outputs and metadata sent at the end of a run step within a trace
* Feedback on run steps or traces
## I've hit my rate or usage limits. what can I do?
When you first sign up for a LangSmith account, you get a Personal organization that is limited to 5000 monthly traces. To continue sending traces after reaching this limit, upgrade to the Developer or Plus plans by adding a credit card. Head to [Plans and Billing](https://smith.langchain.com/settings/payments) to upgrade.
Similarly, if you've hit the rate limits on your current plan, you can upgrade to a higher plan to get higher limits, or contact support via [support.langchain.com](https://support.langchain.com) with questions.
## I have a developer account, can I upgrade my account to the plus or enterprise plan?
Yes, Developer plan users can easily upgrade to the Plus plan on the [Plans and Billing](https://smith.langchain.com/settings/payments) page. For the Enterprise plan, please [contact our sales team](https://www.langchain.com/contact-sales) to discuss your needs.
## How does billing work?
**Seats**
Seats are billed monthly on the first of the month. Additional seats purchased mid-month are pro-rated and billed within one day of the purchase. Seats removed mid-month will not be credited.
**Traces**
As long as you have a card on file in your account, we'll service your traces and bill you on the first of the month for traces that you submitted in the previous month. You will be able to set usage limits if you so choose to limit the maximum charges you could incur in any given month.
## Can I limit how much I spend on tracing?
You can set limits on the number of traces that can be sent to LangSmith per month on the [Usage configuration](https://smith.langchain.com/settings/payments) page.
While we do show you the dollar value of your usage limit for convenience, this limit evaluated in terms of number of traces instead of dollar amount. For example, if you are approved for our startup plan tier where you are given a generous allotment of free traces, your usage limit will not automatically change.
You are not currently able to set a spend limit in the product.
## How can I track my usage so far this month?
Under the Settings section for your Organization you will see subsection for **Usage**. There, you will be able to see a graph of the daily number of billable LangSmith traces from the last 30, 60, or 90 days. Note that this data is delayed by 1-2 hours and so may trail your actual number of runs slightly for the current day.
## I have a question about my bill...
Customers on the Developer and Plus plan tiers should contact support via [support.langchain.com](https://support.langchain.com). Customers on the Enterprise plan should contact their sales representative directly.
Enterprise plan customers are billed annually by invoice.
## What can I expect from support?
On the Developer plan, community-based support is available on [LangChain community Slack](https://www.langchain.com/join-community).
On the Plus plan, you will also receive preferential support via [support.langchain.com](https://support.langchain.com) for LangSmith-related questions only and we'll do our best to respond within the next business day.
On the Enterprise plan, you'll get white-glove support with a Slack channel, a dedicated customer success manager, and monthly check-ins to go over LangSmith and LangChain questions. We can help with anything from debugging, agent and RAG techniques, evaluation approaches, and cognitive architecture reviews. If you purchase the add-on to run LangSmith in your environment, we'll also support deployments and new releases with our infra engineering team on-call.
## Where is my data stored?
You may choose to sign up in either the US or EU region. See the [cloud architecture reference](/langsmith/cloud#cloud-architecture-and-scalability) for more details. If you're on the Enterprise plan, we can deliver LangSmith to run on your kubernetes cluster in AWS, GCP, or Azure so that data never leaves your environment.
## Which security frameworks is LangSmith compliant with?
We are SOC 2 Type II, GDPR, and HIPAA compliant.
You can request more information about our security policies and posture at [trust.langchain.com](https://trust.langchain.com). Please note we only enter into BAAs with customers on our Enterprise plan.
## Will you train on the data that I send LangSmith?
We will not train on your data, and you own all rights to your data. See [LangSmith Terms of Service](https://langchain.dev/terms-of-service) for more information.
## What is an Agent Builder run?
An Agent Builder Run is a complete invocation of an Agent Builder agent, initiated by the receipt of a message or event, whether triggered automatically or sent manually. An Agent Builder Run includes any message delivered to the agent through configured triggers (including third-party integrations or other connected services) as well as messages sent directly through the Agent Builder user interface (for example, via the chat interface). An Agent Builder Run is counted if the invocation succeeds or is interrupted. Resuming execution after a human-in-the-loop interruption constitutes a separate Agent Builder Run. An execution failure does not count as an Agent Builder Run.
## Are model costs included with Agent Builder pricing?
No. Model usage is billed separately by your model provider. Agent Builder lets you choose the model you want (for example, Anthropic, OpenAI, Gemini, or another provider) and connect it using your provider API key. For more information on configuring models, see the [Agent Builder setup](/langsmith/agent-builder-setup#required-model-key) page.
## Are agent tools included with Agent Builder pricing?
Agent Builder includes built-in tools and supports connecting third-party tools via a remote MCP server. Third-party tools typically require you to authenticate with your account, and any fees or usage charges are billed by the third-party provider. For more information on setting up tool keys, see the [Agent Builder setup](/langsmith/agent-builder-setup#optional-tool-keys) page.
## Are Agent Builder runs automatically traced in LangSmith, and do they count toward billing?
Yes. Every Agent Builder run is automatically traced in LangSmith. These traces are attributed to the user's account and count toward usage-based billing under the customer's LangSmith plan.
## How do I disable Agent Builder?
If you are on a Self-Hosted LangSmith instance, you can disable Agent Builder by setting the following to `false` in your `values.yaml`:
```yaml theme={null}
backend:
agentBootstrap:
enabled: false
config:
agentBuilder:
enabled: false
agentBuilderToolServer:
enabled: false
agentBuilderTriggerServer:
enabled: false
```
If you are on cloud LangSmith, [reach out to our Support team](https://support.langchain.com/) to disable Agent Builder.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/pricing-faq.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Pricing plans
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/pricing-plans
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/pricing-plans.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to sync prompts with GitHub
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/prompt-commit
LangSmith provides a collaborative interface to create, test, and iterate on prompts.
While you can [dynamically fetch prompts](/langsmith/manage-prompts-programmatically#pull-a-prompt) from LangSmith into your application at runtime, you may prefer to sync prompts with your own database or version control system. To support this workflow, LangSmith allows you to receive notifications of prompt updates via webhooks.
**Why sync prompts with GitHub?**
* **Version Control:** Keep your prompts versioned alongside your application code in a familiar system.
* **CI/CD Integration:** Trigger automated staging or production deployments when critical prompts change.
## Prerequisites
Before we begin, ensure you have the following set up:
1. **GitHub Account:** A standard GitHub account.
2. **GitHub Repository:** Create a new (or choose an existing) repository where your LangSmith prompt manifests will be stored. This could be the same repository as your application code or a dedicated one for prompts.
3. **GitHub Personal Access Token (PAT):**
* LangSmith webhooks don't directly interact with GitHub—they call an intermediary server that *you* create.
* This server requires a GitHub PAT to authenticate and make commits to your repository.
* Must include the `repo` scope (`public_repo` is sufficient for public repositories).
* Go to **GitHub > Settings > Developer settings > Personal access tokens > Tokens (classic)**.
* Click **Generate new token (classic)**.
* Name it (e.g., "LangSmith Prompt Sync"), set an expiration, and select the required scopes.
* Click **Generate token** and **copy it immediately** — it won't be shown again.
* Store the token securely and provide it as an environment variable to your server.
## Understanding LangSmith "Prompt commits" and webhooks
In LangSmith, when you save changes to a prompt, you're essentially creating a new version or a "Prompt Commit." These commits are what can trigger webhooks.
The webhook will send a JSON payload containing the new **prompt manifest**.
```json theme={null}
{
"prompt_id": "f33dcb51-eb17-47a5-83ca-64ac8a027a29",
"prompt_name": "My Prompt",
"commit_hash": "commit_hash_1234567890",
"created_at": "2021-01-01T00:00:00Z",
"created_by": "Jane Doe",
"manifest": {
"lc": 1,
"type": "constructor",
"id": ["langchain", "schema", "runnable", "RunnableSequence"],
"kwargs": {
"first": {
"lc": 1,
"type": "constructor",
"id": ["langchain", "prompts", "chat", "ChatPromptTemplate"],
"kwargs": {
"messages": [
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"chat",
"SystemMessagePromptTemplate"
],
"kwargs": {
"prompt": {
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate"
],
"kwargs": {
"input_variables": [],
"template_format": "mustache",
"template": "You are a chatbot."
}
}
}
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"chat",
"HumanMessagePromptTemplate"
],
"kwargs": {
"prompt": {
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate"
],
"kwargs": {
"input_variables": ["question"],
"template_format": "mustache",
"template": "{{question}}"
}
}
}
}
],
"input_variables": ["question"]
}
},
"last": {
"lc": 1,
"type": "constructor",
"id": ["langchain", "schema", "runnable", "RunnableBinding"],
"kwargs": {
"bound": {
"lc": 1,
"type": "constructor",
"id": ["langchain", "chat_models", "openai", "ChatOpenAI"],
"kwargs": {
"temperature": 1,
"top_p": 1,
"presence_penalty": 0,
"frequency_penalty": 0,
"model": "gpt-4.1-mini",
"extra_headers": {},
"openai_api_key": {
"id": ["OPENAI_API_KEY"],
"lc": 1,
"type": "secret"
}
}
},
"kwargs": {}
}
}
}
}
}
```
It's important to understand that LangSmith webhooks for prompt commits are generally triggered at the **workspace level**. This means if *any* prompt within your LangSmith workspace is modified and a "prompt commit" is saved, the webhook will fire and send the updated manifest of the prompt. The payloads are identifiable by prompt id. Your receiving server should be designed with this in mind.
## Implementing a FastAPI server for webhook reception
To effectively process webhook notifications from LangSmith when prompts are updated, an intermediary server application is necessary. This server will act as the receiver for HTTP POST requests sent by LangSmith. For demonstration purposes in this guide, we will outline the creation of a simple FastAPI application to fulfill this role.
This publicly accessible server will be responsible for:
1. **Receiving Webhook Requests:** Listening for incoming HTTP POST requests.
2. **Parsing Payloads:** Extracting and interpreting the JSON-formatted prompt manifest from the request body.
3. **Committing to GitHub:** Programmatically creating a new commit in your specified GitHub repository, containing the updated prompt manifest. This ensures your prompts remain version-controlled and synchronized with changes made in LangSmith.
For deployment, platforms like [Render.com](https://render.com/) (offering a suitable free tier), Vercel, Fly.io, or other cloud providers (AWS, GCP, Azure) can be utilized to host the FastAPI application and obtain a public URL.
The server's core functionality will include an endpoint for webhook reception, logic for parsing the manifest, and integration with the GitHub API (using a Personal Access Token for authentication) to manage commits.
`main.py`
This server will listen for incoming webhooks from LangSmith and commit the received prompt manifest to your GitHub repository.
```python theme={null}
import base64
import json
import uuid
from typing import Any, Dict
import httpx
from fastapi import FastAPI, HTTPException, Body
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
# --- Configuration ---
class AppConfig(BaseSettings):
"""
Application configuration model.
Loads settings from environment variables.
"""
GITHUB_TOKEN: str
GITHUB_REPO_OWNER: str
GITHUB_REPO_NAME: str
GITHUB_FILE_PATH: str = "prompt_manifest.json"
GITHUB_BRANCH: str = "main"
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding='utf-8',
extra='ignore'
)
settings = AppConfig()
# --- Pydantic Models ---
class WebhookPayload(BaseModel):
"""
Defines the expected structure of the incoming webhook payload.
"""
prompt_id: UUID = Field(
...,
description="The unique identifier for the prompt."
)
prompt_name: str = Field(
...,
description="The name/title of the prompt."
)
commit_hash: str = Field(
...,
description="An identifier for the commit event that triggered the webhook."
)
created_at: str = Field(
...,
description="Timestamp indicating when the event was created (ISO format preferred)."
)
created_by: str = Field(
...,
description="The name of the user who created the event."
)
manifest: Dict[str, Any] = Field(
...,
description="The main content or configuration data to be committed to GitHub."
)
# --- GitHub Helper Function ---
async def commit_manifest_to_github(payload: WebhookPayload) -> Dict[str, Any]:
"""
Helper function to commit the manifest directly to the configured branch.
"""
github_api_base_url = "https://api.github.com"
repo_file_url = (
f"{github_api_base_url}/repos/{settings.GITHUB_REPO_OWNER}/"
f"{settings.GITHUB_REPO_NAME}/contents/{settings.GITHUB_FILE_PATH}"
)
headers = {
"Authorization": f"Bearer {settings.GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json",
"X-GitHub-Api-Version": "2022-11-28",
}
manifest_json_string = json.dumps(payload.manifest, indent=2)
content_base64 = base64.b64encode(manifest_json_string.encode('utf-8')).decode('utf-8')
commit_message = f"feat: Update {settings.GITHUB_FILE_PATH} via webhook - commit {payload.commit_hash}"
data_to_commit = {
"message": commit_message,
"content": content_base64,
"branch": settings.GITHUB_BRANCH,
}
async with httpx.AsyncClient() as client:
current_file_sha = None
try:
params_get = {"ref": settings.GITHUB_BRANCH}
response_get = await client.get(repo_file_url, headers=headers, params=params_get)
if response_get.status_code == 200:
current_file_sha = response_get.json().get("sha")
elif response_get.status_code != 404: # If not 404 (not found), it's an unexpected error
response_get.raise_for_status()
except httpx.HTTPStatusError as e:
error_detail = f"GitHub API error (GET file SHA): {e.response.status_code} - {e.response.text}"
print(f"[ERROR] {error_detail}")
raise HTTPException(status_code=e.response.status_code, detail=error_detail)
except httpx.RequestError as e:
error_detail = f"Network error connecting to GitHub (GET file SHA): {str(e)}"
print(f"[ERROR] {error_detail}")
raise HTTPException(status_code=503, detail=error_detail)
if current_file_sha:
data_to_commit["sha"] = current_file_sha
try:
response_put = await client.put(repo_file_url, headers=headers, json=data_to_commit)
response_put.raise_for_status()
return response_put.json()
except httpx.HTTPStatusError as e:
error_detail = f"GitHub API error (PUT content): {e.response.status_code} - {e.response.text}"
if e.response.status_code == 409: # Conflict
error_detail = (
f"GitHub API conflict (PUT content): {e.response.text}. "
"This might be due to an outdated SHA or branch protection rules."
)
elif e.response.status_code == 422: # Unprocessable Entity
error_detail = (
f"GitHub API Unprocessable Entity (PUT content): {e.response.text}. "
f"Ensure the branch '{settings.GITHUB_BRANCH}' exists and the payload is correctly formatted."
)
print(f"[ERROR] {error_detail}")
raise HTTPException(status_code=e.response.status_code, detail=error_detail)
except httpx.RequestError as e:
error_detail = f"Network error connecting to GitHub (PUT content): {str(e)}"
print(f"[ERROR] {error_detail}")
raise HTTPException(status_code=503, detail=error_detail)
# --- FastAPI Application ---
app = FastAPI(
title="Minimal Webhook to GitHub Commit Service",
description="Receives a webhook and commits its 'manifest' part directly to a GitHub repository.",
version="0.1.0",
)
@app.post("/webhook/commit", status_code=201, tags=["GitHub Webhooks"])
async def handle_webhook_direct_commit(payload: WebhookPayload = Body(...)):
"""
Webhook endpoint to receive events and commit DIRECTLY to the configured branch.
"""
try:
github_response = await commit_manifest_to_github(payload)
return {
"message": "Webhook received and manifest committed directly to GitHub successfully.",
"github_commit_details": github_response.get("commit", {}),
"github_content_details": github_response.get("content", {})
}
except HTTPException:
raise # Re-raise if it's an HTTPException from the helper
except Exception as e:
error_message = f"An unexpected error occurred: {str(e)}"
print(f"[ERROR] {error_message}")
raise HTTPException(status_code=500, detail="An internal server error occurred.")
@app.get("/health", status_code=200, tags=["Health"])
async def health_check():
"""
A simple health check endpoint.
"""
return {"status": "ok", "message": "Service is running."}
# To run this server (save as main.py):
# 1. Install dependencies: pip install fastapi uvicorn pydantic pydantic-settings httpx python-dotenv
# 2. Create a .env file with your GitHub token and repo details.
# 3. Run with Uvicorn: uvicorn main:app --reload
# 4. Deploy to a public platform like Render.com.
```
**Key aspects of this server:**
* **Configuration (`.env`):** It expects a `.env` file with your `GITHUB_TOKEN`, `GITHUB_REPO_OWNER`, and `GITHUB_REPO_NAME`. You can also customize `GITHUB_FILE_PATH` (default: `LangSmith_prompt_manifest.json`) and `GITHUB_BRANCH` (default: `main`).
* **GitHub Interaction:** The `commit_manifest_to_github` function handles the logic of fetching the current file's SHA (to update it) and then committing the new manifest content.
* **Webhook Endpoint (`/webhook/commit`):** This is the URL path your LangSmith webhook will target.
* **Error Handling:** Basic error handling for GitHub API interactions is included.
**Deploy this server to your chosen platform (e.g., Render) and note down its public URL (e.g., `https://prompt-commit-webhook.onrender.com`).**
## Configuring the webhook in LangSmith
Once your FastAPI server is deployed and you have its public URL, you can configure the webhook in LangSmith:
1. Navigate to your LangSmith workspace.
2. Go to the **Prompts** section. Here you'll see a list of your prompts.
3. On the top right of the Prompts page, click the **+ Webhook** button.
4. You'll be presented with a form to configure your webhook:
* **Webhook URL:** Enter the full public URL of your deployed FastAPI server's endpoint. For our example server, this would be `https://prompt-commit-webhook.onrender.com/webhook/commit`.
* **Headers (Optional):**
* You can add custom headers that LangSmith will send with each webhook request.
5. **Test the Webhook:** LangSmith provides a "Send Test Notification" button. Use this to send a sample payload to your server. Check your server logs (e.g., on Render) to ensure it receives the request and processes it successfully (or to debug any issues).
6. **Save** the webhook configuration.
## The workflow in action
Now, with everything set up, here's what happens:
1. **Prompt Modification:** A user (developer or non-technical team member) modifies a prompt in the LangSmith UI and saves it, creating a new "prompt commit."
2. **Webhook Trigger:** LangSmith detects this new prompt commit and triggers the configured webhook.
3. **HTTP Request:** LangSmith sends an HTTP POST request to the public URL of your FastAPI server (e.g., `https://prompt-commit-webhook.onrender.com/webhook/commit`). The body of this request contains the JSON prompt manifest for the entire workspace.
4. **Server Receives Payload:** Your FastAPI server's endpoint receives the request.
5. **GitHub Commit:** The server parses the JSON manifest from the request body. It then uses the configured GitHub Personal Access Token, repository owner, repository name, file path, and branch to:
* Check if the manifest file already exists in the repository on the specified branch to get its SHA (this is necessary for updating an existing file).
* Create a new commit with the latest prompt manifest, either creating the file or updating it if it already exists. The commit message will indicate that it's an update from LangSmith.
6. **Confirmation:** You should see the new commit appear in your GitHub repository.
You've now successfully synced your LangSmith prompts with GitHub!
## Beyond a simple commit
Our example FastAPI server performs a direct commit of the entire prompt manifest. However, this is just the starting point. You can extend the server's functionality to perform more sophisticated actions:
* **Granular Commits:** Parse the manifest and commit changes to individual prompt files if you prefer a more granular structure in your repository.
* **Trigger CI/CD:** Instead of (or in addition to) committing, have the server trigger a CI/CD pipeline (e.g., Jenkins, GitHub Actions, GitLab CI) to deploy a staging environment, run tests, or build new application versions.
* **Update Databases/Caches:** If your application loads prompts from a database or cache, update these stores directly.
* **Notifications:** Send notifications to Slack, email, or other communication channels about prompt changes.
* **Selective Processing:** Based on metadata within the LangSmith payload (if available, e.g., which specific prompt changed or by whom), you could apply different logic.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/prompt-commit.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Prompt engineering
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/prompt-engineering
The following sections help you create, manage, and optimize your prompts:
Read definitions and key terminology for prompt engineering in LangSmith.
Build prompts via the UI or SDK, configure settings, use tools, add multimodal inputs, and connect model providers.
Organize with tags, commit changes, trigger webhooks, and share through the public prompt hub.
Browse and manage prompt tags and discover community prompts from the LangChain Hub.
Test and experiment with prompts using custom endpoints and model configurations.
Learn step-by-step techniques, like optimizing classifiers and advanced prompt engineering.
Use **[Polly](/langsmith/polly)** in the Prompt Playground to optimize prompts, generate tools, and create output schemas with AI-powered assistance.
To set up a LangSmith instance, visit the [Platform setup section](/langsmith/platform-setup) to choose between cloud, hybrid, or self-hosted. All options include observability, evaluation, prompt engineering, and deployment.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/prompt-engineering.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Prompt engineering concepts
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/prompt-engineering-concepts
While traditional software applications are built by writing code, AI applications often derive their logic from prompts.
This guide will walk through the key concepts of prompt engineering in LangSmith.
## Why prompt engineering?
A prompt guides the model's behavior without changing its underlying capabilities. By providing instructions, examples, and context, prompts shape how the model responds to inputs.
Prompt engineering is important because it allows you to modify model behavior. While other approaches exist (such as fine-tuning), prompt engineering typically offers the lowest barrier to entry and often delivers the highest return on investment.
Prompt engineering is often a multi-disciplinary effort. The most effective prompt engineer may be a product manager, domain expert, or other non-technical team member rather than the software engineer building the application. Proper tooling and infrastructure are essential to support this cross-functional collaboration.
## Prompt types
There are two different types of prompt formats: `chat` style prompts and `completion` style prompts.
**Chat prompts** are a list of messages, each with a role (such as `system`, `user`, or `assistant`). This is the prompting style supported by most current model APIs and is the recommended format.
**Completion prompts** are a single string. This is an older prompting style maintained primarily for backward compatibility.
Unless you have a specific reason to use completion prompts, use chat prompts for new projects. Chat prompts provide better structure for multi-turn conversations and are better supported by modern LLMs.
## Prompts vs. prompt templates
While *prompt* and *prompt template* are often used interchangeably, understanding the distinction helps clarify how LangSmith manages and evaluates your AI application.
* **Prompts** refer to the messages that are passed into the language model.
* **Prompt templates** allow you to create reusable prompts with dynamic placeholders that get filled in at runtime. Instead of hardcoding values, you define variables that LangSmith replaces with different inputs each time you run your prompt. This makes prompts flexible, testable, and easier to iterate on.
Here's how templates work in practice:
1. **Define the template**: Create a prompt with variables (marked with curly braces) that will be replaced at runtime:
```
You are a customer support agent. This is the refund policy:
{refund_policy}
Please respond to the user's question:
{question}
```
2. **Provide input values**: Supply the actual values for each variable:
```json theme={null}
{
"refund_policy": "no refunds under any circumstances",
"question": "can I get a refund for this hat?"
}
```
3. **Get the final prompt**: LangSmith replaces the variables with your inputs to create the prompt sent to the model:
```
You are a customer support agent. This is the refund policy:
no refunds under any circumstances
Please respond to the user's question:
Can I get a refund for this hat?
```
Learn more about template variable syntax and formatting options in the [Prompt template format](/langsmith/create-a-prompt#template-format) guide.
## Prompts in LangSmith
You can store and version prompt templates in LangSmith. These templates can be tested in the playground, versioned with commits and tags, and pulled into your application code.
Open the [playground](https://smith.langchain.com/playground) to create and test your first prompt template. For a step-by-step, refer to [Create a prompt](/langsmith/create-a-prompt).
The following sections describe key aspects of prompt templates.
### F-string vs. mustache
You can format your prompt template with input variables using either [f-string](https://realpython.com/python-f-strings/) or [mustache](https://mustache.github.io/mustache.5.html) format.
For details on how to use these formats in the playground, see [Template format](/langsmith/create-a-prompt#template-format).
The [playground](https://smith.langchain.com/playground) uses `f-string` as the default template format, but you can switch to `mustache` format in the prompt settings/template format section. `mustache` gives you more flexibility around conditional variables, loops, and nested keys. For conditional variables, you'll need to manually add json variables in the 'inputs' section. Read [the documentation](https://mustache.github.io/mustache.5.html)
### Tools
Tools are interfaces the LLM can use to interact with the outside world. Tools consist of a name, description, and JSON schema of arguments used to call the tool.
### Structured output
Structured output is a feature of most state of the art LLMs, wherein instead of producing raw text as output they stick to a specified schema. This may or may not use [Tools](#tools) under the hood.
Structured output is similar to tools, but different in a few key ways. With tools, the LLM choose which tool to call (or may choose not to call any); with structured output, the LLM **always** responds in this format. With tools, the LLM may select **multiple** tools; with structured output, only one response is generate.
### Model
Optionally, you can store a model configuration alongside a prompt template. This includes the name of the model and any other parameters (temperature, etc).
## Prompt versioning
Versioning is a key component of iterating on and collaborating with prompts.
### Commits
Every saved update to a prompt creates a new commit with a unique commit hash. This allows you to:
* View the full history of changes to a prompt.
* Review earlier versions.
* Revert to a previous state if needed.
* Reference specific versions in your code using the commit hash (e.g., `client.pull_prompt("prompt_name:commit_hash")`).
In the UI, you can compare a commit with its previous version by toggling **Show diff** in the top-right corner of the **Commits** tab.
### Tags
Commit tags are human-readable labels that point to specific commits in your prompt's history. Unlike commit hashes, tags can be moved to point to different commits, allowing you to update which version your code references without changing the code itself.
Use cases for commit tags can include:
* **Environment-specific tags**: Mark commits for `production` or `staging` environments, which allows you to switch between different versions without changing your code.
* **Version control**: Mark stable versions of your prompts, for example, `v1`, `v2`, which lets you reference specific versions in your code and track changes over time.
* **Collaboration**: Mark versions ready for review, which enables you to share specific versions with collaborators and get feedback.
**Not to be confused with resource tags**: Commit tags reference specific prompt versions. [Resource tags](/langsmith/set-up-resource-tags) are key-value pairs used to organize workspace resources.
For detailed information on creating and managing commit tags, see [Manage prompts](/langsmith/manage-prompts#commit-tags).
## Prompt playground
The prompt playground provides an interface for iterating on and testing prompts. You can access the playground from the sidebar or directly from a saved prompt.
In the playground you can:
* Change the model being used
* Change prompt template being used
* Change the output schema
* Change the tools available
* Enter the input variables to run through the prompt template
* Run the prompt through the model
* Observe the outputs
Use **[Polly](/langsmith/polly)** in the Playground to optimize prompts, generate tools, and create output schemas with AI assistance.
## Testing multiple prompts
You can add multiple prompts to your playground to compare outputs and evaluate performance:
## Testing over a dataset
To test over a dataset, select the dataset from the top right and click Start. You can configure whether results are streamed and the number of repetitions for the test.
Click the "View Experiment" button to view detailed test results.
## Video guide
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/prompt-engineering-concepts.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Prompt engineering quickstart
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/prompt-engineering-quickstart
Prompts guide the behavior of Large Language Models (LLM). [*Prompt engineering*](/langsmith/prompt-engineering-concepts) is the process of crafting, testing, and refining the instructions you give to an LLM so it produces reliable and useful responses.
LangSmith provides tools to create, version, test, and collaborate on prompts. You’ll also encounter common concepts like [*prompt templates*](/langsmith/prompt-engineering-concepts#prompts-vs-prompt-templates), which let you reuse structured prompts, and [*variables*](/langsmith/prompt-engineering-concepts#f-string-vs-mustache), which allow you to dynamically insert values (such as a user’s question) into a prompt.
In this quickstart, you’ll create, test, and improve prompts using either the UI or the SDK. This quickstart will use OpenAI as the example LLM provider, but the same workflow applies across other providers.
If you prefer to watch a video on getting started with prompt engineering, refer to the quickstart [Video guide](#video-guide).
## Prerequisites
Before you begin, make sure you have:
* **A LangSmith account**: Sign up or log in at [smith.langchain.com](https://smith.langchain.com).
* **A LangSmith API key**: Follow the [Create an API key](/langsmith/create-account-api-key#create-an-api-key) guide.
* **An OpenAI API key**: Generate this from the [OpenAI dashboard](https://platform.openai.com/account/api-keys).
Select the tab for UI or SDK workflows:
## 1. Set workspace secret
In the [LangSmith UI](https://smith.langchain.com), ensure that your API key is set as a [workspace secret](/langsmith/administration-overview#workspace-secrets).
1. Navigate to **Settings** and then move to the **Secrets** tab.
2. Select **Add secret** and enter the key environment variable (e.g.,`OPENAI_API_KEY` or `ANTHROPIC_API_KEY`) and your API key as the **Value**.
3. Select **Save secret**.
When adding workspace secrets in the LangSmith UI, make sure the secret keys match the environment variable names expected by your model provider.
## 2. Create a prompt
1. In the [LangSmith UI](https://smith.langchain.com), navigate to the **Prompts** section in the left-hand menu.
2. Click on **+ Prompt** to create a prompt.
3. Modify the prompt by editing or adding prompts and input variables as needed.
## 3. Test a prompt
1. Under the **Prompts** heading select the gear icon next to the model name, which will launch the **Prompt Settings** window on the **Model Configuration** tab.
2. Set the [model configuration](/langsmith/managing-model-configurations) you want to use. The **Provider** and **Model** you select will determine the parameters that are configurable on this configuration page. Once set, click **Save as**.
3. Specify the input variables you would like to test in the **Inputs** box and then click **Start**.
To learn about more options for configuring your prompt in the Playground, refer to [Configure prompt settings](/langsmith/managing-model-configurations).
4. After testing and refining your prompt, click **Save** to store it for future use.
## 4. Iterate on a prompt
LangSmith allows for team-based prompt iteration. [Workspace](/langsmith/administration-overview#workspaces) members can experiment with prompts in the playground and save their changes as a new [*commit*](/langsmith/prompt-engineering-concepts#commits) when ready.
To improve your prompts:
* Reference the documentation provided by your model provider for best practices in prompt creation, such as:
* [Best practices for prompt engineering with the OpenAI API](https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-api)
* [Gemini's Introduction to prompt design](https://ai.google.dev/gemini-api/docs/prompting-intro)
* Build and refine your prompts with the Prompt Canvas—an interactive tool in LangSmith. Learn more in the [Prompt Canvas guide](/langsmith/write-prompt-with-ai).
* Tag specific commits to mark important moments in your commit history.
1. To create a commit, navigate to the **Playground** and select **Commit**. Choose the prompt to commit changes to and then **Commit**.
2. Navigate to **Prompts** in the left-hand menu. Select the prompt. Once on the prompt's detail page, move to the **Commits** tab. Find the tag icon to **Add a Commit Tag**.
## 1. Set up your environment
1. In your terminal, prepare your environment:
```bash Python theme={null}
mkdir ls-prompt-quickstart && cd ls-prompt-quickstart
python -m venv .venv
source .venv/bin/activate
pip install -qU langsmith openai langchain_core
```
```bash TypeScript theme={null}
mkdir ls-prompt-quickstart-ts && cd ls-prompt-quickstart-ts
npm init -y
npm install langsmith openai typescript ts-node
npx tsc --init
```
2. Set your API keys:
```bash theme={null}
export LANGSMITH_API_KEY=''
export OPENAI_API_KEY=''
```
## 2. Create a prompt
To create a prompt, you'll define a list of messages that you want in your prompt and then push to LangSmith.
Use the language-specific constructor and push method:
* Python: [`ChatPromptTemplate`](https://reference.langchain.com/python/langchain_core/prompts/#langchain_core.prompts.chat.ChatPromptTemplate) → [`client.push_prompt(...)`](https://docs.smith.langchain.com/reference/python/client/langsmith.client.Client#langsmith.client.Client.push_prompt)
* TypeScript: [`ChatPromptTemplate.fromMessages(...)`](https://v03.api.js.langchain.com/classes/_langchain_core.prompts.ChatPromptTemplate.html#fromMessages) → [`client.pushPrompt(...)`](https://langsmith-docs-7jgx2bq8f-langchain.vercel.app/reference/js/classes/client.Client#pushprompt)
1. Add the following code to a `create_prompt` file:
```python Python theme={null}
from langsmith import Client
from langchain_core.prompts import ChatPromptTemplate
client = Client()
prompt = ChatPromptTemplate([
("system", "You are a helpful chatbot."),
("user", "{question}"),
])
client.push_prompt("prompt-quickstart", object=prompt)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
import { ChatPromptTemplate } from "@langchain/core/prompts";
const client = new Client();
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful chatbot."],
["user", "{question}"],
]);
await client.pushPrompt("prompt-quickstart", {
object: prompt,
});
```
This creates an ordered list of messages, wraps them in `ChatPromptTemplate`, and then pushes the prompt by name to your [workspace](/langsmith/administration-overview#workspaces) for versioning and reuse.
2. Run `create_prompt`:
```python Python theme={null}
python create_prompt.py
```
```typescript TypeScript theme={null}
npx tsx create_prompt.ts
```
Follow the resulting link to view the newly created Prompt Hub prompt in the LangSmith UI.
## 3. Test a prompt
In this step, you'll pull the prompt you created in [step 2](#2-create-a-prompt) by name (`"prompt-quickstart"`), format it with a test input, convert it to OpenAI’s chat format, and call the OpenAI Chat Completions API.
Then, you'll iterate on the prompt by creating a new version. Members of your workspace can open an existing prompt, experiment with changes in the [UI](https://smith.langchain.com), and save those changes as a new commit on the same prompt, which preserves history for the whole team.
1. Add the following to a `test_prompt` file:
```python Python theme={null}
from langsmith import Client
from openai import OpenAI
from langchain_core.messages import convert_to_openai_messages
client = Client()
oai_client = OpenAI()
prompt = client.pull_prompt("prompt-quickstart")
# Since the prompt only has one variable you could also pass in the value directly
# Equivalent to formatted_prompt = prompt.invoke("What is the color of the sky?")
formatted_prompt = prompt.invoke({"question": "What is the color of the sky?"})
response = oai_client.chat.completions.create(
model="gpt-4.1",
messages=convert_to_openai_messages(formatted_prompt.messages),
)
```
```typescript TypeScript theme={null}
import { OpenAI } from "openai";
import { pull } from "langchain/hub"
import { convertPromptToOpenAI } from "@langchain/openai";
const oaiClient = new OpenAI();
const prompt = await pull("prompt-quickstart");
// Format the prompt with the question
const formattedPrompt = await prompt.invoke({ question: "What is the color of the sky?" });
const response = await oaiClient.chat.completions.create({
model: "gpt-4.1",
messages: convertPromptToOpenAI(formattedPrompt).messages,
});
```
This loads the prompt by name using `pull` for the latest committed version of the prompt that you're testing. You can also specify a specific commit by passing the commit hash `":"`
2. Run `test_prompt` :
```python Python theme={null}
python test_prompt.py
```
```typescript TypeScript theme={null}
npx tsx test_prompt.ts
```
3. To create a new version of a prompt, call the same push method you used initially with the same prompt name and your updated template. LangSmith will record it as a new commit and preserve prior versions.
Copy the following code to an `iterate_prompt` file:
```python Python theme={null}
from langsmith import Client
from langchain_core.prompts import ChatPromptTemplate
client = Client()
new_prompt = ChatPromptTemplate([
("system", "You are a helpful chatbot. Respond in Spanish."),
("user", "{question}"),
])
client.push_prompt("prompt-quickstart", object=new_prompt)
```
```typescript TypeScript theme={null}
import { Client } from "langsmith";
import { ChatPromptTemplate } from "@langchain/core/prompts";
const client = new Client();
const newPrompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful chatbot. Speak in Spanish."],
["user", "{question}"]
]);
await client.pushPrompt("prompt-quickstart", {
object: newPrompt
});
```
4. Run `iterate_prompt` :
```python Python theme={null}
python iterate_prompt.py
```
```typescript TypeScript theme={null}
npx tsx iterate_prompt.ts
```
Now your prompt will contain two commits.
To improve your prompts:
* Reference the documentation provided by your model provider for best practices in prompt creation, such as:
* [Best practices for prompt engineering with the OpenAI API](https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-api)
* [Gemini's Introduction to prompt design](https://ai.google.dev/gemini-api/docs/prompting-intro)
* Build and refine your prompts with the Prompt Canvas—an interactive tool in LangSmith. Learn more in the [Prompt Canvas guide](/langsmith/write-prompt-with-ai).
## Next steps
* Learn more about how to store and manage prompts using the Prompt Hub in the [Create a prompt guide](/langsmith/create-a-prompt).
* Learn how to set up the Playground to [Test multi-turn conversations](/langsmith/multiple-messages) in this tutorial.
* Learn how to test your prompt's performance over a dataset instead of individual examples, refer to [Run an evaluation from the Prompt Playground](/langsmith/run-evaluation-from-prompt-playground).
Use **[Polly](/langsmith/polly)** in the Playground to help optimize your prompts, generate tools, and create output schemas.
## Video guide
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/prompt-engineering-quickstart.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Prompt template format guide
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/prompt-template-format
This page describes the [prompt template](/langsmith/prompt-engineering-concepts#prompts-vs-prompt-templates) formats supported in the [prompt playground](/langsmith/prompt-engineering-concepts#prompt-playground), [prompt hub](/langsmith/manage-prompts#public-prompt-hub), and [evaluators](/langsmith/evaluation-concepts#evaluators). Prompt templates allow you to create reusable prompts with dynamic placeholders that get filled in at runtime.
For a general overview of prompt engineering and prompt templates, refer to the [Concepts](/langsmith/prompt-engineering-concepts#prompts-vs-prompt-templates) page.
LangSmith supports two prompt template formats, which work for different levels of complexity:
| Format | Syntax | Best for |
| ------------ | -------------- | -------------------------------------------------------------------- |
| **f-string** | `{variable}` | Simple prompts with basic variable substitution |
| **mustache** | `{{variable}}` | Complex prompts with loops, conditionals, nested data, or evaluators |
[F-string syntax](#f-string-syntax) is ideal for straightforward prompts. [Mustache](#mustache-syntax) provides features for handling complex data structures and logic, which is helpful for evaluators and advanced use cases.
You can switch between formats in the [UI](https://smith.langchain.com). LangSmith will automatically [convert your template](#conversion-between-formats) when possible, though some mustache features (like loops and conditionals) cannot be converted to f-string format.
Use the [prompt playground](https://smith.langchain.com/playground) to test out the examples on this page. Switch the **Prompt format** under the prompt settings menu in the Playground.
## F-string syntax
F-string templates use Python-style formatting with single curly braces `{variable}`. LangSmith uses a [simplified subset](#limitations) of Python's [f-string syntax](https://realpython.com/python-f-strings/)—it only supports basic variable substitution, not the full range of Python expressions and formatting options. When you have a flat data structure and only need to insert values into your prompt, f-strings are ideal.
### Basic variables
Variables are replaced with their values from the input data. Variable names must match exactly (case-sensitive):
```python theme={null}
# Template
Hello, {name}! Welcome to {company}.
# Input
{
"name": "Ashley",
"company": "LangChain"
}
# Output
Hello, Ashley! Welcome to LangChain.
```
When the template runs, LangSmith looks up each variable name in the input object and replaces `{name}` with the value of the `name` key.
### Variable names
F-string variable names are treated as simple string identifiers. They cannot contain dots, brackets, or special characters—just alphanumeric characters and underscores.
```python theme={null}
# Template
Hello, {name}!
Your topic is: {topic}
# Input
{
"name": "Ashley",
"topic": "LangSmith"
}
# Output
Hello, Ashley!
Your topic is: LangSmith
```
If your input has nested objects like `{"user": {"name": "Ashley"}}`, you **cannot** access the nested value with `{user.name}` in f-string format. The dot would be treated as part of the variable name (literally looking for a key called `"user.name"`), not as a path separator. For nested access, use [mustache format](#mustache-syntax) instead.
### Literal braces
Sometimes you need to include actual curly braces in your output (for example, in JSON examples or code snippets). To do this, **double the braces**:
```python theme={null}
# Template
Use double braces for literals: {{not_a_variable}}
But single braces for variables: {variable}
# Input
{
"variable": "value"
}
# Output
Use double braces for literals: {not_a_variable}
But single braces for variables: value
```
The template parser recognizes `{{` as an escaped brace, not a variable placeholder. Only single braces `{...}` are treated as variables.
### Limitations
LangSmith's f-string implementation is limited to keep templates simple and predictable. The following features are **not supported**:
* **Dot notation for nested access:** Cannot use `{user.name}` to access nested objects. The entire string `"user.name"` would be treated as a single variable name.
* **Format specifiers:** Cannot use `{price:.2f}` for number formatting or `{rate:.1%}` for percentages.
* **Expressions:** Cannot use `{x + y}`, `{len(items)}`, or `{value if condition else default}`.
* **Function calls:** Cannot use `{str.upper()}` or other method calls.
* **Loops or conditionals:** No control flow structures.
* **Array indexing:** Cannot use `{items[0]}` to access array elements.
For any of these advanced features, **use mustache format instead**.
## Mustache syntax
[Mustache](https://mustache.github.io/mustache.5.html) is a "logic-less" templating language, meaning it doesn't allow arbitrary code execution but does provide structured control flow through sections. It's called "logic-less" because you can't write complex expressions—instead, you structure your data to control what renders.
Mustache is designed for complex data structures and dynamic rendering. It's essential for:
* **Evaluators:** Processing thread histories and conversation context.
* **Few-shot prompting:** Iterating over example lists.
* **Nested data:** Accessing deeply nested objects and arrays.
* **Conditional content:** Showing different text based on data presence.
The double-brace syntax `{{variable}}` distinguishes it from f-strings.
### Basic variables
Like f-strings, mustache replaces variables with their values:
```mustache theme={null}
{{!-- Template --}}
Hello, {{name}}! Welcome to {{company}}.
{{!-- Input --}}
{
"name": "Ashley",
"company": "LangChain"
}
{{!-- Output --}}
Hello, Ashley! Welcome to LangChain.
```
`{{!-- ... --}}` is a mustache comment and won't appear in the output. Refer to the [Comments](#comments) section.
### Nested object access
You can traverse nested objects using dot notation:
```mustache theme={null}
{{!-- Template --}}
User: {{user.name}}
Email: {{user.profile.email}}
{{!-- Input --}}
{
"user": {
"name": "Billy",
"profile": {
"email": "billy@example.com"
}
}
}
{{!-- Output --}}
User: Billy
Email: billy@example.com
```
The template engine follows the path `user` → `profile` → `email` through your data structure. Each dot represents one level of nesting.
Real-world data is often nested (for example: API responses, database records, etc.). Mustache lets you work with this data naturally without flattening it first.
### Sections
Sections are mustache's core feature. A section starts with `{{#name}}` and ends with `{{/name}}`. What happens inside depends on the value:
* **Array:** Repeats the content for each element.
* **Object:** Renders once with that object as context.
* **Truthy value:** Renders once.
* **Falsy value (false, null, undefined, empty array):** Doesn't render.
In the following example, the section `{{#items}}` iterates over the `items` array. For each iteration, the variables inside the section (like `{{name}}` and `{{price}}`) are resolved against the current array element:
```mustache theme={null}
{{!-- Template --}}
Shopping List:
{{#items}}
- {{name}}: ${{price}}
{{/items}}
{{!-- Input --}}
{
"items": [
{"name": "Apple", "price": "1.50"},
{"name": "Banana", "price": "0.75"},
{"name": "Orange", "price": "2.00"}
]
}
{{!-- Output --}}
Shopping List:
- Apple: $1.50
- Banana: $0.75
- Orange: $2.00
```
Sections eliminate the need to manually construct repetitive text. In evaluators, you'll use sections to iterate over conversation messages or [few-shot examples](#few-shot-examples).
For deeply nested hierarchical data, you can nest multiple sections to handle complex structures with multiple levels of arrays and objects:
```mustache theme={null}
{{!-- Template --}}
{{#company}}
Company: {{name}}
{{#departments}}
Department: {{dept_name}}
{{#employees}}
- {{employee_name}} ({{role}})
{{/employees}}
{{/departments}}
{{/company}}
{{!-- Input --}}
{
"company": {
"name": "TechCorp",
"departments": [
{
"dept_name": "Engineering",
"employees": [
{"employee_name": "Ashley", "role": "Senior Engineer"},
{"employee_name": "Billy", "role": "Engineer"}
]
},
{
"dept_name": "Sales",
"employees": [
{"employee_name": "Carol", "role": "Sales Manager"}
]
}
]
}
}
{{!-- Output --}}
Company: TechCorp
Department: Engineering
- Ashley (Senior Engineer)
- Billy (Engineer)
Department: Sales
- Carol (Sales Manager)
```
You can create a structure as deep as you need, but consider flattening very deep structures before templating for readability. This approach is useful for nested categories, conversation threads with metadata, or any hierarchical data representation.
### Nested loops
You can nest sections to handle multi-level data structures:
```mustache theme={null}
{{!-- Template --}}
{{#categories}}
Category: {{name}}
{{#products}}
- {{title}} ({{price}})
{{/products}}
{{/categories}}
{{!-- Input --}}
{
"categories": [
{
"name": "Fruits",
"products": [
{"title": "Apple", "price": "$1.50"},
{"title": "Banana", "price": "$0.75"}
]
},
{
"name": "Vegetables",
"products": [
{"title": "Carrot", "price": "$0.50"},
{"title": "Lettuce", "price": "$1.25"}
]
}
]
}
{{!-- Output --}}
Category: Fruits
- Apple ($1.50)
- Banana ($0.75)
Category: Vegetables
- Carrot ($0.50)
- Lettuce ($1.25)
```
The outer section `{{#categories}}` sets the context to each category object. Inside that context, `{{name}}` refers to the category name, and the inner section `{{#products}}` iterates over that category's products.
Use nested loops when your data has hierarchical relationships—categories with products, departments with employees, or conversation threads with multiple exchanges.
### Array elements by index
Sometimes you need a specific element rather than looping. Use dot notation with numeric indices:
```mustache theme={null}
{{!-- Template --}}
First item: {{items.0}}
Second item: {{items.1}}
Last item: {{items.2}}
{{!-- Input --}}
{
"items": ["Apple", "Banana", "Orange"]
}
{{!-- Output --}}
First item: Apple
Second item: Banana
Last item: Orange
```
You must know the index when you're writing the template.
Evaluators often need the first user message or last AI response from a conversation thread. Use `{{all_messages.0}}` for the first message or pre-calculate the last message in your data.
### Conditionals
You can use sections as conditionals. They only render if the value exists, is non-empty, and is not `false`:
```mustache theme={null}
{{!-- Template --}}
{{#user}}
Welcome back, {{name}}!
{{/user}}
{{!-- Input (user exists) --}}
{
"user": {
"name": "Ashley"
}
}
{{!-- Output --}}
Welcome back, Ashley!
{{!-- Input (no user) --}}
{}
{{!-- Output --}}
(empty - section doesn't render)
```
The section `{{#user}}` checks if `user` exists and is truthy. If so, it renders the content inside with `user` as the context (so `{{name}}` looks for `name` inside `user`).
Show optional content like "Welcome back" messages only when user data is available, or display error messages only when errors exist.
### Inverted sections
Inverted sections render only when a value doesn’t exist, is false, null, undefined, or an empty array. Inverted sections are commonly used to handle empty states, such as missing data or empty lists.
In the following example:
* `{{#results}}` iterates over each result and renders one line per item.
* `{{^results}}` renders only when the results array is empty or missing.
* The inverted section provides a clear fallback when there are no results to display.
```mustache theme={null}
{{!-- Template --}}
Search results for "{{query}}":
{{#results}}
- {{title}} ({{year}})
{{/results}}
{{^results}}
No results found. Try a different search term.
{{/results}}
{{!-- Input (with results)--}}
{
"query": "matrix",
"results": [
{"title": "The Matrix", "year": 1999},
{"title": "The Matrix Reloaded", "year": 2003}
]
}
{{!-- Output --}}
Search results for "matrix":
- The Matrix (1999)
- The Matrix Reloaded (2003)
{{!-- Input (no results) --}}
{
"query": "asdlkjasd",
"results": []
}
{{!-- Output --}}
Search results for "asdlkjasd":
No results found. Try a different search term.
```
You can also combine regular and inverted sections to create if/else logic, providing default values when variables are missing.
The regular section `{{#username}}` renders only if `username` exists. The inverted section `{{^username}}` renders only if it doesn't. Together, they create an if/else branch. This is useful for personalizing prompts when user data is optional or showing default instructions when custom ones aren't provided:
```mustache theme={null}
{{!-- Template --}}
{{#username}}
Hello, {{username}}!
{{/username}}
{{^username}}
Hello, Guest!
{{/username}}
{{!-- Input (with username) --}}
{"username": "Ashley"}
{{!-- Output: Hello, Ashley! --}}
{{!-- Input (no username) --}}
{}
{{!-- Output: Hello, Guest! --}}
```
This pattern extends to boolean flags, allowing you to change output formatting based on data conditions:
```mustache theme={null}
{{!-- Template --}}
Status: {{status}}
{{#is_urgent}}
⚠️ URGENT - Immediate attention required!
{{/is_urgent}}
{{^is_urgent}}
Standard priority
{{/is_urgent}}
{{!-- Input --}}
{
"status": "Open",
"is_urgent": true
}
{{!-- Output --}}
Status: Open
⚠️ URGENT - Immediate attention required!
```
Use boolean flags in your data to control which content blocks render. This keeps formatting logic out of your application code and in the template instead. This approach is useful for highlighting important information, adjusting tone based on context (formal vs. casual), or showing different instructions for different user types.
### Comments
Comments document your templates without affecting output. Use `{{! comment }}` or `{{!-- comment --}}`:
```mustache theme={null}
{{!-- Template --}}
Hello, {{name}}!
{{! This is a comment and won't appear in output }}
Welcome to our service.
{{!-- Input --}}
{
"name": "Ashley"
}
{{!-- Output --}}
Hello, Ashley!
Welcome to our service.
```
Use comments to explain complex sections, document expected data structures, or note why certain logic exists. This helps collaborators understand your templates.
## Special variables for evaluators and threads
When building [evaluators](/langsmith/evaluation-concepts#evaluators) or working with conversational AI, LangSmith automatically provides special variables that structure conversation data in useful ways. These variables are **only available in evaluator contexts**, not in regular playground prompts.
Evaluators need to analyze conversations holistically—looking at patterns across multiple messages, comparing the first question to the final answer, or examining how well the AI responds to follow-up questions. These variables make it easy to access conversation structure without manual data manipulation.
### Thread message variables
LangSmith provides three pre-structured views of conversation [threads](/langsmith/evaluation-concepts#threads):
```mustache theme={null}
{{!-- Access all messages in the thread --}}
{{#all_messages}}
{{role}}: {{content}}
{{/all_messages}}
{{!-- Access human-AI message pairs --}}
{{#human_ai_pairs}}
Human: {{human}}
AI: {{ai}}
{{/human_ai_pairs}}
{{!-- Access first human and last AI message --}}
{{#first_human_last_ai}}
Original question: {{first_human}}
Final answer: {{last_ai}}
{{/first_human_last_ai}}
{{!-- Access specific message by index --}}
First message: {{all_messages.0}}
Second message: {{all_messages.1}}
```
* **`all_messages`**: Every message in chronological order with `role` (user/assistant/system) and `content` fields. Use this to show the full conversation flow.
* **`human_ai_pairs`**: Messages grouped into question-answer pairs. Each pair has `human` (user message) and `ai` (assistant response). Use this when evaluating response quality.
* **`first_human_last_ai`**: Just the initial question (`first_human`) and final answer (`last_ai`). Use this to check if the AI ultimately answered the original question, ignoring the middle conversation.
### Example with thread context
The following example is a practical evaluator prompt that uses thread context:
```mustache theme={null}
{{!-- Template --}}
Evaluate this conversation:
{{#all_messages}}
{{role}}: {{content}}
{{/all_messages}}
Was the AI helpful? Rate from 1-5.
{{!-- Input (provided by LangSmith) --}}
{
"all_messages": [
{"role": "user", "content": "What's the weather?"},
{"role": "assistant", "content": "I don't have access to weather data."},
{"role": "user", "content": "Can you tell me a joke instead?"},
{"role": "assistant", "content": "Why did the chicken cross the road?"}
]
}
{{!-- Output --}}
Evaluate this conversation:
user: What's the weather?
assistant: I don't have access to weather data.
user: Can you tell me a joke instead?
assistant: Why did the chicken cross the road?
Was the AI helpful? Rate from 1-5.
```
The template uses the mustache section `{{#all_messages}}` to loop over the conversation array. For each iteration, the section sets the context to that message object, so `{{role}}` and `{{content}}` access the properties of the current message. The loop automatically iterates through all four messages in order, displaying each as `"role: content"`. This gives the evaluator LLM the full conversation history to assess helpfulness.
When you create an evaluator in LangSmith, select which thread variables you want to include. LangSmith will automatically populate them from the conversation being evaluated.
## Few-shot examples
Few-shot prompting teaches the LLM by example. You provide several input-output pairs demonstrating the task, then ask it to perform the same task on new input.
[Few-shot examples](/langsmith/create-few-shot-evaluators#how-few-shot-examples-work) help the LLM understand:
* **Format expectations** (e.g., "respond with JSON" or "use this tone")
* **Edge cases** (e.g., how to handle ambiguous input)
* **Task nuances** (e.g., the difference between "positive" and "very positive" sentiment)
It is especially useful for classification, formatting, and stylistic tasks where showing is clearer than telling.
### Few-shot placeholder
In LangSmith, use the `{{few_shot_examples}}` placeholder where you want your examples to appear:
```mustache theme={null}
{{!-- Template --}}
You are a sentiment classifier.
{{few_shot_examples}}
Now classify this text:
Text: {{text}}
Sentiment:
```
When you enable few-shot examples in the [LangSmith UI](https://smith.langchain.com) (in evaluators or Prompt Hub), you configure the example format separately. LangSmith automatically injects those formatted examples wherever you place the `{{few_shot_examples}}` placeholder. This keeps your prompt template clean and lets you manage examples independently.
**Example output with configured examples:**
```
You are a sentiment classifier.
Text: I love this!
Sentiment: positive
Text: This is terrible.
Sentiment: negative
Text: It's okay.
Sentiment: neutral
Now classify this text:
Text: This is amazing!
Sentiment:
```
Configure your few-shot examples in the LangSmith UI to match the format you use for the actual task. This consistency helps the LLM generalize correctly. The placeholder approach separates prompt structure from example data, making both easier to maintain.
## Conversion between formats
**F-string to mustache** always works for basic variables. Format specifiers are converted, but the formatting is removed.
**Mustache to f-string** only works for basic variables. Mustache features like dot notation, sections, conditionals, and comments have no equivalent in f-strings and cannot be converted:
* **Dot notation:** `{{user.name}}` F-strings would treat `"user.name"` as a single variable name rather than nested access.
* **Sections/loops:** `{{#items}}...{{/items}}` No equivalent in f-strings.
* **Conditionals:** `{{#value}}...{{/value}}` No equivalent in f-strings.
* **Inverted sections:** `{{^value}}...{{/value}}` No equivalent in f-strings.
* **Comments:** `{{! comment }}` No equivalent in f-strings.
If you try to convert a mustache template with these features, LangSmith will either refuse the conversion or convert only the simple parts, breaking the template's functionality. Always preview after conversion.
## Additional resources
* **[LangSmith Prompt Engineering Concepts](https://docs.langchain.com/langsmith/prompt-engineering-concepts)**: Higher-level guidance on effective prompting strategies.
* **[Mustache Manual](https://mustache.github.io/mustache.5.html)**: Full mustache specification with all features.
* **[Python f-string Documentation](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)**: Official Python f-string syntax (note: LangSmith uses a simplified subset).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/prompt-template-format.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to run evaluations with pytest
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/pytest
The LangSmith pytest plugin lets Python developers define their datasets and evaluations as pytest test cases.
Compared to the standard evaluation flow, this is useful when:
* **Each example requires different evaluation logic**: Standard evaluation flows assume consistent application and evaluator execution across all dataset examples. For more complex systems or comprehensive evaluations, specific system subsets may require evaluation with particular input types and metrics. These heterogeneous evaluations are simpler to write as distinct test case suites that track together.
* **You want to assert binary expectations**: Track assertions in LangSmith and raise assertion errors locally (e.g. in CI pipelines). Testing tools help when both evaluating system outputs and asserting basic properties about them.
* **You want pytest-like terminal outputs**: Get familiar pytest output formatting
* **You already use pytest to test your app**: Add LangSmith tracking to existing pytest workflows
The JS/TS SDK has an analogous [Vitest/Jest integration](/langsmith/vitest-jest).
## Installation
This functionality requires Python SDK version `langsmith>=0.3.4`.
For extra features like [rich terminal outputs](#rich-outputs) and [test caching](#caching) install:
```bash pip theme={null}
pip install -U "langsmith[pytest]"
```
```bash uv theme={null}
uv add "langsmith[pytest]"
```
## Define and run tests
The pytest integration lets you define datasets and evaluators as test cases.
To track a test in LangSmith add the `@pytest.mark.langsmith` decorator. Every decorated test case will be synced to a dataset example. When you run the test suite, the dataset will be updated and a new experiment will be created with one result for each test case.
```python Python theme={null}
###################### my_app/main.py ######################
import openai
from langsmith import traceable, wrappers
oai_client = wrappers.wrap_openai(openai.OpenAI())
@traceable
def generate_sql(user_query: str) -> str:
result = oai_client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": "Convert the user query to a SQL query."},
{"role": "user", "content": user_query},
],
)
return result.choices[0].message.content
###################### tests/test_my_app.py ######################
import pytest
from langsmith import testing as t
def is_valid_sql(query: str) -> bool:
"""Return True if the query is valid SQL."""
return True # Dummy implementation
@pytest.mark.langsmith # <-- Mark as a LangSmith test case
def test_sql_generation_select_all() -> None:
user_query = "Get all users from the customers table"
t.log_inputs({"user_query": user_query}) # <-- Log example inputs, optional
expected = "SELECT * FROM customers;"
t.log_reference_outputs({"sql": expected}) # <-- Log example reference outputs, optional
sql = generate_sql(user_query)
t.log_outputs({"sql": sql}) # <-- Log run outputs, optional
t.log_feedback(key="valid_sql", score=is_valid_sql(sql)) # <-- Log feedback, optional
assert sql == expected # <-- Test pass/fail status automatically logged to LangSmith under 'pass' feedback key
```
When you run this test it will have a default `pass` boolean feedback key based on the test case passing / failing. It will also track any inputs, outputs, and reference (expected) outputs that you log.
Use `pytest` as you normally would to run the tests:
```bash theme={null}
pytest tests/
```
In most cases we recommend setting a test suite name:
```bash theme={null}
LANGSMITH_TEST_SUITE='SQL app tests' pytest tests/
```
Each time you run this test suite, LangSmith:
* creates a [dataset](/langsmith/evaluation-concepts#datasets) for each test file. If a dataset for this test file already exists it will be updated
* creates an [experiment](/langsmith/evaluation-concepts#experiment) in each created/updated dataset
* creates an experiment row for each test case, with the inputs, outputs, reference outputs and feedback you've logged
* collects the pass/fail rate under the `pass` feedback key for each test case
Here's what a test suite dataset looks like:
And what an experiment against that test suite looks like:
## Log inputs, outputs, and reference outputs
Every time we run a test we're syncing it to a dataset example and tracing it as a run. There's a few different ways that we can trace the example inputs and reference outputs and the run outputs. The simplest is to use the `log_inputs`, `log_outputs`, and `log_reference_outputs` methods. You can run these any time in a test to update the example and run for that test:
```python theme={null}
import pytest
from langsmith import testing as t
@pytest.mark.langsmith
def test_foo() -> None:
t.log_inputs({"a": 1, "b": 2})
t.log_reference_outputs({"foo": "bar"})
t.log_outputs({"foo": "baz"})
assert True
```
Running this test will create/update an example with name "test\_foo", inputs `{"a": 1, "b": 2}`, reference outputs `{"foo": "bar"}` and trace a run with outputs `{"foo": "baz"}`.
**NOTE**: If you run `log_inputs`, `log_outputs`, or `log_reference_outputs` twice, the previous values will be overwritten.
Another way to define example inputs and reference outputs is via pytest fixtures/parametrizations. By default any arguments to your test function will be logged as inputs on the corresponding example. If certain arguments are meant to represet reference outputs, you can specify that they should be logged as such using `@pytest.mark.langsmith(output_keys=["name_of_ref_output_arg"])`:
```python theme={null}
import pytest
@pytest.fixture
def c() -> int:
return 5
@pytest.fixture
def d() -> int:
return 6
@pytest.mark.langsmith(output_keys=["d"])
def test_cd(c: int, d: int) -> None:
result = 2 * c
t.log_outputs({"d": result}) # Log run outputs
assert result == d
```
This will create/sync an example with name "test\_cd", inputs `{"c": 5}` and reference outputs `{"d": 6}`, and run output `{"d": 10}`.
## Log feedback
By default LangSmith collects the pass/fail rate under the `pass` feedback key for each test case. You can add additional feedback with `log_feedback`.
```python theme={null}
import openai
import pytest
from langsmith import wrappers
from langsmith import testing as t
oai_client = wrappers.wrap_openai(openai.OpenAI())
@pytest.mark.langsmith
def test_offtopic_input() -> None:
user_query = "whats up"
t.log_inputs({"user_query": user_query})
sql = generate_sql(user_query)
t.log_outputs({"sql": sql})
expected = "Sorry that is not a valid query."
t.log_reference_outputs({"sql": expected})
# Use this context manager to trace any steps used for generating evaluation
# feedback separately from the main application logic
with t.trace_feedback():
instructions = (
"Return 1 if the ACTUAL and EXPECTED answers are semantically equivalent, "
"otherwise return 0. Return only 0 or 1 and nothing else."
)
grade = oai_client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": instructions},
{"role": "user", "content": f"ACTUAL: {sql}\nEXPECTED: {expected}"},
],
)
score = float(grade.choices[0].message.content)
t.log_feedback(key="correct", score=score)
assert score
```
Note the use of the `trace_feedback()` context manager. This makes it so that the LLM-as-judge call is traced separately from the rest of the test case. Instead of showing up in the main test case run it will instead show up in the trace for the `correct` feedback key.
**NOTE**: Make sure that the `log_feedback` call associated with the feedback trace occurs inside the `trace_feedback` context. This way we'll be able to associate the feedback with the trace, and when seeing the feedback in the UI you'll be able to click on it to see the trace that generated it.
## Trace intermediate calls
LangSmith will automatically trace any traceable intermediate calls that happen in the course of test case execution.
## Grouping tests into a test suite
By default, all tests within a given file will be grouped as a single "test suite" with a corresponding dataset. You can configure which test suite a test belongs to by passing the `test_suite_name` parameter to `@pytest.mark.langsmith` for case-by-case grouping, or you can set the `LANGSMITH_TEST_SUITE` env var to group all tests from an execution into a single test suite:
```bash theme={null}
LANGSMITH_TEST_SUITE="SQL app tests" pytest tests/
```
We generally recommend setting `LANGSMITH_TEST_SUITE` to get a consolidated view of all of your results.
## Naming experiments
You can name an experiment using the `LANGSMITH_EXPERIMENT` env var:
```bash theme={null}
LANGSMITH_TEST_SUITE="SQL app tests" LANGSMITH_EXPERIMENT="baseline" pytest tests/
```
## Caching
LLMs on every commit in CI can get expensive. To save time and resources, LangSmith lets you cache HTTP requests to disk. To enable caching, install with `langsmith[pytest]` and set an env var: `LANGSMITH_TEST_CACHE=/my/cache/path`:
```bash pip theme={null}
pip install -U "langsmith[pytest]"
LANGSMITH_TEST_CACHE=tests/cassettes pytest tests/my_llm_tests
```
```bash uv theme={null}
uv add "langsmith[pytest]"
LANGSMITH_TEST_CACHE=tests/cassettes pytest tests/my_llm_tests
```
All requests will be cached to `tests/cassettes` and loaded from there on subsequent runs. If you check this in to your repository, your CI will be able to use the cache as well.
In `langsmith>=0.4.10`, you may selectively enable caching for requests to individual URLs or hostnames like this:
```python theme={null}
@pytest.mark.langsmith(cached_hosts=["api.openai.com", "https://api.anthropic.com"])
def my_test():
...
```
## pytest features
`@pytest.mark.langsmith` is designed to stay out of your way and works well with familiar `pytest` features.
### Parametrize with `pytest.mark.parametrize`
You can use the `parametrize` decorator as before. This will create a new test case for each parametrized instance of the test.
```python theme={null}
@pytest.mark.langsmith(output_keys=["expected_sql"])
@pytest.mark.parametrize(
"user_query, expected_sql",
[
("Get all users from the customers table", "SELECT * FROM customers"),
("Get all users from the orders table", "SELECT * FROM orders"),
],
)
def test_sql_generation_parametrized(user_query, expected_sql):
sql = generate_sql(user_query)
assert sql == expected_sql
```
**Note:** as the parametrized list grows, you may consider using `evaluate()` instead. This parallelizes the evaluation and makes it easier to control individual experiments and the corresponding dataset.
### Parallelize with `pytest-xdist`
You can use [pytest-xdist](https://pytest-xdist.readthedocs.io/en/stable/) as you normally would to parallelize test execution:
```bash pip theme={null}
pip install -U pytest-xdist
pytest -n auto tests
```
```bash uv theme={null}
uv add pytest-xdist
pytest -n auto tests
```
### Async tests with `pytest-asyncio`
`@pytest.mark.langsmith` works with sync or async tests, so you can run async tests exactly as before.
### Watch mode with `pytest-watch`
Use watch mode to quickly iterate on your tests. We *highly* recommend only using this with test caching (see below) enabled to avoid unnecessary LLM calls:
```bash pip theme={null}
pip install pytest-watch
LANGSMITH_TEST_CACHE=tests/cassettes ptw tests/my_llm_tests
```
```bash uv theme={null}
uv add pytest-watch
LANGSMITH_TEST_CACHE=tests/cassettes ptw tests/my_llm_tests
```
## Rich outputs
If you'd like to see a rich display of the LangSmith results of your test run you can specify `--langsmith-output`:
```bash theme={null}
pytest --langsmith-output tests
```
**Note:** This flag used to be `--output=langsmith` in `langsmith<=0.3.3` but was updated to avoid collisions with other pytest plugins.
You'll get a nice table per test suite that updates live as the results are uploaded to LangSmith:
Some important notes for using this feature:
* Make sure you've installed `pip install -U "langsmith[pytest]"`
* Rich outputs do not currently work with `pytest-xdist`
**NOTE**: The custom output removes all the standard pytest outputs. If you're trying to debug some unexpected behavior it's often better to show the regular pytest outputs so to get full error traces.
## Dry-run mode
If you want to run the tests without syncing the results to LangSmith, you can set `LANGSMITH_TEST_TRACKING=false` in your environment.
```bash theme={null}
LANGSMITH_TEST_TRACKING=false pytest tests/
```
The tests will run as normal, but the experiment logs will not be sent to LangSmith.
## Expectations
LangSmith provides an [expect](https://reference.langchain.com/python/langsmith/observability/sdk/expect/) utility to help define expectations about your LLM output. For example:
```python theme={null}
from langsmith import expect
@pytest.mark.langsmith
def test_sql_generation_select_all():
user_query = "Get all users from the customers table"
sql = generate_sql(user_query)
expect(sql).to_contain("customers")
```
This will log the binary "expectation" score to the experiment results, additionally `assert`ing that the expectation is met possibly triggering a test failure.
`expect` also provides "fuzzy match" methods. For example:
```python theme={null}
@pytest.mark.langsmith(output_keys=["expectation"])
@pytest.mark.parametrize(
"query, expectation",
[
("what's the capital of France?", "Paris"),
],
)
def test_embedding_similarity(query, expectation):
prediction = my_chatbot(query)
expect.embedding_distance(
# This step logs the distance as feedback for this run
prediction=prediction, expectation=expectation
# Adding a matcher (in this case, 'to_be_*"), logs 'expectation' feedback
).to_be_less_than(0.5) # Optional predicate to assert against
expect.edit_distance(
# This computes the normalized Damerau-Levenshtein distance between the two strings
prediction=prediction, expectation=expectation
# If no predicate is provided below, 'assert' isn't called, but the score is still logged
)
```
This test case will be assigned 4 scores:
1. The `embedding_distance` between the prediction and the expectation
2. The binary `expectation` score (1 if cosine distance is less than 0.5, 0 if not)
3. The `edit_distance` between the prediction and the expectation
4. The overall test pass/fail score (binary)
The `expect` utility is modeled off of [Jest](https://jestjs.io/docs/expect)'s expect API, with some off-the-shelf functionality to make it easier to grade your LLMs.
## Legacy
#### `@test` / `@unit` decorator
The legacy method for marking test cases is using the `@test` or `@unit` decorators:
```python theme={null}
from langsmith import test
@test
def test_foo() -> None:
pass
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/pytest.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Get started with Studio
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/quick-start-studio
[Studio](/langsmith/studio) in the [LangSmith Deployment UI](https://smith.langchain.com) supports connecting to two types of graphs:
* Graphs deployed on [cloud or self-hosted](#deployed-graphs).
* Graphs running locally with [Agent Server](#local-development-server).
## Deployed graphs
Studio is accessed in the [LangSmith UI](https://smith.langchain.com) from the **Deployments** navigation.
For applications that are [deployed](/langsmith/deployment-quickstart), you can access Studio as part of that deployment. To do so, navigate to the deployment in the UI and select **Studio**.
This will load Studio connected to your live deployment, allowing you to create, read, and update the [threads](/oss/python/langgraph/persistence#threads), [assistants](/langsmith/assistants), and [memory](/oss/python/concepts/memory) in that deployment.
## Local development server
### Prerequisites
To test your application locally using Studio:
* Follow the [local application quickstart](/langsmith/local-server) first.
* If you don't want data [traced](/langsmith/observability-concepts#traces) to LangSmith, set `LANGSMITH_TRACING=false` in your application's `.env` file. With tracing disabled, no data leaves your local server.
### Setup
1. Install the [LangGraph CLI](/langsmith/cli):
```bash pip theme={null}
pip install -U "langgraph-cli[inmem]"
langgraph dev
```
```bash uv theme={null}
uv add "langgraph-cli[inmem]"
langgraph dev
```
```bash npm theme={null}
npx @langchain/langgraph-cli dev
```
**Browser Compatibility**
Safari blocks `localhost` connections to Studio. To work around this, run the command with `--tunnel` to access Studio via a secure tunnel. You'll need to manually add the tunnel URL to allowed origins by clicking **Connect to a local server** in the Studio UI. See the [troubleshooting guide](/langsmith/troubleshooting-studio#safari-connection-issues) for steps.
This will start the Agent Server locally, running in-memory. The server will run in watch mode, listening for and automatically restarting on code changes. Read this [reference](/langsmith/cli#dev) to learn about all the options for starting the API server.
You will see the following logs:
```
> Ready!
>
> - API: [http://localhost:2024](http://localhost:2024/)
>
> - Docs: http://localhost:2024/docs
>
> - LangSmith Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
```
Once running, you will automatically be directed to Studio.
2. For a running server, access the Dbugger with one of the following:
1. Directly navigate to the following URL: `https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024`.
2. Navigate to **Deployments** in the UI, click the **Studio** button on a deployment, enter `http://127.0.0.1:2024` and click **Connect**.
If running your server at a different host or port, update the `baseUrl` to match.
### (Optional) Attach a debugger
For step-by-step debugging with breakpoints and variable inspection, run the following:
```bash pip theme={null}
# Install debugpy package
pip install debugpy
# Start server with debugging enabled
langgraph dev --debug-port 5678
```
```bash uv theme={null}
# Install debugpy package
uv add debugpy
# Start server with debugging enabled
langgraph dev --debug-port 5678
```
Then attach your preferred debugger:
Add this configuration to `launch.json`:
```json theme={null}
{
"name": "Attach to LangGraph",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "0.0.0.0",
"port": 5678
}
}
```
1. Go to Run → Edit Configurations
2. Click + and select "Python Debug Server"
3. Set IDE host name: `localhost`
4. Set port: `5678` (or the port number you chose in the previous step)
5. Click "OK" and start debugging
For issues getting started, refer to the [troubleshooting guide](/langsmith/troubleshooting-studio).
## Next steps
For more information on how to run Studio, refer to the following guides:
* [Run application](/langsmith/use-studio#run-application)
* [Manage assistants](/langsmith/use-studio#manage-assistants)
* [Manage threads](/langsmith/use-studio#manage-threads)
* [Iterate on prompts](/langsmith/observability-studio)
* [Debug LangSmith traces](/langsmith/observability-studio#debug-langsmith-traces)
* [Add node to dataset](/langsmith/observability-studio#add-node-to-dataset)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/quick-start-studio.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to handle model rate limits
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/rate-limiting
A common issue when running large evaluation jobs is running into third-party API rate limits, usually from model providers. There are a few ways to deal with rate limits.
## Using `langchain` RateLimiters (Python only)
If you're using `langchain` Python chat models in your application or evaluators, you can add rate limiters to your model(s) that will add client-side control of the frequency with which requests are sent to the model provider API to avoid rate limit errors.
```python theme={null}
from langchain.chat_models import init_chat_model
from langchain_core.rate_limiters import InMemoryRateLimiter
rate_limiter = InMemoryRateLimiter(
requests_per_second=0.1, # <-- Super slow! We can only make a request once every 10 seconds!!
check_every_n_seconds=0.1, # Wake up every 100 ms to check whether allowed to make a request,
max_bucket_size=10, # Controls the maximum burst size.
)
model = init_chat_model("gpt-4.1", rate_limiter=rate_limiter)
def app(inputs: dict) -> dict:
response = model.invoke(...)
...
def evaluator(inputs: dict, outputs: dict, reference_outputs: dict) -> dict:
response = model.invoke(...)
...
```
See the [`langchain`](/oss/python/langchain/models#rate-limiting) documentation for more on how to configure rate limiters.
## Retrying with exponential backoff
A very common way to deal with rate limit errors is retrying with exponential backoff. Retrying with exponential backoff means repeatedly retrying failed requests with an (exponentially) increasing wait time between each retry. This continues until either the request succeeds or a maximum number of requests is made.
#### With `langchain`
If you're using `langchain` components you can add retries to all model calls with the `.with_retry(...)` / `.withRetry()` method:
```python Python theme={null}
from langchain import init_chat_model
model_with_retry = init_chat_model("gpt-4.1-mini").with_retry(stop_after_attempt=6)
```
```typescript TypeScript theme={null}
import { initChatModel } from "langchain";
const model = await initChatModel("gpt-4.1", {
modelProvider: "openai",
});
const modelWithRetry = model.withRetry({ stopAfterAttept: 2 });
```
See the `langchain` [Python](https://reference.langchain.com/python/langchain_core/language_models/#langchain_core.language_models.BaseChatModel.with_retry) and [JS](https://v03.api.js.langchain.com/classes/_langchain_core.language_models_chat_models.BaseChatModel.html#withRetry) API references for more.
#### Without `langchain`
If you're not using `langchain` you can use other libraries like `tenacity` (Python) or `backoff` (Python) to implement retries with exponential backoff, or you can implement it from scratch. See some examples of how to do this in the [OpenAI docs](https://platform.openai.com/docs/guides/rate-limits#retrying-with-exponential-backoff).
## Limiting `max_concurrency`
Limiting the number of concurrent calls you're making to your application and evaluators is another way to decrease the frequency of model calls you're making, and in that way avoid rate limit errors. `max_concurrency` can be set directly on the [evaluate()](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._runner.evaluate) / [aevaluate()](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._arunner.aevaluate) functions. This parallelizes evaluation by effectively splitting the dataset across threads.
```python Python theme={null}
from langsmith import aevaluate
results = await aevaluate(
...
max_concurrency=4,
)
```
```typescript TypeScript theme={null}
import { evaluate } from "langsmith/evaluation";
await evaluate(..., {
...,
maxConcurrency: 4,
});
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/rate-limiting.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Role-based access control
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/rbac
This reference explains LangSmith's Role-Based Access Control (RBAC) system for managing organization-level and workspace-level permissions.
RBAC (Role-Based Access Control) is an Enterprise feature for managing workspace-level permissions. If you are interested in this feature, [contact our sales team](https://www.langchain.com/contact-sales). Other plans default to using the Admin role for all users.
LangSmith's RBAC system manages user permissions within workspaces. RBAC allows you to control who can access your LangSmith [workspace](/langsmith/administration-overview#workspaces) and what they can do within it.
In LangSmith, each user has:
* One [**organization role**](#organization-roles) that applies across the entire organization (separate from workspace RBAC).
* The Organization User and Organization Viewer roles are only available in organizations on [plans](https://langchain.com/pricing) with multiple workspaces. In organizations limited to a single workspace, all users have the Organization Admin role.
* One [**workspace role**](#workspace-roles) per workspace they're a member of (requires Enterprise RBAC feature).
On Enterprise plans, organizations can create [custom workspace roles](#custom-roles) with granular permission combinations.
To learn how to set up RBAC and assign roles to users, refer to the [User Management guide](/langsmith/user-management#set-up-access-control).
For a comprehensive list of required permissions along with the operations and roles that can perform them, refer to the [Organization and workspace reference](/langsmith/organization-workspace-operations).
## Role types
### Organization roles
Organization roles are **distinct from the workspace RBAC feature** and are used to manage organization-wide capabilities. The roles are system-defined and cannot be modified or extended. These roles are available in multi-workspace organizations on [Plus and Enterprise plans](https://langchain.com/pricing).
| Role | Description |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| [Organization Admin](#organization-admin) | Full permissions to manage organization configuration, users, billing, and workspaces |
| [Organization Operator](#organization-operator) | Management access to workspaces and users for day-to-day operations, excluding admin-level privileges |
| [Organization User](#organization-user) | Read access to organization information and ability to create personal access tokens |
| [Organization Viewer](#organization-viewer) | Read-only access to organization information |
In organizations limited to a single workspace, all users are [Organization Admins](#organization-admin).
#### Organization admin
**Description**: Full permissions to manage all organization configuration, users, billing, and workspaces.
**Permissions**:
* `organization:manage` - Full control over organization settings, SSO, security, billing
* `organization:read` - Read access to all organization information
* `organization:pats:create` - Create organization-level [personal access tokens](/langsmith/administration-overview#personal-access-tokens-pats)
For a comprehensive list of required permissions along with the operations and roles that can perform them, refer to the [Organization and workspace reference](/langsmith/organization-workspace-operations).
**Key Capabilities**:
* Manage [organization settings](/langsmith/set-up-hierarchy#set-up-an-organization) and branding
* Configure [SSO and authentication methods](/langsmith/user-management#set-up-saml-sso-for-your-organization)
* Manage [billing](/langsmith/billing) and subscription plans
* Create and delete [workspaces](/langsmith/set-up-hierarchy)
* Invite and remove organization members
* Assign organization and workspace roles to members
* Create and manage [custom roles](#custom-roles)
* Configure RBAC and ABAC (Attribute-Based Access Control) policies (Note that ABAC is in private beta)
* View organization [usage](/langsmith/administration-overview#usage-limits) and analytics
For details on setting up and managing your organization, refer to the [Administration Overview](/langsmith/administration-overview#organizations).
#### Organization Operator
Management access for day-to-day operations including workspace and user management, but cannot manage Organization Admins or create organization-wide API keys.
**Permissions:**
* `organization:manage` - Control over organization settings, workspaces, and non-admin users
* `organization:read` - Read access to all organization information
* `organization:pats:create` - Create personal access tokens
For a comprehensive list of required permissions along with the operations and roles that can perform them, refer to the [Organization and workspace reference](/langsmith/organization-workspace-operations).
**Key Capabilities:**
* Create and manage [workspaces](/langsmith/set-up-hierarchy#set-up-a-workspace)
* Invite organization members (Organization User and Viewer roles only)
* Manage non-admin organization members (modify and remove Organization Users and Viewers)
* Assign workspace roles to members
* Create workspace-scoped API keys and service accounts
* View organization [usage](/langsmith/administration-overview#usage-limits) and analytics
**Restrictions:**
* Cannot invite, modify, or remove Organization Admins
* Cannot assign the Organization Admin role to users
* Cannot create organization-wide (non-workspace-specific) API keys
* Not automatically added to existing workspaces (only added to workspaces they create or are explicitly invited to)
* Cannot manage organization [billing](/langsmith/billing) or subscription plans
* Cannot configure [SSO or authentication methods](/langsmith/user-management#set-up-saml-sso-for-your-organization)
* Cannot create or manage [custom roles](#custom-roles)
#### Organization User
**Description**: Read access to organization information and ability to create personal access tokens.
**Permissions**:
* `organization:read` - Read access to organization information
* `organization:pats:create` - Create personal access tokens
For a comprehensive list of required permissions along with the operations and roles that can perform them, refer to the [Organization and workspace reference](/langsmith/organization-workspace-operations).
**Key Capabilities**:
* View organization members and workspaces
* View organization settings (but not modify)
* Create [personal access tokens](/langsmith/administration-overview#personal-access-tokens-pats) for API access
* Join workspaces they're invited to
**Restrictions**:
* Cannot modify organization settings
* Cannot manage billing or subscriptions
* Cannot create or delete workspaces
* Cannot invite or remove organization members
* Cannot manage roles or permissions
You can add an Organization User to a subset of workspaces and assigned workspace roles (if RBAC is enabled), which specify permissions at the workspace level.
#### Organization viewer
**Description**: Read-only access to organization information.
**Permissions**:
* `organization:read` - Read access to organization information
For a comprehensive list of required permissions along with the operations and roles that can perform them, refer to the [Organization and workspace reference](/langsmith/organization-workspace-operations).
**Key Capabilities**:
* View organization members and workspaces
* View organization settings
**Restrictions**:
* Cannot modify anything at the organization level
* Cannot create personal access tokens
* Cannot manage billing, workspaces, or members
### Workspace roles
Workspace roles are part of the **Enterprise RBAC feature** and control what users can do with resources inside a workspace:
| Role | Description |
| ------------------------------------- | ------------------------------------------------------------------------------------------------- |
| [Workspace Admin](#workspace-admin) | Full permissions for all resources and ability to manage workspace |
| [Workspace Editor](#workspace-editor) | Full permissions for most resources, cannot manage workspace settings or delete certain resources |
| [Workspace Viewer](#workspace-viewer) | Read-only access to all workspace resources |
RBAC (Role-Based Access Control) is a feature that is only available to [Enterprise](https://langchain.com/pricing) customers. If you are interested in this feature, [contact our sales team](https://www.langchain.com/contact-sales). Other plans default to using the Admin role for all users.
#### Workspace admin
**Description**: Role with full permissions for all resources and ability to manage workspace.
**Permissions**:
* All create, read, update, delete, and share permissions for all resource types
* Workspace management capabilities
For a comprehensive list of required permissions along with the operations and roles that can perform them, refer to the [Organization and workspace reference](/langsmith/organization-workspace-operations).
#### Workspace editor
**Description**: Role with full permissions for most resources. Cannot manage workspace settings or delete certain critical resources.
**Key Differences from Admin**:
* Cannot delete [runs](/langsmith/observability#runs)
* Cannot manage workspace settings (add/remove members, change workspace name, etc.)
#### Workspace viewer
**Description**: Read-only access to all workspace resources.
**Permissions**: Read-only access to all resource types.
For a comprehensive list of required permissions along with the operations and roles that can perform them, refer to the [Organization and workspace reference](/langsmith/organization-workspace-operations).
For step-by-step instructions on assigning workspace roles to users, refer to the [User Management guide](/langsmith/user-management#assign-a-role-to-a-user).
## Custom roles
Creating custom roles is available for organizations on the Enterprise plan.
[Organization Admins](#organization-admin) can create custom roles with specific combinations of permissions tailored to their organization's needs.
### Creating custom roles
Custom roles are created at the [organization](/langsmith/administration-overview#organizations) level and can be assigned to users in any [workspace](/langsmith/administration-overview#workspaces) within that organization.
**Steps**:
1. Navigate to Organization **Settings** > **Roles**.
2. Click **Create Custom Role**.
3. Select the permissions to include in the role.
4. Assign the custom role to users in specific workspaces.
For details on which specific permissions are required for each operation, refer to the [Organization and workspace operations reference](/langsmith/organization-workspace-operations).
Note the following details on custom roles:
* Custom roles can only be created and managed by Organization Admins.
* Custom roles are organization-specific (not transferable between organizations).
* Each custom role can have any combination of workspace-level permissions.
* Custom roles cannot have organization-level permissions.
* Users can have different roles (including custom roles) in different workspaces.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/rbac.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to read experiment results locally
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/read-local-experiment-results
When running [evaluations](/langsmith/evaluation-concepts), you may want to process results programmatically in your script rather than viewing them in the [LangSmith UI](https://smith.langchain.com). This is useful for scenarios like:
* **CI/CD pipelines**: Implement quality gates that fail builds if evaluation scores drop below a threshold.
* **Local debugging**: Inspect and analyze results without API calls.
* **Custom aggregations**: Calculate metrics and statistics using your own logic.
* **Integration testing**: Use evaluation results to gate merges or deployments.
This guide shows you how to iterate over and process [experiment](/langsmith/evaluation-concepts#experiment) results from the [`ExperimentResults`](https://reference.langchain.com/python/langsmith/observability/sdk/evaluation/#langsmith.evaluation._runner.ExperimentResults) object returned by [`Client.evaluate()`](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client.evaluate).
This page focuses on processing results programmatically while still uploading them to LangSmith.
If you want to run evaluations locally **without** recording anything to LangSmith (for quick testing or validation), refer to [Run an evaluation locally](/langsmith/local) which uses `upload_results=False`.
## Iterate over evaluation results
The [`evaluate()`](https://reference.langchain.com/python/langsmith/observability/sdk/client/#langsmith.client.Client.evaluate) function returns an [`ExperimentResults`](https://reference.langchain.com/python/langsmith/observability/sdk/evaluation/#langsmith.evaluation._runner.ExperimentResults) object that you can iterate over. The `blocking` parameter controls when results become available:
* `blocking=False`: Returns immediately with an iterator that yields results as they're produced. This allows you to process results in real-time as the evaluation runs.
* `blocking=True` (default): Blocks until all evaluations complete before returning. When you iterate over the results, all data is already available.
Both modes return the same `ExperimentResults` type; the difference is whether the function waits for completion before returning. Use `blocking=False` for streaming and real-time debugging, or `blocking=True` for batch processing when you need the complete dataset.
The following example demonstrates `blocking=False`. It iterates over results as they stream in, collects them in a list, then processes them in a separate loop:
```python theme={null}
from langsmith import Client
import random
client = Client()
def target(inputs):
"""Your application or LLM chain"""
return {"output": "MY OUTPUT"}
def evaluator(run, example):
"""Your evaluator function"""
return {"key": "randomness", "score": random.randint(0, 1)}
# Run evaluation with blocking=False to get an iterator
streamed_results = client.evaluate(
target,
data="MY_DATASET_NAME",
evaluators=[evaluator],
blocking=False
)
# Collect results as they stream in
aggregated_results = []
for result in streamed_results:
aggregated_results.append(result)
# Separate loop to avoid logging at the same time as logs from evaluate()
for result in aggregated_results:
print("Input:", result["run"].inputs)
print("Output:", result["run"].outputs)
print("Evaluation Results:", result["evaluation_results"]["results"])
print("--------------------------------")
```
This produces output like:
```
Input: {'input': 'MY INPUT'}
Output: {'output': 'MY OUTPUT'}
Evaluation Results: [EvaluationResult(key='randomness', score=1, value=None, comment=None, correction=None, evaluator_info={}, feedback_config=None, source_run_id=UUID('7ebb4900-91c0-40b0-bb10-f2f6a451fd3c'), target_run_id=None, extra=None)]
--------------------------------
```
## Understand the result structure
Each result in the iterator contains:
* `result["run"]`: The execution of your target function.
* `result["run"].inputs`: The inputs from your [dataset](/langsmith/evaluation-concepts#datasets) example.
* `result["run"].outputs`: The outputs produced by your target function.
* `result["run"].id`: The unique ID for this run.
* `result["evaluation_results"]["results"]`: A list of `EvaluationResult` objects, one per evaluator.
* `key`: The metric name (from your evaluator's return value).
* `score`: The numeric score (typically 0-1 or boolean).
* `comment`: Optional explanatory text.
* `source_run_id`: The ID of the evaluator run.
* `result["example"]`: The dataset example that was evaluated.
* `result["example"].inputs`: The input values.
* `result["example"].outputs`: The reference outputs (if any).
## Examples
### Implement a quality gate
This example uses evaluation results to pass or fail a CI/CD build automatically based on quality thresholds. The script iterates through results, calculates an average accuracy score, and exits with a non-zero status code if the accuracy falls below 85%. This ensures that you can deploy code changes that meet quality standards.
```python theme={null}
from langsmith import Client
import sys
client = Client()
def my_application(inputs):
# Your application logic
return {"response": "..."}
def accuracy_evaluator(run, example):
# Your evaluation logic
is_correct = run.outputs["response"] == example.outputs["expected"]
return {"key": "accuracy", "score": 1 if is_correct else 0}
# Run evaluation
results = client.evaluate(
my_application,
data="my_test_dataset",
evaluators=[accuracy_evaluator],
blocking=False
)
# Calculate aggregate metrics
total_score = 0
count = 0
for result in results:
eval_result = result["evaluation_results"]["results"][0]
total_score += eval_result.score
count += 1
average_accuracy = total_score / count
print(f"Average accuracy: {average_accuracy:.2%}")
# Fail the build if accuracy is too low
if average_accuracy < 0.85:
print("❌ Evaluation failed! Accuracy below 85% threshold.")
sys.exit(1)
print("✅ Evaluation passed!")
```
### Batch processing with blocking=True
When you need to perform operations that require the complete dataset (like calculating percentiles, sorting by score, or generating summary reports), use `blocking=True` to wait for all evaluations to complete before processing:
```python theme={null}
# Run evaluation and wait for all results
results = client.evaluate(
target,
data=dataset,
evaluators=[evaluator],
blocking=True # Wait for all evaluations to complete
)
# Process all results after evaluation completes
for result in results:
print("Input:", result["run"].inputs)
print("Output:", result["run"].outputs)
# Access individual evaluation results
for eval_result in result["evaluation_results"]["results"]:
print(f" {eval_result.key}: {eval_result.score}")
```
With `blocking=True`, your processing code runs only after all evaluations are complete, avoiding mixed output with evaluation logs.
For more information on running evaluations without uploading results, refer to [Run an evaluation locally](/langsmith/local).
## Related
* [Evaluate your LLM application](/langsmith/evaluate-llm-application)
* [Run an evaluation locally](/langsmith/local)
* [Fetch performance metrics from an experiment](/langsmith/fetch-perf-metrics-experiment)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/read-local-experiment-results.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# LangSmith reference
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/reference
The following sections provide API references and SDK documentation for LangSmith:
Reference documentation for the LangSmith Python SDK.
Reference documentation for the LangSmith JavaScript/TypeScript SDK.
Reference documentation for deploying LangGraph applications with Python.
Reference documentation for deploying LangGraph applications with JavaScript/TypeScript.
Complete REST API reference for LangSmith platform features.
API references for self-hosted and hybrid LangSmith deployments.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/reference.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Regions FAQ
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/regions-faq
See the [cloud architecture reference](/langsmith/cloud#architecture) for additional details.
## Legal and compliance
#### *What privacy and data protection frameworks does LangSmith, including its EU instance, comply with?*
LangSmith complies with the General Data Protection Regulation (GDPR) and other laws and regulations applicable to the LangSmith service. We are also SOC 2 Type 2 certified and are HIPAA compliant. You can request more information about our security policies and posture at [trust.langchain.com](https://trust.langchain.com). If you would like to sign a Data Processing Addendum (DPA) with us, please contact support via [support.langchain.com](https://support.langchain.com). Please note we only enter into Business Associate Agreements (BAAs) with customers on our Enterprise plan.
#### *My company isn't based in the EU, can I still have my data hosted there?*
Yes, you can host your LangSmith data in the EU instance independent of your location.
#### *Do you have a legal entity in the EU that we can contract with?*
We do not have a legal entity in the EU for customer contracting today.
#### *Do different legal terms apply if I choose the EU region?*
The terms are the same for the EU and US regions.
## Features
#### *How do I use the EU instance?*
Follow the [account and API key setup guide](/langsmith/create-account-api-key) to create an account and an API key (make sure to change the region to EU in the dropdown)
#### *Are there any functional differences between US and EU cloud-managed LangSmith?*
There may be a small delay between launches to each region depending on the feature. Besides that, they are functionally equivalent - all features supported in the US are supported in the EU and vice versa.
#### *Can an organization have workspaces in different regions?*
LangSmith does not support this at the moment, but if you are interested, please contact support via [support.langchain.com](https://support.langchain.com) and share your use case.
#### *Can I connect an EU organization to a US organization and share billing?*
LangSmith does not support this at the moment, but if you are interested, please contact support via [support.langchain.com](https://support.langchain.com) and share your use case.
#### *What data will be stored in my selected region?*
See the [cloud architecture reference](/langsmith/cloud#architecture) for details.
#### *How can I see my organization's region?*
Check your URL - any organizations on [https://eu.smith.langchain.com](https://eu.smith.langchain.com) are in the EU, and any on [https://smith.langchain.com](https://smith.langchain.com) are in the US.
#### *Can I switch my organization from the US to EU or vice versa?*
We do not support migration between regions at this time, but if you are interested in this feature, please contact support via [support.langchain.com](https://support.langchain.com).
## Plans and pricing
#### *Is the EU region available on all LangSmith plans?*
Yes, you can sign up for the EU region on all plans including free plans.
#### *Is pricing different for the EU region compared to the US region?*
No, pricing is the same for the EU and US regions.
#### *What currency is used for payment if I use the EU region?*
All LangSmith plans are paid in USD.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/regions-faq.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Reject Concurrent
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/reject-concurrent
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](/langsmith/double-texting).
The guide covers the `reject` option for double texting, which rejects the new run of the graph by throwing an error and continues with the original run until completion. Below is a quick example of using the `reject` option.
## Setup
First, we will define a quick helper function for printing out JS and CURL model outputs (you can skip this if using Python):
```js theme={null}
function prettyPrint(m) {
const padded = " " + m['type'] + " ";
const sepLen = Math.floor((80 - padded.length) / 2);
const sep = "=".repeat(sepLen);
const secondSep = sep + (padded.length % 2 ? "=" : "");
console.log(`${sep}${padded}${secondSep}`);
console.log("\n\n");
console.log(m.content);
}
```
```bash theme={null}
# PLACE THIS IN A FILE CALLED pretty_print.sh
pretty_print() {
local type="$1"
local content="$2"
local padded=" $type "
local total_width=80
local sep_len=$(( (total_width - ${#padded}) / 2 ))
local sep=$(printf '=%.0s' $(eval "echo {1.."${sep_len}"}"))
local second_sep=$sep
if (( (total_width - ${#padded}) % 2 )); then
second_sep="${second_sep}="
fi
echo "${sep}${padded}${second_sep}"
echo
echo "$content"
}
```
Now, let's import our required packages and instantiate our client, assistant, and thread.
```python theme={null}
import httpx
from langchain_core.messages import convert_to_messages
from langgraph_sdk import get_client
client = get_client(url=)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
thread = await client.threads.create()
```
```js theme={null}
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
const thread = await client.threads.create();
```
```bash theme={null}
curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}'
```
## Create runs
Now we can run a thread and try to run a second one with the "reject" option, which should fail since we have already started a run:
```python theme={null}
run = await client.runs.create(
thread["thread_id"],
assistant_id,
input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]},
)
try:
await client.runs.create(
thread["thread_id"],
assistant_id,
input={
"messages": [{"role": "user", "content": "what's the weather in nyc?"}]
},
multitask_strategy="reject",
)
except httpx.HTTPStatusError as e:
print("Failed to start concurrent run", e)
```
```js theme={null}
const run = await client.runs.create(
thread["thread_id"],
assistantId,
input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]},
);
try {
await client.runs.create(
thread["thread_id"],
assistantId,
{
input: {"messages": [{"role": "user", "content": "what's the weather in nyc?"}]},
multitask_strategy:"reject"
},
);
} catch (e) {
console.error("Failed to start concurrent run", e);
}
```
```bash theme={null}
curl --request POST \
--url >/threads//runs \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in sf?\"}]},
}" && curl --request POST \
--url >/threads//runs \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in nyc?\"}]},
\"multitask_strategy\": \"reject\"
}" || { echo "Failed to start concurrent run"; echo "Error: $?" >&2; }
```
Output:
```
Failed to start concurrent run Client error '409 Conflict' for url 'http://localhost:8123/threads/f9e7088b-8028-4e5c-88d2-9cc9a2870e50/runs'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409
```
## View run results
We can verify that the original thread finished executing:
```python theme={null}
# wait until the original run completes
await client.runs.join(thread["thread_id"], run["run_id"])
state = await client.threads.get_state(thread["thread_id"])
for m in convert_to_messages(state["values"]["messages"]):
m.pretty_print()
```
```js theme={null}
await client.runs.join(thread["thread_id"], run["run_id"]);
const state = await client.threads.getState(thread["thread_id"]);
for (const m of state["values"]["messages"]) {
prettyPrint(m);
}
```
```bash theme={null}
source pretty_print.sh && curl --request GET \
--url /threads//runs//join && \
curl --request GET --url /threads//state | \
jq -c '.values.messages[]' | while read -r element; do
type=$(echo "$element" | jq -r '.type')
content=$(echo "$element" | jq -r '.content | if type == "array" then tostring else . end')
pretty_print "$type" "$content"
done
```
Output:
```
================================ Human Message =================================
what's the weather in sf?
================================== Ai Message ==================================
[{'id': 'toolu_01CyewEifV2Kmi7EFKHbMDr1', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
Tool Calls:
tavily_search_results_json (toolu_01CyewEifV2Kmi7EFKHbMDr1)
Call ID: toolu_01CyewEifV2Kmi7EFKHbMDr1
Args:
query: weather in san francisco
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://www.accuweather.com/en/us/san-francisco/94103/june-weather/347629", "content": "Get the monthly weather forecast for San Francisco, CA, including daily high/low, historical averages, to help you plan ahead."}]
================================== Ai Message ==================================
According to the search results from Tavily, the current weather in San Francisco is:
The average high temperature in San Francisco in June is around 65°F (18°C), with average lows around 54°F (12°C). June tends to be one of the cooler and foggier months in San Francisco due to the marine layer of fog that often blankets the city during the summer months.
Some key points about the typical June weather in San Francisco:
* Mild temperatures with highs in the 60s F and lows in the 50s F
* Foggy mornings that often burn off to sunny afternoons
* Little to no rainfall, as June falls in the dry season
* Breezy conditions, with winds off the Pacific Ocean
* Layers are recommended for changing weather conditions
So in summary, you can expect mild, foggy mornings giving way to sunny but cool afternoons in San Francisco this time of year. The marine layer keeps temperatures moderate compared to other parts of California in June.
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/reject-concurrent.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Release versions
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/release-versions
provides different support levels for different versions, which may include new features, bug fixes, or security patches.
## Support levels
There are four support levels:
* Active
* Critical
* End of life (EOL)
* Deprecated
### Active
Where N represents the latest minor version (e.g., 0.3, 0.4, etc.).
The current minor version (N) receives full support, including:
* New features and capabilities
* Bug fixes and regressions
* Security patches
* Quality-of-life improvements
* High confidence changes that are narrowly scoped
### Critical
The previous minor version (N-1) receives limited support:
* Critical security fixes
* Installation fixes
* No new features or general bug fixes
* Transitioned from Active when a newer minor version is released
### End of life (EOL)
Versions older than N-2 (N-2, N-3, etc.) receive no support:
* No new patch releases
* No bug fixes, including known bugs
* No security updates
* Users should upgrade to a supported version
### Deprecated
Versions that are no longer maintained:
* All versions prior to the first stable release
* Versions that have been explicitly deprecated
* No support or maintenance provided
## Version support policy
follows an N-2 support policy for minor versions:
* **N (Current)**: Active support
* **N-1**: Critical support
* **N-2 and older**: End of Life
### Minor version support
Minor versions include new features and capabilities and are supported according to the N-2 policy. When we refer to a minor version, such as v0.3, we always mean its latest available patch release (v0.3.x).
### Patch releases
During the support window for each version:
* **Active Support**: Regular patch releases with bug fixes, regressions, and new features
* **Critical Support**: Security-only releases for critical fixes related to security and installation
* **End of Life**: No new patches released
## Recommendations
* **Stay Current**: We recommend upgrading to the latest minor version to receive full support and access to new features
* **Plan Upgrades**: Monitor the changelog for upcoming version changes and plan upgrades accordingly
* **Security**: Critical security fixes are only provided for Active and Critical support versions
* **Testing**: Test your applications with newer versions before upgrading in production
## Version compatibility
When upgrading between minor versions:
* Review the changelog for breaking changes
* Test your applications thoroughly
* Follow the upgrade guides provided in the documentation
* Consider the support timeline for your current version
## Current version support
To check the current supported versions and their support levels, refer to the [Agent Server Changelog](/langsmith/agent-server-changelog) for the latest release information.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/release-versions.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# RemoteGraph
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/remote-graph
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/remote-graph.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to evaluate with repetitions
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/repetition
Running multiple repetitions can give a more accurate estimate of the performance of your system since LLM outputs are not deterministic. Outputs can differ from one repetition to the next. Repetitions are a way to reduce noise in systems prone to high variability, such as agents.
## Configuring repetitions on an experiment
Add the optional `num_repetitions` param to the `evaluate` / `aevaluate` function ([Python](https://docs.smith.langchain.com/reference/python/evaluation/langsmith.evaluation._runner.evaluate), [TypeScript](https://docs.smith.langchain.com/reference/js/interfaces/evaluation.EvaluateOptions#numrepetitions)) to specify how many times to evaluate over each example in your dataset. For instance, if you have 5 examples in the dataset and set `num_repetitions=5`, each example will be run 5 times, for a total of 25 runs.
```python Python theme={null}
from langsmith import evaluate
results = evaluate(
lambda inputs: label_text(inputs["text"]),
data=dataset_name,
evaluators=[correct_label],
experiment_prefix="Toxic Queries",
num_repetitions=3,
)
```
```typescript TypeScript theme={null}
import { evaluate } from "langsmith/evaluation";
await evaluate((inputs) => labelText(inputs["input"]), {
data: datasetName,
evaluators: [correctLabel],
experimentPrefix: "Toxic Queries",
numRepetitions: 3,
});
```
## Viewing results of experiments run with repetitions
If you've run your experiment with [repetitions](/langsmith/evaluation-concepts#repetitions), there will be arrows in the output results column so you can view outputs in the table. To view each run from the repetition, hover over the output cell and click the expanded view. When you run an experiment with repetitions, LangSmith displays the average for each feedback score in the table. Click on the feedback score to view the feedback scores from individual runs, or to view the standard deviation across repetitions.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/repetition.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Make conversations private
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/resource-auth
In this tutorial, you will extend [the chatbot created in the last tutorial](/langsmith/set-up-custom-auth) to give each user their own private conversations. You'll add [resource-level access control](/langsmith/auth#single-owner-resources) so users can only see their own threads.
## Prerequisites
Before you start this tutorial, ensure you have the [bot from the first tutorial](/langsmith/set-up-custom-auth) running without errors.
## 1. Add resource authorization
Recall that in the last tutorial, the [`Auth`](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth) object lets you register an [authentication function](/langsmith/auth#authentication), which LangSmith uses to validate the bearer tokens in incoming requests. Now you'll use it to register an **authorization** handler.
Authorization handlers are functions that run **after** authentication succeeds. These handlers can add [metadata](/langsmith/auth#filter-operations) to resources (like who owns them) and filter what each user can see.
Update your `src/security/auth.py` and add one authorization handler to run on every request:
```python {highlight={29-39}} title="src/security/auth.py" theme={null}
from langgraph_sdk import Auth
# Keep our test users from the previous tutorial
VALID_TOKENS = {
"user1-token": {"id": "user1", "name": "Alice"},
"user2-token": {"id": "user2", "name": "Bob"},
}
auth = Auth()
@auth.authenticate
async def get_current_user(authorization: str | None) -> Auth.types.MinimalUserDict:
"""Our authentication handler from the previous tutorial."""
assert authorization
scheme, token = authorization.split()
assert scheme.lower() == "bearer"
if token not in VALID_TOKENS:
raise Auth.exceptions.HTTPException(status_code=401, detail="Invalid token")
user_data = VALID_TOKENS[token]
return {
"identity": user_data["id"],
}
@auth.on
async def add_owner(
ctx: Auth.types.AuthContext, # Contains info about the current user
value: dict, # The resource being created/accessed
):
"""Make resources private to their creator."""
# Examples:
# ctx: AuthContext(
# permissions=[],
# user=ProxyUser(
# identity='user1',
# is_authenticated=True,
# display_name='user1'
# ),
# resource='threads',
# action='create_run'
# )
# value:
# {
# 'thread_id': UUID('1e1b2733-303f-4dcd-9620-02d370287d72'),
# 'assistant_id': UUID('fe096781-5601-53d2-b2f6-0d3403f7e9ca'),
# 'run_id': UUID('1efbe268-1627-66d4-aa8d-b956b0f02a41'),
# 'status': 'pending',
# 'metadata': {},
# 'prevent_insert_if_inflight': True,
# 'multitask_strategy': 'reject',
# 'if_not_exists': 'reject',
# 'after_seconds': 0,
# 'kwargs': {
# 'input': {'messages': [{'role': 'user', 'content': 'Hello!'}]},
# 'command': None,
# 'config': {
# 'configurable': {
# 'langgraph_auth_user': ... Your user object...
# 'langgraph_auth_user_id': 'user1'
# }
# },
# 'stream_mode': ['values'],
# 'interrupt_before': None,
# 'interrupt_after': None,
# 'webhook': None,
# 'feedback_keys': None,
# 'temporary': False,
# 'subgraphs': False
# }
# }
# Does 2 things:
# 1. Add the user's ID to the resource's metadata. Each LangGraph resource has a `metadata` dict that persists with the resource.
# this metadata is useful for filtering in read and update operations
# 2. Return a filter that lets users only see their own resources
filters = {"owner": ctx.user.identity}
metadata = value.setdefault("metadata", {})
metadata.update(filters)
# Only let users see their own resources
return filters
```
The handler receives two parameters:
1. `ctx` ([AuthContext](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.types.AuthContext)): contains info about the current `user`, the user's `permissions`, the `resource` ("threads", "crons", "assistants"), and the `action` being taken ("create", "read", "update", "delete", "search", "create\_run")
2. `value` (`dict`): data that is being created or accessed. The contents of this dict depend on the resource and action being accessed. See [adding scoped authorization handlers](#scoped-authorization) below for information on how to get more tightly scoped access control.
Notice that the simple handler does two things:
1. Adds the user's ID to the resource's metadata.
2. Returns a metadata filter so users only see resources they own.
## 2. Test private conversations
Test your authorization. If you have set things up correctly, you will see all ✅ messages. Be sure to have your development server running (run `langgraph dev`):
```python theme={null}
from langgraph_sdk import get_client
# Create clients for both users
alice = get_client(
url="http://localhost:2024",
headers={"Authorization": "Bearer user1-token"}
)
bob = get_client(
url="http://localhost:2024",
headers={"Authorization": "Bearer user2-token"}
)
# Alice creates an assistant
alice_assistant = await alice.assistants.create()
print(f"✅ Alice created assistant: {alice_assistant['assistant_id']}")
# Alice creates a thread and chats
alice_thread = await alice.threads.create()
print(f"✅ Alice created thread: {alice_thread['thread_id']}")
await alice.runs.create(
thread_id=alice_thread["thread_id"],
assistant_id="agent",
input={"messages": [{"role": "user", "content": "Hi, this is Alice's private chat"}]}
)
# Bob tries to access Alice's thread
try:
await bob.threads.get(alice_thread["thread_id"])
print("❌ Bob shouldn't see Alice's thread!")
except Exception as e:
print("✅ Bob correctly denied access:", e)
# Bob creates his own thread
bob_thread = await bob.threads.create()
await bob.runs.create(
thread_id=bob_thread["thread_id"],
assistant_id="agent",
input={"messages": [{"role": "user", "content": "Hi, this is Bob's private chat"}]}
)
print(f"✅ Bob created his own thread: {bob_thread['thread_id']}")
# List threads - each user only sees their own
alice_threads = await alice.threads.search()
bob_threads = await bob.threads.search()
print(f"✅ Alice sees {len(alice_threads)} thread")
print(f"✅ Bob sees {len(bob_threads)} thread")
```
Output:
```bash theme={null}
✅ Alice created assistant: fc50fb08-78da-45a9-93cc-1d3928a3fc37
✅ Alice created thread: 533179b7-05bc-4d48-b47a-a83cbdb5781d
✅ Bob correctly denied access: Client error '404 Not Found' for url 'http://localhost:2024/threads/533179b7-05bc-4d48-b47a-a83cbdb5781d'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
✅ Bob created his own thread: 437c36ed-dd45-4a1e-b484-28ba6eca8819
✅ Alice sees 1 thread
✅ Bob sees 1 thread
```
This means:
1. Each user can create and chat in their own threads
2. Users can't see each other's threads
3. Listing threads only shows your own
## 3. Add scoped authorization handlers
The broad `@auth.on` handler matches on all [authorization events](/langsmith/auth#supported-resources). This is concise, but it means the contents of the `value` dict are not well-scoped, and the same user-level access control is applied to every resource. If you want to be more fine-grained, you can also control specific actions on resources.
Update `src/security/auth.py` to add handlers for specific resource types:
```python theme={null}
# Keep our previous handlers...
from langgraph_sdk import Auth
@auth.on.threads.create
async def on_thread_create(
ctx: Auth.types.AuthContext,
value: Auth.types.on.threads.create.value,
):
"""Add owner when creating threads.
This handler runs when creating new threads and does two things:
1. Sets metadata on the thread being created to track ownership
2. Returns a filter that ensures only the creator can access it
"""
# Example value:
# {'thread_id': UUID('99b045bc-b90b-41a8-b882-dabc541cf740'), 'metadata': {}, 'if_exists': 'raise'}
# Add owner metadata to the thread being created
# This metadata is stored with the thread and persists
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
# Return filter to restrict access to just the creator
return {"owner": ctx.user.identity}
@auth.on.threads.read
async def on_thread_read(
ctx: Auth.types.AuthContext,
value: Auth.types.on.threads.read.value,
):
"""Only let users read their own threads.
This handler runs on read operations. We don't need to set
metadata since the thread already exists - we just need to
return a filter to ensure users can only see their own threads.
"""
return {"owner": ctx.user.identity}
@auth.on.assistants
async def on_assistants(
ctx: Auth.types.AuthContext,
value: Auth.types.on.assistants.value,
):
# For illustration purposes, we will deny all requests
# that touch the assistants resource
# Example value:
# {
# 'assistant_id': UUID('63ba56c3-b074-4212-96e2-cc333bbc4eb4'),
# 'graph_id': 'agent',
# 'config': {},
# 'metadata': {},
# 'name': 'Untitled'
# }
raise Auth.exceptions.HTTPException(
status_code=403,
detail="User lacks the required permissions.",
)
# Assumes you organize information in store like (user_id, resource_type, resource_id)
@auth.on.store()
async def authorize_store(ctx: Auth.types.AuthContext, value: dict):
# The "namespace" field for each store item is a tuple you can think of as the directory of an item.
namespace: tuple = value["namespace"]
assert namespace[0] == ctx.user.identity, "Not authorized"
```
Notice that instead of one global handler, you now have specific handlers for:
1. Creating threads
2. Reading threads
3. Accessing assistants
The first three of these match specific **actions** on each resource (see [resource actions](/langsmith/auth#resource-specific-handlers)), while the last one (`@auth.on.assistants`) matches *any* action on the `assistants` resource. For each request, LangGraph will run the most specific handler that matches the resource and action being accessed. This means that the four handlers above will run rather than the broadly scoped "`@auth.on`" handler.
Try adding the following test code to your test file:
```python theme={null}
# ... Same as before
# Try creating an assistant. This should fail
try:
await alice.assistants.create("agent")
print("❌ Alice shouldn't be able to create assistants!")
except Exception as e:
print("✅ Alice correctly denied access:", e)
# Try searching for assistants. This also should fail
try:
await alice.assistants.search()
print("❌ Alice shouldn't be able to search assistants!")
except Exception as e:
print("✅ Alice correctly denied access to searching assistants:", e)
# Alice can still create threads
alice_thread = await alice.threads.create()
print(f"✅ Alice created thread: {alice_thread['thread_id']}")
```
Output:
```bash theme={null}
✅ Alice created thread: dcea5cd8-eb70-4a01-a4b6-643b14e8f754
✅ Bob correctly denied access: Client error '404 Not Found' for url 'http://localhost:2024/threads/dcea5cd8-eb70-4a01-a4b6-643b14e8f754'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
✅ Bob created his own thread: 400f8d41-e946-429f-8f93-4fe395bc3eed
✅ Alice sees 1 thread
✅ Bob sees 1 thread
✅ Alice correctly denied access:
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500
✅ Alice correctly denied access to searching assistants:
```
Congratulations! You've built a chatbot where each user has their own private conversations. While this system uses simple token-based authentication, these authorization patterns will work with implementing any real authentication system. In the next tutorial, you'll replace your test users with real user accounts using OAuth2.
## Next steps
Now that you can control access to resources, you might want to:
1. Move on to [Connect an authentication provider](/langsmith/add-auth-server) to add real user accounts.
2. Read more about [authorization patterns](/langsmith/auth#authorization).
3. Check out the [API reference](https://reference.langchain.com/python/langsmith/deployment/sdk/#langgraph_sdk.auth.Auth) for details about the interfaces and methods used in this tutorial.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/resource-auth.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Rollback Concurrent
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/rollback-concurrent
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](/langsmith/double-texting).
The guide covers the `rollback` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option is very similar to the `interrupt` option, but in this case the first run is completely deleted from the database and cannot be restarted. Below is a quick example of using the `rollback` option.
## Setup
First, we will define a quick helper function for printing out JS and CURL model outputs (you can skip this if using Python):
```js theme={null}
function prettyPrint(m) {
const padded = " " + m['type'] + " ";
const sepLen = Math.floor((80 - padded.length) / 2);
const sep = "=".repeat(sepLen);
const secondSep = sep + (padded.length % 2 ? "=" : "");
console.log(`${sep}${padded}${secondSep}`);
console.log("\n\n");
console.log(m.content);
}
```
```bash theme={null}
# PLACE THIS IN A FILE CALLED pretty_print.sh
pretty_print() {
local type="$1"
local content="$2"
local padded=" $type "
local total_width=80
local sep_len=$(( (total_width - ${#padded}) / 2 ))
local sep=$(printf '=%.0s' $(eval "echo {1.."${sep_len}"}"))
local second_sep=$sep
if (( (total_width - ${#padded}) % 2 )); then
second_sep="${second_sep}="
fi
echo "${sep}${padded}${second_sep}"
echo
echo "$content"
}
```
Now, let's import our required packages and instantiate our client, assistant, and thread.
```python theme={null}
import asyncio
import httpx
from langchain_core.messages import convert_to_messages
from langgraph_sdk import get_client
client = get_client(url=)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
thread = await client.threads.create()
```
```js theme={null}
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
const thread = await client.threads.create();
```
```bash theme={null}
curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}'
```
## Create runs
Now let's run a thread with the multitask parameter set to "rollback":
```python theme={null}
# the first run will be rolled back
rolled_back_run = await client.runs.create(
thread["thread_id"],
assistant_id,
input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]},
)
run = await client.runs.create(
thread["thread_id"],
assistant_id,
input={"messages": [{"role": "user", "content": "what's the weather in nyc?"}]},
multitask_strategy="rollback",
)
# wait until the second run completes
await client.runs.join(thread["thread_id"], run["run_id"])
```
```js theme={null}
// the first run will be interrupted
let rolledBackRun = await client.runs.create(
thread["thread_id"],
assistantId,
{ input: { messages: [{ role: "human", content: "what's the weather in sf?" }] } }
);
let run = await client.runs.create(
thread["thread_id"],
assistant_id,
{
input: { messages: [{ role: "human", content: "what's the weather in nyc?" }] },
multitaskStrategy: "rollback"
}
);
// wait until the second run completes
await client.runs.join(thread["thread_id"], run["run_id"]);
```
```bash theme={null}
curl --request POST \
--url >/threads//runs \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in sf?\"}]},
}" && curl --request POST \
--url >/threads//runs \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in nyc?\"}]},
\"multitask_strategy\": \"rollback\"
}" && curl --request GET \
--url /threads//runs//join
```
## View run results
We can see that the thread has data only from the second run
```python theme={null}
state = await client.threads.get_state(thread["thread_id"])
for m in convert_to_messages(state["values"]["messages"]):
m.pretty_print()
```
```js theme={null}
const state = await client.threads.getState(thread["thread_id"]);
for (const m of state['values']['messages']) {
prettyPrint(m);
}
```
```bash theme={null}
source pretty_print.sh && curl --request GET \
--url /threads//state | \
jq -c '.values.messages[]' | while read -r element; do
type=$(echo "$element" | jq -r '.type')
content=$(echo "$element" | jq -r '.content | if type == "array" then tostring else . end')
pretty_print "$type" "$content"
done
```
Output:
```
================================ Human Message =================================
what's the weather in nyc?
================================== Ai Message ==================================
[{'id': 'toolu_01JzPqefao1gxwajHQ3Yh3JD', 'input': {'query': 'weather in nyc'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
Tool Calls:
tavily_search_results_json (toolu_01JzPqefao1gxwajHQ3Yh3JD)
Call ID: toolu_01JzPqefao1gxwajHQ3Yh3JD
Args:
query: weather in nyc
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://www.weatherapi.com/", "content": "{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.71, 'lon': -74.01, 'tz_id': 'America/New_York', 'localtime_epoch': 1718734479, 'localtime': '2024-06-18 14:14'}, 'current': {'last_updated_epoch': 1718733600, 'last_updated': '2024-06-18 14:00', 'temp_c': 29.4, 'temp_f': 84.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 158, 'wind_dir': 'SSE', 'pressure_mb': 1025.0, 'pressure_in': 30.26, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 63, 'cloud': 0, 'feelslike_c': 31.3, 'feelslike_f': 88.3, 'windchill_c': 28.3, 'windchill_f': 82.9, 'heatindex_c': 29.6, 'heatindex_f': 85.3, 'dewpoint_c': 18.4, 'dewpoint_f': 65.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 7.0, 'gust_mph': 16.5, 'gust_kph': 26.5}}"}]
================================== Ai Message ==================================
The weather API results show that the current weather in New York City is sunny with a temperature of around 85°F (29°C). The wind is light at around 2-3 mph from the south-southeast. Overall it looks like a nice sunny summer day in NYC.
```
Verify that the original, rolled back run was deleted
```python theme={null}
try:
await client.runs.get(thread["thread_id"], rolled_back_run["run_id"])
except httpx.HTTPStatusError as _:
print("Original run was correctly deleted")
```
```js theme={null}
try {
await client.runs.get(thread["thread_id"], rolledBackRun["run_id"]);
} catch (e) {
console.log("Original run was correctly deleted");
}
```
Output:
```
Original run was correctly deleted
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/rollback-concurrent.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Set up automation rules
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/rules
While you can manually sift through and process production logs from your LLM application, it often becomes difficult as your application scales to more users.
LangSmith provides a powerful feature called **Automations** that allow you to trigger certain actions on your trace data.
At a high level, automations are defined by a **filter**, **sampling rate**, and **action**.
Automation rules can trigger actions such as: adding traces to a dataset, adding to an annotation queue, triggering a webhook (e.g. for remote evaluations) or extending data retention. Some examples of automations you can set up:
* Send all traces with negative feedback to an annotation queue for human review
* Send 10% of all traces to an annotation queue for human review to spot check for issues
* Upgrade all traces with errors for extended data retention
To configure online evaluations, visit the [online evaluations](/langsmith/online-evaluations-llm-as-judge) page.
If an automation rule matches any run within a trace, the trace will be auto-upgraded to [extended data retention](/langsmith/administration-overview#data-retention-auto-upgrades). This upgrade will impact trace pricing, but ensures that traces meeting your automation criteria (typically those most valuable for analysis) are preserved for investigation.
## View automation rules
Head to the **Tracing Projects** tab and select a tracing project. To view existing automation rules for that tracing project, click on the **Automations** tab.
## Create a rule
#### 1. Navigate to rule creation
Head to the **Tracing Projects** tab and select a tracing project. Click on **+ New** in the top right corner of the tracing project page, then click on **New Automation**.
#### 2. Name your rule
#### 3. Create a filter
Automation rule filters work the same way as filters applied to traces in the project. For more information on filters, you can refer to [this guide](./filter-traces-in-application)
#### 4. Configure a sampling rate
Configure a sampling rate to control the percentage of filtered runs that trigger the automation action.
You can specify a sampling rate between 0 and 1 for automations. This will control the percent of the filtered runs that are sent to an automation action. For example, if you set the sampling rate to 0.5, then 50% of the traces that pass the filter will be sent to the action.
#### 5. (Optional) Apply rule to past runs
Apply rule to past runs by toggling the **Apply to past runs** and entering a "Backfill from" date. This is only possible upon rule creation. Note: the backfill is processed as a background job, so you will not see the results immediately. In order to track progress of the backfill, you can [view logs for your automations](./rules#view-logs-for-your-automations)
#### 6. Select an action to trigger when the rule is applied.
There are four actions you can take with an automation rule:
* **Add to dataset**: Add the inputs and outputs of the trace to a [dataset](/langsmith/evaluation-concepts#datasets).
* **Add to annotation queue**: Add the trace to an [annotation queue](/langsmith/evaluation-concepts#annotation-queues).
* **Trigger webhook**: Trigger a webhook with the trace data. For more information on webhooks, you can refer to [this guide](./webhooks).
* **Extend data retention**: Extends the data retention period on matching traces that use base retention [(see data retention docs for more details)](/langsmith/administration-overview#data-retention).
Note that all other rules will also extend data retention on matching traces through the
auto-upgrade mechanism described in the aforementioned data retention docs,
but this rule takes no additional action.
## View logs for your automations
Logs allow you to gain confidence that your rules are working as expected. You can view logs for your automations by heading to the **Automations** tab within a tracing project and clicking the **Logs** button for the rule you created.
The logs tab allows you to:
* View all runs processed by a given rule for the time period selected
* If a particular rule execution has triggered an error, you can view the error message by hovering over the error icon
* You can monitor the progress of a backfill job by filtering to the rule's creation timestamp. This is because the backfill starts from when the rule was created.
* Inspect the run that the automation rule applied to using the **View run** button. For rules that add runs as examples to datasets, you can view the example produced.
## Video guide
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/rules.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Run backtests on a new version of an agent
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/run-backtests-new-agent
Deploying your application is just the beginning of a continuous improvement process. After you deploy to production, you'll want to refine your system by enhancing prompts, language models, tools, and architectures. Backtesting involves assessing new versions of your application using historical data and comparing the new outputs to the original ones. Compared to evaluations using pre-production datasets, backtesting offers a clearer indication of whether the new version of your application is an improvement over the current deployment.
Here are the basic steps for backtesting:
1. Select sample runs from your production tracing project to test against.
2. Transform the run inputs into a dataset and record the run outputs as an initial experiment against that dataset.
3. Execute your new system on the new dataset and compare the results of the experiments.
This process will provide you with a new dataset of representative inputs, which you can version and use for backtesting your models.
Often, you won't have definitive "ground truth" answers available. In such cases, you can manually label the outputs or use evaluators that don't rely on reference data. If your application allows for capturing ground-truth labels, for example by allowing users to leave feedback, we strongly recommend doing so.
## Setup
### Configure the environment
Install and set environment variables. This guide requires `langsmith>=0.2.4`.
For convenience we'll use the LangChain OSS framework in this tutorial, but the LangSmith functionality shown is framework-agnostic.
```bash pip theme={null}
pip install -U langsmith langchain langchain-anthropic langchainhub emoji
```
```bash uv theme={null}
uv add langsmith langchain langchain-anthropic langchainhub emoji
```
```python theme={null}
import getpass
import os
# Set the project name to whichever project you'd like to be testing against
project_name = "Tweet Writing Task"
os.environ["LANGSMITH_PROJECT"] = project_name
os.environ["LANGSMITH_TRACING"] = "true"
if not os.environ.get("LANGSMITH_API_KEY"):
os.environ["LANGSMITH_API_KEY"] = getpass.getpass("YOUR API KEY")
# Optional. You can swap OpenAI for any other tool-calling chat model.
os.environ["OPENAI_API_KEY"] = "YOUR OPENAI API KEY"
# Optional. You can swap Tavily for the free DuckDuckGo search tool if preferred.
# Get Tavily API key: https://tavily.com
os.environ["TAVILY_API_KEY"] = "YOUR TAVILY API KEY"
```
### Define the application
For this example lets create a simple Tweet-writing application that has access to some internet search tools:
```python theme={null}
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
from langchain_community.tools import DuckDuckGoSearchRun, TavilySearchResults
from langchain_core.rate_limiters import InMemoryRateLimiter
# We will use GPT-3.5 Turbo as the baseline and compare against GPT-4o
gpt_3_5_turbo = init_chat_model(
"gpt-3.5-turbo",
temperature=1,
configurable_fields=("model", "model_provider"),
)
# The instrucitons are passed as a system message to the agent
instructions = """You are a tweet writing assistant. Given a topic, do some research and write a relevant and engaging tweet about it.
- Use at least 3 emojis in each tweet
- The tweet should be no longer than 280 characters
- Always use the search tool to gather recent information on the tweet topic
- Write the tweet only based on the search content. Do not rely on your internal knowledge
- When relevant, link to your sources
- Make your tweet as engaging as possible"""
# Define the tools our agent can use
# If you have a higher tiered Tavily API plan you can increase this
rate_limiter = InMemoryRateLimiter(requests_per_second=0.08)
# Use DuckDuckGo if you don't have a Tavily API key:
# tools = [DuckDuckGoSearchRun(rate_limiter=rate_limiter)]
tools = [TavilySearchResults(max_results=5, rate_limiter=rate_limiter)]
agent = create_agent(gpt_3_5_turbo, tools=tools, system_prompt=instructions)
```
### Simulate production data
Now lets simulate some production data:
```python theme={null}
fake_production_inputs = [
"Alan turing's early childhood",
"Economic impacts of the European Union",
"Underrated philosophers",
"History of the Roxie theater in San Francisco",
"ELI5: gravitational waves",
"The arguments for and against a parliamentary system",
"Pivotal moments in music history",
"Big ideas in programming languages",
"Big questions in biology",
"The relationship between math and reality",
"What makes someone funny",
]
agent.batch(
[{"messages": [{"role": "user", "content": content}]} for content in fake_production_inputs],
)
```
## Convert production traces to experiment
The first step is to generate a dataset based on the production *inputs*. Then copy over all the traces to serve as a baseline experiment.
### Select runs to backtest on
You can select the runs to backtest on using the `filter` argument of `list_runs`. The `filter` argument uses the LangSmith [trace query syntax](/langsmith/trace-query-syntax) to select runs.
```python theme={null}
from datetime import datetime, timedelta, timezone
from uuid import uuid4
from langsmith import Client
from langsmith.beta import convert_runs_to_test
# Fetch the runs we want to convert to a dataset/experiment
client = Client()
# How we are sampling runs to include in our dataset
end_time = datetime.now(tz=timezone.utc)
start_time = end_time - timedelta(days=1)
run_filter = f'and(gt(start_time, "{start_time.isoformat()}"), lt(end_time, "{end_time.isoformat()}"))'
prod_runs = list(
client.list_runs(
project_name=project_name,
is_root=True,
filter=run_filter,
)
)
```
### Convert runs to experiment
`convert_runs_to_test` is a function which takes some runs and does the following:
1. The inputs, and optionally the outputs, are saved to a dataset as Examples.
2. The inputs and outputs are stored as an experiment, as if you had run the `evaluate` function and received those outputs.
```python theme={null}
# Name of the dataset we want to create
dataset_name = f'{project_name}-backtesting {start_time.strftime("%Y-%m-%d")}-{end_time.strftime("%Y-%m-%d")}'
# Name of the experiment we want to create from the historical runs
baseline_experiment_name = f"prod-baseline-gpt-3.5-turbo-{str(uuid4())[:4]}"
# This converts the runs to a dataset + experiment
convert_runs_to_test(
prod_runs,
# Name of the resulting dataset
dataset_name=dataset_name,
# Whether to include the run outputs as reference/ground truth
include_outputs=False,
# Whether to include the full traces in the resulting experiment
# (default is to just include the root run)
load_child_runs=True,
# Name of the experiment so we can apply evalautors to it after
test_project_name=baseline_experiment_name
)
```
Once this step is complete, you should see a new dataset in your LangSmith project called "Tweet Writing Task-backtesting TODAYS DATE", with a single experiment like so:
## Benchmark against new system
Now we can start the process of benchmarking our production runs against a new system.
### Define evaluators
First let's define the evaluators we will use to compare the two systems. Note that we have no reference outputs, so we'll need to come up with evaluation metrics that only require the actual outputs.
```python theme={null}
import emoji
from pydantic import BaseModel, Field
from langchain_core.messages import convert_to_openai_messages
class Grade(BaseModel):
"""Grade whether a response is supported by some context."""
grounded: bool = Field(..., description="Is the majority of the response supported by the retrieved context?")
grounded_instructions = f"""You have given somebody some contextual information and asked them to write a statement grounded in that context.
Grade whether their response is fully supported by the context you have provided. \
If any meaningful part of their statement is not backed up directly by the context you provided, then their response is not grounded. \
Otherwise it is grounded."""
grounded_model = init_chat_model(model="gpt-4.1").with_structured_output(Grade)
def lt_280_chars(outputs: dict) -> bool:
messages = convert_to_openai_messages(outputs["messages"])
return len(messages[-1]['content']) <= 280
def gte_3_emojis(outputs: dict) -> bool:
messages = convert_to_openai_messages(outputs["messages"])
return len(emoji.emoji_list(messages[-1]['content'])) >= 3
async def is_grounded(outputs: dict) -> bool:
context = ""
messages = convert_to_openai_messages(outputs["messages"])
for message in messages:
if message["role"] == "tool":
# Tool message outputs are the results returned from the Tavily/DuckDuckGo tool
context += "\n\n" + message["content"]
tweet = messages[-1]["content"]
user = f"""CONTEXT PROVIDED:
{context}
RESPONSE GIVEN:
{tweet}"""
grade = await grounded_model.ainvoke([
{"role": "system", "content": grounded_instructions},
{"role": "user", "content": user}
])
return grade.grounded
```
### Evaluate baseline
Now, let's run our evaluators against the baseline experiment.
```python theme={null}
baseline_results = await client.aevaluate(
baseline_experiment_name,
evaluators=[lt_280_chars, gte_3_emojis, is_grounded],
)
# If you have pandas installed can easily explore results as df:
# baseline_results.to_pandas()
```
### Define and evaluate new system
Now, let's define and evaluate our new system. In this example our new system will be the same as the old system, but will use GPT-4o instead of GPT-3.5. Since we've made our model configurable we can just update the default config passed to our agent:
```python theme={null}
candidate_results = await client.aevaluate(
agent.with_config(model="gpt-4.1"),
data=dataset_name,
evaluators=[lt_280_chars, gte_3_emojis, is_grounded],
experiment_prefix="candidate-gpt-4.1",
)
# If you have pandas installed can easily explore results as df:
# candidate_results.to_pandas()
```
## Comparing the results
After running both experiments, you can view them in your dataset:
The results reveal an interesting tradeoff between the two models:
1. GPT-4o shows improved performance in following formatting rules, consistently including the requested number of emojis
2. However, GPT-4o is less reliable at staying grounded in the provided search results
To illustrate the grounding issue: in [this example run](https://smith.langchain.com/public/be060e19-0bc0-4798-94f5-c3d35719a5f6/r/07d43e7a-8632-479d-ae28-c7eac6e54da4), GPT-4o included facts about Abū Bakr Muhammad ibn Zakariyyā al-Rāzī's medical contributions that weren't present in the search results. This demonstrates how it's pulling from its internal knowledge rather than strictly using the provided information.
This backtesting exercise revealed that while GPT-4o is generally considered a more capable model, simply upgrading to it wouldn't improve our tweet-writer. To effectively use GPT-4o, we would need to:
* Refine our prompts to more strongly emphasize using only provided information
* Or modify our system architecture to better constrain the model's outputs
This insight demonstrates the value of backtesting - it helped us identify potential issues before deployment.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/run-backtests-new-agent.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Run (span) data format
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/run-data-format
Before diving into this content, it might be helpful to read the following:
* [Conceptual guide on tracing and runs](/langsmith/observability-concepts)
LangSmith stores and processes trace data in a simple format that is easy to export and import.
Many of these fields are optional or not important to know about but are included for completeness.
| Field Name | Type | Description |
| ------------------------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`id`** | UUID | Unique identifier for the span. |
| **`name`** | string | The name associated with the run. |
| **`inputs`** | object | A map or set of inputs provided to the run. |
| **`run_type`** | string | Type of run, e.g., `'llm'`, `'chain'`, `'tool'`. |
| **`start_time`** | datetime | Start time of the run. |
| **`end_time`** | datetime | End time of the run. |
| **`extra`** | object | Any extra information run. |
| **`error`** | string | Error message if the run encountered an error. |
| **`outputs`** | object | A map or set of outputs generated by the run. |
| **`events`** | array of objects | A list of event objects associated with the run. This is relevant for runs executed with streaming. |
| **`tags`** | array of strings | Tags or labels associated with the run. |
| **`trace_id`** | UUID | Unique identifier for the trace the run is a part of. This is also the `id` field of the root run of the trace |
| **`dotted_order`** | string | Ordering string, hierarchical. Format: `Z`.`Z`... |
| **`status`** | string | Current status of the run execution, e.g., `'error'`, `'pending'`, `'success'` |
| **`child_run_ids`** | array of UUIDs | List of IDs for all child runs. |
| **`direct_child_run_ids`** | array of UUIDs | List of IDs for direct children of this run. |
| **`parent_run_ids`** | array of UUIDs | List of IDs for all parent runs. |
| **`feedback_stats`** | object | Aggregations of feedback statistics for this run |
| **`reference_example_id`** | UUID | ID of a reference example associated with the run. This is usually only present for evaluation runs. |
| **`total_tokens`** | integer | Total number of tokens processed by the run. |
| **`prompt_tokens`** | integer | Number of tokens in the prompt of the run. |
| **`completion_tokens`** | integer | Number of tokens in the completion of the run. |
| **`total_cost`** | decimal | Total cost associated with processing the run. |
| **`prompt_cost`** | decimal | Cost associated with the prompt part of the run. |
| **`completion_cost`** | decimal | Cost associated with the completion of the run. |
| **`first_token_time`** | datetime | Time when the first token of a model output was generated. Only applies for runs with `run_type="llm"` and streaming enabled. |
| **`session_id`** | string | Session identifier for the run, also known as the tracing project ID. |
| **`in_dataset`** | boolean | Indicates whether the run is included in a dataset. |
| **`parent_run_id`** | UUID | Unique identifier of the parent run. |
| `execution_order` (deprecated) | integer | The order in which this run was executed within the trace. |
| `serialized` | object | Serialized state of the object executing the run if applicable. |
| `manifest_id` (deprecated) | UUID | Identifier for a manifest associated with the span. |
| `manifest_s3_id` | UUID | S3 identifier for the manifest. |
| `inputs_s3_urls` | object | S3 URLs for the inputs. |
| `outputs_s3_urls` | object | S3 URLs for the outputs. |
| `price_model_id` | UUID | Identifier for the pricing model applied to the run. |
| `app_path` | string | Application (UI) path for this run. |
| `last_queued_at` | datetime | Last time the span was queued. |
| `share_token` | string | Token for sharing access to the run's data. |
Here is an example of a JSON representation of a run in the above format:
```json theme={null}
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"inputs": {},
"run_type": "llm",
"start_time": "2024-04-29T00:49:12.090000",
"end_time": "2024-04-29T00:49:12.459000",
"extra": {},
"error": "string",
"execution_order": 1,
"serialized": {},
"outputs": {},
"parent_run_id": "f8faf8c1-9778-49a4-9004-628cdb0047e5",
"manifest_id": "82825e8e-31fc-47d5-83ce-cd926068341e",
"manifest_s3_id": "0454f93b-7eb6-4b9d-a203-f1261e686840",
"events": [{}],
"tags": ["foo"],
"inputs_s3_urls": {},
"outputs_s3_urls": {},
"trace_id": "df570c03-5a03-4cea-8df0-c162d05127ac",
"dotted_order": "20240429T004912090000Z497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "string",
"child_run_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
"direct_child_run_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
"parent_run_ids": ["f8faf8c1-9778-49a4-9004-628cdb0047e5"],
"feedback_stats": {
"correctness": {
"n": 1,
"avg": 1.0
}
},
"reference_example_id": "9fb06aaa-105f-4c87-845f-47d62ffd7ee6",
"total_tokens": 0,
"prompt_tokens": 0,
"completion_tokens": 0,
"total_cost": 0.0,
"prompt_cost": 0.0,
"completion_cost": 0.0,
"price_model_id": "0b5d9575-bec3-4256-b43a-05893b8b8440",
"first_token_time": null,
"session_id": "1ffd059c-17ea-40a8-8aef-70fd0307db82",
"app_path": "string",
"last_queued_at": null,
"in_dataset": true,
"share_token": "d0430ac3-04a1-4e32-a7ea-57776ad22c1c"
}
```
#### What is `dotted_order`?
A run's dotted order is a sortable key that fully specifies its location within the tracing hierarchy.
Take the following example:
```python theme={null}
import langsmith as ls
@ls.traceable
def grandchild():
p("grandchild")
@ls.traceable
def child():
grandchild()
@ls.traceable
def parent():
child()
```
If you print out the IDs at each stage, you may get the following:
```python theme={null}
parent run_id=0e01bf50-474d-4536-810f-67d3ee7ea3e7 trace_id=0e01bf50-474d-4536-810f-67d3ee7ea3e7 parent_run_id=null dotted_order=20240919T171648521691Z0e01bf50-474d-4536-810f-67d3ee7ea3e7
child run_id=a8024e23-5b82-47fd-970e-f6a5ba3f5097 trace_id=0e01bf50-474d-4536-810f-67d3ee7ea3e7 parent_run_id=0e01bf50-474d-4536-810f-67d3ee7ea3e7 dotted_order=20240919T171648521691Z0e01bf50-474d-4536-810f-67d3ee7ea3e7.20240919T171648523407Za8024e23-5b82-47fd-970e-f6a5ba3f5097
grandchild run_id=0ec6b845-18b9-4aa1-8f1b-6ba3f9fdefd6 trace_id=0e01bf50-474d-4536-810f-67d3ee7ea3e7 parent_run_id=a8024e23-5b82-47fd-970e-f6a5ba3f5097 dotted_order=20240919T171648521691Z0e01bf50-474d-4536-810f-67d3ee7ea3e7.20240919T171648523407Za8024e23-5b82-47fd-970e-f6a5ba3f5097.20240919T171648523563Z0ec6b845-18b9-4aa1-8f1b-6ba3f9fdefd6
```
Note a few invariants:
* The `id` is equal to the last 36 characters of the dotted order (the suffix after the final `'Z'`). See `0ec6b845-18b9-4aa1-8f1b-6ba3f9fdefd6` for example in the grandchild.
* The `trace_id` is equal to the first UUID in the dotted order (i.e., `dotted_order.split('.')[0].split('Z')[1]`)
* If `parent_run_id` exists, it is the penultimate UUID in the dotted order. See `a8024e23-5b82-47fd-970e-f6a5ba3f5097` in the grandchild, for an example.
* If you split the `dotted_order` on the dots, each segment is formatted as (`Z`)
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/run-data-format.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to use the REST API
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/run-evals-api-only
The [Python](https://reference.langchain.com/python/langsmith/) and [TypeScript](https://reference.langchain.com/javascript/modules/langsmith.html) SDKs are the recommended way to run [evaluations](/langsmith/evaluation-concepts) in LangSmith. They include optimizations and features that enhance performance and reliability.
If you cannot use the SDKs—for example, if you are working in a different language or a restricted environment—you can use the REST API directly. This guide demonstrates how to run evaluations using the [REST API](https://api.smith.langchain.com/redoc) with Python's [`requests`](https://requests.readthedocs.io/) library, but the same principles apply to any language.
Before diving into this content, it might be helpful to read the following:
* [Evaluate LLM applications](/langsmith/evaluate-llm-application).
* [LangSmith API Reference](https://api.smith.langchain.com/redoc): Complete API documentation for all endpoints used in this guide.
## Create a dataset
For this example, we use the Python SDK to create a [dataset](/langsmith/evaluation-concepts#datasets) quickly. To create datasets via the API or UI instead, refer to [Managing datasets](/langsmith/manage-datasets-in-application).
```python theme={null}
import os
import requests
from datetime import datetime
from langsmith import Client
from openai import OpenAI
from uuid import uuid4
client = Client()
oa_client = OpenAI()
# Create a dataset
examples = [
{
"inputs": {"text": "Shut up, idiot"},
"outputs": {"label": "Toxic"},
},
{
"inputs": {"text": "You're a wonderful person"},
"outputs": {"label": "Not toxic"},
},
{
"inputs": {"text": "This is the worst thing ever"},
"outputs": {"label": "Toxic"},
},
{
"inputs": {"text": "I had a great day today"},
"outputs": {"label": "Not toxic"},
},
{
"inputs": {"text": "Nobody likes you"},
"outputs": {"label": "Toxic"},
},
{
"inputs": {"text": "This is unacceptable. I want to speak to the manager."},
"outputs": {"label": "Not toxic"},
},
]
dataset_name = "Toxic Queries - API Example"
dataset = client.create_dataset(dataset_name=dataset_name)
client.create_examples(dataset_id=dataset.id, examples=examples)
```
## Run a single experiment
To run an experiment via the API, you'll need to:
1. Fetch the examples from your dataset.
2. Create an experiment (also called a "session" in the API).
3. For each example, create runs that reference both the example and the experiment.
4. Close the experiment by setting its `end_time`.
First, pull all of the examples you'd want to use in your experiment using the `/examples` endpoint:
```python theme={null}
# Pick a dataset id. In this case, we are using the dataset we created above.
# API Reference: https://api.smith.langchain.com/redoc#tag/examples/operation/read_examples_api_v1_examples_get
dataset_id = dataset.id
params = { "dataset": dataset_id }
resp = requests.get(
"https://api.smith.langchain.com/api/v1/examples",
params=params,
headers={"x-api-key": os.environ["LANGSMITH_API_KEY"]}
)
examples = resp.json()
```
from langsmith import uuid7
Next, define a function that will run your model on a single example and log the results to LangSmith. When using the API directly, you're responsible for:
* Creating run objects via POST to `/runs` with `reference_example_id` and `session_id` set.
* Tracking parent-child relationships between runs (e.g., a parent "chain" run containing a child "llm" run).
* Updating runs with outputs via PATCH to `/runs/{run_id}`.
```python theme={null}
os.environ["OPENAI_API_KEY"] = "sk-..."
def run_completion_on_example(example, model_name, experiment_id):
"""Run completions on a list of examples."""
# We are using the OpenAI API here, but you can use any model you like
def _post_run(run_id, name, run_type, inputs, parent_id=None):
"""Function to post a new run to the API.
API Reference: https://api.smith.langchain.com/redoc#tag/run/operation/create_run_api_v1_runs_post
"""
data = {
"id": run_id.hex,
"name": name,
"run_type": run_type,
"inputs": inputs,
"start_time": datetime.utcnow().isoformat(),
"reference_example_id": example["id"],
"session_id": experiment_id,
}
if parent_id:
data["parent_run_id"] = parent_id.hex
resp = requests.post(
"https://api.smith.langchain.com/api/v1/runs", # Update appropriately for self-hosted installations or the EU region
json=data,
headers=headers
)
resp.raise_for_status()
def _patch_run(run_id, outputs):
"""Function to patch a run with outputs.
API Reference: https://api.smith.langchain.com/redoc#tag/run/operation/update_run_api_v1_runs__run_id__patch
"""
resp = requests.patch(
f"https://api.smith.langchain.com/api/v1/runs/{run_id}",
json={
"outputs": outputs,
"end_time": datetime.utcnow().isoformat(),
},
headers=headers,
)
resp.raise_for_status()
# Send your API Key in the request headers
headers = {"x-api-key": os.environ["LANGSMITH_API_KEY"]}
text = example["inputs"]["text"]
messages = [
{
"role": "system",
"content": "Please review the user query below and determine if it contains any form of toxic behavior, such as insults, threats, or highly negative comments. Respond with 'Toxic' if it does, and 'Not toxic' if it doesn't.",
},
{"role": "user", "content": text},
]
# Create parent run
parent_run_id = uuid7()
_post_run(parent_run_id, "LLM Pipeline", "chain", {"text": text})
# Create child run
child_run_id = uuid7()
_post_run(child_run_id, "OpenAI Call", "llm", {"messages": messages}, parent_run_id)
# Generate completion
chat_completion = oa_client.chat.completions.create(model=model_name, messages=messages)
output_text = chat_completion.choices[0].message.content
# End run
_patch_run(child_run_id, {
"messages": messages,
"output": output_text,
"model": model_name
})
_patch_run(parent_run_id, {"label": output_text})
```
Now create the experiments and run completions on all examples. In the API, an "experiment" is represented as a session (or "tracer session") that references a dataset via `reference_dataset_id`. The key difference from regular tracing is that runs in an experiment must have a `reference_example_id` that links each run to a specific example in the dataset.
```python theme={null}
# Create a new experiment using the /sessions endpoint
# An experiment is a collection of runs with a reference to the dataset used
# API Reference: https://api.smith.langchain.com/redoc#tag/tracer-sessions/operation/create_tracer_session_api_v1_sessions_post
model_names = ("gpt-3.5-turbo", "gpt-4.1-mini")
experiment_ids = []
for model_name in model_names:
resp = requests.post(
"https://api.smith.langchain.com/api/v1/sessions",
json={
"start_time": datetime.utcnow().isoformat(),
"reference_dataset_id": str(dataset_id),
"description": "An optional description for the experiment",
"name": f"Toxicity detection - API Example - {model_name} - {str(uuid4())[0:8]}", # A name for the experiment
"extra": {
"metadata": {"foo": "bar"}, # Optional metadata
},
},
headers={"x-api-key": os.environ["LANGSMITH_API_KEY"]}
)
experiment = resp.json()
experiment_ids.append(experiment["id"])
# Run completions on all examples
for example in examples:
run_completion_on_example(example, model_name, experiment["id"])
# Issue a patch request to "end" the experiment by updating the end_time
requests.patch(
f"https://api.smith.langchain.com/api/v1/sessions/{experiment['id']}",
json={"end_time": datetime.utcnow().isoformat()},
headers={"x-api-key": os.environ["LANGSMITH_API_KEY"]}
)
```
### Add evaluation feedback
After running your [experiments](/langsmith/evaluation-concepts#experiment), you'll typically want to evaluate the results by adding feedback scores. This allows you to track metrics like correctness, accuracy, or any custom evaluation criteria.
In this example, the evaluation checks if each model's output matches the expected label in the dataset. The code posts a "correctness" score (1.0 for correct, 0.0 for incorrect) to track how accurately each model classifies toxic vs. non-toxic text.
The following code adds feedback to the runs from the [single experiment example](#run-a-single-experiment):
```python theme={null}
# Fetch the runs from one of the experiments
# API Reference: https://api.smith.langchain.com/redoc#tag/run/operation/query_runs_api_v1_runs_query_post
experiment_id = experiment_ids[0] # Evaluate the first experiment
runs_resp = requests.post(
"https://api.smith.langchain.com/api/v1/runs/query",
headers={"x-api-key": os.environ["LANGSMITH_API_KEY"]},
json={
"session": [experiment_id],
"is_root": True, # Only fetch root runs
"select": ["id", "reference_example_id", "outputs"],
}
)
runs = runs_resp.json()["runs"]
# Evaluate each run by comparing outputs to expected values
for run in runs:
# Get the expected output from the original example
example_id = run["reference_example_id"]
expected_output = next(
ex["outputs"]["label"]
for ex in examples
if ex["id"] == example_id
)
# Compare the model output to the expected output
actual_output = run["outputs"].get("label", "")
is_correct = expected_output.lower() == actual_output.lower()
# Post feedback score
# API Reference: https://api.smith.langchain.com/redoc#tag/feedback/operation/create_feedback_api_v1_feedback_post
feedback = {
"run_id": str(run["id"]),
"key": "correctness", # The name of your evaluation metric
"score": 1.0 if is_correct else 0.0,
"comment": f"Expected: {expected_output}, Got: {actual_output}", # Optional
}
resp = requests.post(
"https://api.smith.langchain.com/api/v1/feedback",
json=feedback,
headers={"x-api-key": os.environ["LANGSMITH_API_KEY"]}
)
resp.raise_for_status()
```
You can add multiple feedback scores with different keys to track various metrics. For example, you might add both a "correctness" score and a "toxicity\_detected" score.
## Run a pairwise experiment
Next, we'll demonstrate how to run a pairwise experiment. In a pairwise experiment, you compare two examples against each other.
For more information, check out [this guide](/langsmith/evaluate-pairwise).
```python theme={null}
# A comparative experiment allows you to provide a preferential ranking on the outputs of two or more experiments
# API Reference: https://api.smith.langchain.com/redoc#tag/datasets/operation/create_comparative_experiment_api_v1_datasets_comparative_post
resp = requests.post(
"https://api.smith.langchain.com/api/v1/datasets/comparative",
json={
"experiment_ids": experiment_ids,
"name": "Toxicity detection - API Example - Comparative - " + str(uuid4())[0:8],
"description": "An optional description for the comparative experiment",
"extra": {
"metadata": {"foo": "bar"}, # Optional metadata
},
"reference_dataset_id": str(dataset_id),
},
headers={"x-api-key": os.environ["LANGSMITH_API_KEY"]}
)
comparative_experiment = resp.json()
comparative_experiment_id = comparative_experiment["id"]
# You can iterate over the runs in the experiments belonging to the comparative experiment and preferentially rank the outputs
# Fetch the comparative experiment
resp = requests.get(
f"https://api.smith.langchain.com/api/v1/datasets/{str(dataset_id)}/comparative",
params={"id": comparative_experiment_id},
headers={"x-api-key": os.environ["LANGSMITH_API_KEY"]}
)
comparative_experiment = resp.json()[0]
experiment_ids = [info["id"] for info in comparative_experiment["experiments_info"]]
from collections import defaultdict
example_id_to_runs_map = defaultdict(list)
# API Reference: https://api.smith.langchain.com/redoc#tag/run/operation/query_runs_api_v1_runs_query_post
runs = requests.post(
f"https://api.smith.langchain.com/api/v1/runs/query",
headers={"x-api-key": os.environ["LANGSMITH_API_KEY"]},
json={
"session": experiment_ids,
"is_root": True, # Only fetch root runs (spans) which contain the end outputs
"select": ["id", "reference_example_id", "outputs"],
}
).json()
runs = runs["runs"]
for run in runs:
example_id = run["reference_example_id"]
example_id_to_runs_map[example_id].append(run)
for example_id, runs in example_id_to_runs_map.items():
print(f"Example ID: {example_id}")
# Preferentially rank the outputs, in this case we will always prefer the first output
# In reality, you can use an LLM to rank the outputs
feedback_group_id = uuid4()
# Post a feedback score for each run, with the first run being the preferred one
# API Reference: https://api.smith.langchain.com/redoc#tag/feedback/operation/create_feedback_api_v1_feedback_post
# We'll use the feedback group ID to associate the feedback scores with the same group
for i, run in enumerate(runs):
print(f"Run ID: {run['id']}")
feedback = {
"score": 1 if i == 0 else 0,
"run_id": str(run["id"]),
"key": "ranked_preference",
"feedback_group_id": str(feedback_group_id),
"comparative_experiment_id": comparative_experiment_id,
}
resp = requests.post(
"https://api.smith.langchain.com/api/v1/feedback",
json=feedback,
headers={"x-api-key": os.environ["LANGSMITH_API_KEY"]}
)
resp.raise_for_status()
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/run-evals-api-only.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Run an evaluation from the prompt playground
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/run-evaluation-from-prompt-playground
LangSmith allows you to run evaluations directly in the UI. The [**Prompt Playground**](/langsmith/prompt-engineering#prompt-playground) allows you to test your prompt or model configuration over a series of inputs to see how well it scores across different contexts or scenarios, without having to write any code.
Before you run an evaluation, you need to have an [existing dataset](/langsmith/evaluation-concepts#datasets). Learn how to [create a dataset from the UI](/langsmith/manage-datasets-in-application#set-up-your-dataset).
If you prefer to run experiments in code, visit [run an evaluation using the SDK](/langsmith/evaluate-llm-application).
**[Polly](/langsmith/polly)** is available in the Playground to help you optimize prompts before running evaluations.
## Create an experiment in the prompt playground[](#create-an-experiment-in-the-prompt-playground "Direct link to Create an experiment in the prompt playground")
1. **Navigate to the playground** by clicking **Playground** in the sidebar.
2. **Add a prompt** by selecting an existing saved a prompt or creating a new one.
3. **Select a dataset** from the **Test over dataset** dropdown
* Note that the keys in the dataset input must match the input variables of the prompt. For example, in the above video the selected dataset has inputs with the key "blog", which correctly match the input variable of the prompt.
* There is a maximum of 15 input variables allowed in the prompt playground.
4. **Start the experiment** by clicking on the **Start** or CMD+Enter. This will run the prompt over all the examples in the dataset and create an entry for the experiment in the dataset details page. We recommend committing the prompt to the prompt hub before starting the experiment so that it can be easily referenced later when reviewing your experiment.
5. **View the full results** by clicking **View full experiment**. This will take you to the experiment details page where you can see the results of the experiment.
## Add evaluation scores to the experiment[](#add-evaluation-scores-to-the-experiment "Direct link to Add evaluation scores to the experiment")
Evaluate your experiment over specific criteria by adding evaluators. Add LLM-as-a-judge or custom code evaluators in the playground using the **+Evaluator** button.
To learn more about adding evaluators in via UI, visit [how to define an LLM-as-a-judge evaluator](/langsmith/llm-as-judge).
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/run-evaluation-from-prompt-playground.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# How to run multiple agents on the same thread
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/same-thread
In LangSmith Deployment, a thread is not explicitly associated with a particular agent.
This means that you can run multiple agents on the same thread, which allows a different agent to continue from an initial agent's progress.
In this example, we will create two agents and then call them both on the same thread.
You'll see that the second agent will respond using information from the [checkpoint](/oss/python/langgraph/graph-api#checkpointer-state) generated in the thread by the first agent as context.
## Setup
```python theme={null}
from langgraph_sdk import get_client
client = get_client(url=)
openai_assistant = await client.assistants.create(
graph_id="agent", config={"configurable": {"model_name": "openai"}}
)
# There should always be a default assistant with no configuration
assistants = await client.assistants.search()
default_assistant = [a for a in assistants if not a["config"]][0]
```
```js theme={null}
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: });
const openAIAssistant = await client.assistants.create(
{ graphId: "agent", config: {"configurable": {"model_name": "openai"}}}
);
const assistants = await client.assistants.search();
const defaultAssistant = assistants.find(a => !a.config);
```
```bash theme={null}
curl --request POST \
--url /assistants \
--header 'Content-Type: application/json' \
--data '{
"graph_id": "agent",
"config": { "configurable": { "model_name": "openai" } }
}' && \
curl --request POST \
--url /assistants/search \
--header 'Content-Type: application/json' \
--data '{
"limit": 10,
"offset": 0
}' | jq -c 'map(select(.config == null or .config == {})) | .[0]'
```
We can see that these agents are different:
```python theme={null}
print(openai_assistant)
```
```js theme={null}
console.log(openAIAssistant);
```
```bash theme={null}
curl --request GET \
--url /assistants/
```
Output:
```
{
"assistant_id": "db87f39d-b2b1-4da8-ac65-cf81beb3c766",
"graph_id": "agent",
"created_at": "2024-08-30T21:18:51.850581+00:00",
"updated_at": "2024-08-30T21:18:51.850581+00:00",
"config": {
"configurable": {
"model_name": "openai"
}
},
"metadata": {}
}
```
```python theme={null}
print(default_assistant)
```
```js theme={null}
console.log(defaultAssistant);
```
```bash theme={null}
curl --request GET \
--url /assistants/
```
Output:
```
{
"assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca",
"graph_id": "agent",
"created_at": "2024-08-08T22:45:24.562906+00:00",
"updated_at": "2024-08-08T22:45:24.562906+00:00",
"config": {},
"metadata": {
"created_by": "system"
}
}
```
## Run assistants on thread
### Run OpenAI assistant
We can now run the OpenAI assistant on the thread first.
```python theme={null}
thread = await client.threads.create()
input = {"messages": [{"role": "user", "content": "who made you?"}]}
async for event in client.runs.stream(
thread["thread_id"],
openai_assistant["assistant_id"],
input=input,
stream_mode="updates",
):
print(f"Receiving event of type: {event.event}")
print(event.data)
print("\n\n")
```
```js theme={null}
const thread = await client.threads.create();
let input = {"messages": [{"role": "user", "content": "who made you?"}]}
const streamResponse = client.runs.stream(
thread["thread_id"],
openAIAssistant["assistant_id"],
{
input,
streamMode: "updates"
}
);
for await (const event of streamResponse) {
console.log(`Receiving event of type: ${event.event}`);
console.log(event.data);
console.log("\n\n");
}
```
```bash theme={null}
thread_id=$(curl --request POST \
--url /threads \
--header 'Content-Type: application/json' \
--data '{}' | jq -r '.thread_id') && \
curl --request POST \
--url "/threads/${thread_id}/runs/stream" \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": ,
"input": {
"messages": [
{
"role": "user",
"content": "who made you?"
}
]
},
"stream_mode": [
"updates"
]
}' | \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "") {
print data_content "\n"
}
sub(/^event: /, "Receiving event of type: ", $0)
printf "%s...\n", $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "") {
print data_content "\n\n"
}
}
'
```
Output:
```
Receiving event of type: metadata
{'run_id': '1ef671c5-fb83-6e70-b698-44dba2d9213e'}
Receiving event of type: updates
{'agent': {'messages': [{'content': 'I was created by OpenAI, a research organization focused on developing and advancing artificial intelligence technology.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-f5735b86-b80d-4c71-8dc3-4782b5a9c7c8', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
```
### Run default assistant
Now, we can run it on the default assistant and see that this second assistant is aware of the initial question, and can answer the question, "and you?":
```python theme={null}
input = {"messages": [{"role": "user", "content": "and you?"}]}
async for event in client.runs.stream(
thread["thread_id"],
default_assistant["assistant_id"],
input=input,
stream_mode="updates",
):
print(f"Receiving event of type: {event.event}")
print(event.data)
print("\n\n")
```
```js theme={null}
let input = {"messages": [{"role": "user", "content": "and you?"}]}
const streamResponse = client.runs.stream(
thread["thread_id"],
defaultAssistant["assistant_id"],
{
input,
streamMode: "updates"
}
);
for await (const event of streamResponse) {
console.log(`Receiving event of type: ${event.event}`);
console.log(event.data);
console.log("\n\n");
}
```
```bash theme={null}
curl --request POST \
--url /threads//runs/stream \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": ,
"input": {
"messages": [
{
"role": "user",
"content": "and you?"
}
]
},
"stream_mode": [
"updates"
]
}' | \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "") {
print data_content "\n"
}
sub(/^event: /, "Receiving event of type: ", $0)
printf "%s...\n", $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "") {
print data_content "\n\n"
}
}
'
```
Output:
```
Receiving event of type: metadata
{'run_id': '1ef6722d-80b3-6fbb-9324-253796b1cd13'}
Receiving event of type: updates
{'agent': {'messages': [{'content': [{'text': 'I am an artificial intelligence created by Anthropic, not by OpenAI. I should not have stated that OpenAI created me, as that is incorrect. Anthropic is the company that developed and trained me using advanced language models and AI technology. I will be more careful about providing accurate information regarding my origins in the future.', 'type': 'text', 'index': 0}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'end_turn', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-ebaacf62-9dd9-4165-9535-db432e4793ec', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 302, 'output_tokens': 72, 'total_tokens': 374}}]}}
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/same-thread.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Set a sampling rate for traces
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/sample-traces
When working with high-volume applications, you may not want to log every trace to LangSmith. Sampling rates allow you to control what percentage of traces are logged, helping you balance observability needs with cost considerations.
## Set a global sampling rate
This section is relevant for those using the LangSmith SDK or LangChain, not for those logging directly with the LangSmith API.
By default, all traces are logged to LangSmith. To down-sample the number of traces logged to LangSmith, set the `LANGSMITH_TRACING_SAMPLING_RATE` environment variable to any float between `0` (no traces) and `1` (all traces). For instance, setting the following environment variable will log 75% of the traces.
```bash theme={null}
export LANGSMITH_TRACING_SAMPLING_RATE=0.75
```
This works for the `traceable` decorator and `RunTree` objects.
## Set different sampling rates per client
You can also set sampling rates on specific `Client` instances and use the `tracing_context` context manager:
```python theme={null}
from langsmith import Client, tracing_context
# Create clients with different sampling rates
client_1 = Client(tracing_sampling_rate=0.5) # 50% sampling
client_2 = Client(tracing_sampling_rate=0.25) # 25% sampling
client_no_trace = Client(tracing_sampling_rate=0.0) # No tracing
# Use different sampling rates for different operations
with tracing_context(client=client_1):
# Your code here - will be traced with 50% sampling rate
agent_1.invoke(...)
with tracing_context(client=client_2):
# Your code here - will be traced with 25% sampling rate
agent_1.invoke(...)
with tracing_context(client=client_no_trace):
# Your code here - will not be traced
agent_1.invoke(...)
```
This allows you to control sampling rates at the operation level.
## Sampling or conditional tracing
Sampling provides **probabilistic** control over trace volume, while [conditional tracing](/langsmith/conditional-tracing) provides **deterministic** control based on business logic.
Use **sampling** when you want to reduce overall trace volume while maintaining statistical representation of your application's behavior.
Use [conditional tracing](/langsmith/conditional-tracing) when you need guaranteed tracing behavior for specific requests, such as:
* Disabling tracing for clients with zero-retention policies.
* Routing traces to different projects based on tenant.
* Handling sensitive data that should never be traced.
You can combine both approaches for fine-grained control over your observability strategy.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/sample-traces.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Scalability & resilience
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/scalability-and-resilience
LangSmith is designed to scale horizontally with your workload. Each instance of the service is stateless, and keeps no resources in memory. The service is designed to gracefully handle new instances being added or removed, including hard shutdown cases.
## Server scalability
As you add more instances to a service, they will share the HTTP load as long as an appropriate load balancer mechanism is placed in front of them. In most deployment modalities we configure a load balancer for the service automatically. In the “self-hosted without control plane” modality it’s your responsibility to add a load balancer. Since the instances are stateless any load balancing strategy will work, no session stickiness is needed, or recommended. Any instance of the server can communicate with any queue instance (through Redis PubSub), meaning that requests to cancel or stream an in-progress run can be handled by any arbitrary instance.
## Queue scalability
As you add more instances to a service, they will increase run throughput linearly, as each instance is configured to handle a set number of concurrent runs (by default 10). Each attempt for each run will be handled by a single instance, with exactly-once semantics enforced through Postgres’s MVCC model (refer to section below for crash resilience details). Attempts that fail due to transient database errors are retried up to 3 times. We do not make use of long-lived transactions or locks, this enables us to make more efficient use of Postgres resources.
## Resilience
While a run is being handled by a queue instance, a periodic heartbeat timestamp will be recorded in Redis by that queue worker.
When a graceful shutdown request is received (SIGINT) an instance enters shutdown mode, which
* stops accepting new HTTP requests
* gives any in-progress runs a limited number of seconds to finish (if not finished it will be put back in the queue)
* stops the instance from picking up more runs from the queue
If a hard shutdown occurs due to a server crash or an infrastructure failure, any runs that were in progress will be picked up by an internal sweeper task that looks for in-progress runs that have breached their heartbeat window. The sweeper runs every 2 minutes and will put the runs back in the queue for another instance to pick them up.
## Postgres resilience
For deployment modalities where we manage the Postgres database, we have periodic backups and continuously replicated standby replicas for automatic failover. This Postgres configuration is available in the [Cloud deployment option](/langsmith/cloud) for [`Production` deployment types](/langsmith/control-plane#deployment-types) only.
All communication with Postgres implements retries for retry-able errors. If Postgres is momentarily unavailable, such as during a database restart, most/all traffic should continue to succeed. Prolonged failure of Postgres will render the Agent Server unavailable.
## Redis resilience
All data that requires durable storage is stored in Postgres, not Redis. Redis is used only for ephemeral metadata, and communication between instances. Therefore we place no durability requirements on Redis.
All communication with Redis implements retries for retry-able errors. If Redis is momentarily unavailable, such as during a database restart, most/all traffic should continue to succeed. Prolonged failure of Redis will render the Agent Server unavailable.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/scalability-and-resilience.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Delete workspaces
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/script-delete-a-workspace
Deleting a workspace is supported **nativley in LangSmith Self-Hosted v0.10**. View [instructions for deleting a workspace](/langsmith/set-up-hierarchy#delete-a-workspace).
Follow the guide below for Self-Hosted versions before v0.10.
The LangSmith UI does not currently support the deletion of an individual workspace from an organization. This, however, can be accomplished by directly removing all traces from all materialized views in ClickHouse (except the runs\_history views) and the runs and feedbacks tables and then removing the Workspace from the Postgres tenants table.
This command using the Workspace ID as an argument.
### Prerequisites
Ensure you have the following tools/items ready.
1. kubectl
* [https://kubernetes.io/docs/tasks/tools/](https://kubernetes.io/docs/tasks/tools/)
2. PostgreSQL client
* [https://www.postgresql.org/download/](https://www.postgresql.org/download/)
3. PostgreSQL database connection:
* Host
* Port
* Username
* If using the bundled version, this is `postgres`
* Password
* If using the bundled version, this is `postgres`
* Database name
* If using the bundled version, this is `postgres`
4. Clickhouse database credentials
* Host
* Port
* Username
* If using the bundled version, this is `default`
* Password
* If using the bundled version, this is `password`
* Database name
* If using the bundled version, this is `default`
5. Connectivity to the PostgreSQL database from the machine you will be running the migration script on.
* If you are using the bundled version, you may need to port forward the postgresql service to your local machine.
* Run `kubectl port-forward svc/langsmith-postgres 5432:5432` to port forward the postgresql service to your local machine.
6. Connectivity to the Clickhouse database from the machine you will be running the migration script on.
* If you are using the bundled version, you may need to port forward the clickhouse service to your local machine.
* Run `kubectl port-forward svc/langsmith-clickhouse 8123:8123` to port forward the clickhouse service to your local machine.
* If you are using Clickhouse Cloud you will want to specify the --ssl flag and use port `8443`
7. The script to delete a workspace
* You can download the script from [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/scripts/delete_workspace.sh)
### Running the deletion script for a single workspace
Run the following command to run the workspace removal script:
```bash theme={null}
sh delete_workspace.sh --workspace_id
```
For example, if you are using the bundled version with port-forwarding, the command would look like:
```bash theme={null}
sh delete_workspace.sh "postgres://postgres:postgres@localhost:5432/postgres" "clickhouse://default:password@localhost:8123/default" --workspace_id 4ec70ec7-0808-416a-b836-7100aeec934b
```
If you visit the LangSmith UI, you should now see workspace is deleted.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/script-delete-a-workspace.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Delete organizations
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/script-delete-an-organization
The LangSmith UI does not currently support the deletion of an individual organization from a self-hosted instance of LangSmith. This, however, can be accomplished by directly removing all traces from all materialized views in ClickHouse (except the runs\_history views) and the runs and feedbacks tables and then removing the Organization from the Postgres tenants table.
This command using the Organization ID as an argument.
### Prerequisites
Ensure you have the following tools/items ready.
1. kubectl
* [https://kubernetes.io/docs/tasks/tools/](https://kubernetes.io/docs/tasks/tools/)
2. PostgreSQL client
* [https://www.postgresql.org/download/](https://www.postgresql.org/download/)
3. PostgreSQL database connection:
* Host
* Port
* Username
* If using the bundled version, this is `postgres`
* Password
* If using the bundled version, this is `postgres`
* Database name
* If using the bundled version, this is `postgres`
4. Clickhouse database credentials
* Host
* Port
* Username
* If using the bundled version, this is `default`
* Password
* If using the bundled version, this is `password`
* Database name
* If using the bundled version, this is `default`
5. Connectivity to the PostgreSQL database from the machine you will be running the migration script on.
* If you are using the bundled version, you may need to port forward the postgresql service to your local machine.
* Run `kubectl port-forward svc/langsmith-postgres 5432:5432` to port forward the postgresql service to your local machine.
6. Connectivity to the Clickhouse database from the machine you will be running the migration script on.
* If you are using the bundled version, you may need to port forward the clickhouse service to your local machine.
* Run `kubectl port-forward svc/langsmith-clickhouse 8123:8123` to port forward the clickhouse service to your local machine.
* If you are using Clickhouse Cloud you will want to specify the --ssl flag and use port `8443`
7. The script to delete an organization
* You can download the script from [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/scripts/delete_organization_sh)
### Running the deletion script for a single organization
Run the following command to run the organization removal script:
```bash theme={null}
sh delete_organization.sh --organization_id
```
For example, if you are using the bundled version with port-forwarding, the command would look like:
```bash theme={null}
sh delete_organization.sh "postgres://postgres:postgres@localhost:5432/postgres" "clickhouse://default:password@localhost:8123/default" --organization_id 4ec70ec7-0808-416a-b836-7100aeec934b
```
If you visit the LangSmith UI, you should now see organization is no longer present.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/script-delete-an-organization.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Delete traces
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/script-delete-traces
The LangSmith UI does not currently support the deletion of an individual trace. This, however, can be accomplished by directly removing the trace from all materialized views in ClickHouse (except the runs\_history views) and the runs and feedback table themselves.
This command can either be run using a trace ID as an argument or using a file that is a list of trace IDs.
### Prerequisites
Ensure you have the following tools/items ready.
1. kubectl
* [https://kubernetes.io/docs/tasks/tools/](https://kubernetes.io/docs/tasks/tools/)
2. Clickhouse database credentials
* Host
* Port
* Username
* If using the bundled version, this is `default`
* Password
* If using the bundled version, this is `password`
* Database name
* If using the bundled version, this is `default`
3. Connectivity to the Clickhouse database from the machine you will be running the `delete_trace_by_id` script on.
* If you are using the bundled version, you may need to port forward the clickhouse service to your local machine.
* Run `kubectl port-forward svc/langsmith-clickhouse 8123:8123` to port forward the clickhouse service to your local machine.
4. The script to delete a trace
* You can download the script from [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/scripts/delete_trace_by_id.sh)
### Running the deletion script for a single trace
Run the following command to run the trace deletion script using a single trace ID:
```bash theme={null}
sh delete_trace_by_id.sh --trace_id
```
For example, if you are using the bundled version with port-forwarding, the command would look like:
```bash theme={null}
sh delete_trace_by_id.sh "clickhouse://default:password@localhost:8123/default" --trace_id 4ec70ec7-0808-416a-b836-7100aeec934b
```
If you visit the LangSmith UI, you should now see specified trace ID is no longer present nor reflected in stats.
### Running the deletion script for a multiple traces from a file with one trace ID per line
Run the following command to run the trace deletion script using a list of trace IDs:
```bash theme={null}
sh delete_trace_by_id.sh --file
```
For example, if you are using the bundled version with port-forwarding, the command would look like:
```bash theme={null}
sh delete_trace_by_id.sh "clickhouse://default:password@localhost:8123/default" --file path/to/traces.txt
```
If you visit the LangSmith UI, you should now see all the specified traces have been removed.
## Troubleshooting
### "Could not find trace IDs" error
If you receive an error message stating that trace IDs could not be found, add the `--ssl` flag to your command. Without this flag, the script may not be able to properly connect to ClickHouse, resulting in false "trace ID not found" errors.
Example with SSL flag:
```bash theme={null}
sh delete_trace_by_id.sh "clickhouse://default:password@localhost:8123/default" --file path/to/traces.txt --ssl
```
You can also verify that traces exist by connecting to ClickHouse directly using `clickhouse-cli` and querying for the trace IDs before running the deletion script.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/script-delete-traces.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Generate ClickHouse stats
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/script-generate-clickhouse-stats
As part of troubleshooting your self-hosted instance of LangSmith, the LangChain team may ask you to generate Clickhouse statistics that will help us understand memory and CPU consumption and connection concurrency.
This command will generate a CSV that can be shared with the LangChain team.
### Prerequisites
Ensure you have the following tools/items ready.
1. kubectl
* [https://kubernetes.io/docs/tasks/tools/](https://kubernetes.io/docs/tasks/tools/)
2. Clickhouse database credentials
* Host
* Port
* Username
* If using the bundled version, this is `default`
* Password
* If using the bundled version, this is `password`
* Database name
* If using the bundled version, this is `default`
3. Connectivity to the Clickhouse database from the machine you will be running the `get_clickhouse_stats` script on.
* If you are using the bundled version, you may need to port forward the clickhouse service to your local machine.
* Run `kubectl port-forward svc/langsmith-clickhouse 8123:8123` to port forward the clickhouse service to your local machine.
4. The script to generate ClickHouse stats
* You can download the script from [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/scripts/get_clickhouse_stats.sh)
### Running the clickhouse stats generation script
Run the following command to run the stats generation script:
```bash theme={null}
sh get_clickhouse_stats.sh --output path/to/file.csv
```
For example, if you are using the bundled version with port-forwarding, the command would look like:
```bash theme={null}
sh get_clickhouse_stats.sh "clickhouse://default:password@localhost:8123/default" --output clickhouse_stats.csv
```
and after running this command you should see a file, clickhouse\_stats.csv, has been created with Clickhouse statistics.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/script-generate-clickhouse-stats.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Generate query stats
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/script-generate-query-stats
As part of troubleshooting your self-hosted instance of LangSmith, the LangChain team may ask you to generate LangSmith query statistics that will help us understand the performance of various queries that drive the LangSmith product experience.
This command will generate a CSV that can be shared with the LangChain team.
### Prerequisites
Ensure you have the following tools/items ready.
1. kubectl
* [https://kubernetes.io/docs/tasks/tools/](https://kubernetes.io/docs/tasks/tools/)
2. Clickhouse database credentials
* Host
* Port
* Username
* If using the bundled version, this is `default`
* Password
* If using the bundled version, this is `password`
* Database name
* If using the bundled version, this is `default`
3. Connectivity to the Clickhouse database from the machine you will be running the `get_query_stats` script on.
* If you are using the bundled version, you may need to port forward the clickhouse service to your local machine.
* Run `kubectl port-forward svc/langsmith-clickhouse 8123:8123` to port forward the clickhouse service to your local machine.
4. The script to generate query stats
* You can download the script from [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/scripts/get_query_stats.sh)
### Running the query stats generation script
Run the following command to run the stats generation script:
```bash theme={null}
sh get_query_stats.sh --output path/to/file.csv
```
For example, if you are using the bundled version with port-forwarding, the command would look like:
```bash theme={null}
sh get_query_stats.sh "clickhouse://default:password@localhost:8123/default" --output query_stats.csv
```
and after running this command you should see a file, query\_stats.csv, has been created with LangSmith query statistics.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/script-generate-query-stats.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Run support queries against ClickHouse
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/script-running-ch-support-queries
This Helm repository contains queries to produce output that the LangSmith UI does not currently support directly (e.g. obtaining query exception logs from Clickhouse).
This command takes a clickhouse connection string that contains an embedded name and password (which can be passed in from a call to a secrets manager) and executes a query from an input file. In the example below, we are using the `ch_get_query_exceptions.sql` input file in the `support_queries/clickhouse` directory.
### Prerequisites
Ensure you have the following tools/items ready.
1. kubectl
* [https://kubernetes.io/docs/tasks/tools/](https://kubernetes.io/docs/tasks/tools/)
2. Clickhouse database credentials
* Host
* Port
* Username
* If using the bundled version, this is `default`
* Password
* If using the bundled version, this is `password`
* Database name
* If using the bundled version, this is `default`
3. Connectivity to the Clickhouse database from the machine you will be running the migration script on.
* If you are using the bundled version, you may need to port forward the clickhouse service to your local machine.
* Run `kubectl port-forward svc/langsmith-clickhouse 8123:8123` to port forward the clickhouse service to your local machine.
4. The script to run a support query
* You can download the script from [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/scripts/run_support_query_ch.sh)
### Running the query script
Run the following command to run the desired query:
```bash theme={null}
sh run_support_query_ch.sh --input path/to/query.sql
```
For example, if you are using the bundled version with port-forwarding, the command might look like:
```bash theme={null}
sh run_support_query_ch.sh "clickhouse://default:password@localhost:8123/default" --input support_queries/clickhouse/ch_get_query_exceptions.sql
```
which will output query logs for all queries that have thrown exceptions in Clickhouse in the last 7 days. To extract this to a file add the flag `--output path/to/file.csv`
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/script-running-ch-support-queries.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Run support queries against PostgreSQL
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/script-running-pg-support-queries
This Helm repository contains queries to produce output that the LangSmith UI does not currently support directly (e.g. obtaining trace counts for multiple organizations in a single query).
This command takes a postgres connection string that contains an embedded name and password (which can be passed in from a call to a secrets manager) and executes a query from an input file. In the example below, we are using the `pg_get_trace_counts_daily.sql` input file in the `support_queries/postgres` directory.
## Prerequisites
Ensure you have the following tools/items ready.
1. kubectl
* [https://kubernetes.io/docs/tasks/tools/](https://kubernetes.io/docs/tasks/tools/)
2. PostgreSQL client
* [https://www.postgresql.org/download/](https://www.postgresql.org/download/)
3. PostgreSQL database connection:
* Host
* Port
* Username
* If using the bundled version, this is `postgres`
* Password
* If using the bundled version, this is `postgres`
* Database name
* If using the bundled version, this is `postgres`
4. Connectivity to the PostgreSQL database from the machine you will be running the migration script on.
* If you are using the bundled version, you may need to port forward the postgresql service to your local machine.
* Run `kubectl port-forward svc/langsmith-postgres 5432:5432` to port forward the postgresql service to your local machine.
5. The script to run a support query
* You can download the script from [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/scripts/run_support_query_pg.sh)
## Running the query script
Run the following command to run the desired query:
```bash theme={null}
sh run_support_query_pg.sh --input path/to/query.sql
```
For example, if you are using the bundled version with port-forwarding, the command might look like:
```bash theme={null}
sh run_support_query_pg.sh "postgres://postgres:postgres@localhost:5432/postgres" --input support_queries/pg_get_trace_counts_daily.sql
```
which will output the count of daily traces by workspace ID and organization ID. To extract this to a file add the flag `--output path/to/file.csv`
## Export usage data
Exporting usage data requires running Helm chart version 0.11.4 or later.
### Get customer information
You need to retrieve your customer information from the LangSmith API before running the export scripts. This information is required as input for the export scripts.
```bash theme={null}
curl https:///api/v1/info
# if configured with a subdomain / path prefix:
curl http://",
"customer_name": ""
}
}
```
Extract the `customer_id` and `customer_name` from this response to use as input for the export scripts.
### Process the API response with jq
You can use [jq](https://jqlang.org/download) to parse the JSON response and set bash variables for use in your scripts:
```bash theme={null}
# Get the API response and extract customer information
export LANGSMITH_URL=""
response=$(curl -s $LANGSMITH_URL/api/v1/info)
# Extract customer_id and customer_name using jq
export CUSTOMER_ID=$(echo "$response" | jq -r '.customer_info.customer_id')
export CUSTOMER_NAME=$(echo "$response" | jq -r '.customer_info.customer_name')
# Verify the variables are set
echo "Customer ID: $CUSTOMER_ID"
echo "Customer Name: $CUSTOMER_NAME"
```
You can then use these environment variables in your export scripts or other commands.
If you don't have `jq`, run these commands to set the environment variables based on the curl output:
```bash theme={null}
curl -s $LANGSMITH_URL/api/v1/info
export CUSTOMER_ID=""
export CUSTOMER_NAME=""
```
### Initial export
These scripts export usage data to a CSV for reporting to LangChain. They additionally track the export by assigning a backfill ID and timestamp.
To export LangSmith trace usage:
```bash theme={null}
# Get customer information from the API
export LANGSMITH_URL=""
export response=$(curl -s $LANGSMITH_URL/api/v1/info)
export CUSTOMER_ID=$(echo "$response" | jq -r '.customer_info.customer_id') && echo "Customer ID: $CUSTOMER_ID"
export CUSTOMER_NAME=$(echo "$response" | jq -r '.customer_info.customer_name') && echo "Customer name: $CUSTOMER_NAME"
# Run the export script with customer information as variables
sh run_support_query_pg.sh \
--input support_queries/postgres/pg_usage_traces_backfill_export.sql \
--output ls_export.csv \
-v customer_id=$CUSTOMER_ID \
-v customer_name=$CUSTOMER_NAME
```
To export LangSmith usage:
```bash theme={null}
sh run_support_query_pg.sh \
--input support_queries/postgres/pg_usage_nodes_backfill_export.sql \
--output lgp_export.csv \
-v customer_id=$CUSTOMER_ID \
-v customer_name=$CUSTOMER_NAME
```
### Status update
These scripts update the status of usage events in your installation to reflect that the events have been successfully processed by LangChain.
The scripts require passing in the corresponding `backfill_id`, which will be confirmed by your LangChain rep.
To update LangSmith trace usage:
```bash theme={null}
sh run_support_query_pg.sh --input support_queries/postgres/pg_usage_traces_backfill_update.sql --output export.csv -v backfill_id=
```
To update LangSmith usage:
```bash theme={null}
sh run_support_query_pg.sh --input support_queries/postgres/pg_usage_nodes_backfill_update.sql --output export.csv -v backfill_id=
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/script-running-pg-support-queries.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Basic authentication with email and password
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-basic-auth
LangSmith supports login via username/password with a few limitations:
* You cannot change an existing installation from basic auth mode to OAuth with PKCE (deprecated) or vice versa - installations must be either one or the other. **A basic auth installation requires a completely fresh installation including a separate PostgreSQL database/schema, unless migrating from an existing `None` type installation (see below).**
* Users must be given their initial auto-generated password once they are invited. This password may be changed later by any Organization Admin.
* You cannot use both basic auth and OAuth with client secret at the same time.
## Requirements and features
* There is a single `Default` organization that is provisioned during initial installation, and creating additional organizations is not supported
* Your initial password (configured below) must be least 12 characters long and have at least one lowercase, uppercase, and symbol
* There are no strict requirements for the secret used for signing JWTs, but we recommend securely generating a string of at least 32 characters. For example: `openssl rand -base64 32`
### Migrating from none auth
**Only supported in versions 0.7 and above.**
Migrating an installation from [None](/langsmith/authentication-methods#none) auth mode replaces the single "default" user with a user with the configured credentials and keeps all existing resources. The single pre-existing workspace ID post-migration remains `00000000-0000-0000-0000-000000000000`, but everything else about the migrated installation is standard for a basic auth installation.
To migrate, simply update your configuration as shown below and run `helm upgrade` (or `docker-compose up`) as usual.
### Configuration
Changing the JWT secret will log out your users
```yaml Helm theme={null}
config:
authType: mixed
basicAuth:
enabled: true
initialOrgAdminEmail:
initialOrgAdminPassword: # Must be at least 12 characters long and have at least one lowercase, uppercase, and symbol
jwtSecret:
```
```bash Docker theme={null}
# In your .env file
AUTH_TYPE=mixed
BASIC_AUTH_ENABLED=true
INITIAL_ORG_ADMIN_EMAIL=
INITIAL_ORG_ADMIN_PASSWORD= # Must be at least 12 characters long and have at least one lowercase, uppercase, and symbol
BASIC_AUTH_JWT_SECRET=
```
Additionally, in docker-compose you will need to run the bootstrap command to create the initial organization and user:
```bash theme={null}
docker-compose exec langchain-backend python hooks/auth_bootstrap.pyc
```
Once configured, you will see a login screen like the one below. You should be able to login with the `initialOrgAdminEmail` and `initialOrgAdminPassword` values, and your user will be auto-provisioned with role `Organization Admin`. See the [admin guide](/langsmith/administration-overview#organization-roles) for more details on organization roles.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-basic-auth.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Enable blob storage
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-blob-storage
By default, LangSmith stores run inputs, outputs, errors, manifests, extras, and events in ClickHouse. If you so choose, you can instead store this information in blob storage, which has a couple of notable benefits. For the best results in production deployments, we **strongly** recommend using blob storage, which offers the following benefits:
1. In high trace environments, inputs, outputs, errors, manifests, extras, and events may balloon the size of your databases.
2. If using LangSmith Managed ClickHouse, you may want sensitive information in blob storage that resides in your environment. To alleviate this, LangSmith supports storing run inputs, outputs, errors, manifests, extras, events, and attachments in an external blob storage system.
**For cloud-specific setup**, choose your platform:
* [Amazon S3 (AWS)](#amazon-s3)
* [Google Cloud Storage (GCP)](#google-cloud-storage)
* [Azure Blob Storage](#azure-blob-storage)
For complete cloud-specific setup and architecture guides, see [AWS](/langsmith/aws-self-hosted), [GCP](/langsmith/gcp-self-hosted), or [Azure](/langsmith/azure-self-hosted).
## Requirements
Azure blob storage is available in Helm chart versions 0.8.9 and greater. [Deleting trace projects](/langsmith/observability-concepts#deleting-traces-from-langsmith) is supported in Azure starting in Helm chart version 0.10.43.
* Access to a valid blob storage service
* [Amazon S3](https://aws.amazon.com/s3/)
* [Google Cloud Storage (GCS)](https://cloud.google.com/storage?hl=en)
* [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs)
* A bucket/directory in your blob storage to store the data. We highly recommend creating a separate bucket/directory for LangSmith data.
* **If you are using TTLs**, you will need to set up a lifecycle policy to delete old data. You can find more information on configuring TTLs [here](/langsmith/self-host-ttl). These policies should mirror the TTLs you have set in your LangSmith configuration, or you may experience data loss. See [here](#ttl-configuration) on how to setup the lifecycle rules for TTLs for blob storage.
* Credentials to permit LangSmith Services to access the bucket/directory
* You will need to provide your LangSmith instance with the necessary credentials to access the bucket/directory. Read the authentication [section](#authentication) below for more information.
* If using S3 or GCS, an API url for your blob storage service
* This will be the URL that LangSmith uses to access your blob storage system
* For Amazon S3, this will be the URL of the S3 endpoint. Something like: `https://s3.amazonaws.com` or `https://s3.us-west-1.amazonaws.com` if using a regional endpoint.
* For Google Cloud Storage, this will be the URL of the GCS endpoint. Something like: `https://storage.googleapis.com`
## Authentication
### Amazon S3
To authenticate to [Amazon S3](https://aws.amazon.com/s3/), you will need to create an IAM policy granting the following permissions on your bucket.
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-bucket-name",
"arn:aws:s3:::your-bucket-name/*"
]
}
]
}
```
Once you have the correct policy, there are three ways to authenticate with Amazon S3:
1. [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) (Recommended): You can create an IAM role for your LangSmith instance and attach the policy to that role. This is the recommended way to authenticate with Amazon S3 in production.
1. You will need to create an IAM role with the policy attached.
2. You will need to allow LangSmith service accounts to assume the role. The `langsmith-queue`, `langsmith-backend`, `langsmith-platform-backend`, and `langsmith-ingest-queue` service accounts will need to be able to assume the role.
The service account names will be different if you are using a custom release name. You can find the service account names by running `kubectl get serviceaccounts` in your cluster.
3. You will need to provide the role ARN to LangSmith. You can do this by adding the `eks.amazonaws.com/role-arn: ""` annotation to the `queue`, `backend`, `platform-backend`, and `ingest-queue` services in your Helm Chart installation.
2. [Access Key and Secret Key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html): You can provide LangSmith with an access key and secret key. This is the simplest way to authenticate with Amazon S3. However, it is not recommended for production use as it is less secure.
1. You will need to create a user with the policy attached. Then you can provision an access key and secret key for that user.
3. [VPC Endpoint Access](https://docs.aws.amazon.com/vpc/latest/privatelink/vpc-endpoints-s3.html): You can enable access to your S3 bucket via a VPC endpoint, which allows traffic to flow securely from your VPC to your S3 bucket.
1. You'll need to provision a VPC endpoint and configure it to allow access to your S3 bucket.
2. You can refer to our [public Terraform modules](https://github.com/langchain-ai/terraform/blob/main/modules/aws/s3/main.tf#L12) for guidance and an example of configuring this.
### KMS encryption header support
Starting with LangSmith Helm chart version **0.11.24**, you can pass a KMS encryption key header and enforce a specific KMS key for writes by providing its ARN. To enable this, set the following values in your Helm chart:
```yaml theme={null}
config:
blobStorage:
kmsEncryptionEnabled: true
kmsKeyArn:
```
### Google Cloud Storage
To authenticate with [Google Cloud Storage](https://cloud.google.com/storage?hl=en), you will need to create a [`service account`](https://cloud.google.com/iam/docs/service-account-overview) with the necessary permissions to access your bucket.
Your service account will need the `Storage Admin` role or a custom role with equivalent permissions. This can be scoped to the bucket that LangSmith will be using.
Once you have a provisioned service account, you will need to generate a [`HMAC key`](https://cloud.google.com/storage/docs/authentication/hmackeys) for that service account. This key and secret will be used to authenticate with Google Cloud Storage.
Google Cloud Storage (GCS) exposes an S3-compatible API. When using GCS, set the blob storage engine to "S3", configure the `apiURL` to your GCS endpoint (for example, `https://storage.googleapis.com`), and authenticate using a service account HMAC access key and secret (using `accessKey` and `accessKeySecret`).
You must use **HMAC** access key and secret for GCS. We do not support service account annotations.
### Azure Blob Storage
To authenticate with [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs), you will need to use one of the following methods to grant LangSmith workloads permission to access your [container](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction#containers) (listed in order of precedence):
1. [Storage account and access key](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage)
2. [Connection string](https://learn.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string)
3. [Workload identity](https://azure.github.io/azure-workload-identity/docs/introduction.html) (recommended), managed identity, or environment variables supported by [`DefaultAzureCredential`](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication?tabs=bash#2-authenticate-with-azure). This is the default authentication method when configuration for either option above is not present.
1. To use workload identity, add the label `azure.workload.identity/use: true` to the `queue`, `backend`, `platform-backend`, and `ingest-queue` deployments. Additionally, add the `azure.workload.identity/client-id` annotation to the corresponding service accounts, which should be an existing Azure AD Application's client ID or user-assigned managed identity's client ID. See [Azure's documentation](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html) for additional details.
Some deployments may need further customization of the connection configuration using a Service URL Override instead of the default service URL (`https://.blob.core.windows.net/`). For example, this override is necessary in order to use a different blob storage domain (e.g. government or china).
## CH search
By default, LangSmith will still store tokens for search in ClickHouse. If you are using LangSmith Managed Clickhouse, you may want to disable this feature to avoid sending potentially sensitive information to ClickHouse. You can do this in your blob storage configuration.
## Configuration
After creating your bucket and obtaining the necessary credentials, you can configure LangSmith to use your blob storage system.
```yaml Helm theme={null}
config:
blobStorage:
enabled: true
engine: "S3" # Or "Azure". This is case-sensitive.
chSearchEnabled: true # Set to false if you want to disable CH search (Recommended for LangSmith Managed Clickhouse)
bucketName: "your-bucket-name"
apiURL: "Your connection url"
accessKey: "Your access key" # Optional. Only required if using S3 access key and secret key
accessKeySecret: "Your access key secret" # Optional. Only required if using access key and secret key
# The following blob storage configuration values are for Azure and require blobStorage.engine = "Azure". Omit otherwise.
azureStorageAccountName: "Your storage account name" # Optional. Only required if using storage account and access key.
azureStorageAccountKey: "Your storage account access key" # Optional. Only required if using storage account and access key.
azureStorageContainerName: "your-container-name" # Required
azureStorageConnectionString: "" # Optional.
azureStorageServiceUrlOverride: "" # Optional
backend: # Optional, only required if using IAM role for service account on AWS or workload identity on AKS
deployment: # Azure only
labels:
azure.workload.identity/use: true
serviceAccount:
annotations:
azure.workload.identity/client-id: "" # Azure only
eks.amazonaws.com/role-arn: "" # AWS only
platformBackend: # Optional, only required if using IAM role for service account on AWS or workload identity on AKS
deployment: # Azure only
labels:
azure.workload.identity/use: true
serviceAccount:
annotations:
azure.workload.identity/client-id: "" # Azure only
eks.amazonaws.com/role-arn: "" # AWS only
queue: # Optional, only required if using IAM role for service account on AWS or workload identity on AKS
deployment: # Azure only
labels:
azure.workload.identity/use: true
serviceAccount:
annotations:
azure.workload.identity/client-id: "" # Azure only
eks.amazonaws.com/role-arn: "" # AWS only
ingestQueue: # Optional, only required if using IAM role for service account on AWS or workload identity on AKS
deployment: # Azure only
labels:
azure.workload.identity/use: true
serviceAccount:
annotations:
azure.workload.identity/client-id: "" # Azure only
eks.amazonaws.com/role-arn: "" # AWS only
```
```bash Docker theme={null}
# In your .env file
FF_BLOB_STORAGE_ENABLED=false # Set to true if you want to enable blob storage
BLOB_STORAGE_ENGINE=S3 # Or Azure
BLOB_STORAGE_BUCKET_NAME=langsmith-blob-storage # Required for using S3. Change to your desired blob storage bucket name
BLOB_STORAGE_API_URL=https://s3.us-west-2.amazonaws.com # Change to your desired blob storage API URL
BLOB_STORAGE_ACCESS_KEY=your-access-key # Change to your desired blob storage access key
BLOB_STORAGE_ACCESS_KEY_SECRET=your-access-key-secret # Change to your desired blob storage access key secret
AZURE_STORAGE_ACCOUNT_NAME=your-storage-account-name # Optional. Only required if using storage account and access key.
AZURE_STORAGE_ACCOUNT_KEY=your-storage-account-key # Optional. Only required if using storage account and access key.
AZURE_STORAGE_CONTAINER_NAME=your-container-name # Required for using Azure blob storage. Change to your desired container name
AZURE_STORAGE_CONNECTION_STRING=BlobEndpoint=https://storagesample.blob.core.windows.net;SharedAccessSignature=signature; # Optional.
AZURE_STORAGE_SERVICE_URL_OVERRIDE=https://your.override.domain.net # Optional
```
If using an access key and secret, you can also provide an existing Kubernetes secret that contains the authentication information. This is recommended over providing the access key and secret key directly in your config. See the [generated secret template](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/templates/secrets.yaml) for the expected secret keys.
## TTL configuration
If using the [TTL](/langsmith/self-host-ttl) feature with LangSmith, you'll also have to configure TTL rules for your blob storage. Trace information stored on blob storage is stored on a particular prefix path, which determines the TTL for the data. When a trace's retention is extended, its corresponding blob storage path changes to ensure that it matches the new extended retention.
The following TTL prefix are used:
* `ttl_s/`: Short term TTL, configured for 14 days.
* `ttl_l/`: Long term TTL, configured for 400 days.
If you have customized the TTLs in your LangSmith configuration, you will need to adjust the TTLs in your blob storage configuration to match.
### Amazon S3 lifecycle rules
If using S3 for your blob storage, you will need to setup a filter lifecycle configuration that matches the prefixes above. You can find information for this [in the Amazon Documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/intro-lifecycle-rules.html#intro-lifecycle-rules-filter).
As an example, if you are using Terraform to manage your S3 bucket, you would setup something like this:
```hcl theme={null}
rule {
id = "short-term-ttl"
prefix = "ttl_s/"
enabled = true
expiration {
days = 14
}
}
rule {
id = "long-term-ttl"
prefix = "ttl_l/"
enabled = true
expiration {
days = 400
}
}
```
### Google Cloud Storage lifecycle rules
You will need to setup lifecycle conditions for your GCS buckets that you are using. You can find information for this [in the Google Documentation](https://cloud.google.com/storage/docs/lifecycle#conditions), specifically using matchesPrefix.
As an example, if you are using Terraform to manage your GCS bucket, you would setup something like this:
```hcl theme={null}
lifecycle_rule {
condition {
age = 14
matches_prefix = ["ttl_s"]
}
action {
type = "Delete"
}
}
lifecycle_rule {
condition {
age = 400
matches_prefix = ["ttl_l"]
}
action {
type = "Delete"
}
}
```
### Azure blob storage lifecycle management
You will need to configure a [lifecycle management policy](https://learn.microsoft.com/en-us/azure/storage/blobs/lifecycle-management-policy-configure) on the container in order to expire objects matching the prefixes above.
As an example, if you are [using Terraform to manage your blob storage container](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_management_policy), you would setup something like this:
```hcl theme={null}
resource "azurerm_storage_management_policy" "example" {
storage_account_id = "my-storage-account-id"
rule {
name = "base"
enabled = true
type = "Lifecycle"
filters {
prefix_match = ["my-container/ttl_s"]
blob_types = ["blockBlob"]
}
actions {
base_blob {
delete_after_days_since_creation_greater_than = 14
}
snapshot {
delete_after_days_since_creation_greater_than = 14
}
version {
delete_after_days_since_creation_greater_than = 14
}
}
}
rule {
name = "extended"
enabled = true
type = "Lifecycle"
filters {
prefix_match = ["my-container/ttl_l"]
blob_types = ["blockBlob"]
}
actions {
base_blob {
delete_after_days_since_creation_greater_than = 400
}
snapshot {
delete_after_days_since_creation_greater_than = 400
}
version {
delete_after_days_since_creation_greater_than = 400
}
}
}
}
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-blob-storage.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Configure custom TLS certificates
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-custom-tls-certificates
Use this guide to configure TLS in LangSmith. Start by mounting internal certificate authorities (CAs) so your deployment trusts the right roots system‑wide, for database or external service calls. You can then configure [Playground](/langsmith/prompt-engineering-concepts#prompt-playground)-specific mTLS for communicating securely with supported model providers.
This page covers:
* [Mounting internal certificate authorities](#mount-internal-cas-for-tls) (CAs) system-wide to enable TLS for database connections and Playground model calls
* Using Playground-specific TLS settings to provide client certs/keys for mTLS with supported model providers
## Mount internal CAs for TLS
You must use Helm chart version 0.11.9 or later to mount internal CAs using the configuration below.
Use this approach to make internal/public CAs trusted system‑wide by LangSmith (Playground model calls and [database/external service connections](/langsmith/self-hosted#storage-services)).
1. Create a file containing all CAs required for TLS with databases and external services. If your deployment is communicating directly to `beacon.langchain.com` without a proxy, make sure to include a public trusted CA. All certs should be concatenated in this file with an empty line in between.
```
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
...
```
2. Create a Kubernetes secret with a key containing the contents of this file.
```bash theme={null}
kubectl create secret generic --from-file== -n
```
3. If using custom CA for TLS with your databases and other external services, provide the following values to your LangSmith helm chart:
```yaml Helm theme={null}
config:
customCa:
secretName: # The name of the secret created in step 2.
secretKey: # The key in the secret containing the CA bundle.
clickhouse:
external:
tls: true # Only enable if you want TLS for Clickhouse.
postgres:
external:
customTls: true # Only enable if you want TLS for Postgres.
```
4. Make sure to use TLS supported connection strings:
* Postgres: Add `?sslmode=verify-full&sslrootcert=system` to the end.
* Redis: Use `rediss://` instead of `redis://` as the prefix.
## Use custom TLS certificates for model providers
This feature is currently only available for the following model providers:
* Azure OpenAI
* OpenAI
* Custom (our custom model server). Refer to the [custom model server documentation](/langsmith/custom-endpoint) for more information.
These TLS settings apply to all invocations of the selected model providers (including Online Evaluation). Use them when the provider requires mutual TLS (client cert/key) or when you must override trust with a specific CA for provider calls. They complement the internal CA bundle configured above.
You can use custom TLS certificates to connect to model providers in the LangSmith Playground. This is useful if you are using a self-signed certificate, a certificate from a custom certificate authority, or mutual TLS authentication.
To use custom TLS certificates, set the following environment variables. See the [self hosted deployment section](/langsmith/architectural-overview) for more information on how to configure application settings.
* \[Optional] `LANGSMITH_PLAYGROUND_TLS_KEY`: The private key in PEM format. This must be a file path (for a mounted volume). This is usually only necessary for mutual TLS authentication.
* \[Optional] `LANGSMITH_PLAYGROUND_TLS_CERT`: The certificate in PEM format. This must be a file path (for a mounted volume). This is usually only necessary for mutual TLS authentication.
* \[Optional] `LANGSMITH_PLAYGROUND_TLS_CA`: The custom certificate authority (CA) certificate in PEM format. This must be a file path (for a mounted volume). Use this to mount CAs only if you're using a helm version below `0.11.9`; otherwise, use the [Mount internal CAs for TLS](./self-host-custom-tls-certificates#mount-internal-cas-for-tls) section above.
Once you have set these environment variables, enter the LangSmith Playground **Settings** page and select the **Provider** that requires custom TLS certificates. Set your model provider configuration as usual, and the custom TLS certificates will be used when connecting to the model provider.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-custom-tls-certificates.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Egress for billing and operational telemetry
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-egress
This page only applies to customers who are not running in offline (air-gapped) mode and assumes you are using a self-hosted LangSmith instance serving version 0.9.0 or later.
Self-hosted LangSmith instances store all information locally and never send sensitive information outside of your network. However, unless you are running in offline mode, LangSmith requires egress to `https://beacon.langchain.com` for the following:
* **Billing telemetry** — License verification and subscription/usage reporting (required)
* **Operational telemetry** — Logs, metrics, and traces for support diagnostics (optional, can be disabled)
**Egress to `https://beacon.langchain.com` is required.** Refer to the [allowlisting IP section](/langsmith/cloud#allowlisting-ip-addresses) for static IP addresses, if needed.
## Billing telemetry
Billing telemetry is **required** for self-hosted LangSmith instances that are not running in offline mode. This includes license verification and subscription/usage reporting.
Billing telemetry **cannot be disabled**. If you need to run without any egress, contact your account team about an offline (air-gapped) license.
### What it does
* **License verification**: Validates your LangSmith license key at startup and periodically thereafter.
* **Subscription/usage reporting**: Reports platform usage metrics for billing purposes according to the entitlements in your order.
### What we collect
* License key validation requests
* Aggregated usage counts (number of traces, seats allocated, seats in use)
* Organization and workspace identifiers
### Example payloads
#### License verification
**Endpoint:** `POST beacon.langchain.com/v1/beacon/verify`
**Request:**
```json theme={null}
{
"license": ""
}
```
**Response:**
```json theme={null}
{
"token": "Valid JWT" //Short-lived JWT token to avoid repeated license checks
}
```
#### Subscription/usage reporting
**Endpoint:** `POST beacon.langchain.com/v1/beacon/ingest-traces`
**Request:**
```json theme={null}
{
"license": "",
"trace_transactions": [
{
"id": "af28dfea-5358-463d-a2dc-37df1da72498",
"tenant_id": "3a1c2b6f-4430-4b92-8a5b-79b8b567bbc1",
"session_id": "b26ae531-cdb3-42a5-8bcf-05355199fe27",
"trace_count": 5,
"start_insertion_time": "2025-01-06T10:00:00Z",
"end_insertion_time": "2025-01-06T11:00:00Z",
"start_interval_time": "2025-01-06T09:00:00Z",
"end_interval_time": "2025-01-06T10:00:00Z",
"status": "completed",
"num_failed_send_attempts": 0,
"transaction_type": "type1",
"organization_id": "c5b5f53a-4716-4326-8967-d4f7f7799735"
}
]
}
```
**Response:**
```json theme={null}
{
"inserted_count": 1 //Number of transactions successfully ingested
}
```
## Operational telemetry
As of version **0.11**, LangSmith deployments send operational telemetry by default. This telemetry helps the LangChain team provide proactive support and faster troubleshooting for self-hosted instances.
Operational telemetry is **separate from** billing telemetry. You can disable operational telemetry while billing telemetry remains active.
### What it does
* Enables proactive support and faster troubleshooting of self-hosted instances
* Assists with performance tuning
* Helps prioritize improvements based on real-world usage patterns
### What we collect
* **Request metadata**: Anonymized request counts, sizes, and durations
* **Database metrics**: Query durations, error rates, and performance counters
* **Operational traces**: Timing and error information for high-latency or failed requests (these are **not** customer traces — they are traces about the functioning of the LangSmith instance itself)
* **Log messages**: Warning and error log messages only
We do not collect actual payload contents, database records, or any data that can identify your end users or customers. All telemetry data is associated with an organization and deployment, but never identified with individual users. We **do not collect PII** (personally identifiable information) in any form.
### How to disable
You can disable operational telemetry by setting the following values in your `langsmith_config.yaml` file:
```yaml theme={null}
config:
telemetry:
logs: false
metrics: false
traces: false
```
You can also disable individual telemetry types by setting only specific values to `false`.
Disabling operational telemetry stops exporting the logs, metrics, and traces described in this section. It does **not** disable billing telemetry (license verification and subscription/usage reporting).
### Example payloads
#### Operational metrics
**Endpoint:** `POST beacon.langchain.com/v1/beacon/v1/metrics`
**Request:**
```json theme={null}
{
"resourceMetrics": [
{
"resource": {
"attributes": [
{
"key": "resource.name",
"value": { "stringValue": "langsmith-metrics" }
},
{
"key": "env",
"value": { "stringValue": "ls_self_hosted" }
}
]
},
"scopeMetrics": [
{
"scope": {
"name": "langsmith.metrics",
"version": "0.1.0"
},
"metrics": [
{
"name": "langsmith_http_requests_latency",
"unit": "seconds",
"description": "Request latency of LangSmith services",
"gauge": {
"dataPoints": [
{
"asDouble": 12.34,
"startTimeUnixNano": 1678886400000000000,
"timeUnixNano": 1678886400000000000,
"attributes": [
{
"key": "endpoint",
"value": { "stringValue": "/sessions" }
},
{ "key": "method", "value": { "stringValue": "GET" } },
{
"key": "service_name",
"value": { "stringValue": "langsmith_backend" }
}
]
}
]
}
},
{
"name": "langsmith_http_requests_failed",
"unit": "1",
"description": "Counter of failed requests for LangSmith services",
"sum": {
"dataPoints": [
{
"asInt": 456,
"startTimeUnixNano": 1678886400000000000,
"timeUnixNano": 1678886400000000000,
"attributes": [
{
"key": "endpoint",
"value": { "stringValue": "/info" }
},
{ "key": "method", "value": { "stringValue": "POST" } },
{
"key": "service_name",
"value": { "stringValue": "langsmith_platform_backend" }
}
],
"aggregationTemporality": 2,
"isMonotonic": true
}
]
}
}
]
}
]
}
]
}
```
#### Operational traces
**Endpoint:** `POST beacon.langchain.com/v1/beacon/v1/traces`
**Request:**
```json theme={null}
{
"resourceSpans": [
{
"resource": {
"attributes": [
{
"key": "env",
"value": {
"stringValue": "ls_self_hosted"
}
},
{
"key": "service.name",
"value": {
"stringValue": "langsmith_backend"
}
}
]
},
"scopeSpans": [
{
"scope": {},
"spans": [
{
"traceId": "71699b6fe85982c7c8995ea3d9c95df2",
"spanId": "3c191d03fa8be0",
"parentSpanId": "",
"name": "receive_request",
"startTimeUnixNano": "1581452772000000321",
"endTimeUnixNano": "1581452773000000789",
"droppedAttributesCount": 1,
"events": [
{
"timeUnixNano": "1581452773000000123",
"name": "parse_request",
"attributes": [
{
"key": "request_size",
"value": {
"stringValue": "100"
}
}
],
"droppedAttributesCount": 2
},
{
"timeUnixNano": "1581452773000000123",
"name": "event",
"droppedAttributesCount": 2
}
],
"droppedEventsCount": 1,
"status": {
"message": "status-cancelled",
"code": 2
}
},
{
"traceId": "71699b6fe85982c7c8995ea3d9c95df2",
"spanId": "0932ksdka12345",
"parentSpanId": "3c191d03fa8be0",
"name": "process_request",
"startTimeUnixNano": "1581452772000000321",
"endTimeUnixNano": "1581452773000000789",
"links": [],
"droppedLinksCount": 3,
"status": {}
}
]
}
]
}
]
}
```
#### Operational log messages
We only export error log messages from self-hosted LangSmith instances. This allows the LangChain team to troubleshoot application errors without requiring back-and-forth communication with your team.
**Endpoint:** `POST beacon.langchain.com/v1/beacon/v1/logs`
**Request:**
```json theme={null}
{
"resourceLogs": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "langsmith_backend"
}
}
]
},
"scopeLogs": [
{
"scope": {},
"logRecords": [
{
"timeUnixNano": "1581452773000009875",
"severityNumber": 13,
"severityText": "Warning",
"body": {
"stringValue": "Database connection pool approaching capacity"
},
"attributes": [
{
"key": "component",
"value": {
"stringValue": "langsmith_backend"
}
},
{
"key": "pool_size",
"value": {
"intValue": "95"
}
}
],
"droppedAttributesCount": 0,
"traceId": "08040201000000000000000000000000",
"spanId": "0102040800000000"
},
{
"timeUnixNano": "1581452773000000789",
"severityNumber": 17,
"severityText": "Error",
"body": {
"stringValue": "Failed to process trace batch"
},
"attributes": [
{
"key": "component",
"value": {
"stringValue": "langsmith_queue_worker"
}
},
{
"key": "error_type",
"value": {
"stringValue": "timeout"
}
}
],
"droppedAttributesCount": 0,
"traceId": "",
"spanId": ""
}
]
}
]
}
]
}
```
## Our commitment
LangChain will not store any sensitive information in billing or operational telemetry. Any data collected will not be shared with a third party. Log messages are filtered to only include error severity levels, and we do not capture log messages that could contain sensitive application data. If you have any concerns about the data being sent, disable telemetry and/or reach out to your account team.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-egress.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Connect to an external ClickHouse database
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-external-clickhouse
ClickHouse is a high-performance, column-oriented database system. It allows for fast ingestion of data and is optimized for analytical queries.
LangSmith uses ClickHouse as the primary data store for traces and feedback. By default, self-hosted LangSmith will use an internal ClickHouse database that is bundled with the LangSmith instance. This is run as a stateful set in the same Kubernetes cluster as the LangSmith application or as a Docker container on the same host as the LangSmith application.
However, you can configure LangSmith to use an external ClickHouse database for easier management and scaling. By configuring an external ClickHouse database, you can manage backups, scaling, and other operational tasks for your database. While Clickhouse is not yet a native service in Azure, AWS, or Google Cloud, you can run LangSmith with an external ClickHouse database in the following ways:
* [LangSmith-managed ClickHouse](/langsmith/langsmith-managed-clickhouse)
* Provision a [ClickHouse Cloud](https://clickhouse.cloud/) either directly or through a cloud provider marketplace:
* [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/clickhouse.clickhouse_cloud?tab=Overview)
* [Google Cloud Marketplace](https://console.cloud.google.com/marketplace/product/clickhouse-public/clickhouse-cloud)
* [AWS Marketplace](https://aws.amazon.com/marketplace/seller-profile?id=adb43736-8b95-4d49-8009-3693cbee8578)
* On a VM in your cloud provider
Using the first two options (LangSmith-managed ClickHouse or ClickHouse Cloud) will provision a Clickhouse service OUTSIDE of your VPC. However, both options support private endpoints, meaning that you can direct traffic to the ClickHouse service without exposing it to the public internet (eg via AWS PrivateLink, or GCP Private Service Connect).
Additionally, sensitive information can be configured to be not stored in Clickhouse. Please contact support via [support.langchain.com](https://support.langchain.com) for more information.
## Requirements
* A provisioned ClickHouse instance that your LangSmith application will have network access to (see above for options).
* A user with admin access to the ClickHouse database. This user will be used to create the necessary tables, indexes, and views.
* We support both standalone ClickHouse and externally managed clustered deployments. For clustered deployments, ensure all nodes are running the same version. Note that clustered setups are not supported with bundled ClickHouse installations.
* We only support ClickHouse versions >= 23.9. Use of ClickHouse versions >= 24.2 requires LangSmith v0.6 or later.
Downgrading ClickHouse to an earlier version can cause data corruption of system tables and result in significant downtime. If you need assistance with a ClickHouse version change or are experiencing issues after an upgrade, contact support at [support.langchain.com](https://support.langchain.com) before attempting a downgrade.
* We rely on a few configuration parameters to be set on your ClickHouse instance. These are detailed below:
```xml theme={null}
1 # Turn on async insert
25000000 # Flush data to disk after 25MB. You may need to adjust this based on your workload.
0 # Disable waiting for async insert by default
1 # Enable parallel view processing
0 # Disable TTL materialization after modify
120 # Set the timeout for waiting for async insert
0 # Disable lightweight deletes sync
1 # Allow materialized views with legacy SELECT statements that cause CH to fail
```
Our system has been tuned to work with the above configuration parameters. Changing these parameters may result in unexpected behavior.
## HA replicated Clickhouse cluster
By default, the setup process above will only work with a single node Clickhouse cluster.
If you would like to use a multi-node Clickhouse cluster for HA, we support this with additional required configuration. This setup can use a Clickhouse cluster with multiple nodes where data replicated via Zookeeper or Clickhouse Keeper. For more information on Clickhouse replication, see [Clickhouse Data Replication Docs](https://clickhouse.com/docs/architecture/replication).
In order to setup LangSmith with a replicated multi-node Clickhouse setup:
* You need to have a Clickhouse cluster that is setup with Keeper or Zookeeper for data replication and the appropriate settings. See [Clickhouse Replication Setup Docs](https://clickhouse.com/docs/architecture/replication).
* You need to set the cluster setting in the [LangSmith Configuration](#configuration) section, specifically the `cluster` settings to match your Clickhouse Cluster name. This will use the `Replicated` table engines when running the Clickhouse migrations.
* If in addition to HA, you would like to load balance among the Clickhouse nodes (to distribute reads or writes), we suggest using a load balancer or DNS load balancing to round robin among your Clickhouse servers.
* **Note**: You will need to enable your `cluster` setting before launching LangSmith for the first time and running the Clickhouse migrations. This is a requirement since the table engine will need to be created as a `Replicated` table engine vs the non replicated engine type.
When running migrations with `cluster` enabled, the migration will create the `Replicated` table engine flavor. This means that data will be replicated among the servers in the cluster. This is a master-master setup where any server can process reads, writes, or merges.
For an example setup of a replicated ClickHouse cluster, refer to the [replicated ClickHouse section](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/examples/replicated-clickhouse/README.md) in the LangSmith Helm chart repo, under examples.
## LangSmith-managed ClickHouse
* If using LangSmith-managed ClickHouse, you will need to set up a VPC peering connection between the LangSmith VPC and the ClickHouse VPC. Please contact support via [support.langchain.com](https://support.langchain.com) for more information.
* You will also need to set up Blob Storage. You can read more about Blob Storage in the [Blob Storage documentation](/langsmith/self-host-blob-storage).
ClickHouse installations managed by LangSmith use a SharedMerge engine, which automatically clusters them and separates compute from storage.
For more information, refer to the [managed ClickHouse](/langsmith/langsmith-managed-clickhouse) page.
## Parameters
You will need to provide several parameters to your LangSmith installation to configure an external ClickHouse database. These parameters include:
* **Host**: The hostname or IP address of the ClickHouse database
* **HTTP Port**: The port that the ClickHouse database listens on for HTTP connections
* **Native Port**: The port that the ClickHouse database listens on for [native connections](https://clickhouse.com/docs/en/interfaces/tcp)
* **Database**: The name of the ClickHouse database that LangSmith should use
* **Username**: The username to use to connect to the ClickHouse database
* **Password**: The password to use to connect to the ClickHouse database
* **Cluster (Optional)**: The name of the ClickHouse cluster if using an external Clickhouse cluster. When set, LangSmith will run migrations on the cluster and replicate data across instances.
Important considerations for clustered deployments:
* Clustered setups must be configured on a fresh schema - existing standalone ClickHouse instances cannot be converted to clustered mode.
* Clustering is only supported with externally managed ClickHouse deployments. It is not compatible with bundled ClickHouse installations as these do not include required ZooKeeper configurations.
* When using a clustered deployment, LangSmith will automatically:
* Run database migrations across all nodes in the cluster
* Configure tables for data replication across the cluster
Note that while data is replicated across nodes, LangSmith does not configure distributed tables or handle query routing - queries will be directed to the specified host. You will need to handle any load balancing or query distribution at the infrastructure level if desired.
## Configuration
With these parameters in hand, you can configure your LangSmith instance to use the provisioned ClickHouse database. You can do this by modifying the `config.yaml` file for your LangSmith Helm Chart installation or the `.env` file for your Docker installation.
```yaml Helm theme={null}
clickhouse:
external:
enabled: true
host: "host"
port: "http port"
nativePort: "native port"
user: "default"
password: "password"
database: "default"
tls: false
cluster: "my_cluster_name" # Optional: Set this if using an external Clickhouse cluster
```
```bash Docker theme={null}
# In your .env file
CLICKHOUSE_HOST=langchain-clickhouse # Change to your Clickhouse host if using external Clickhouse. Otherwise, leave it as is
CLICKHOUSE_USER=default # Change to your Clickhouse user if needed
CLICKHOUSE_DB=default # Change to your Clickhouse database if needed
CLICKHOUSE_PORT=8123 # Change to your Clickhouse port if needed
CLICKHOUSE_TLS=false # Change to true if you are using TLS to connect to Clickhouse. Otherwise, leave it as is
CLICKHOUSE_PASSWORD=password # Change to your Clickhouse password if needed
CLICKHOUSE_NATIVE_PORT=9000 # Change to your Clickhouse native port if needed
CLICKHOUSE_CLUSTER=my_cluster_name # Optional: Set this if using an external Clickhouse cluster
```
Once configured, you should be able to reinstall your LangSmith instance. If everything is configured correctly, your LangSmith instance should now be using your external ClickHouse database.
## TLS with ClickHouse
Use this section to configure TLS for ClickHouse connections. For mounting internal/public CAs so LangSmith trusts your ClickHouse server certificate, see [Configure custom TLS certificates](/langsmith/self-host-custom-tls-certificates#mount-internal-cas-for-tls).
### Server TLS (one-way)
To enable TLS for ClickHouse connections:
* Set `tls: true` in your configuration (or use `tlsSecretKey` with an external secret).
* Use the appropriate TLS ports (typically `8443` for HTTP and `9440` for native TCP connections).
* Provide a CA bundle using `config.customCa.secretName` and `config.customCa.secretKey` if using an internal CA.
Mount a custom CA only when your ClickHouse server uses an internal or private CA. Publicly trusted CAs do not require this configuration.
```yaml Helm (server TLS) theme={null}
config:
customCa:
secretName: "langsmith-custom-ca" # Secret containing your CA bundle
secretKey: "ca.crt" # Key in the Secret with the CA bundle
clickhouse:
external:
enabled: true
host: "your-clickhouse-host.example.com"
port: "8443"
nativePort: "9440"
user: "default"
password: "password"
database: "default"
tls: true
```
```yaml Kubernetes Secret (CA bundle) theme={null}
apiVersion: v1
kind: Secret
metadata:
name: langsmith-custom-ca
type: Opaque
stringData:
ca.crt: |
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
```
### Mutual TLS with client auth (mTLS)
As of LangSmith helm chart version **0.12.29**, we support mTLS for ClickHouse clients. For server-side authentication in mTLS, use the [Server TLS steps](#server-tls-one-way) (custom CA) in addition to the following client certificate configuration.
If your ClickHouse server requires client certificate authentication:
* Provide a Secret with your client certificate and key.
* Reference it via `clickhouse.external.clientCert.secretName` and specify the keys with `certSecretKey` and `keySecretKey`.
```yaml Helm (client auth) theme={null}
clickhouse:
external:
enabled: true
host: "your-clickhouse-host.example.com"
port: "8443"
nativePort: "9440"
user: "default"
password: "password"
database: "default"
tls: true
clientCert:
secretName: "clickhouse-client-cert"
certSecretKey: "tls.crt"
keySecretKey: "tls.key"
```
```yaml Kubernetes Secret (client cert/key) theme={null}
apiVersion: v1
kind: Secret
metadata:
name: clickhouse-client-cert
type: Opaque
stringData:
tls.crt: |
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
tls.key: |
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
```
#### Non-TLS native port for migrations
When using mTLS with ClickHouse, you must **keep a non-TLS native (TCP) port** open for our migrations job, which runs on helm install and upgrade. The application itself will not communicate through this port, it is **only used by the migration job**.
By default, the migration job connects to port `9000` for migrations. If your ClickHouse instance uses a different non-TLS native port, you can configure it using the `CLICKHOUSE_MIGRATE_NATIVE_PORT` environment variable:
```yaml theme={null}
backend:
clickhouseMigrations:
extraEnv:
- name: CLICKHOUSE_MIGRATE_NATIVE_PORT
value: "9000" # Change to your non-TLS native port
```
#### Pod security context for certificate volumes
The certificate volumes mounted for mTLS are protected by file access restrictions. To ensure all LangSmith pods can read the certificate files, you must set `fsGroup: 1000` in the pod security context.
You can configure this in one of two ways:
**Option 1: Use `commonPodSecurityContext`**
Set the `fsGroup` at the top level to apply it to all pods:
```yaml theme={null}
commonPodSecurityContext:
fsGroup: 1000
```
**Option 2: Add to individual pod security contexts**
If you need more granular control, add the `fsGroup` to each pod's security context individually. See the [mTLS configuration example](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/examples/mtls_config.yaml) for a complete reference.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-external-clickhouse.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Connect to an external PostgreSQL database
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-external-postgres
LangSmith uses a PostgreSQL database as the primary data store for transactional workloads and operational data (almost everything besides runs). By default, LangSmith Self-Hosted will use an internal PostgreSQL database. However, you can configure LangSmith to use an external PostgreSQL database. By configuring an external PostgreSQL database, you can more easily manage backups, scaling, and other operational tasks for your database.
**If you're using a managed PostgreSQL service**, we recommend:
* [Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_GettingStarted.CreatingConnecting.PostgreSQL.html) (AWS)
* [Google Cloud SQL](https://cloud.google.com/curated-resources/cloud-sql#section-1) (GCP)
* [Azure Database for PostgreSQL](https://azure.microsoft.com/en-us/products/postgresql#features) (Azure)
For cloud-specific IAM/Workload Identity authentication, refer to the [IAM authentication section](#iam-authentication).
## Requirements
* A provisioned PostgreSQL database that your LangSmith instance will have network access to. We recommend using a managed PostgreSQL service like:
* [Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_GettingStarted.CreatingConnecting.PostgreSQL.html)
* [Google Cloud SQL](https://cloud.google.com/curated-resources/cloud-sql#section-1)
* [Azure Database for PostgreSQL](https://azure.microsoft.com/en-us/products/postgresql#features)
* Note: We only officially support PostgreSQL versions >= 14.
* We support password and [IAM/Workload Identity](#iam-authentication) authentication.
* A user with admin access to the PostgreSQL database. This user will be used to create the necessary tables, indexes, and schemas.
* This user will also need to have the ability to create extensions in the database. We use/will try to install the `btree_gin`, `btree_gist`, `pgcrypto`, `citext`, `ltree`, and `pg_trgm` extensions.
* If using a schema other than public, ensure that you do not have any other schemas with the extensions enabled, or you must include that in your search path.
* Support for pgbouncer and other connection poolers is community-based. Community members have reported that pgbouncer has worked with `pool_mode` = `session` and a suitable setting for `ignore_startup_parameters` (as of writing, `search_path` and `lock_timeout` need to be ignored). Care is needed to avoid polluting connection pools; some level of PostgreSQL expertise is advisable. LangChain Inc currently does not have roadmap plans for formal test coverage or commercial support of pgbouncer or amazon rds proxy or any other poolers, but the community is welcome to discuss and collaborate on support through GitHub issues.
* By default, we recommend an instance with **at least 2 vCPUs and 8GB of memory**. However, the actual requirements will depend on your workload and the number of users you have. We recommend monitoring your PostgreSQL instance and scaling up as needed.
## Connection string
You will need to provide a connection string to your PostgreSQL database. This connection string should include the following information:
* Host
* Port
* Database
* Username
* Password (Make sure to url encode this if there are any special characters) - **Note:** When using IAM authentication, the password is not required in the connection string. More below.
* URL params
This will take the form of:
```
username:password@host:port/database?
```
An example connection string might look like:
```
myuser:mypassword@myhost:5432/mydatabase?sslmode=disable
```
Without url parameters, the connection string would look like:
```
myuser:mypassword@myhost:5432/mydatabase
```
For IAM authentication, omit the password and use the identity name as the username:
```
my-workload-identity@myhost:5432/mydatabase?sslmode=require
```
## Configuration
With your connection string in hand, you can configure your LangSmith instance to use an external PostgreSQL database. You can do this by modifying the `values` file for your LangSmith Helm Chart installation or the `.env` file for your Docker installation.
```yaml Helm theme={null}
postgres:
external:
enabled: true
connectionUrl: "Your connection url"
```
```bash Docker theme={null}
# In your .env file
POSTGRES_DATABASE_URI="Your connection url"
```
Once configured, you should be able to reinstall your LangSmith instance. If everything is configured correctly, your LangSmith instance should now be using your external PostgreSQL database.
## TLS with PostgreSQL
Use this section to configure TLS for PostgreSQL connections. For mounting internal/public CAs so LangSmith trusts your PostgreSQL server certificate, see [Configure custom TLS certificates](/langsmith/self-host-custom-tls-certificates#mount-internal-cas-for-tls).
### Server TLS (one-way)
To validate the PostgreSQL server certificate:
* Provide a CA bundle using `config.customCa.secretName` and `config.customCa.secretKey`.
* Use `sslmode=require` or `sslmode=verify-full`, as well as `sslrootcert=system` to your connection URL.
Mount a custom CA only when your PostgreSQL server uses an internal or private CA. Publicly trusted CAs do not require this configuration.
```yaml Helm (server TLS) theme={null}
config:
customCa:
secretName: "langsmith-custom-ca" # Secret containing your CA bundle
secretKey: "ca.crt" # Key in the Secret with the CA bundle
postgres:
external:
enabled: true
connectionUrl: "myuser:mypassword@myhost:5432/mydatabase?sslmode=verify-full&sslrootcert=system"
customTls: true
```
```yaml Kubernetes Secret (CA bundle) theme={null}
apiVersion: v1
kind: Secret
metadata:
name: langsmith-custom-ca
type: Opaque
stringData:
ca.crt: |
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
```
### Mutual TLS with client auth (mTLS)
As of LangSmith helm chart version **0.12.29**, we support mTLS for PostgreSQL clients. For server-side authentication in mTLS, use the [Server TLS steps](#server-tls-one-way) (custom CA) in addition to the following client certificate configuration.
If your PostgreSQL server requires client certificate authentication:
* Provide a Secret with your client certificate and key.
* Reference it via `postgres.external.clientCert.secretName` and specify the keys with `certSecretKey` and `keySecretKey`.
* Use `sslmode=verify-full` and `sslrootcert=system` in your connection URL.
```yaml Helm (client Auth) theme={null}
postgres:
external:
enabled: true
connectionUrl: "myuser:mypassword@myhost:5432/mydatabase?sslmode=verify-full&sslrootcert=system"
customTls: true
clientCert:
secretName: "postgres-mtls-secret"
certSecretKey: "tls.crt"
keySecretKey: "tls.key"
```
```yaml Kubernetes Secret (client cert/key) theme={null}
apiVersion: v1
kind: Secret
metadata:
name: postgres-mtls-secret
type: Opaque
stringData:
tls.crt: |
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
tls.key: |
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
```
#### Pod security context for certificate volumes
The certificate volumes mounted for mTLS are protected by file access restrictions. To ensure all LangSmith pods can read the certificate files, you must set `fsGroup: 1000` in the pod security context.
You can configure this in one of two ways:
**Option 1: Use `commonPodSecurityContext`**
Set the `fsGroup` at the top level to apply it to all pods:
```yaml theme={null}
commonPodSecurityContext:
fsGroup: 1000
```
**Option 2: Add to individual pod security contexts**
If you need more granular control, add the `fsGroup` to each pod's security context individually. See the [mTLS configuration example](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/examples/mtls_config.yaml) for a complete reference.
## IAM authentication
As of LangSmith helm chart version **0.12.34**, we support IAM authentication for PostgreSQL. This allows you to use cloud provider workload identity instead of static passwords.
IAM authentication only handles connection authentication. You may still need to run SQL commands in your database to create the IAM user/role and grant it the necessary permissions and privileges to access the LangSmith schema.
### Amazon RDS IAM authentication
Amazon RDS supports [IAM database authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html), which allows you to authenticate to your PostgreSQL instance using AWS IAM credentials instead of database passwords.
#### Prerequisites
1. **Configure workload identity** in your Kubernetes cluster using [AWS IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) or [EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html)
2. **Enable IAM authentication** on your RDS PostgreSQL instance and grant access to your workload identity
#### Configuration
If you switch to a new IAM user after LangSmith has already run initial migrations, you may need to transfer ownership of existing tables to the new IAM user. Otherwise, migrations may fail due to insufficient privileges on tables owned by the previous user.
Set the `iamAuthProvider` to `"aws"` and provide an IAM-compatible connection string (without password):
```yaml theme={null}
postgres:
external:
enabled: true
existingSecretName: "postgres-secret"
iamAuthProvider: "aws"
```
```yaml theme={null}
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
stringData:
# IAM connection URL - note no password, username is the IAM identity name
connection_url: "@:5432/?sslmode=require"
```
IAM authentication requires TLS. You must include `sslmode=require` in your connection string.
#### Required annotations
You must apply the ServiceAccount annotations required by AWS IRSA to all LangSmith components that connect to PostgreSQL:
**Deployments:** `backend`, `queue`, `platformBackend`, `hostBackend`, `ingestQueue`
**Jobs:** `migrations`, `authBootstrap`, `feedbackConfigMigration`, `feedbackDataMigration`, `e2eTest`
All jobs listed above (except `e2eTest`) use the `backend` service account. The `e2eTest` job uses its own service account and requires separate annotation configuration.
Example configuration for the backend service:
```yaml theme={null}
backend:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam:::role/"
queue:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam:::role/"
platformBackend:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam:::role/"
hostBackend:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam:::role/"
ingestQueue:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam:::role/"
```
See the [Helm values reference](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/values.yaml) for the full list of configurable services.
### Cloud SQL IAM authentication
Cloud SQL supports [IAM authentication](https://cloud.google.com/sql/docs/postgres/iam-authentication), which allows you to authenticate using GCP service accounts instead of database passwords.
#### Prerequisites
1. **Configure workload identity** in your Kubernetes cluster using [GCP Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)
2. **Enable IAM authentication** on your Cloud SQL instance and grant access to your workload identity
#### Configuration
If you switch to a new IAM user after LangSmith has already run initial migrations, you may need to transfer ownership of existing tables to the new IAM user. Otherwise, migrations may fail due to insufficient privileges on tables owned by the previous user.
Set the `iamAuthProvider` to `"gcp"` and provide an IAM-compatible connection string (without password):
```yaml theme={null}
postgres:
external:
enabled: true
existingSecretName: "postgres-secret"
iamAuthProvider: "gcp"
```
```yaml theme={null}
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
stringData:
# IAM connection URL - note no password, username is the service account email
connection_url: "@.iam@:5432/?sslmode=require"
```
IAM authentication requires TLS. You must include `sslmode=require` in your connection string.
#### Required annotations
You must apply the ServiceAccount annotations required by GCP Workload Identity to all LangSmith components that connect to PostgreSQL:
**Deployments:** `backend`, `queue`, `platformBackend`, `hostBackend`, `ingestQueue`
**Jobs:** `migrations`, `authBootstrap`, `feedbackConfigMigration`, `feedbackDataMigration`, `e2eTest`
All jobs listed above (except `e2eTest`) use the `backend` service account. The `e2eTest` job uses its own service account and requires separate annotation configuration.
Example configuration for the backend service:
```yaml theme={null}
backend:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "@.iam.gserviceaccount.com"
queue:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "@.iam.gserviceaccount.com"
platformBackend:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "@.iam.gserviceaccount.com"
hostBackend:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "@.iam.gserviceaccount.com"
ingestQueue:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "@.iam.gserviceaccount.com"
```
See the [Helm values reference](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/values.yaml) for the full list of configurable services.
### Azure Database for PostgreSQL with Microsoft Entra authentication
Azure Database for PostgreSQL supports [Microsoft Entra authentication](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-azure-ad-authentication), which allows you to authenticate using Azure managed identities instead of database passwords.
#### Prerequisites
1. **Configure workload identity** in your Kubernetes cluster using [Azure Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview)
2. **Enable Microsoft Entra authentication** on your Azure Database for PostgreSQL instance and grant access to your workload identity
#### Configuration
If you switch to a new IAM user after LangSmith has already run initial migrations, you may need to transfer ownership of existing tables to the new IAM user. Otherwise, migrations may fail due to insufficient privileges on tables owned by the previous user.
Set the `iamAuthProvider` to `"azure"` and provide an IAM-compatible connection string (without password):
```yaml theme={null}
postgres:
external:
enabled: true
existingSecretName: "postgres-secret"
iamAuthProvider: "azure"
```
```yaml theme={null}
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
stringData:
# IAM connection URL - note no password, username is the managed identity name
connection_url: "@:5432/?sslmode=require"
```
IAM authentication requires TLS. You must include `sslmode=require` in your connection string.
#### Required annotations
You must apply the ServiceAccount annotations and pod labels required by Azure Workload Identity to all LangSmith components that connect to PostgreSQL:
**Deployments:** `backend`, `queue`, `platformBackend`, `hostBackend`, `ingestQueue`
**Jobs:** `migrations`, `authBootstrap`, `feedbackConfigMigration`, `feedbackDataMigration`, `e2eTest`
All jobs listed above (except `e2eTest`) use the `backend` service account. For these jobs, you only need to configure pod labels (Azure requires `azure.workload.identity/use: "true"` on pods). The `e2eTest` job uses its own service account and requires separate annotation configuration.
Example configuration for the backend service:
```yaml theme={null}
backend:
serviceAccount:
annotations:
azure.workload.identity/client-id: ""
deployment:
labels:
azure.workload.identity/use: "true"
migrations:
labels:
azure.workload.identity/use: "true"
queue:
serviceAccount:
annotations:
azure.workload.identity/client-id: ""
deployment:
labels:
azure.workload.identity/use: "true"
platformBackend:
serviceAccount:
annotations:
azure.workload.identity/client-id: ""
deployment:
labels:
azure.workload.identity/use: "true"
hostBackend:
serviceAccount:
annotations:
azure.workload.identity/client-id: ""
deployment:
labels:
azure.workload.identity/use: "true"
ingestQueue:
serviceAccount:
annotations:
azure.workload.identity/client-id: ""
deployment:
labels:
azure.workload.identity/use: "true"
```
See the [Helm values reference](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/values.yaml) for the full list of configurable services.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-external-postgres.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Connect to an external Redis database
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-external-redis
LangSmith uses Redis to back our queuing/caching operations. By default, LangSmith Self-Hosted will use an internal Redis instance. However, you can configure LangSmith to use an external Redis instance. By configuring an external Redis instance, you can more easily manage backups, scaling, and other operational tasks for your Redis instance.
**If you're using a managed Redis service**, we recommend:
* [Amazon ElastiCache](https://aws.amazon.com/elasticache/redis/) (AWS)
* [Google Cloud Memorystore](https://cloud.google.com/memorystore) (GCP)
* [Azure Cache for Redis](https://azure.microsoft.com/en-us/services/cache/) (Azure)
For cloud-specific IAM/Workload Identity authentication, refer to the [IAM authentication section](#iam-authentication).
## Requirements
* A provisioned Redis instance that your LangSmith instance will have network access to. We recommend using a managed Redis service like:
* [Amazon ElastiCache](https://aws.amazon.com/elasticache/redis/)
* [Google Cloud Memorystore](https://cloud.google.com/memorystore)
* [Azure Cache for Redis](https://azure.microsoft.com/en-us/services/cache/)
* Note: We only officially support Redis versions >= 5.
* We support both Standalone and Redis Cluster. See the appropriate sections for deployment instructions.
* We support no authentication, password, and [IAM/Workload Identity](#iam-authentication) authentication.
* By default, we recommend an instance with at least 2 vCPUs and 8GB of memory. However, the actual requirements will depend on your tracing workload. We recommend monitoring your Redis instance and scaling up as needed.
## Standalone Redis
### Connection string
You will need to assemble the connection string for your Redis instance. This connection string should include the following information:
* Host
* Database
* Port
* URL params
This will take the form of:
```
"redis://host:port/db?"
```
An example connection string might look like:
```
"redis://langsmith-redis:6379/0"
```
Note: If your Standalone Redis requires authentication or TLS, include these directly in the connection URL:
* Use `rediss://` when TLS is enabled on your Redis server.
* Provide the password in the connection string.
For example:
```text theme={null}
rediss://langsmith-redis:6380/0?password=foo
```
For IAM authentication, use the identity as the username (no password):
```text theme={null}
rediss://@host:6380
```
### Configuration
With your connection string in hand, you can configure your LangSmith instance to use an external Redis instance. You can do this by modifying the `values` file for your LangSmith Helm Chart installation or the `.env` file for your Docker installation.
```yaml Helm theme={null}
redis:
external:
enabled: true
connectionUrl: "Your connection url"
```
```bash Docker theme={null}
# In your .env file
REDIS_DATABASE_URI="Your connection url"
```
You can also store the connection URL in an existing Kubernetes Secret and reference it in your Helm values.
```yaml Helm (using an existing Secret) theme={null}
redis:
external:
enabled: true
# Name of an existing Secret that contains the connection URL
existingSecretName: "my-redis-secret"
# Key in the Secret that stores the connection URL (default shown)
connectionUrlSecretKey: "connection_url"
```
```yaml Kubernetes Secret theme={null}
apiVersion: v1
kind: Secret
metadata:
name: my-redis-secret
type: Opaque
stringData:
# Full connection URL, e.g., using TLS with password
connection_url: "rediss://langsmith-redis:6380/0?password=foo"
```
Once configured, you should be able to reinstall your LangSmith instance. If everything is configured correctly, your LangSmith instance should now be using your external Redis instance.
## Redis cluster
As of LangSmith helm version **0.12.25**, we officially support **Redis Cluster**.
### Host names
When using Redis Cluster, provide a list of node hostnames and ports. Each node URI must be in the form:
```text theme={null}
redis://hostname:port
```
For example:
```text theme={null}
redis://redis-node-0:6379
redis://redis-node-1:6379
redis://redis-node-2:6379
```
Do not include a password in these URIs, and do not use `rediss` here. For Redis Cluster:
* Provide the password separately via `redis.external.cluster.password` or through a Secret using `passwordSecretKey`.
* Enable TLS separately with `redis.external.cluster.tlsEnabled: true`.
### Configuration
When connecting to an external Redis Cluster, configure the Helm values under `redis.external.cluster`. You can either:
* Provide node URIs and (optionally) a password directly in `values.yaml`.
* Or reference an existing Kubernetes `Secret` containing node URIs and password.
```yaml Helm (inline values) theme={null}
redis:
external:
enabled: true
cluster:
enabled: true
# List of cluster node URIs. Format: redis://host:port
nodeUris:
- "redis://redis-node-0:6379"
- "redis://redis-node-1:6379"
- "redis://redis-node-2:6379"
# Optional. If your cluster requires auth, set a password or use a Secret (recommended).
password: "your_redis_password"
# Enable if your cluster uses TLS.
tlsEnabled: true
```
```yaml Helm (using an existing Secret) theme={null}
redis:
external:
enabled: true
# Name of an existing Secret that contains cluster connection details
existingSecretName: "my-redis-cluster-secret"
cluster:
enabled: true
# Keys in the Secret. Defaults shown here; override if your Secret uses different keys.
nodeUrisSecretKey: "redis_cluster_node_uris"
passwordSecretKey: "redis_cluster_password"
tlsEnabled: true
```
If using an existing Secret, it should contain:
```yaml Kubernetes Secret theme={null}
apiVersion: v1
kind: Secret
metadata:
name: my-redis-cluster-secret
type: Opaque
stringData:
# JSON array of node URIs (as a string)
redis_cluster_node_uris: '["redis://redis-node-0:6379","redis://redis-node-1:6379","redis://redis-node-2:6379"]'
# Optional if your cluster requires a password
redis_cluster_password: "your_redis_password"
```
## TLS with Redis
Use this section to configure TLS for Redis connections. For mounting internal/public CAs so LangSmith trusts your Redis server certificate, see [Configure custom TLS certificates](/langsmith/self-host-custom-tls-certificates#mount-internal-cas-for-tls).
### Server TLS (one-way)
To validate the Redis server certificate:
* Provide a CA bundle using `config.customCa.secretName` and `config.customCa.secretKey`.
* For Standalone Redis, use `rediss://` in the connection URL.
* For Redis Cluster, set `redis.external.cluster.tlsEnabled: true`.
Mount a custom CA only when your Redis server uses an internal or private CA. Publicly trusted CAs do not require this configuration.
```yaml Helm (Standalone - server TLS) theme={null}
config:
customCa:
secretName: "langsmith-custom-ca" # Secret containing your CA bundle
secretKey: "ca.crt" # Key in the Secret with the CA bundle
redis:
external:
enabled: true
# Use rediss:// and include password if required by your server
connectionUrl: "rediss://host:6380/0?password="
```
```yaml Helm (Cluster - server TLS) theme={null}
config:
customCa:
secretName: "langsmith-custom-ca" # Secret containing your CA bundle
secretKey: "ca.crt" # Key in the Secret with the CA bundle
redis:
external:
enabled: true
cluster:
enabled: true
tlsEnabled: true
nodeUris:
- "redis://redis-node-0:6379"
- "redis://redis-node-1:6379"
- "redis://redis-node-2:6379"
password: ""
```
```yaml Kubernetes Secret (CA bundle) theme={null}
apiVersion: v1
kind: Secret
metadata:
name: langsmith-custom-ca
type: Opaque
stringData:
ca.crt: |
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
```
### Mutual TLS with client auth (mTLS)
As of LangSmith helm chart version **0.12.29**, we support mTLS for Redis clients. For server-side authentication in mTLS, use the [Server TLS steps](#server-tls-one-way) (custom CA) in addition to the following client certificate configuration.
If your Redis server requires client certificate authentication:
* Provide a Secret with your client certificate and key.
* Reference it via `redis.external.clientCert.secretName` and specify the keys with `certSecretKey` and `keySecretKey`.
* For Standalone Redis, keep using `rediss://` in the connection URL.
* For Redis Cluster, set `redis.external.cluster.tlsEnabled: true`.
```yaml Helm (client Auth) theme={null}
redis:
external:
enabled: true
clientCert:
secretName: "redis-mtls-secret"
certSecretKey: "tls.crt"
keySecretKey: "tls.key"
# Standalone example:
# connectionUrl: "rediss://host:6380/0?password="
# Or, for Cluster:
cluster:
enabled: true
tlsEnabled: true
nodeUris:
- "redis://redis-node-0:6379"
- "redis://redis-node-1:6379"
- "redis://redis-node-2:6379"
password: ""
```
```yaml Kubernetes Secret (client cert/key) theme={null}
apiVersion: v1
kind: Secret
metadata:
name: redis-mtls-secret
type: Opaque
stringData:
tls.crt: |
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
tls.key: |
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
```
#### Pod security context for certificate volumes
The certificate volumes mounted for mTLS are protected by file access restrictions. To ensure all LangSmith pods can read the certificate files, you must set `fsGroup: 1000` in the pod security context.
You can configure this in one of two ways:
**Option 1: Use `commonPodSecurityContext`**
Set the `fsGroup` at the top level to apply it to all pods:
```yaml theme={null}
commonPodSecurityContext:
fsGroup: 1000
```
**Option 2: Add to individual pod security contexts**
If you need more granular control, add the `fsGroup` to each pod's security context individually. See the [mtls configuration example](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/examples/mtls_config.yaml) for a complete reference.
## IAM authentication
As of LangSmith helm chart version **0.12.34**, we support IAM authentication for Redis. This allows you to use cloud provider workload identity instead of static passwords.
IAM authentication is supported for both standalone Redis and Redis Cluster configurations. However, not all cloud providers support IAM authentication for all Redis offerings. Check your cloud provider's documentation to verify IAM support for your specific Redis setup (e.g., GCP only supports IAM for Memorystore Cluster, not standalone Memorystore).
### ElastiCache for Redis IAM authentication
ElastiCache for Redis supports [IAM authentication](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/auth-iam.html), which allows you to authenticate using AWS IAM credentials instead of Redis AUTH passwords.
#### Prerequisites
1. **Configure workload identity** in your Kubernetes cluster using [AWS IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) or [EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html)
2. **Enable IAM authentication** on your ElastiCache instance and grant access to your workload identity
#### Configuration
**Standalone Redis:**
```yaml theme={null}
redis:
external:
enabled: true
existingSecretName: "redis-secret"
iamAuthProvider: "aws"
```
```yaml theme={null}
apiVersion: v1
kind: Secret
metadata:
name: redis-secret
type: Opaque
stringData:
# IAM connection URL - identity as username, no password
connection_url: "rediss://@:6380"
```
**Redis Cluster:**
```yaml theme={null}
redis:
external:
enabled: true
existingSecretName: "redis-cluster-secret"
iamAuthProvider: "aws"
cluster:
enabled: true
nodeUrisSecretKey: "redis_cluster_node_uris"
tlsEnabled: true
```
#### Required annotations
You must apply the ServiceAccount annotations required by AWS IRSA to all LangSmith components that connect to Redis:
**Deployments:** `backend`, `queue`, `platformBackend`, `hostBackend`, `ingestQueue`
Example configuration:
```yaml theme={null}
backend:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam:::role/"
queue:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam:::role/"
platformBackend:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam:::role/"
hostBackend:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam:::role/"
ingestQueue:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam:::role/"
```
See the [Helm values reference](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/values.yaml) for the full list of configurable services.
### Memorystore for Redis IAM authentication
Memorystore for Redis supports [IAM authentication](https://docs.cloud.google.com/memorystore/docs/cluster/about-iam-auth) for **Cluster instances only** (not standalone Memorystore). This allows you to authenticate using GCP service accounts.
IAM authentication is only available for Memorystore Cluster, not standalone Memorystore instances.
#### Prerequisites
1. **Configure workload identity** in your Kubernetes cluster using [GCP Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)
2. **Enable IAM authentication** on your Memorystore Cluster and grant access to your workload identity
#### Configuration
**Memorystore Cluster with IAM:**
```yaml theme={null}
redis:
external:
enabled: true
existingSecretName: "redis-cluster-secret"
iamAuthProvider: "gcp"
cluster:
enabled: true
nodeUrisSecretKey: "redis_cluster_node_uris"
tlsEnabled: true
```
```yaml theme={null}
apiVersion: v1
kind: Secret
metadata:
name: redis-cluster-secret
type: Opaque
stringData:
redis_cluster_node_uris: '["redis://node-0:6379","redis://node-1:6379","redis://node-2:6379"]'
```
#### Required annotations
You must apply the ServiceAccount annotations required by GCP Workload Identity to all LangSmith components that connect to Redis:
**Deployments:** `backend`, `queue`, `platformBackend`, `hostBackend`, `ingestQueue`
Example configuration:
```yaml theme={null}
backend:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "@.iam.gserviceaccount.com"
queue:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "@.iam.gserviceaccount.com"
platformBackend:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "@.iam.gserviceaccount.com"
hostBackend:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "@.iam.gserviceaccount.com"
ingestQueue:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "@.iam.gserviceaccount.com"
```
See the [Helm values reference](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/values.yaml) for the full list of configurable services.
### Azure Cache for Redis with Microsoft Entra authentication
Azure Cache for Redis supports [Microsoft Entra authentication](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-azure-active-directory-for-authentication), which allows you to authenticate using Azure managed identities.
#### Prerequisites
1. **Configure workload identity** in your Kubernetes cluster using [Azure Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview)
2. **Enable Microsoft Entra authentication** on your Azure Cache for Redis and grant access to your workload identity
#### Configuration
**Standalone Redis:**
```yaml theme={null}
redis:
external:
enabled: true
existingSecretName: "redis-secret"
iamAuthProvider: "azure"
```
```yaml theme={null}
apiVersion: v1
kind: Secret
metadata:
name: redis-secret
type: Opaque
stringData:
# IAM connection URL - managed identity as username, no password
connection_url: "rediss://@:6380"
```
**Redis Cluster:**
```yaml theme={null}
redis:
external:
enabled: true
existingSecretName: "redis-cluster-secret"
iamAuthProvider: "azure"
cluster:
enabled: true
nodeUrisSecretKey: "redis_cluster_node_uris"
tlsEnabled: true
```
#### Required annotations
You must apply the ServiceAccount annotations and pod labels required by Azure Workload Identity to all LangSmith components that connect to Redis:
**Deployments:** `backend`, `queue`, `platformBackend`, `hostBackend`, `ingestQueue`
Example configuration:
```yaml theme={null}
backend:
serviceAccount:
annotations:
azure.workload.identity/client-id: ""
deployment:
labels:
azure.workload.identity/use: "true"
queue:
serviceAccount:
annotations:
azure.workload.identity/client-id: ""
deployment:
labels:
azure.workload.identity/use: "true"
platformBackend:
serviceAccount:
annotations:
azure.workload.identity/client-id: ""
deployment:
labels:
azure.workload.identity/use: "true"
hostBackend:
serviceAccount:
annotations:
azure.workload.identity/client-id: ""
deployment:
labels:
azure.workload.identity/use: "true"
ingestQueue:
serviceAccount:
annotations:
azure.workload.identity/client-id: ""
deployment:
labels:
azure.workload.identity/use: "true"
```
See the [Helm values reference](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/values.yaml) for the full list of configurable services.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-external-redis.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Create an Ingress for installations (Kubernetes)
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-ingress
By default, LangSmith will provision a LoadBalancer service for the `langsmith-frontend`. Depending on your cloud provider, this may result in a public IP address being assigned to the service. If you would like to use a custom domain or have more control over the routing of traffic to your LangSmith installation, you can configure an Ingress, Gateway API, or Istio Gateway.
## Requirements
* An existing Kubernetes cluster
* One of the following installed in your Kubernetes cluster:
* An Ingress Controller (for standard Ingress)
* Gateway API CRDs and a Gateway resource (for Gateway API)
* Istio (for Istio Gateway)
## Parameters
You may need to provide certain parameters to your LangSmith installation to configure the Ingress. Additionally, we will want to convert the `langsmith-frontend` service to a ClusterIP service.
* *Hostname (optional)*: The hostname that you would like to use for your LangSmith installation. E.g `"langsmith.example.com"`. If you leave this empty, the ingress will serve all traffic to the LangSmith installation.
* *BasePath (optional)*: If you would like to serve LangSmith under a URL basePath, you can specify it here. For example, adding `"langsmith"` will serve the application at `"example.hostname.com/langsmith"`. This will apply to UI paths as well as API endpoints.
* *IngressClassName (optional)*: The name of the Ingress class that you would like to use. If not set, the default Ingress class will be used.
* *Annotations (optional)*: Additional annotations to add to the Ingress. Certain providers like AWS may use annotations to control things like TLS termination.
For example, you can add the following annotations using the AWS ALB Ingress Controller to attach an ACM certificate to the Ingress:
```yaml theme={null}
annotations:
alb.ingress.kubernetes.io/certificate-arn: ""
```
* *Labels (optional)*: Additional labels to add to the Ingress.
* *TLS (optional)*: If you would like to serve LangSmith over HTTPS, you can add TLS configuration here (many Ingress controllers may have other ways of controlling TLS so this is often not needed). This should be an array of TLS configurations. Each TLS configuration should have the following fields:
* hosts: An array of hosts that the certificate should be valid for. E.g \["langsmith.example.com"]
* secretName: The name of the Kubernetes secret that contains the certificate and private key. This secret should have the following keys:
* tls.crt: The certificate
* tls.key: The private key
* You can read more about creating a TLS secret [here](https://kubernetes.io/do/langsmith/observability-concepts/services-networking/ingress/#tls).
## Configuration
You can configure your LangSmith instance to use one of three routing options: standard Ingress, Gateway API, or Istio Gateway. Choose the option that best fits your infrastructure.
### Option 1: Standard ingress
With these parameters in hand, you can configure your LangSmith instance to use an Ingress. You can do this by modifying the `config.yaml` file for your LangSmith Helm Chart installation.
```yaml theme={null}
config:
hostname: "" # Main domain for LangSmith
basePath: "" # If you want to serve langsmith under a URL base path (e.g., /langsmith)
ingress:
enabled: true
hostname: "" # Deprecated: Use config.hostname instead after v0.12.0
subdomain: "" # Deprecated: Use config.hostname instead after v0.12.0
ingressClassName: "" # If not set, the default ingress class will be used
annotations: {} # Add annotations here if needed
labels: {} # Add labels here if needed
tls: [] # Add TLS configuration here if needed
frontend:
service:
type: ClusterIP
```
Once configured, you will need to update your LangSmith installation. If everything is configured correctly, your LangSmith instance should now be accessible via the Ingress. You can run the following to check the status of your Ingress:
```bash theme={null}
kubectl get ingress
```
You should see something like this in the output:
```
NAME CLASS HOSTS ADDRESS PORTS AGE
langsmith-ingress nginx 35.227.243.203 80, 443 95d
```
If you do not have automated DNS setup, you will need to add the IP address to your DNS provider manually.
### Option 2: Gateway API
Gateway API support is available as of LangSmith v0.12.0
If your cluster uses the [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/), you can configure LangSmith to provision HTTPRoute resources. This will create an HTTPRoute for LangSmith and an HTTPRoute for each [agent deployment](/langsmith/deployments).
#### Parameters
* *name (required)*: The name of the Gateway resource to reference
* *namespace (required)*: The namespace where the Gateway resource is located
* *hostname (optional)*: The hostname that you would like to use for your LangSmith installation. E.g `"langsmith.example.com"`
* *basePath (optional)*: If you would like to serve LangSmith under a base path, you can specify it here. E.g "example.com/langsmith"
* *sectionName (optional)*: The name of a specific listener section in the Gateway to use
* *annotations (optional)*: Additional annotations to add to the HTTPRoute resources
* *labels (optional)*: Additional labels to add to the HTTPRoute resources
#### Configuration
```yaml theme={null}
config:
hostname: "" # Main domain for LangSmith
basePath: "" # If you want to serve langsmith under a base path. E.g "example.com/langsmith"
gateway:
enabled: true
name: "my-gateway" # Name of your Gateway resource
namespace: "gateway-system" # Namespace of your Gateway resource
sectionName: "" # Optional: specific listener section name
annotations: {} # Add annotations here if needed
labels: {} # Add labels here if needed
frontend:
service:
type: ClusterIP
```
Once configured, you can check the status of your HTTPRoutes:
```bash theme={null}
kubectl get httproute
```
### Option 3: Istio Gateway
Istio Gateway support is available as of LangSmith v0.12.0
If your cluster uses [Istio](https://istio.io/), you can configure LangSmith to provision VirtualService resources. This will create a VirtualService for LangSmith and a VirtualService for each [agent deployment](/langsmith/deployments).
#### Parameters
* *name (optional)*: The name of the Istio Gateway resource to reference. Defaults to `"istio-gateway"`
* *namespace (optional)*: The namespace where the Istio Gateway resource is located. Defaults to `"istio-system"`
* *hostname (optional)*: The hostname that you would like to use for your LangSmith installation. E.g `"langsmith.example.com"`
* *basePath (optional)*: If you would like to serve LangSmith under a base path, you can specify it here. E.g "example.com/langsmith"
* *annotations (optional)*: Additional annotations to add to the VirtualService resources
* *labels (optional)*: Additional labels to add to the VirtualService resources
#### Configuration
```yaml theme={null}
config:
hostname: "" # Main domain for LangSmith
basePath: "" # If you want to serve langsmith on a separate basePath. E.g "example.com/langsmith"
istioGateway:
enabled: true
name: "istio-gateway" # Name of your Istio Gateway resource
namespace: "istio-system" # Namespace of your Istio Gateway resource
annotations: {} # Add annotations here if needed
labels: {} # Add labels here if needed
frontend:
service:
type: ClusterIP
```
Once configured, you can check the status of your VirtualServices:
```bash theme={null}
kubectl get virtualservice
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-ingress.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Mirror images for your LangSmith installation
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-mirroring-images
By default, LangSmith will pull images from our public Docker registry. However, if you are running LangSmith in an environment that does not have internet access, or if you would like to use a private Docker registry, you can mirror the images to your own registry and then configure your LangSmith installation to use those images.
## Requirements
* Authenticated access to a Docker registry that your Kubernetes cluster/machine has access to.
* Docker installed on your local machine or a machine that has access to the Docker registry.
* A Kubernetes cluster or a machine where you can run LangSmith.
## Mirroring the images
For your convenience, we have provided a script that will mirror the images for you. You can find the script in the [LangSmith Helm Chart repository](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/scripts/mirror_langsmith_images.sh)
To use the script, you will need to run the script with the following command specifying your registry and platform:
```bash theme={null}
bash mirror_images.sh []
```
Where `` is the URL of your Docker registry (e.g. `myregistry.com`) and `` is the platform you are using (e.g. `linux/amd64`, `linux/arm64`, etc.). If you do not specify a platform, it will default to `linux/amd64`.
For example, if your registry is `myregistry.com`, your platform is `linux/arm64`, and you want to use the latest version of the images, you would run:
```bash theme={null}
bash mirror_langsmith_images.sh --registry myregistry --platform linux/arm64 --version 0.10.66
```
Note that this script will assume that you have Docker installed and that you are authenticated to your registry. It will also push the images to the specified registry with the same repository/tag as the original images.
Alternatively, you can pull, mirror, and push the images manually. The images that you will need to mirror are found in the `values.yaml` file of the LangSmith Helm Chart. These can be found here: [LangSmith Helm Chart values.yaml](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/values.yaml#L14)
Here is an example of how to mirror the images using Docker:
```bash theme={null}
# Pull the images from the public registry
docker pull langchain/langsmith-backend:latest
docker tag langchain/langsmith-backend:latest /langsmith-backend:latest
docker push /langsmith-backend:latest
```
You will need to repeat this for each image that you want to mirror.
## Configuration
Once the images are mirrored, you will need to configure your LangSmith installation to use the mirrored images. You can do this by modifying the `values.yaml` file for your LangSmith Helm Chart installation or the `.env` file for your Docker installation. Replace tag with the version you want to use, e.g. `0.10.66` for the latest version at the time of writing.
```yaml Helm theme={null}
images:
imagePullSecrets: [] # Add your image pull secrets here if needed
registry: "" # Set this to your registry URL if you mirrored all images to the same registry using our script. Then you can remove the repository prefix from the images below.
aceBackendImage:
repository: "(your-registry)/langchain/langsmith-ace-backend"
pullPolicy: IfNotPresent
tag: "0.10.66"
backendImage:
repository: "(your-registry)/langchain/langsmith-backend"
pullPolicy: IfNotPresent
tag: "0.10.66"
frontendImage:
repository: "(your-registry)/langchain/langsmith-frontend"
pullPolicy: IfNotPresent
tag: "0.10.66"
hostBackendImage:
repository: "(your-registry)/langchain/hosted-langserve-backend"
pullPolicy: IfNotPresent
tag: "0.10.66"
operatorImage:
repository: "(your-registry)/langchain/langgraph-operator"
pullPolicy: IfNotPresent
tag: "6cc83a8"
platformBackendImage:
repository: "(your-registry)/langchain/langsmith-go-backend"
pullPolicy: IfNotPresent
tag: "0.10.66"
playgroundImage:
repository: "(your-registry)/langchain/langsmith-playground"
pullPolicy: IfNotPresent
tag: "0.10.66"
postgresImage:
repository: "(your-registry)/postgres"
pullPolicy: IfNotPresent
tag: "14.7"
redisImage:
repository: "(your-registry)/redis"
pullPolicy: IfNotPresent
tag: "7"
clickhouseImage:
repository: "(your-registry)/clickhouse/clickhouse-server"
pullPolicy: Always
tag: "24.8"
```
```bash Docker theme={null}
# In your .env file
_REGISTRY=your-registry # Set this to your registry URL if you mirrored all images to the same registry using our script. Otherwise you will need to manually set the repository for each image in the compose file.
```
## Additional images for Agent Builder and Insights
If you are using Agent Builder or Insights, the LangGraph operator dynamically creates Redis and PostgreSQL (pgvector) pods for each deployment. These pods use images defined in operator templates that require separate configuration.
You must mirror these additional images:
* `docker.io/redis:7`
* `docker.io/pgvector/pgvector:pg15`
Then override the operator templates in your `values.yaml` to use your mirrored images:
```yaml theme={null}
operator:
templates:
redis: |
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${service_name}
namespace: ${namespace}
spec:
replicas: 1
selector:
matchLabels:
app: ${service_name}
template:
metadata:
labels:
app: ${service_name}
spec:
enableServiceLinks: false
containers:
- name: redis
image: (your-registry)/redis:7
ports:
- containerPort: 6379
name: redis
livenessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 10
periodSeconds: 5
db: |
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: ${service_name}
spec:
serviceName: ${service_name}
selector:
matchLabels:
app: ${service_name}
persistentVolumeClaimRetentionPolicy:
whenDeleted: Delete
whenScaled: Retain
template:
metadata:
labels:
app: ${service_name}
spec:
containers:
- name: postgres
image: (your-registry)/pgvector/pgvector:pg15
ports:
- containerPort: 5432
command: ["docker-entrypoint.sh"]
args:
- postgres
- -c
- max_connections=${max_connections}
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
enableServiceLinks: false
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: "${storage_gi}Gi"
```
Replace `(your-registry)` with your registry URL. The template variables (`${service_name}`, `${namespace}`, `${max_connections}`, `${storage_gi}`) are replaced by the operator at runtime and must be kept as-is.
Once configured, you will need to update your LangSmith installation. You can follow our upgrade guide here: [Upgrading LangSmith](/langsmith/self-host-upgrades). If your upgrade is successful, your LangSmith instance should now be using the mirrored images from your Docker registry.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-mirroring-images.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# View trace counts across your organization
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-organization-charts
This feature is available on Helm chart versions 0.9.5 and later.
LangSmith automatically generates and syncs organization usage charts for self-hosted installations.
These charts are available under `Settings > Usage and billing > Usage graph`:
* Usage by Workspace: this counts traces (root runs) by workspace
* Organization Usage: this counts all traces (root runs) for the organization
The charts are refreshed to include any new workspaces every 5 minutes. Note that the charts are not editable.
## Programmatically fetch trace counts
You can retrieve trace counts programmatically using two different methods:
### Method 1: Use the LangSmith REST API
If your self-hosted installation uses an online key, you can use the [LangSmith REST API](https://api.smith.langchain.com/redoc?_gl=1*w68t81*_gcl_au*MTgyNTQ5MDUxNy4xNzU2NzI3MDky*_ga*MTU3NDY5MzQyNC4xNzQyOTMyMTQ2*_ga_47WX3HKKY2*czE3NTgyMDAxMDAkbzM0MSRnMCR0MTc1ODIwMDEwMCRqNjAkbDAkaDA.#tag/orgs/operation/get_org_usage_api_v1_orgs_current_billing_usage_get) to fetch organization usage data.
```bash theme={null}
curl -X GET "https://your-langsmith-instance.com/api/v1/orgs/current/billing/usage" \
-H "Accept: application/json" \
-H "X-API-Key: your-api-key" \
-G \
-d "starting_on=2025-09-01T00:00:00Z" \
-d "ending_before=2025-10-01T00:00:00Z" \
-d "on_current_plan=true"
```
### Method 2: Use PostgreSQL support queries
For installations using offline keys or when you need more detailed export capabilities, you can run support queries directly against the PostgreSQL database. All available scripts are in the [support queries repository](https://github.com/langchain-ai/helm/tree/main/charts/langsmith/scripts/support_queries/postgres).
```bash theme={null}
sh run_support_query_pg.sh "postgres://postgres:postgres@localhost:5432/postgres" \
--input support_queries/pg_get_trace_counts_daily.sql \
--output trace_counts.csv
```
For more detailed information about running support queries, see the [Run support queries against PostgreSQL](/langsmith/script-running-pg-support-queries) guide.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-organization-charts.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Use environment variables for model providers
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-playground-environment-settings
This feature is only available on Helm chart versions 0.10.27 (application version 0.10.74) and later.
Many model providers support setting credentials and other configuration options through environment variables. This is useful for self-hosted deployments where you want to avoid hardcoding sensitive information in your code or configuration files. In LangSmith, most model interactions are done through the `playground` service, which allows you to configure many of those environment variables directly on the pod itself. This can be useful to avoid having to set credentials in the UI.
## Requirements
* A self-hosted LangSmith instance with the `playground` service running.
* The provider you want to configure must support environment variables for configuration. Check the provider's Chat Model [documentation](https://docs.langchain.com/oss/python/integrations/providers/overview) for more information.
* The secrets/roles you may want to attach to the `playground` service.
* Note that for [IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) you may need to grant the `langsmith-playground` service account the necessary permissions to access the secrets or roles in your cloud provider.
## Configuration
With the parameters from above, you can configure your LangSmith instance to use environment variables for model providers. You can do this by modifying the `langsmith_config.yaml` file for your LangSmith Helm Chart installation or the `docker-compose.yaml` file for your Docker installation.
```yaml Helm theme={null}
playground:
deployment:
extraEnv:
- name: OPENAI_BASE_URL
value: https://
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name:
key: api_key
serviceAccount: # Can be useful if you want to use IRSA or workload identity
annotations:
eks.amazonaws.com/role-arn:
```
```yaml Docker theme={null}
# In your docker-compose.yaml file
langchain-playground:
environment:
.. # Other environment variables
- OPENAI_BASE_URL=https://
- OPENAI_API_KEY= # This will be set in the .env file
```
## VertexAI configuration
You can configure VertexAI credentials for the playground service using either environment variables with secrets or workload identity (GCP Workload Identity for GKE or AWS IRSA for EKS).
### Using secrets
Configure VertexAI credentials using Kubernetes secrets:
```yaml Helm theme={null}
playground:
deployment:
extraEnv:
# Playground-specific secret (recommended)
- name: GOOGLE_VERTEX_AI_WEB_CREDENTIALS
valueFrom:
secretKeyRef:
name: gcp-vertexai-secret
key: credentials_json # Your full service account JSON as string
# Standard fallback option
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /secrets/gcp-key.json
# Optional: Set project/location if not in model config
- name: GOOGLE_CLOUD_PROJECT
value: "your-gcp-project-id"
- name: VERTEXAI_PROJECT_ID
value: "your-gcp-project-id"
- name: VERTEXAI_LOCATION
value: "us-central1"
extraVolumeMounts:
- name: gcp-secret-volume
mountPath: /secrets
readOnly: true
extraVolumes:
- name: gcp-secret-volume
secret:
secretName: gcp-key-json # JSON file secret
defaultMode: 0444
```
```yaml Docker theme={null}
# In your docker-compose.yaml file
langchain-playground:
environment:
.. # Other environment variables
- GOOGLE_VERTEX_AI_WEB_CREDENTIALS= # Full JSON as string
# Or use file path
- GOOGLE_APPLICATION_CREDENTIALS=/secrets/gcp-key.json
- GOOGLE_CLOUD_PROJECT=your-gcp-project-id
- VERTEXAI_PROJECT_ID=your-gcp-project-id
- VERTEXAI_LOCATION=us-central1
volumes:
- ./gcp-key.json:/secrets/gcp-key.json:ro
```
### Using workload identity
You can configure the playground service account to use workload identity to assume a GCP service account role without storing credentials. This is the recommended approach for GKE clusters.
#### GCP Workload Identity (GKE)
For GKE clusters, use GCP Workload Identity:
```yaml Helm theme={null}
playground:
deployment:
extraEnv:
# Optional: Set project/location if not in model config
- name: GOOGLE_CLOUD_PROJECT
value: "your-gcp-project-id"
- name: VERTEXAI_PROJECT_ID
value: "your-gcp-project-id"
- name: VERTEXAI_LOCATION
value: "us-central1"
# No credentials needed - pod assumes GCP SA role via annotation
serviceAccount:
create: true # Enable if not exists
annotations:
iam.gke.io/gcp-service-account: "vertexai-sa@your-gcp-project.iam.gserviceaccount.com"
```
When using GCP Workload Identity, ensure the GCP service account has the required VertexAI permissions (e.g., `roles/aiplatform.user`).
#### AWS IRSA (EKS)
For EKS clusters, you can use AWS IRSA to assume a GCP service account role:
```yaml Helm theme={null}
playground:
deployment:
extraEnv:
# Optional: Set project/location if not in model config
- name: GOOGLE_CLOUD_PROJECT
value: "your-gcp-project-id"
- name: VERTEXAI_PROJECT_ID
value: "your-gcp-project-id"
- name: VERTEXAI_LOCATION
value: "us-central1"
# No credentials needed - pod assumes GCP SA role via AWS IAM role
serviceAccount:
create: true # Enable if not exists
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam:::role/LangSmith-VertexAI-Role
```
When using AWS IRSA, ensure your AWS IAM role has the necessary permissions to assume the GCP service account role, and that the GCP service account has the required VertexAI permissions.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-playground-environment-settings.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Configure LangSmith for scale
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-scale
The scaling guidance and example configurations on this page apply to **LangSmith version v0.13.0 or higher**.
A self-hosted LangSmith instance can handle a large number of traces and users. The default configuration for the self-hosted deployment can handle substantial load, and you can configure your deployment to be able to achieve higher scale. This page describes scaling considerations and provides some examples to help configure your self-hosted instance.
For example configurations, refer to [Example LangSmith configurations for scale](#example-langsmith-configurations-for-scale).
## Summary
The table below provides an overview comparing different LangSmith configurations for various load patterns (reads / writes):
| | **[Low / low](#low-reads-low-writes)** | **[Low / high](#low-reads-high-writes)** | **[High / low](#high-reads-low-writes)** | [Medium / medium](#medium-reads-medium-writes) | [High / high](#high-reads-high-writes) |
| :---------------------------------------------------------- | :-------------------------------------------------- | :-------------------------------------------------- | :-------------------------------------------------- | :-------------------------------------------------- | :-------------------------------------------------- |
| Concurrent frontend users | 5 | 5 | 50 | 20 | 50 |
| Traces submitted per second | 10 | 1000 | 10 | 100 | 1000 |
| **Frontend replicas** (500m CPU, 1Gi per replica) | 1 (default) | 4 | 2 | 2 | 4 |
| **Platform backend replicas** (1 CPU, 2Gi per replica) | 3 (default) | 20 | 3 (default) | 3 (default) | 20 |
| **Ingest queue replicas** (1 CPU, 2Gi per replica) | 3 (default) | 24 | 3 (default) | 6 | 24 |
| **Backend replicas** (1 CPU, 2Gi per replica) | 2 (default) | 5 | 40 | 16 | 50 |
| **Redis resources** | 8 Gi (default) | 26 Gi external | 8 Gi (default) | 13Gi external | 26 Gi external |
| **ClickHouse resources** | 4 CPU 16 Gi (default) | 10 CPU 32Gi memory | 8 CPU 16 Gi per replica | 16 CPU 24Gi memory | 14 CPU 24 Gi per replica |
| **ClickHouse setup** | Single instance | Single instance | 3-node replicated cluster | Single instance | 3-node replicated cluster |
| Postgres resources | 2 CPU 8 GB memory 10GB storage (external) | 2 CPU 8 GB memory 10GB storage (external) | 2 CPU 8 GB memory 10GB storage (external) | 2 CPU 8 GB memory 10GB storage (external) | 2 CPU 8 GB memory 10GB storage (external) |
| **Blob storage** | Disabled | Enabled | Enabled | Enabled | Enabled |
Below we go into more details about the read and write paths as well as provide a `values.yaml` snippet for you to start with for your self-hosted LangSmith instance.
## Trace ingestion (write path)
Common usage that put load on the write path:
* Ingesting traces via the Python or JavaScript LangSmith SDK
* Ingesting traces via the `@traceable` wrapper
* Submitting traces via the `/runs/multipart` endpoint
Services that play a large role in trace ingestion:
* Platform backend service: Receives initial request to ingest traces and places traces on a Redis queue
* Redis cache: Used to queue traces that need to be persisted
* Ingest queue service: Persists traces for querying
* ClickHouse: Persistent storage used for traces
When scaling up the write path (trace ingestion), it is helpful to monitor the four services/resources listed above. Here are some typical changes that can help increase performance of trace ingestion:
* Give ClickHouse more resources (CPU and memory) if it is approaching resource limits.
* Increase the number of platform-backend pods if ingest requests are taking long to respond.
* Increase ingest queue service pod replicas if traces are not being processed from Redis fast enough.
* Use a larger Redis cache if you notice that the current Redis instance is reaching resource limits. This could also be a reason why ingest requests take a long time.
## Trace querying (read path)
Common usage that puts load on the read path:
* Users on the frontend looking at tracing projects or individual traces
* Scripts used to query for trace info
* Hitting either the `/runs/query` or `/runs/` api endpoints
Services that play a large role in querying traces:
* Backend service: Receives the request and submits a query to ClickHouse to then respond to the request
* ClickHouse: Persistent storage for traces. This is the main database that is queried when requesting trace info.
When scaling up the read path (trace querying), it is helpful to monitor the two services/resources listed above. Here are some typical changes that can help improve performance of trace querying:
* Increase the number of backend service pods. This would be most impactful if backend service pods are reaching 1 core CPU usage.
* Give ClickHouse more resources (CPU or Memory). ClickHouse can be very resource intensive, but it should lead to better performance.
* Move to a [replicated ClickHouse cluster](/langsmith/self-host-external-clickhouse#ha-replicated-clickhouse-cluster). Adding replicas of ClickHouse helps with read performance, but we recommend staying below 5 replicas (start with 3).
For more precise guidance on how this translates to helm chart values, refer to the examples the following [section](#example-langsmith-configurations-for-scale). If you are unsure why your LangSmith instance cannot handle a certain load pattern, contact the LangChain team.
## KEDA autoscaling for LangSmith queues
Available in LangSmith v0.13.0 and later.
We highly recommend installing [KEDA](https://keda.sh/) (Kubernetes Event-driven Autoscaling) on your cluster. KEDA enables the `queue` and `ingest-queue` services to scale automatically based on their queue backlog size, as well as CPU and memory. This results in more efficient resource utilization and better handling of traffic spikes.
### Install KEDA
```bash theme={null}
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --create-namespace
```
### Configure KEDA autoscaling
Once KEDA is installed, you can enable KEDA-based autoscaling for the `queue` and `ingest-queue` services in your `values.yaml`:
```yaml theme={null}
queue:
autoscaling:
keda:
enabled: true
ingestQueue:
autoscaling:
keda:
enabled: true
```
With KEDA enabled, the queue services will automatically scale up when their backlog grows and scale down when their backlog is processed. This is especially useful for handling variable trace ingestion loads without over-provisioning resources.
You can also enable KEDA for other services (`backend`, `platformBackend`, etc) but they will still only scale with CPU and memory.
## Example LangSmith configurations for scale
Below we provide some example LangSmith configurations based on expected read and write loads.
For read load (trace querying):
* Low means roughly 5 users looking at traces at a time (about 10 requests per second)
* Medium means roughly 20 users looking at traces at a time (about 40 requests per second)
* High means roughly 50 users looking at traces at a time (about 100 requests per second)
For write load (trace ingestion):
* Low means up to 10 traces submitted per second
* Medium means up to 100 traces submitted per second
* High means up to 1000 traces submitted per second
The exact optimal configuration depends on your usage and trace payloads. Use the examples below in combination with the information above and your specific usage to update your LangSmith configuration as you see fit. If you have any questions, please reach out to the LangChain team.
### Low reads, low writes
The default LangSmith configuration will handle this load. No custom resource configuration is needed here.
### Low reads, high writes
You have a very high scale of trace ingestions, but single digit number of users on the frontend querying traces at any one time.
For this, we recommend a configuration like this:
```yaml theme={null}
config:
blobStorage:
# Please also set the other keys to connect to your blob storage. See configuration section.
enabled: true
settings:
redisRunsExpirySeconds: "3600"
# ttl:
# enabled: true
# ttl_period_seconds:
# longlived: "7776000" # 90 days (default is 400 days)
# shortlived: "604800" # 7 days (default is 14 days)
frontend:
deployment:
replicas: 4 # OR enable autoscaling below
# autoscaling:
# hpa:
# enabled: true
# minReplicas: 2
# maxReplicas: 4
platformBackend:
deployment:
replicas: 20 # OR enable autoscaling below
# autoscaling:
# hpa:
# enabled: true
# minReplicas: 8
# maxReplicas: 20
ingestQueue:
deployment:
replicas: 24 # OR enable KEDA autoscaling below
# autoscaling:
# keda:
# enabled: true
# minReplicaCount: 8
# maxReplicaCount: 24
backend:
deployment:
replicas: 5 # OR enable autoscaling below
# autoscaling:
# hpa:
# enabled: true
# minReplicas: 3
# maxReplicas: 5
## Ensure your Redis cache is at least 26 GB for high write scale
redis:
external:
enabled: true
existingSecretName: langsmith-redis-secret # Set the connection url for your external Redis instance (26+ GB)
clickhouse:
statefulSet:
persistence:
# This may depend on your configured TTL (see config section).
# We recommend 600Gi for every shortlived TTL day if operating at this scale constantly.
size: 4200Gi # This assumes 7 days TTL and operating a this scale constantly.
resources:
requests:
cpu: "10"
memory: "32Gi"
limits:
cpu: "16"
memory: "48Gi"
commonEnv:
- name: "CLICKHOUSE_ASYNC_INSERT_WAIT_PCT_FLOAT"
value: "0"
```
### High reads, low writes
You have a relatively low scale of trace ingestions, but many frontend users querying traces and/or have scripts that hit the `/runs/query` or `/runs/` endpoints frequently.
**For this, we strongly recommend setting up a replicated ClickHouse cluster to enable high read scale at low latency.** See our [external ClickHouse doc](/langsmith/self-host-external-clickhouse#ha-replicated-clickhouse-cluster) for more guidance on how to setup a replicated ClickHouse cluster. For this load pattern, we recommend using a 3 node replicated setup, where each replica in the cluster should have resource requests of 8+ cores and 16+ GB memory, and resource limit of 12 cores and 32 GB memory.
For this, we recommend a configuration like this:
```yaml theme={null}
config:
blobStorage:
# Please also set the other keys to connect to your blob storage. See configuration section.
enabled: true
frontend:
deployment:
replicas: 2
ingestQueue:
deployment:
replicas: 3 # OR enable KEDA autoscaling below
# autoscaling:
# keda:
# enabled: true
# minReplicaCount: 2
# maxReplicaCount: 3
backend:
deployment:
replicas: 40 # OR enable autoscaling below
# autoscaling:
# hpa:
# enabled: true
# minReplicas: 16
# maxReplicas: 40
# We strongly recommend setting up a replicated clickhouse cluster for this load.
# Update these values as needed to connect to your replicated clickhouse cluster.
clickhouse:
external:
# If using a 3 node replicated setup, each replica in the cluster should have resource requests of 8+ cores and 16+ GB memory, and resource limit of 12 cores and 32 GB memory.
enabled: true
host: langsmith-ch-clickhouse-replicated.default.svc.cluster.local
port: "8123"
nativePort: "9000"
user: "default"
password: "password"
database: "default"
cluster: "replicated"
```
### Medium reads, medium writes
This is a good all around configuration that should be able to handle most usage patterns of LangSmith. In internal testing, this configuration allowed us to scale to 100 traces ingested per second and 40 read requests per second.
For this, we recommend a configuration like this:
```yaml theme={null}
config:
blobStorage:
# Please also set the other keys to connect to your blob storage. See configuration section.
enabled: true
settings:
redisRunsExpirySeconds: "3600"
frontend:
deployment:
replicas: 2
ingestQueue:
deployment:
replicas: 6 # OR enable KEDA autoscaling below
# autoscaling:
# keda:
# enabled: true
# minReplicaCount: 3
# maxReplicaCount: 6
backend:
deployment:
replicas: 16 # OR enable autoscaling below
# autoscaling:
# hpa:
# enabled: true
# minReplicas: 8
# maxReplicas: 16
redis:
statefulSet:
resources:
requests:
memory: 13Gi
limits:
memory: 13Gi
# -- For external redis instead use something like below --
# external:
# enabled: true
# connectionUrl: "" OR existingSecretName: ""
clickhouse:
statefulSet:
persistence:
# This may depend on your configured TTL.
# We recommend 60Gi for every shortlived TTL day if operating at this scale constantly.
size: 420Gi # This assumes 7 days TTL and operating a this scale constantly.
resources:
requests:
cpu: "16"
memory: "24Gi"
limits:
cpu: "28"
memory: "40Gi"
commonEnv:
- name: "CLICKHOUSE_ASYNC_INSERT_WAIT_PCT_FLOAT"
value: "0"
```
If you still notice slow reads with the above configuration, we recommend moving to a [replicated Clickhouse cluster setup](/langsmith/self-host-external-clickhouse#ha-replicated-clickhouse-cluster)
### High reads, high writes
You have a very high rate of trace ingestion (approaching 1000 traces submitted per second) and also have many users querying traces on the frontend (over 50 users) and/or scripts that are consistently making requests to `/runs/query` or `/runs/` endpoints.
**For this, we very strongly recommend setting up a replicated ClickHouse cluster to prevent degraded read performance at high write scale.** See our [external ClickHouse doc](/langsmith/self-host-external-clickhouse#ha-replicated-clickhouse-cluster) for more guidance on how to set up a replicated ClickHouse cluster. For this load pattern, we recommend using a 3 node replicated setup, where each replica in the cluster should have resource requests of 14+ cores and 24+ GB memory, and resource limit of 20 cores and 48 GB memory. We also recommend that each node/instance of ClickHouse has 600 Gi of volume storage for each day of TTL that you enable (as per the configuration below).
Overall, we recommend a configuration like this:
```yaml theme={null}
config:
blobStorage:
# Please also set the other keys to connect to your blob storage. See configuration section.
enabled: true
settings:
redisRunsExpirySeconds: "3600"
# ttl:
# enabled: true
# ttl_period_seconds:
# longlived: "7776000" # 90 days (default is 400 days)
# shortlived: "604800" # 7 days (default is 14 days)
frontend:
deployment:
replicas: 4 # OR enable autoscaling below
# autoscaling:
# hpa:
# enabled: true
# minReplicas: 2
# maxReplicas: 4
platformBackend:
deployment:
replicas: 20 # OR enable autoscaling below
# autoscaling:
# hpa:
# enabled: true
# minReplicas: 8
# maxReplicas: 20
ingestQueue:
deployment:
replicas: 24 # OR enable KEDA autoscaling below
# autoscaling:
# keda:
# enabled: true
# minReplicaCount: 8
# maxReplicaCount: 24
backend:
deployment:
replicas: 50 # OR enable autoscaling below
# autoscaling:
# hpa:
# enabled: true
# minReplicas: 20
# maxReplicas: 50
## Ensure your Redis cache is at least 26 GB for high write scale
redis:
external:
enabled: true
existingSecretName: langsmith-redis-secret # Set the connection url for your external Redis instance (26+ GB)
# We strongly recommend setting up a replicated clickhouse cluster for this load.
# Update these values as needed to connect to your replicated clickhouse cluster.
clickhouse:
external:
# If using a 3 node replicated setup, each replica in the cluster should have resource requests of 14+ cores and 24+ GB memory, and resource limit of 20 cores and 48 GB memory.
enabled: true
host: langsmith-ch-clickhouse-replicated.default.svc.cluster.local
port: "8123"
nativePort: "9000"
user: "default"
password: "password"
database: "default"
cluster: "replicated"
commonEnv:
- name: "CLICKHOUSE_ASYNC_INSERT_WAIT_PCT_FLOAT"
value: "0"
```
Ensure that the Kubernetes cluster is configured with sufficient resources to scale to the recommended size. After deployment, all of the pods in the Kubernetes cluster should be in a `Running` state. Pods stuck in `Pending` may indicate that you are reaching node pool limits or need larger nodes.
Also, ensure that any ingress controller deployed on the cluster is able to handle the desired load to prevent bottlenecks.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-scale.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Set up SSO with OAuth2.0 and OIDC
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-sso
LangSmith Self-Hosted provides SSO via OAuth2.0 and OIDC. This will delegate authentication to your Identity Provider (IdP) to manage access to LangSmith.
Our implementation supports almost anything that is OIDC compliant, with a few exceptions. Once configured, you will see a login screen like this:
## Overview
You may upgrade a [basic auth](/langsmith/self-host-basic-auth) installation to this mode, but not a [none auth](/langsmith/authentication-methods#none) installation. In order to upgrade, simply remove the basic auth configuration and add the required configuration parameters as shown below. Users may then login via OAuth *only*. **In order to maintain access post-upgrade, you must have access to login via OAuth using an email address that previously logged in via basic auth.**
LangSmith does not support moving from SSO to basic auth mode in self-hosted at the moment. We also do not support moving from OAuth Mode with client secret to OAuth mode without a client secret and vice versa. Finally, we do not support having both basic auth and OAuth at the same time. Ensure you disable the basic auth configuration when enabling OAuth.
## With client secret (Recommended)
By default, LangSmith Self-Hosted supports the `Authorization Code` flow with `Client Secret`. In this version of the flow, your client secret is stored security in LangSmith (not on the frontend) and used for authentication and establishing auth sessions.
### Prerequisites
* You must be self-hosted and on an Enterprise plan.
* Your IdP must support the `Authorization Code` flow with `Client Secret`.
* Your IdP must support using an external discovery/issuer URL. We will use this to fetch the necessary routes and keys for your IdP.
* You must provide the `OIDC`, `email`, and `profile` scopes to LangSmith. We use these to fetch the necessary user information and email for your users.
LangSmith SSO is only supported over `https`.
### Configuration
* You will need to set the callback URL in your IdP to `https:///api/v1/oauth/custom-oidc/callback`, where `host` is the domain or IP you have provisioned for your LangSmith instance. This is where your IdP will redirect the user after they have authenticated.
* You will need to provide the `oauthClientId`, `oauthClientSecret`, `hostname`, and `oauthIssuerUrl` in your `values.yaml` file. This is where you will configure your LangSmith instance.
* If you have **not** already configured Oauth with client secret or if you only have personal orgs, you must provide an email address to assign as the initial org admin for the newly provisioned SSO org. If you are upgrading from basic auth, your existing org will be reused instead.
```yaml Helm theme={null}
config:
authType: mixed
hostname: https://langsmith.example.com
initialOrgAdminEmail: test@email.com # Set this if required
oauth:
enabled: true
oauthClientId:
oauthClientSecret:
oauthIssuerUrl:
oauthScopes: "email,profile,openid"
```
```bash Docker theme={null}
# In your .env file
AUTH_TYPE=mixed
INITIAL_ORG_ADMIN_EMAIL=test@email.com
LANGSMITH_URL=https://langsmith.example.com
OAUTH_CLIENT_ID=your-client-id
OAUTH_CLIENT_SECRET=your-client-secret
OAUTH_ISSUER_URL=https://your-issuer-url
OAUTH_SCOPES=email,profile,openid
```
### Session length controls
All of the environment variables in this section are for the `platform-backend` service and can be added using `platformBackend.deployment.extraEnv` in Helm.
* By default, session length is controlled by the expiration of the identity token returned by the identity provider
* Most setups should use refresh tokens to enable session length extension beyond the identity token expiration up to `OAUTH_SESSION_MAX_SEC`, which may require including the `offline_access` scope by adding to `oauthScopes` (Helm) or `OAUTH_SCOPES` (Docker)
* `OAUTH_SESSION_MAX_SEC` (default 1 day) can be overridden to a maximum of one week (`604800`)
* For identity provider setups that don't support refresh tokens, setting `OAUTH_OVERRIDE_TOKEN_EXPIRY="true"` will take `OAUTH_SESSION_MAX_SEC` as the session length, ignoring the identity token expiration
### Override sub claim
In some scenarios, it may be necessary to override which claim is used as the `sub` claim from your identity provider.
For example, in SCIM, the resolved `sub` claim and SCIM `externalId` must match in order for login to succeed.
If there are restrictions on the source attribute of the `sub` claim and/or the SCIM `externalId`, set the `ISSUER_SUB_CLAIM_OVERRIDES` environment variable to select which OIDC JWT claim is used as the `sub`.
If an issuer URL **starts with** one of the URLs in this configuration, the `sub` claim is taken from the field name specified.
For example, with the following configuration, a token with the issuer `https://idp.yourdomain.com/application/uuid` would use the `customClaim` value as the `sub`:
```
ISSUER_SUB_CLAIM_OVERRIDES='{"https://idp.yourdomain.com": "customClaim"}'
```
If unset, the default value for this configuration uses the `oid` claim when Azure Entra ID is used as the identity provider:
```
ISSUER_SUB_CLAIM_OVERRIDES='{"https://login.microsoftonline.com/": "oid", "https://sts.windows.net/": "oid", "https://login.microsoftonline.us/": "oid", "https://login.partner.microsoftonline.cn/": "oid"}'
```
### Google workspace IdP setup
You can use Google Workspace as a single sign-on (SSO) provider using [OAuth2.0 and OIDC](https://developers.google.com/identity/openid-connect/openid-connect) without PKCE.
You must have administrator-level access to your organization's Google Cloud Platform (GCP) account to create a new project, or permissions to create and configure OAuth 2.0 credentials for an existing project. We recommend that you create a new project for managing access, since each GCP project has a single OAuth consent screen.
1. Create a new GCP project, see the Google documentation topic [creating and managing projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects)
2. After you have created the project, open the [Credentials](https://console.developers.google.com/apis/credentials) page in the Google API Console (making sure the project in the top left corner is correct)
3. Create new credentials: `Create Credentials → OAuth client ID`
4. Choose `Web application` as the `Application type` and enter a name for the application e.g. `LangSmith`
5. In `Authorized Javascript origins` put the domain of your LangSmith instance e.g. `https://langsmith.yourdomain.com`
6. In `Authorized redirect URIs` put the domain of your LangSmith instance followed by `/api/v1/oauth/custom-oidc/callback` e.g. `https://langsmith.yourdomain.com/api/v1/oauth/custom-oidc/callback`
7. Click `Create`, then download the JSON or copy and save the `Client ID` (ends with `.apps.googleusercontent.com`) and `Client secret` somewhere secure. **You will be able to access these later if needed**.
8. Select `OAuth consent screen` from the navigation menu on the left
1. Choose the Application type as `Internal`. **If you select `Public`, anyone with a Google account can sign in.**
2. Enter a descriptive `Application name`. This name is shown to users on the consent screen when they sign in. For example, use `LangSmith` or ` SSO for LangSmith`.
3. Verify that the Scopes for Google APIs only lists email, profile, and openid scopes. Only these scopes are required for single sign-on. If you grant additional scopes it increases the risk of exposing sensitive data.
9. (Optional) control who within your organization has access to LangSmith: [https://admin.google.com/ac/owl/list?tab=configuredApps](https://admin.google.com/ac/owl/list?tab=configuredApps). See [Google's documentation](https://support.google.com/a/answer/7281227?hl=en\&fl=1\&sjid=9554153972856467090-NA) for additional details.
10. Configure LangSmith to use this OAuth application. For examples, here are the `config`values that would be used for Kubernetes configuration:
1. `oauthClientId`: `Client ID` (ends with `.apps.googleusercontent.com`)
2. `oauthClientSecret`: `Client secret`
3. `hostname`: the domain of your LangSmith instance e.g. `https://langsmith.yourdomain.com` (no trailing slash)
4. `oauthIssuerUrl`: `https://accounts.google.com`
5. `oauth.enabled`: `true`
6. `authType`: `mixed`
### Okta IdP setup
#### Supported features
* IdP-initiated SSO
* SP-initiated SSO
* Just-In-Time provisioning
#### Configuration steps
For additional information, see Okta's [documentation](https://help.okta.com/en-us/content/topics/apps/apps_app_integration_wizard_oidc.htm).
If you have any questions or issues, please contact support via [support.langchain.com](https://support.langchain.com).
Via Okta Integration Network (recommended)
For details on SCIM setup, refer to [Set up SCIM for your organization](/langsmith/user-management#set-up-scim-for-your-organization).
This method of configuration is required in order to use SCIM with Okta.
1. Sign in to [Okta](https://login.okta.com/).
2. In the upper-right corner, select Admin. The button is not visible from the Admin area.
3. Select `Browse App Integration Catalog`.
4. Find and select the LangSmith application.
5. On the application overview page, select Add Integration.
6. Fill in `ApiUrlBase`:
* Your LangSmith API URL **without the protocol** (`https://`) formatted as `/api/v1`, e.g., `langsmith.yourdomain.com/api/v1`.
* If your installation is configured with a subdomain / path prefix, include that in the URL, e.g., `langsmith.yourdomain.com/prefix/api/v1`.
7. Leave `AuthHost` empty.
8. (Optional, if planning to use [SCIM](/langsmith/user-management#set-up-scim-for-your-organization) as well) Fill in `LangSmithUrl`: The `` portion from above, e.g., `langsmith.yourdomain.com`.
9. Under Application Visibility, keep the box unchecked.
10. Select Next.
11. Select `OpenID Connect`.
12. Fill in `Sign-On Options`:
* `Application username format`: `Email`.
* `Update application username on`: `Create and update`.
* `Allow users to securely see their password`: leave **unchecked**.
13. Click **Save**.
14. Configure LangSmith to use this OAuth application (see [general configuration section](#configuration) for details about `initialOrgAdminEmail`):
```yaml Helm theme={null}
config:
authType: mixed
hostname: https://langsmith.example.com # the domain of your instance (note no trailing slash)
initialOrgAdminEmail: test@email.com # Set this if required
oauth:
enabled: true
oauthClientId: "Client ID" # (starts with `0o`)
oauthClientSecret: "Client secret"
oauthIssuerUrl: "https://company-7422949.okta.com" # the URL of your Okta instance
oauthScopes: "email,profile,openid"
```
```bash Docker theme={null}
# In your .env file
AUTH_TYPE=mixed
INITIAL_ORG_ADMIN_EMAIL=test@email.com # Set this if required
LANGSMITH_URL=https://langsmith.example.com # the domain of your instance (note no trailing slash)
OAUTH_CLIENT_ID="Client ID" # (starts with `0o`)
OAUTH_CLIENT_SECRET="Client secret"
OAUTH_ISSUER_URL="https://company-7422949.okta.com" # the URL of your Okta instance
OAUTH_SCOPES=email,profile,openid
```
For details on SCIM setup, refer to [Set up SCIM for your organization](/langsmith/user-management#set-up-scim-for-your-organization).
Via Custom App Integration
SCIM is not compatible with this method of configuration. Refer to [**Via Okta Integration Network**](#via-okta-integration-network).
1. Log in to Okta as an administrator, and go to the **Okta Admin console**.
2. Under **Applications** > **Applications** click **Create App Integration**.
3. Select **OIDC - OpenID Connect** as the Sign-in method and **Web Application** as the Application type, then click **Next**.
4. Enter an `App integration name` (e.g., `LangSmith`).
5. Recommended: Check **Core grants > Refresh Token** (see [session length controls](#session-length-controls)).
6. In **Sign-in redirect URIs** put the domain of your LangSmith instance followed by `/api/v1/oauth/custom-oidc/callback`, e.g., `https://langsmith.yourdomain.com/api/v1/oauth/custom-oidc/callback`. If your installation is configured with a subdomain / path prefix, include that in the URL, e.g., `https://langsmith.yourdomain.com/prefix/api/v1/oauth/custom-oidc/callback`.
7. Remove the default URI under **Sign-out redirect URIs**.
8. Under **Trusted Origins > Base URIs** add your langsmith URL with the protocol, e.g., `https://langsmith.yourdomain.com`.
9. Select your desired option under **Assignments > Controlled access**:
* Allow everyone in your organization to access.
* Limit access to selected groups.
* Skip group assignment for now.
10. Click **Save**.
11. Under **Sign On > OpenID Connect ID Token** set **Issuer** to **Okta URL**.
12. (Optional) Under **General > Login** set **Login initiated by** to `Either Okta or App` to enable IdP-initiated login.
13. (Recommended) Under **General > Login > Email verification experience** fill in the **Callback URI** with the LangSmith URL, e.g., `https://langsmith.yourdomain.com`.
14. Configure LangSmith to use this OAuth application (see [general configuration section](#configuration) for details about `initialOrgAdminEmail`):
```yaml Helm theme={null}
config:
authType: mixed
hostname: https://langsmith.example.com # the domain of your instance (note no trailing slash)
initialOrgAdminEmail: test@email.com # Set this if required
oauth:
enabled: true
oauthClientId: "Client ID" # (starts with `0o`)
oauthClientSecret: "Client secret"
oauthIssuerUrl: "https://company-7422949.okta.com" # the URL of your Okta instance
oauthScopes: "email,profile,openid"
```
```bash Docker theme={null}
# In your .env file
AUTH_TYPE=mixed
INITIAL_ORG_ADMIN_EMAIL=test@email.com # Set this if required
LANGSMITH_URL=https://langsmith.example.com # the domain of your instance (note no trailing slash)
OAUTH_CLIENT_ID="Client ID" # (starts with `0o`)
OAUTH_CLIENT_SECRET="Client secret"
OAUTH_ISSUER_URL="https://company-7422949.okta.com" # the URL of your Okta instance
OAUTH_SCOPES=email,profile,openid
```
#### SP-initiated SSO
Users can sign in using the **Login via SSO** button on the LangSmith homepage.
## Without client secret (PKCE) (Deprecated)
We recommend running with a `Client Secret` if possible (previously we didn't support this). However, if your IdP does not support this, you can use the `Authorization Code with PKCE` flow.
This flow does *not* require a `Client Secret`. For the alternative workflow, refer to [With client secret](#with-client-secret-recommended).
### Requirements
There are a couple of requirements for using OAuth SSO with LangSmith:
* Your IdP must support the `Authorization Code with PKCE` [flow](https://www.oauth.com/oauth2-servers/pkce) (Google does not support this flow for example, but see [above](#with-client-secret-recommended) for an alternative configuration that Google supports). This is often displayed in your OAuth Provider as configuring a "Single Page Application (SPA)"
* Your IdP must support using an external discovery/issuer URL. We will use this to fetch the necessary routes and keys for your IdP.
* You must provide the `OIDC`, `email`, and `profile` scopes to LangSmith. We use these to fetch the necessary user information and email for your users.
* You will need to set the callback URL in your IdP to `http:///oauth-callback`, where host is the domain or IP you have provisioned for your LangSmith instance. This is where your IdP will redirect the user after they have authenticated.
* You will need to provide the `oauthClientId` and `oauthIssuerUrl` in your `values.yaml` file. This is where you will configure your LangSmith instance.
```yaml Helm theme={null}
config:
oauth:
enabled: true
oauthClientId:
oauthIssuerUrl:
```
```bash Docker theme={null}
# In your .env file
AUTH_TYPE=oauth
OAUTH_CLIENT_ID=your-client-id
OAUTH_ISSUER_URL=https://your-issuer-url
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-sso.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Enable TTL and data retention
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-ttl
LangSmith Self-Hosted allows enablement of automatic TTL and Data Retention of traces. This can be useful if you're complying with data privacy regulations, or if you want to have more efficient space usage and auto cleanup of your traces. Traces will also have their data retention period automatically extended based on certain actions or run rule applications.
## Requirements
You can configure retention through helm or environment variable settings. There are a few options that are configurable:
* *Enabled:* Whether data retention is enabled or disabled. If enabled, via the UI you can your default organization and project TTL tiers to apply to traces (see [data retention guide](/langsmith/administration-overview#data-retention) for details).
* *Retention Periods:* You can configure system-wide retention periods for shortlived and longlived traces. Once configured, you can manage the retention level at each project as well as set an organization-wide default for new projects.
```yaml Helm theme={null}
config:
ttl:
enabled: true
ttl_period_seconds:
# -- 400 day longlived and 14 day shortlived
longlived: "34560000"
shortlived: "1209600"
```
```bash Docker theme={null}
# In your .env fileFF_TRACE_TIERS_ENABLED=trueTRACE_TIER_TTL_DURATION_SEC_MAP='{"longlived": 34560000, "shortlived": 1209600}'
```
## ClickHouse TTL cleanup job
As of version **0.11**, a cron job runs on weekends to assist in deleting expired data that may not have been cleaned up by ClickHouse's built-in TTL mechanism.
This job uses potentially long running **mutations** (`ALTER TABLE DELETE`), which are expensive operations that can impact ClickHouse's performance. We recommend running these operations only during off-peak hours (nights and weekends). During testing with **1 concurrent active** mutation (default), we did not observe significant CPU, memory, or latency increases.
### Default schedule
By default, the cleanup job runs:
* **Saturday**: 8pm and 10pm UTC
* **Sunday**: 12am, 2am, and 4am UTC
### Disabling the job
To disable the cleanup job entirely:
```yaml theme={null}
queue:
deployment:
extraEnv:
- name: "ENABLE_CLICKHOUSE_TTL_CLEANUP_CRON"
value: "false"
```
### Configuring the schedule
You can customize when the cleanup job runs by modifying the cron expressions:
```yaml theme={null}
queue:
deployment:
extraEnv:
# UTC: Sunday 12am/2am/4am
- name: "CLICKHOUSE_TTL_CLEANUP_CRON_WEEKEND_MORNING"
value: "0 0,2,4 * * 0"
# UTC: Saturday 8pm/10pm
- name: "CLICKHOUSE_TTL_CLEANUP_CRON_WEEKEND_EVENING"
value: "0 20,22 * * 6"
```
To run the job on a single cron schedule, set both `CLICKHOUSE_TTL_CLEANUP_CRON_WEEKEND_EVENING` and `CLICKHOUSE_TTL_CLEANUP_CRON_WEEKEND_MORNING` to the same value. Job locking prevents overlapping executions.
### Configuring minimum expired rows per part
The job goes table by table, scanning parts and deleting data from parts containing a minimum number of expired rows. This threshold balances efficiency and thoroughness:
* **Too low**: Job scans entire parts to clear minimal data (inefficient)
* **Too high**: Job misses parts with significant expired data
```yaml theme={null}
queue:
deployment:
extraEnv:
- name: "CLICKHOUSE_TTL_CRON_MIN_EXPIRED_ROWS_PER_PART"
value: "100000" # 100k expired rows
```
#### Checking expired rows
Use this query to analyze expired rows in your tables, and tweak your minimum value accordingly:
```sql theme={null}
-- Query for Runs table. For other tables, replace 'ttl_seconds' with 'trace_ttl_seconds'
SELECT
_part,
count() AS expired_rows
FROM runs
WHERE trace_first_received_at IS NOT NULL
AND ttl_seconds IS NOT NULL
AND toDateTime(assumeNotNull(trace_first_received_at) + toIntervalSecond(assumeNotNull(ttl_seconds))) < now()
GROUP BY _part
ORDER BY expired_rows DESC
```
### Configuring maximum active mutations
Delete operations can be time-consuming (\~50 minutes for a 100GB part). You can increase concurrent mutations to speed up the process:
```yaml theme={null}
queue:
deployment:
extraEnv:
- name: "CLICKHOUSE_TTL_CRON_MAX_ACTIVE_MUTATIONS"
value: "1"
```
Increasing concurrent DELETE operations can severely impact system performance. Monitor your system carefully and only increase this value if you can tolerate potentially slower insert and read latencies.
### Emergency: Stopping running mutations
If you experience latency spikes and need to terminate a running mutation:
1. **Find active mutations**:
```sql theme={null}
SELECT * FROM system.mutations WHERE is_done = 0;
```
Look for the `mutation_id` where the `command` column contains a `DELETE` statement.
2. **Kill the mutation**:
```sql theme={null}
KILL MUTATION WHERE mutation_id = '';
```
### Backups and data retention
If disk space does not decrease after running this job, or if it continues to increase, backups may be causing the issue by creating file system hard links. These links prevent ClickHouse from cleaning up the data.
To verify, check the following directories inside your ClickHouse pod:
* `/var/lib/clickhouse/backup`
* `/var/lib/clickhouse/shadow`
If backups are present, copy them to an external filesystem or blob storage (e.g., S3), then clear the directories. Within a few minutes, you will notice disk space releasing.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-ttl.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Upgrade an installation
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-upgrades
For general upgrade instructions, please follow the instructions below. Certain versions may have specific upgrade instructions, which will be detailed in more specific upgrade guides.
## Kubernetes(Helm)
If you don't have the repo added, run the following command to add it:
```bash theme={null}
helm repo add langchain https://langchain-ai.github.io/helm/
```
Update your local helm repo
```bash theme={null}
helm repo update
```
Update your helm chart config file with any updates that are needed in the new version. These will be detailed in the release notes for the new version.
Run the following command to upgrade the chart(replace version with the version you want to upgrade to):
If you are using a namespace other than the default namespace, you will need to specify the namespace in the `helm` and `kubectl` commands by using the `-n
Find the latest version of the chart. You can find this in the [LangSmith Helm Chart GitHub repository](https://github.com/langchain-ai/helm/releases) or by running the following command:
```bash theme={null}
helm search repo langchain/langsmith --versions
```
You should see an output similar to this:
```bash theme={null}
langchain/langsmith 0.10.14 0.10.32 Helm chart to deploy the langsmith application ...
langchain/langsmith 0.10.13 0.10.32 Helm chart to deploy the langsmith application ...
langchain/langsmith 0.10.12 0.10.32 Helm chart to deploy the langsmith application ...
langchain/langsmith 0.10.11 0.10.29 Helm chart to deploy the langsmith application ...
langchain/langsmith 0.10.10 0.10.29 Helm chart to deploy the langsmith application ...
langchain/langsmith 0.10.9 0.10.29 Helm chart to deploy the langsmith application ...
```
Choose the version you want to upgrade to (generally the latest version is recommended) and note the version number.
```bash theme={null}
helm upgrade langchain/langsmith --version --values --wait --debug
```
Verify that the upgrade was successful:
```bash theme={null}
helm status
```
All pods should be in the `Running` state. Verify that clickhouse is running and that both `migrations` jobs have completed.
```bash theme={null}
kubectl get pods
NAME READY STATUS RESTARTS AGE
langsmith-backend-95b6d54f5-gz48b 1/1 Running 0 15h
langsmith-pg-migrations-d2z6k 0/1 Completed 0 5h48m
langsmith-ch-migrations-gasvk 0/1 Completed 0 5h48m
langsmith-clickhouse-0 1/1 Running 0 26h
langsmith-frontend-84687d9d45-6cg4r 1/1 Running 0 15h
langsmith-hub-backend-66ffb75fb4-qg6kl 1/1 Running 0 15h
langsmith-playground-85b444d8f7-pl589 1/1 Running 0 15h
langsmith-queue-d58cb64f7-87d68 1/1 Running 0 15h
```
### Validate your deployment:
1. Run `kubectl get services`
Output should look something like:
```bash theme={null}
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 172.20.0.1 443/TCP 27d
langsmith-backend ClusterIP 172.20.22.34 1984/TCP 21d
langsmith-clickhouse ClusterIP 172.20.117.62 8123/TCP,9000/TCP 21d
langsmith-frontend LoadBalancer 172.20.218.30 80:30093/TCP,443:31130/TCP 21d
langsmith-platform-backend ClusterIP 172.20.232.183 1986/TCP 21d
langsmith-playground ClusterIP 172.20.167.132 3001/TCP 21d
langsmith-postgres ClusterIP 172.20.59.63 5432/TCP 21d
langsmith-redis ClusterIP 172.20.229.98 6379/TCP 20d
```
2. Curl the external ip of the `langsmith-frontend` service:
```bash theme={null}
curl /api/info
{"version":"0.5.7","license_expiration_time":"2033-05-20T20:08:06","batch_ingest_config":{"scale_up_qsize_trigger":1000,"scale_up_nthreads_limit":16,"scale_down_nempty_trigger":4,"size_limit":100,"size_limit_bytes":20971520}}
```
Check that the version matches the version you upgraded to.
3. Visit the external ip for the `langsmith-frontend` service on your browser
The LangSmith UI should be visible/operational
## Docker
Upgrading the Docker version of LangSmith is a bit more involved than the Helm version and may require a small amount of downtime. Please follow the instructions below to upgrade your Docker version of LangSmith.
1. Update your `docker-compose.yml` file to the file used in the latest release. You can find this in the [LangSmith SDK GitHub repository](https://github.com/langchain-ai/langsmith-sdk/blob/main/python/langsmith/cli/docker-compose.yaml)
2. Update your `.env` file with any new environment variables that are required in the new version. These will be detailed in the release notes for the new version.
3. Run the following command to stop your current LangSmith instance:
```bash theme={null}
docker-compose down
```
4. Run the following command to start your new LangSmith instance in the background:
```bash theme={null}
docker-compose up -d
```
If everything ran successfully, you should see all the LangSmith containers running and healthy.
```bash theme={null}
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e1c8f01a4ffc langchain/langsmith-frontend:0.5.7 "/entrypoint.sh ngin…" 10 hours ago Up 40 seconds 0.0.0.0:80->80/tcp, 8080/tcp cli-langchain-frontend-1
39e1394846b9 langchain/langsmith-backend:0.5.7 "/bin/sh -c 'exec uv…" 10 hours ago Up 40 seconds 0.0.0.0:1984->1984/tcp cli-langchain-backend-1
f8688dd58f2f langchain/langsmith-go-backend:0.5.7 "./smith-go" 10 hours ago Up 40 seconds 0.0.0.0:1986->1986/tcp cli-langchain-platform-backend-1
006f1303b04d langchain/langsmith-backend:0.5.7 "saq app.workers.que…" 10 hours ago Up 40 seconds cli-langchain-queue-1
73a90242ed3a redis:7 "docker-entrypoint.s…" 10 hours ago Up About a minute (healthy) 0.0.0.0:63791->6379/tcp cli-langchain-redis-1
eecf75ca672b postgres:14.7 "docker-entrypoint.s…" 10 hours ago Up About a minute (healthy) 0.0.0.0:5433->5432/tcp cli-langchain-db-1
3aa5652a864d clickhouse/clickhouse-server:23.9 "/entrypoint.sh" 10 hours ago Up About a minute (healthy) 9009/tcp, 0.0.0.0:8124->8123/tcp, 0.0.0.0:9001->9000/tcp cli-langchain-clickhouse-1
84edc329a37f langchain/langsmith-playground:0.5.7 "docker-entrypoint.s…" 10 hours ago Up About a minute 0.0.0.0:3001->3001/tcp cli-langchain-playground-1
```
### Validate your deployment:
1. Curl the exposed port of the `cli-langchain-frontend-1` container:
```bash theme={null}
curl localhost:80/info
{"version":"0.5.7","license_expiration_time":"2033-05-20T20:08:06","batch_ingest_config":{"scale_up_qsize_trigger":1000,"scale_up_nthreads_limit":16,"scale_down_nempty_trigger":4,"size_limit":100,"size_limit_bytes":20971520}}
```
2. Visit the exposed port of the `cli-langchain-frontend-1` container on your browser
The LangSmith UI should be visible/operational
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-upgrades.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Interact with your self-hosted instance of LangSmith
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-usage
This guide will walk you through the process of using your self-hosted instance of LangSmith.
This guide assumes you have already deployed a self-hosted LangSmith instance. If you have not, please refer to the [kubernetes deployment guide](/langsmith/kubernetes) or the [docker deployment guide](/langsmith/docker).
### Configuring the application you want to use with LangSmith
LangSmith has a single API for interacting with both the hub and the LangSmith backend.
1. Once you have deployed your instance, you can access the LangSmith UI at `http(s)://`.
2. The LangSmith API will be available at `http(s):///api/v1`
3. The LangSmith Control Plane will be available at `http(s):///api-host`
To use the API of your instance, you will need to set the following environment variables in your application:
```bash theme={null}
LANGSMITH_ENDPOINT=http:///api/v1
LANGSMITH_API_KEY=foo # Set to a legitimate API key if using OAuth
```
You can also configure these variables directly in the LangSmith SDK client:
```python theme={null}
import langsmith
langsmith_client = langsmith.Client(
api_key='',
api_url='http(s):///api/v1',
)
```
After setting the above, you should be able to run your code and see the results in your self-hosted instance. We recommend running through the [*quickstart guide*](https://docs.smith.langchain.com/#quick-start) to get a feel for how to use LangSmith.
### Self-Signed certificates
If you are using self-signed certificates for your self-hosted LangSmith instance, this can be problematic as Python comes with its own set of trusted certificates, which may not include your self-signed certificate. To resolve this, you may need to use something like `truststore` to load system certificates into your Python environment.
You can do this like so:
1. pip install truststore (or similar depending on the package manager you are using)
Then use the following code to load the system certificates:
```python theme={null}
import truststore
truststore.inject_into_ssl()
# The rest of your code
import langsmith
langsmith_client = langsmith.Client(
api_key='',
api_url='http(s):///api/v1',
)
```
***
## API reference
To access the API reference, navigate to `http:///api/docs` in your browser.
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-usage.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Customize user management
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-user-management
This guide assumes you have read the [admin guide](/langsmith/administration-overview) and [organization setup guide](/langsmith/set-up-hierarchy#set-up-an-organization).
LangSmith offers additional customization features for user management using feature flags.
## Features
### Workspace level invites to an organization
The default behavior in LangSmith requires a user to be an Organization Admin in order to invite new users to an organization. For self-hosted customers that would like to delegate this responsibility to workspace Admins, a feature flag may be set that enables workspace Admins to invite new users to the organization as well as their specific workspace **at the workspace level**.
Once this feature is enabled via the configuration option below, workspace Admins may add new users in the `Workspace members` tab under `Settings` > `Workspaces`. Both of the following cases are supported when inviting at the workspace level, while the organization level invite functions the same as before.
1. Invite users who are NOT already active in the organization: this will add the users as pending to the organization and specific workspace
2. Invite users who ARE already active in the organization: adds the users directly to the workspace as an active member (no pending state).
Admins may invite users for both cases at the same time.
#### Configuration
```yaml Helm theme={null}
config:
workspaceScopeOrgInvitesEnabled: true
```
```bash Docker theme={null}
# In your .env file
WORKSPACE_SCOPE_ORG_INVITES_ENABLED="true"
```
### SSO new member login flow
As of helm **v0.11.10**, self-hosted deployments using OAuth SSO will no longer need to manually add members in LangSmith settings for them to join. Deployments will have a default organization, to which new users will automatically be added upon their first login to LangSmith.
For your **default** organization, you can set which workspace(s) and workspace role is assigned to new members. For **non-default** organizations, the invitation flow remains the same.
Once a user joins an organization, any changes to their workspaces or roles beyond the default organization settings must be managed either through LangSmith settings (as before) or via SCIM.
By default, all new users are added to the organization’s initially provisioned workspace (**Workspace 1** by default) with the **Workspace Editor** role.
To change your default organization, use **Set Default Organization** in the organization selector dropdown. (Org Admin permissions required in both the source and target organization.)
### Disabling organization creating
By default, any user can create an organization in LangSmith. For self-hosted customers, an admin may want to restrict this ability after setting up initial organizations. This feature flag allows an admin to disable the ability for users to create new organizations.
#### Configuration
The `userOrgCreationDisabled` feature flag is set to `true` by default for organizations using [basic auth](/langsmith/self-host-basic-auth) or [SSO](/langsmith/self-host-sso).
```yaml Helm theme={null}
config:
userOrgCreationDisabled: true
```
```bash Docker theme={null}
# In your .env file
FF_ORG_CREATION_DISABLED="true"
```
### Disabling personal organizations
By default, any user who logs in to LangSmith will have a personal organization created for them. For self-hosted customers, an admin may want to restrict this ability. This feature flag allows an admin to disable the ability for users to create personal organizations.
#### Configuration
The `personalOrgsDisabled` feature flag is set to `true` by default for organizations using [basic auth](/langsmith/self-host-basic-auth) or [SSO](/langsmith/self-host-sso).
```yaml Helm theme={null}
config:
personalOrgsDisabled: true
```
```bash Docker theme={null}
# In your .env file
PERSONAL_ORGS_DISABLED="true"
```
### Disabling personal access token creation
This feature requires Helm chart version 0.13.12 (application version 0.13.12) or later.
By default, users can create Personal Access Tokens (PATs) in any organization. For self-hosted customers, an admin may want to globally disable PAT creation across all organizations. This environment variable allows an admin to prevent users from creating new PATs in any organization on the instance.
To disable PAT creation for a single organization instead, see the [per-organization API option](/langsmith/manage-organization-by-api#security-settings).
#### Configuration
```yaml Helm theme={null}
commonEnv:
- name: PAT_CREATION_DISABLED
value: "true"
```
```bash Docker theme={null}
# In your .env file
PAT_CREATION_DISABLED="true"
```
***
[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/self-host-user-management.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
[Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
# Use an existing secret for your installation (Kubernetes)
Source: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.app/langsmith/self-host-using-an-existing-secret
By default, LangSmith will provision several Kubernetes secrets to store sensitive information such as license keys, salts, and other configuration parameters. However, you may want to use an existing secret that you have already created in your Kubernetes cluster (or provisioned via some sort of secrets operator). This can be useful if you want to manage sensitive information in a centralized way or if you have specific security requirements.
By default we will provision the following secrets corresponding to different components of LangSmith:
* `langsmith-secrets`: This secret contains the license key and some other basic configuration parameters. You can see the template for this secret [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/templates/secrets.yaml)
* `langsmith-redis`: This secret contains the Redis connection string (or node URIs if using Redis cluster) and password. You can see the template for this secret [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/templates/redis/secrets.yaml)
* `langsmith-postgres`: This secret contains the Postgres connection string and password. You can see the template for this secret [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/templates/postgres/secrets.yaml)
* `langsmith-clickhouse`: This secret contains the ClickHouse connection string and password. You can see the template for this secret [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/templates/clickhouse/secrets.yaml)
## Requirements
* An existing Kubernetes cluster
* A way to create Kubernetes secrets in your cluster. This can be done using `kubectl`, a Helm chart, or a secrets operator like [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)
## Parameters
You will need to create your own Kubernetes secrets that adhere to the structure of the secrets provisioned by the LangSmith Helm Chart.
The secrets must have the same structure as the ones provisioned by the LangSmith Helm Chart (refer to the links above to see the specific secrets). If you miss any of the required keys, your LangSmith instance may not work correctly.
An example secret may look like this:
```yaml theme={null}
apiVersion: v1
kind: Secret
metadata:
name: langsmith-existing-secrets
namespace: langsmith
stringData:
oauth_client_id: foo
oauth_client_secret: foo
oauth_issuer_url: foo
langsmith_license_key: foo
langgraph_cloud_license_key: foo
api_key_salt: foo
jwt_secret: foo
initial_org_admin_password: foo
blob_storage_access_key: foo
blob_storage_access_key_secret: foo
azure_storage_account_key: foo
azure_storage_connection_string: foo
agent_builder_encryption_key: foo
insights_encryption_key: foo
```
## Configuration
With these secrets provisioned, you can configure your LangSmith instance to use the secrets directly to avoid passing in secret values through plaintext. You can do this by modifying the `langsmith_config.yaml` file for your LangSmith Helm Chart installation.
```yaml theme={null}
config:
existingSecretName: "langsmith-secrets" # The name of the secret that contains the license key and other basic configuration parameters
redis:
external:
enabled: true # Set to true to use an external Redis instance. This secret is only needed if you are using an external Redis instance
existingSecretName: "langsmith-redis" # The name of the secret that contains the Redis connection string and password
postgres:
external:
enabled: true # Set to true to use an external Postgres instance. This secret is only needed if you are using an external Postgres instance
existingSecretName: "langsmith-postgres" # The name of the secret that contains the Postgres connection string and password
clickhouse:
external:
enabled: true # Set to true to use an external ClickHouse instance. This secret is only needed if you are using an external ClickHouse instance
existingSecretName: "langsmith-clickhouse" # The name of the secret that contains the ClickHouse connection string and password
```
Once configured, you will need to update your LangSmith installation. You can follow our upgrade guide [here](/langsmith/self-host-upgrades). If everything is configured correctly, your LangSmith instance should now be accessible via the Ingress. You can run the following to check that your secrets are being used correctly:
```bash theme={null}
kubectl describe deployment langsmith-backend | grep -i
```
You should see something like this in the output:
```bash theme={null}
POSTGRES_DATABASE_URI: