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

1from typing import Literal, TypedDict
2
3import instructor
4from openai import OpenAI
5from respan_tracing import RespanTelemetry, workflow
6from respan_tracing.exporters import propagate_attributes
7from respan_instrumentation_instructor import InstructorInstrumentor
8
9telemetry = RespanTelemetry(
10 app_name="instructor-support-api",
11 is_auto_instrument=False,
12)
13InstructorInstrumentor().activate()
14
15client = instructor.from_openai(
16 OpenAI(),
17 mode=instructor.Mode.TOOLS,
18)
19
20class SupportTicket(TypedDict):
21 customer: str
22 priority: Literal["low", "medium", "high"]
23 summary: str
24
25@workflow(name="support_ticket_extraction")
26def extract_ticket(message: str) -> SupportTicket:
27 return client.create(
28 response_model=SupportTicket,
29 max_retries=2,
30 messages=[
31 {
32 "role": "user",
33 "content": f"Extract the support ticket from this message: {message}",
34 }
35 ],
36 model="gpt-4o-mini",
37 )
38
39with propagate_attributes(
40 customer_identifier="customer_123",
41 thread_identifier="ticket_thread_abc",
42 metadata={"feature": "support-ticket-extraction"},
43):
44 ticket = extract_ticket(
45 "ACME cannot log in before a security audit and needs help today."
46 )
47 print(dict(ticket))
48
49telemetry.flush()
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.

1from respan_tracing import RespanTelemetry
2from respan_instrumentation_instructor import InstructorInstrumentor
3
4telemetry = RespanTelemetry(
5 app_name="structured-output-api",
6 is_auto_instrument=False,
7 customer_identifier="user_123",
8 metadata={"service": "extraction-api", "version": "1.0.0"},
9)
10InstructorInstrumentor().activate()

With propagate_attributes

Override per request using a context scope.

1from respan_tracing.exporters import propagate_attributes
2
3with propagate_attributes(
4 customer_identifier="user_456",
5 thread_identifier="conversation_abc",
6 metadata={
7 "route": "invoice-extraction",
8 "example_script": "01_create.py",
9 },
10):
11 invoice = client.create(
12 response_model=Invoice,
13 messages=[{"role": "user", "content": "Extract this invoice..."}],
14 model="gpt-4o-mini",
15 )
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.

1from typing import TypedDict
2
3from respan_tracing import task, workflow
4
5class Invoice(TypedDict):
6 vendor: str
7 invoice_id: str
8 total_usd: float
9
10@task(name="extract_invoice")
11def extract_invoice(text: str) -> Invoice:
12 return client.create(
13 response_model=Invoice,
14 messages=[{"role": "user", "content": text}],
15 model="gpt-4o-mini",
16 )
17
18@workflow(name="invoice_processing")
19def invoice_processing(raw_invoice: str) -> None:
20 invoice = extract_invoice(raw_invoice)
21 print(dict(invoice))
22
23invoice_processing("Northwind invoice NW-1042 totals 298.00 USD.")
24telemetry.flush()

Examples

Structured extraction with create

1from typing import TypedDict
2
3class InvoiceLine(TypedDict):
4 sku: str
5 quantity: int
6 unit_price_usd: float
7
8class Invoice(TypedDict):
9 vendor: str
10 invoice_id: str
11 line_items: list[InvoiceLine]
12 total_usd: float
13
14invoice = client.create(
15 response_model=Invoice,
16 messages=[
17 {
18 "role": "user",
19 "content": "Northwind invoice NW-1042 has 2 LOG-001 at 49.50 and 1 SEC-010 at 199.00. Total is 298.00.",
20 }
21 ],
22 model="gpt-4o-mini",
23)
24print(dict(invoice))

Parsed output plus raw completion

1from typing import Literal, TypedDict
2
3class ReleaseNote(TypedDict):
4 title: str
5 category: Literal["feature", "fix", "docs"]
6 bullets: list[str]
7
8release_note, completion = client.create_with_completion(
9 response_model=ReleaseNote,
10 messages=[
11 {
12 "role": "user",
13 "content": "Create a release note for preserving Instructor tool schemas in traces.",
14 }
15 ],
16 model="gpt-4o-mini",
17)
18
19print(dict(release_note))
20print(completion.id)

Multiple objects with create_iterable

1from typing import Literal, TypedDict
2
3class ActionItem(TypedDict):
4 owner: str
5 task: str
6 status: Literal["new", "blocked", "done"]
7
8items = list(
9 client.create_iterable(
10 response_model=ActionItem,
11 messages=[
12 {
13 "role": "user",
14 "content": "Maya will send the launch checklist. Noah is blocked on legal approval.",
15 }
16 ],
17 model="gpt-4o-mini",
18 )
19)
20print([dict(item) for item in items])

Async create

1import asyncio
2from typing import TypedDict
3
4import instructor
5from openai import AsyncOpenAI
6
7async_client = instructor.from_openai(
8 AsyncOpenAI(),
9 mode=instructor.Mode.TOOLS,
10)
11
12class ProjectBrief(TypedDict):
13 title: str
14 owner: str
15 milestones: list[str]
16
17@workflow(name="project_brief")
18async def create_project_brief() -> ProjectBrief:
19 return await async_client.create(
20 response_model=ProjectBrief,
21 messages=[
22 {
23 "role": "user",
24 "content": "Build a project brief for the Respan Instructor tracing launch.",
25 }
26 ],
27 model="gpt-4o-mini",
28 )
29
30asyncio.run(create_project_brief())
31telemetry.flush()

Hooks

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

1from instructor.core.hooks import HookName
2
3def on_completion_kwargs(**kwargs):
4 print({"model": kwargs.get("model"), "tool_count": len(kwargs.get("tools", []))})
5
6client.on(HookName.COMPLETION_KWARGS, on_completion_kwargs)
7
8try:
9 ticket = client.create(
10 response_model=SupportTicket,
11 max_retries=2,
12 messages=[{"role": "user", "content": "ACME needs urgent login help."}],
13 model="gpt-4o-mini",
14 )
15finally:
16 client.off(HookName.COMPLETION_KWARGS, on_completion_kwargs)