Instructor (tracing)

Instructor is a Python LLM SDK for extracting structured data from model responses. Respan captures Instructor calls as chat spans and uses respan-tracing for workflow names, metadata propagation, and export.

Instructor does not create its own workflow trace object. Use @workflow, @task, and propagate_attributes from respan-tracing when you want recognizable trace names around Instructor calls.

Create an account at platform.respan.ai and grab an API key.

Run npx @respan/cli setup to set up with your coding agent.

See Instructor gateway setup to route this integration through the Respan gateway.

  • Example repo root: respan-example-projects/python/tracing/instructor

Setup

1

Install packages

pip install respan-tracing respan-instrumentation-instructor instructor openai
2

Set environment variables

export RESPAN_API_KEY="YOUR_RESPAN_API_KEY"
export OPENAI_API_KEY="YOUR_OPENAI_API_KEY"

RESPAN_API_KEY exports traces to Respan. OPENAI_API_KEY is used by the OpenAI client in this tracing-only example.

3

Initialize and run

from typing import Literal, TypedDict
import instructor
from openai import OpenAI
from respan_tracing import RespanTelemetry, workflow
from respan_tracing.exporters import propagate_attributes
from respan_instrumentation_instructor import InstructorInstrumentor
telemetry = RespanTelemetry(
app_name="instructor-support-api",
is_auto_instrument=False,
)
InstructorInstrumentor().activate()
client = instructor.from_openai(
OpenAI(),
mode=instructor.Mode.TOOLS,
)
class SupportTicket(TypedDict):
customer: str
priority: Literal["low", "medium", "high"]
summary: str
@workflow(name="support_ticket_extraction")
def extract_ticket(message: str) -> SupportTicket:
return client.create(
response_model=SupportTicket,
max_retries=2,
messages=[
{
"role": "user",
"content": f"Extract the support ticket from this message: {message}",
}
],
model="gpt-4o-mini",
)
with propagate_attributes(
customer_identifier="customer_123",
thread_identifier="ticket_thread_abc",
metadata={"feature": "support-ticket-extraction"},
):
ticket = extract_ticket(
"ACME cannot log in before a security audit and needs help today."
)
print(dict(ticket))
4

View your trace

Open the Traces page to see the named workflow and the Instructor chat span. High-level Instructor calls are named by API when context is available, such as instructor.create, instructor.create_with_completion, instructor.create_iterable, and instructor.async_create.

Configuration

ParameterTypeDefaultDescription
api_keystr | NoneNoneFalls back to RESPAN_API_KEY env var.
base_urlstr | NoneNoneFalls back to RESPAN_BASE_URL env var.
app_namestr | NoneNoneService name attached to exported spans.
is_auto_instrumentboolTrueSet to False when activating InstructorInstrumentor manually to avoid duplicate generic OpenAI spans.
customer_identifierstr | NoneNoneDefault customer identifier for all spans.
metadatadict | NoneNoneDefault metadata attached to all spans.
environmentstr | NoneNoneEnvironment tag, for example "production".

InstructorInstrumentor() does not require constructor options. Activate it after RespanTelemetry is initialized.

What gets captured

Instructor APICaptured by Respan
client.create(...)One chat span for the structured-output request.
client.create_with_completion(...)One chat span plus the parsed response returned to your code.
client.create_iterable(...)One chat span with the consumed iterable output after iteration completes.
client.create_partial(...)One chat span for the partial-object request.
Async clientsThe same span behavior for AsyncInstructor.

Captured attributes include prompt messages, serialized output, model, token usage, provider, response schema in llm.request.functions, customer_identifier, thread_identifier, and custom metadata.

Attributes

In RespanTelemetry

Set defaults at initialization. These apply to Instructor spans exported by this telemetry instance.

from respan_tracing import RespanTelemetry
from respan_instrumentation_instructor import InstructorInstrumentor
telemetry = RespanTelemetry(
app_name="structured-output-api",
is_auto_instrument=False,
customer_identifier="user_123",
metadata={"service": "extraction-api", "version": "1.0.0"},
)
InstructorInstrumentor().activate()

With propagate_attributes

Override per request using a context scope.

from respan_tracing.exporters import propagate_attributes
with propagate_attributes(
customer_identifier="user_456",
thread_identifier="conversation_abc",
metadata={
"route": "invoice-extraction",
"example_script": "01_create.py",
},
):
invoice = client.create(
response_model=Invoice,
messages=[{"role": "user", "content": "Extract this invoice..."}],
model="gpt-4o-mini",
)
AttributeTypeDescription
customer_identifierstrIdentifies the end user in Respan analytics.
thread_identifierstrGroups related structured-output calls into a conversation or job.
metadatadictCustom key-value pairs. Merged with default metadata.

Decorators

Decorators are optional for capture, but recommended for Instructor because Instructor itself does not create named workflow traces. Use @workflow to name an extraction pipeline and @task to split multi-step parsing.

from typing import TypedDict
from respan_tracing import task, workflow
class Invoice(TypedDict):
vendor: str
invoice_id: str
total_usd: float
@task(name="extract_invoice")
def extract_invoice(text: str) -> Invoice:
return client.create(
response_model=Invoice,
messages=[{"role": "user", "content": text}],
model="gpt-4o-mini",
)
@workflow(name="invoice_processing")
def invoice_processing(raw_invoice: str) -> None:
invoice = extract_invoice(raw_invoice)
print(dict(invoice))
invoice_processing("Northwind invoice NW-1042 totals 298.00 USD.")

Examples

Structured extraction with create

from typing import TypedDict
class InvoiceLine(TypedDict):
sku: str
quantity: int
unit_price_usd: float
class Invoice(TypedDict):
vendor: str
invoice_id: str
line_items: list[InvoiceLine]
total_usd: float
invoice = client.create(
response_model=Invoice,
messages=[
{
"role": "user",
"content": "Northwind invoice NW-1042 has 2 LOG-001 at 49.50 and 1 SEC-010 at 199.00. Total is 298.00.",
}
],
model="gpt-4o-mini",
)
print(dict(invoice))

Parsed output plus raw completion

from typing import Literal, TypedDict
class ReleaseNote(TypedDict):
title: str
category: Literal["feature", "fix", "docs"]
bullets: list[str]
release_note, completion = client.create_with_completion(
response_model=ReleaseNote,
messages=[
{
"role": "user",
"content": "Create a release note for preserving Instructor tool schemas in traces.",
}
],
model="gpt-4o-mini",
)
print(dict(release_note))
print(completion.id)

Multiple objects with create_iterable

from typing import Literal, TypedDict
class ActionItem(TypedDict):
owner: str
task: str
status: Literal["new", "blocked", "done"]
items = list(
client.create_iterable(
response_model=ActionItem,
messages=[
{
"role": "user",
"content": "Maya will send the launch checklist. Noah is blocked on legal approval.",
}
],
model="gpt-4o-mini",
)
)
print([dict(item) for item in items])

Async create

import asyncio
from typing import TypedDict
import instructor
from openai import AsyncOpenAI
async_client = instructor.from_openai(
AsyncOpenAI(),
mode=instructor.Mode.TOOLS,
)
class ProjectBrief(TypedDict):
title: str
owner: str
milestones: list[str]
@workflow(name="project_brief")
async def create_project_brief() -> ProjectBrief:
return await async_client.create(
response_model=ProjectBrief,
messages=[
{
"role": "user",
"content": "Build a project brief for the Respan Instructor tracing launch.",
}
],
model="gpt-4o-mini",
)
asyncio.run(create_project_brief())

Hooks

Instructor hooks run inside the same traced request. Use them for local diagnostics; use Respan metadata for filtering and analytics.

from instructor.core.hooks import HookName
def on_completion_kwargs(**kwargs):
print({"model": kwargs.get("model"), "tool_count": len(kwargs.get("tools", []))})
client.on(HookName.COMPLETION_KWARGS, on_completion_kwargs)
try:
ticket = client.create(
response_model=SupportTicket,
max_retries=2,
messages=[{"role": "user", "content": "ACME needs urgent login help."}],
model="gpt-4o-mini",
)
finally:
client.off(HookName.COMPLETION_KWARGS, on_completion_kwargs)