Skip to content

Integration Examples ​

Examples of integrating elsai Guardrails with popular frameworks and agent runtimes.

Overview ​

IntegrationGuardrail TypeSample
Flask / FastAPI / Django / StreamlitContent + token budget via LLMRailsBelow
LangChainInput/output wrapperBelow
LangGraph — Tool AuthorizationAgent hookstoolauth/agent_langgraph.py
LangGraph — Rate LimitingAgent hooks + sessionsratelimit/agent_langgraph.py
FastAPI — Data ExfiltrationOutput checks on API responsesBelow
FastAPI — ARMS StoragePersist runs per requestBelow

Content guardrails (toxicity, PII/PHI, semantic) integrate through LLMRails or GuardrailSystem.check_input() / check_output(). Agent guardrails (tool authorization, rate limiting) require hook nodes in your agent graph.


Flask Integration ​

python
from flask import Flask, request, jsonify
from elsai_guardrails.guardrails import LLMRails, RailsConfig

app = Flask(__name__)

config = RailsConfig.from_content(config_path="config.yml")
rails = LLMRails(config=config)

@app.route('/chat', methods=['POST'])
def chat():
    try:
        data = request.json
        messages = data.get('messages', [])

        if not messages:
            return jsonify({'error': 'No messages provided'}), 400

        result = rails.generate(messages=messages, return_details=True)

        if result['blocked']:
            return jsonify({
                'error': result['block_reason'],
                'message': result['final_response'],
                'token_budget_input_check': result.get('token_budget_input_check'),
                'token_budget_output_check': result.get('token_budget_output_check'),
            }), 400

        return jsonify({'response': result['final_response']})

    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run(debug=True)

Handle common block_reason values: input, output, token_budget_input, token_budget_output, llm_error.


FastAPI Integration ​

python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from elsai_guardrails.guardrails import LLMRails, RailsConfig
from typing import List

app = FastAPI()

config = RailsConfig.from_content(config_path="config.yml")
rails = LLMRails(config=config)

class Message(BaseModel):
    role: str
    content: str

class ChatRequest(BaseModel):
    messages: List[Message]

@app.post("/chat")
async def chat(request: ChatRequest):
    try:
        messages = [{"role": m.role, "content": m.content} for m in request.messages]
        result = await rails.generate_async(messages=messages, return_details=True)

        if result['blocked']:
            raise HTTPException(
                status_code=400,
                detail={
                    'error': result['block_reason'],
                    'message': result['final_response'],
                },
            )

        return {"response": result['final_response']}

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

Django Integration ​

python
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
import json
from elsai_guardrails.guardrails import LLMRails, RailsConfig

config = RailsConfig.from_content(config_path="config.yml")
rails = LLMRails(config=config)

@csrf_exempt
@require_http_methods(["POST"])
def chat_view(request):
    try:
        data = json.loads(request.body)
        messages = data.get('messages', [])

        if not messages:
            return JsonResponse({'error': 'No messages provided'}, status=400)

        result = rails.generate(messages=messages, return_details=True)

        if result['blocked']:
            return JsonResponse({
                'error': result['block_reason'],
                'message': result['final_response'],
            }, status=400)

        return JsonResponse({'response': result['final_response']})

    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)

Streamlit Integration ​

python
import streamlit as st
from elsai_guardrails.guardrails import LLMRails, RailsConfig

@st.cache_resource
def get_rails():
    config = RailsConfig.from_content(config_path="config.yml")
    return LLMRails(config=config)

rails = get_rails()

st.title("Chat with Guardrails")

if "messages" not in st.session_state:
    st.session_state.messages = []

for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

if prompt := st.chat_input("What is up?"):
    st.session_state.messages.append({"role": "user", "content": prompt})

    with st.chat_message("user"):
        st.markdown(prompt)

    result = rails.generate(
        messages=st.session_state.messages,
        return_details=True,
    )

    if result['blocked']:
        response = f"⚠️ [{result['block_reason']}] {result['final_response']}"
    else:
        response = result['final_response']

    st.session_state.messages.append({"role": "assistant", "content": response})

    with st.chat_message("assistant"):
        st.markdown(response)

LangChain Integration ​

Wrap a LangChain chain with input and output guardrail checks:

python
from elsai_guardrails.guardrails import GuardrailSystem, GuardrailConfig

guardrail = GuardrailSystem(config=GuardrailConfig())

class GuardedLLMChain:
    def __init__(self, chain, guardrail):
        self.chain = chain
        self.guardrail = guardrail

    def run(self, input_text):
        input_result = self.guardrail.check_input(input_text)
        if not input_result.passed:
            return f"Input blocked: {input_result.message}"

        response = self.chain.run(input_text)

        output_result = self.guardrail.check_output(response)
        if not output_result.passed:
            return f"Output blocked: {output_result.message}"

        return response

LangGraph — Tool Authorization ​

Restrict which tools each role can invoke using a pre-execution authorization node.

Repository samples:

  • Config: toolauth/config.yaml
  • Agent: toolauth/agent_langgraph.py

Graph flow: agent → authorization → tools → agent

Policy ​

yaml
guardrails:
  tool_authorization:
    enabled: true
    denied_tools:
      - execute_shell
    sensitive_tools:
      - delete_record
    roles:
      analyst:
        allowed_tools:
          - search_web
          - calculator
      engineer:
        allowed_tools:
          - search_web
          - calculator
          - delete_record

Setup ​

python
from elsai_guardrails.guardrails import GuardrailSystem
from elsai_guardrails.guardrails.guardrail_policy import GuardrailPolicy
from langchain_core.messages import AIMessage, ToolMessage
from langgraph.graph import END, START, StateGraph
from langgraph.prebuilt import ToolNode

guardrails = GuardrailSystem(
    guardrail_policy=GuardrailPolicy.from_file("toolauth/config.yaml"),
)

Authorization Node ​

python
def authorization_node(state):
    """Run before ToolNode — block unauthorized calls without executing tools."""
    last_message: AIMessage = state["messages"][-1]
    if not getattr(last_message, "tool_calls", None):
        return {}

    blocked = []
    for call in last_message.tool_calls:
        result = guardrails.before_tool_call(
            tool_name=call["name"],
            user_role=state.get("user_role", "unknown"),
            raise_on_block=False,
        )
        if not result.passed:
            blocked.append(
                ToolMessage(
                    tool_call_id=call["id"],
                    content=f"GUARDRAIL BLOCKED: {result.error}",
                )
            )

    return {"messages": blocked} if blocked else {}

Routing ​

python
def should_continue(state):
    last = state["messages"][-1]
    if isinstance(last, ToolMessage):
        return "agent"   # blocked — surface error to LLM
    if isinstance(last, AIMessage) and last.tool_calls:
        return "authorization"
    return END

graph = StateGraph(AgentState)
graph.add_node("agent", agent_node)
graph.add_node("authorization", authorization_node)
graph.add_node("tools", ToolNode(TOOLS))
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", should_continue, {
    "authorization": "authorization", END: END,
})
graph.add_conditional_edges("authorization", lambda s: (
    "tools" if isinstance(s["messages"][-1], AIMessage) else "agent"
), {"tools": "tools", "agent": "agent"})
graph.add_edge("tools", "agent")
app = graph.compile()

Run the demo:

bash
cd toolauth
pip install langgraph langchain-openai langchain-core pyyaml python-dotenv
python agent_langgraph.py

See Tool Authorization for policy details.


LangGraph — Rate Limiting ​

Enforce per-session request and tool call quotas with a rate-limit node and session tracking.

Repository samples:

  • Config: ratelimit/config.yaml
  • Agent: ratelimit/agent_langgraph.py

Graph flow: agent → rate_limit → tools → agent

Policy ​

yaml
guardrails:
  rate_limit:
    enabled: true
    max_requests_per_session: 5
    max_tool_calls_per_session: 50
    max_tool_execution_seconds: 60

Setup ​

python
from elsai_guardrails.guardrails import GuardrailPolicy, GuardrailSystem

guardrails = GuardrailSystem(
    guardrail_policy=GuardrailPolicy.from_file("ratelimit/config.yaml"),
)
rate_limit_config = guardrails.guardrail_policy.to_rate_limit_config()

session = guardrails.create_session()
session_id = session.session_id

Agent Node (Request Limit) ​

python
def agent_node(state):
    result = guardrails.before_request(state["session_id"], raise_on_block=False)
    if not result.passed:
        return {"messages": [AIMessage(content=f"RATE LIMIT BLOCKED: {result.error}")]}
    response = llm_with_tools.invoke([SYSTEM_PROMPT] + state["messages"])
    return {"messages": [response]}

Rate Limit Node (Tool Call Quota) ​

python
def rate_limit_node(state):
    last: AIMessage = state["messages"][-1]
    if not getattr(last, "tool_calls", None):
        return {}

    blocked = []
    for call in last.tool_calls:
        result = guardrails.check_tool_call_limit(
            state["session_id"], raise_on_block=False,
        )
        if not result.passed:
            blocked.append(ToolMessage(
                tool_call_id=call["id"],
                content=f"RATE LIMIT BLOCKED: {result.error}",
            ))
    return {"messages": blocked} if blocked else {}

Inside Tools (Record + Timer) ​

python
@tool
def search_web(query: str, session_id: str) -> str:
    guardrails.record_tool_call(session_id)
    t = guardrails.start_execution_timer()
    result = f"Results for: {query}"
    guardrails.end_execution_timer(t)
    return result

Run the demo:

bash
cd ratelimit
pip install langgraph langchain-openai langchain-core pyyaml python-dotenv
python agent_langgraph.py

See Rate Limiting for full details.


LangGraph — Combined Agent Safety ​

Use one policy file for both tool authorization and rate limiting. See ratelimit/config.yaml which includes both sections.

yaml
guardrails:
  rate_limit:
    enabled: true
    max_requests_per_session: 5
    max_tool_calls_per_session: 50
    max_tool_execution_seconds: 60

  tool_authorization:
    enabled: true
    denied_tools:
      - execute_shell
    sensitive_tools:
      - delete_record
    roles:
      analyst:
        allowed_tools:
          - search_web
          - calculator

Recommended graph with both guardrails:

agent (before_request)
  → authorization (before_tool_call)
  → rate_limit (check_tool_call_limit)
  → tools (record_tool_call + timer)
  → agent

See Advanced Examples Example 17 for a programmatic combined safety step.


FastAPI — Data Exfiltration on Output ​

Validate LLM responses before returning them to API clients. Block or mask credential leaks and bulk PII exports.

python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from elsai_guardrails.guardrails import GuardrailConfig, GuardrailSystem
from elsai_guardrails.guardrails.guardrail_policy import GuardrailPolicy

app = FastAPI()

policy = GuardrailPolicy.from_file("config.yaml")
guardrails = GuardrailSystem(
    config=GuardrailConfig(
        check_toxicity=False,
        check_sensitive_data=False,
        check_semantic=False,
    ),
    output_checks=True,
    guardrail_policy=policy,
)

class GenerateRequest(BaseModel):
    llm_output: str

@app.post("/validate-output")
def validate_output(body: GenerateRequest):
    result = guardrails.check_output(body.llm_output)

    if not result.passed:
        raise HTTPException(
            status_code=400,
            detail={
                "message": result.message,
                "exfiltration": result.exfiltration,
            },
        )

    exfil = result.exfiltration or {}
    response_text = exfil.get("processed_text", body.llm_output)

    return {
        "response": response_text,
        "exfiltration": exfil,
    }

See Data Exfiltration Detection.


FastAPI — ARMS Storage per Request ​

Persist guardrail check results to the ARMS Backend for each API request.

python
import os
import uuid
from fastapi import FastAPI
from pydantic import BaseModel
from elsai_guardrails.guardrails import GuardrailConfig, GuardrailSystem
from elsai_guardrails.guardrails.guardrail_policy import GuardrailPolicy

app = FastAPI()

policy = GuardrailPolicy.from_file("config.yaml")
guardrails = GuardrailSystem(
    config=GuardrailConfig(),
    guardrail_policy=policy,
)._with_storage_hook()

class ChatRequest(BaseModel):
    message: str

@app.post("/chat")
def chat(body: ChatRequest):
    run_id = uuid.uuid4().hex
    project_id = os.environ.get("GUARDRAILS_ARMS_PROJECT_ID", "demo-project-001")

    guardrails.link_run_context(
        run_id=run_id,
        project_id=project_id,
        project="demo-app",
    )

    with guardrails.storage_run_context(session_id=run_id):
        input_result = guardrails.check_input(body.message)
        if not input_result.passed:
            guardrails.end_run()
            return {"blocked": True, "stage": "input", "message": input_result.message}

        # Replace with your LLM call
        llm_output = f"Echo: {body.message}"
        output_result = guardrails.check_output(llm_output)
        if not output_result.passed:
            guardrails.end_run()
            return {"blocked": True, "stage": "output", "message": output_result.message}

    saved = guardrails.end_run()
    return {"response": llm_output, "run_id": saved}

Environment:

bash
export API_BASE_URL=https://your-arms-backend
export ELSAI_ARMS_API_KEY=your-api-key
export GUARDRAILS_ARMS_PROJECT_ID=demo-project-001

The Backend routes persistence to MongoDB, DynamoDB, or ClickHouse based on your ARMS deployment. See ARMS Storage.


Next Steps ​

Released under the MIT License.