Customize traces

Use decorators to create structured, nested traces that show how your app flows.
  1. Sign up — Create an account at platform.respan.ai
  2. Create an API key — Generate one on the API keys page
  3. Add credits or a provider key — Add credits on the Credits page or connect your own provider key on the Integrations page

Add the Docs MCP to your AI coding tool to get help building with Respan. No API key needed.

1{
2 "mcpServers": {
3 "respan-docs": {
4 "url": "https://mcp.respan.ai/mcp/docs"
5 }
6 }
7}

The Respan tracing SDK provides decorators and wrappers to instrument your code. This page covers the detailed features.


Decorators & span types

Use decorators (Python) or wrapper methods (JS/TS) to create spans. Each decorator creates a different span type in the trace tree.

DecoratorSpan typeUse for
@workflow / withWorkflowWorkflowTop-level parent span
@task / withTaskTaskIndividual steps within a workflow
@agent / withAgentAgentAgent-level operations
@tool / withToolToolTool/function calls
1from openai import OpenAI
2from respan import Respan
3from respan.decorators import workflow, task
4
5Respan()
6client = OpenAI()
7
8@task(name="joke_creation")
9def create_joke():
10 completion = client.chat.completions.create(
11 model="gpt-5.4",
12 messages=[{"role": "user", "content": "Tell me a joke about opentelemetry"}],
13 )
14 return completion.choices[0].message.content
15
16@task(name="pirate_joke_translation")
17def translate_joke_to_pirate(joke: str):
18 completion = client.chat.completions.create(
19 model="gpt-5.4",
20 messages=[{"role": "user", "content": "translate to pirate language:\n\n" + joke}],
21 )
22 return completion.choices[0].message.content
23
24@workflow(name="joke_workflow")
25def joke_workflow():
26 joke = create_joke()
27 pirate_joke = translate_joke_to_pirate(joke)
28 return pirate_joke
29
30result = joke_workflow()
31print(result)

For the full SDK API reference, see the Python SDK or TypeScript SDK.


Class-based workflows

Apply @workflow to a class with method_name to designate the entry point. Use @task on individual methods.

1from openai import OpenAI
2from respan import Respan
3from respan.decorators import workflow, task
4
5Respan()
6client = OpenAI()
7
8@workflow(name="joke_agent", method_name="run")
9class JokeAgent:
10 @task(name="joke_creation")
11 def create_joke(self):
12 completion = client.chat.completions.create(
13 model="gpt-5.4",
14 messages=[{"role": "user", "content": "Tell me a joke"}],
15 )
16 return completion.choices[0].message.content
17
18 def run(self):
19 return self.create_joke()
20
21agent = JokeAgent()
22result = agent.run()

Override span input & output

Control what appears as a span’s input/output in the trace UI using OpenTelemetry attributes:

Python
1import json
2from opentelemetry.semconv_ai import SpanAttributes
3from respan import Respan
4from respan.decorators import workflow
5
6respan = Respan()
7client = respan.getClient()
8
9@workflow(name="update_attributes_test")
10def update_attributes_test(input: str):
11 force_set_attributes = {
12 SpanAttributes.TRACELOOP_ENTITY_INPUT: json.dumps({
13 "args": [],
14 "kwargs": {"text": "custom input"}
15 }),
16 }
17
18 client.update_current_span(
19 attributes=force_set_attributes,
20 name="update_attributes_test",
21 respan_params={"metadata": {"test": "test"}},
22 )
23
24 return "Some desired output"
25
26if __name__ == "__main__":
27 update_attributes_test("Some input")

Use update_current_span(attributes=...) for overriding displayed input/output. Values must be JSON-serializable strings via json.dumps(...).

You can also update spans after they’ve been logged via the Update Span API.