DSPy

DSPy is a framework for programming language model systems with signatures, modules, tools, agents, and evaluation loops. Respan’s DSPy integration registers a native DSPy callback and sends module, LLM, adapter, tool, and evaluation spans through respan-tracing.

Create an account at platform.respan.ai and grab an API key. For gateway, also add credits or a provider key.

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

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

Setup

1

Install packages

$pip install respan-ai respan-instrumentation-dspy dspy
2

Set environment variables

$export RESPAN_API_KEY="YOUR_RESPAN_API_KEY"
$export OPENAI_API_KEY="YOUR_OPENAI_API_KEY"
$export RESPAN_DSPY_MODEL="openai/gpt-4o-mini"

RESPAN_API_KEY exports traces to Respan. OPENAI_API_KEY is used by DSPy’s underlying OpenAI-compatible model client in this tracing-only setup.

3

Initialize and run

1import os
2
3import dspy
4from respan import Respan
5from respan_instrumentation_dspy import DSPyInstrumentor
6
7respan_api_key = os.environ["RESPAN_API_KEY"]
8openai_api_key = os.environ["OPENAI_API_KEY"]
9model = os.getenv("RESPAN_DSPY_MODEL", "openai/gpt-4o-mini")
10
11respan = Respan(
12 api_key=respan_api_key,
13 app_name="dspy-quickstart",
14 instrumentations=[DSPyInstrumentor()],
15)
16
17dspy.configure(
18 lm=dspy.LM(
19 model,
20 api_key=openai_api_key,
21 cache=False,
22 temperature=0.1,
23 )
24)
25
26class QA(dspy.Signature):
27 """Answer the question with one concise sentence."""
28
29 question: str = dspy.InputField()
30 answer: str = dspy.OutputField()
31
32predict = dspy.Predict(QA)
33prediction = predict(question="What does DSPy help developers build?")
34
35print(prediction.answer)
36respan.flush()
4

View your trace

Open the Traces page to see your DSPy program with module spans, adapter formatting/parsing spans, LLM calls, tool calls, and evaluation spans.

Configuration

Respan

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 shown on exported DSPy spans.
instrumentationslist[]Plugin instrumentations to activate, for example DSPyInstrumentor().
customer_identifierstr | NoneNoneDefault customer identifier for all spans.
metadatadict | NoneNoneDefault metadata attached to all spans.
environmentstr | NoneNoneEnvironment tag, for example "production".

DSPyInstrumentor

ParameterTypeDefaultDescription
targetAny | NoneNoneOptional DSPy object to instrument. If omitted, the callback is registered globally with dspy.configure(callbacks=...).
include_contentboolTrueCapture span inputs and outputs. Set to False to omit prompt, completion, tool input, and tool output content.

What gets traced

DSPy operationRespan spanNotes
dspy.Predict, dspy.ChainOfThought, and most modulesdspy.moduleCaptures module input and prediction output.
dspy.ReActdspy.module with agent log typeCaptures the agent trajectory and final answer.
dspy.LM callsdspy.lmCaptures chat messages, completion content, model, provider, and token usage when DSPy/LiteLLM provides it.
dspy.Tool callsdspy.toolCaptures {name, arguments} as input and the tool result as output.
DSPy adaptersdspy.adapterCaptures prompt formatting and completion parsing.
dspy.Evaluatedspy.evaluateCaptures evaluator inputs, score, and summarized results.

Attributes

In Respan()

Set defaults at initialization — these apply to all spans.

1from respan import Respan
2from respan_instrumentation_dspy import DSPyInstrumentor
3
4respan = Respan(
5 app_name="dspy-api",
6 instrumentations=[DSPyInstrumentor()],
7 customer_identifier="user_123",
8 metadata={"service": "dspy-api", "version": "1.0.0"},
9)

With propagate_attributes

Override per-request using a context scope.

1import dspy
2from respan import Respan
3from respan_instrumentation_dspy import DSPyInstrumentor
4
5respan = Respan(instrumentations=[DSPyInstrumentor()])
6
7class QA(dspy.Signature):
8 question: str = dspy.InputField()
9 answer: str = dspy.OutputField()
10
11predict = dspy.ChainOfThought(QA)
12
13def handle_request(user_id: str, question: str):
14 with respan.propagate_attributes(
15 customer_identifier=user_id,
16 thread_identifier="conv_abc_123",
17 trace_group_identifier="dspy-support-request",
18 metadata={"plan": "pro"},
19 ):
20 result = predict(question=question)
21 print(result.answer)
AttributeTypeDescription
customer_identifierstrIdentifies the end user in Respan analytics.
thread_identifierstrGroups related DSPy calls into a conversation.
trace_group_identifierstrGroups related traces for search and filtering.
metadatadictCustom key-value pairs. Merged with default metadata.

Decorators (optional)

Decorators are not required. DSPy module calls, LLM calls, adapter work, tool calls, and evaluation calls are auto-traced by the instrumentor. Use Respan workflow spans when you want a recognizable root span around a full DSPy script or request.

1import json
2
3import dspy
4from opentelemetry.semconv_ai import SpanAttributes
5from respan import Respan
6from respan_instrumentation_dspy import DSPyInstrumentor
7
8respan = Respan(
9 app_name="dspy-support",
10 instrumentations=[DSPyInstrumentor()],
11)
12
13class QA(dspy.Signature):
14 question: str = dspy.InputField()
15 answer: str = dspy.OutputField()
16
17answerer = dspy.ChainOfThought(QA)
18
19def support_workflow(question: str):
20 client = respan.telemetry.get_client()
21
22 with respan.propagate_attributes(
23 trace_group_identifier="support-workflow-001",
24 metadata={"workflow": "support_qa"},
25 ):
26 with client.start_span("dspy_support.workflow", kind="workflow") as span:
27 span.set_attribute(
28 SpanAttributes.TRACELOOP_ENTITY_INPUT,
29 json.dumps({"question": question}),
30 )
31 prediction = answerer(question=question)
32 span.set_attribute(
33 SpanAttributes.TRACELOOP_ENTITY_OUTPUT,
34 json.dumps({"answer": prediction.answer}),
35 )
36 return prediction.answer
37
38print(support_workflow("Why is tracing useful for DSPy programs?"))
39respan.flush()

Examples

Tool calls

Tool calls are captured as dspy.tool spans with JSON input shaped as {name, arguments} and the returned tool result as output.

1import dspy
2
3def lookup_order_status(order_id: str) -> str:
4 statuses = {
5 "ord-1001": "ord-1001 is shipped and arriving tomorrow.",
6 "ord-1002": "ord-1002 is waiting for carrier pickup.",
7 }
8 return statuses.get(order_id, f"No status found for {order_id}.")
9
10tool = dspy.Tool(
11 lookup_order_status,
12 name="lookup_order_status",
13 desc="Look up the shipping status for an order id.",
14)
15
16status = tool(order_id="ord-1001")
17print(status)

ReAct with a tool

1import dspy
2
3class CityQuestion(dspy.Signature):
4 """Answer the user's city question with one sentence."""
5
6 question: str = dspy.InputField()
7 answer: str = dspy.OutputField()
8
9def lookup_city_fact(city: str) -> str:
10 facts = {
11 "tokyo": "Tokyo has one of the world's busiest rail networks.",
12 "paris": "Paris is known for the Louvre and the Eiffel Tower.",
13 }
14 return facts.get(city.lower(), f"No stored fact for {city}.")
15
16agent = dspy.ReAct(CityQuestion, tools=[lookup_city_fact], max_iters=3)
17prediction = agent(
18 question=(
19 "Use lookup_city_fact for Tokyo, then answer with the fact in "
20 "one sentence."
21 )
22)
23print(prediction.answer)