Skip to content

Examples

Runnable versions live in examples/.

A complete blog agent backend

# models.py
from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()


class Article(models.Model):
    class Status(models.TextChoices):
        DRAFT = "draft", "Draft"
        PUBLISHED = "published", "Published"

    title = models.CharField(max_length=255)
    body = models.TextField(blank=True, default="")
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="articles")
    status = models.CharField(max_length=20, choices=Status.choices, default=Status.DRAFT)
# serializers.py
from rest_framework import serializers


class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = ["id", "title", "body", "author", "status"]
        read_only_fields = ["id"]
# views.py
from rest_framework import viewsets
from drf_llm_gateway import expose_as_tool


@expose_as_tool(
    actions=["list", "retrieve", "create", "partial_update"],
    examples={"create": [{"title": "My First Post", "body": "Hello!", "author": 1}]},
)
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

Wiring into an OpenAI Chat Completions loop

import openai
from drf_llm_gateway import execute_tool, to_openai_tools

client = openai.OpenAI()
tools = to_openai_tools()

response = client.chat.completions.create(
    model="gpt-4.1",
    messages=[{"role": "user", "content": "Create a draft article titled 'Hello'"}],
    tools=tools,
)

for tool_call in response.choices[0].message.tool_calls or []:
    import json

    arguments = json.loads(tool_call.function.arguments)
    result = execute_tool(tool_call.function.name, arguments, user=request.user)
    print(result.status_code, result.data)

Wiring into an MCP server

from drf_llm_gateway import execute_tool, to_mcp_tools


def list_tools() -> dict:
    return {"tools": to_mcp_tools()}


def call_tool(name: str, arguments: dict, *, user) -> dict:
    result = execute_tool(name, arguments, user=user)
    return {"content": [{"type": "text", "text": str(result.data)}], "isError": result.status_code >= 400}

A read-only public tool set alongside a full internal one

@expose_as_tool(actions=["list", "retrieve", "create", "update", "partial_update", "destroy"])
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [IsAuthenticated]


@expose_as_tool(actions=["list", "retrieve"], name_prefix="public_article")
class PublicArticleViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Article.objects.filter(status=Article.Status.PUBLISHED)
    serializer_class = ArticleSerializer
    permission_classes = []

An anonymous agent identity (execute_tool(..., user=None)) can call public_article_list/public_article_retrieve but is denied (raises ToolPermissionDeniedError) on the internal article_* tools.