docs: add governed action span metadata example - #4390
Conversation
|
Jason Wang seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
📝 WalkthroughWalkthroughThe sample app adds governed agent action metadata to an OpenTelemetry span. It defines action, governance, approval, proof, and verifier attributes, initializes Traceloop, creates an ChangesGoverned action metadata
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/sample-app/sample_app/governed_action_span_attributes.py (1)
14-22: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winUse a project-owned namespace for custom attributes.
gen_ai.agent.action.refandgen_ai.agent.action.hashare application-defined keys, but they use the existinggen_aisemantic-convention namespace. OpenTelemetry recommends avoiding existing semantic-convention namespaces for new application-specific attributes. If these keys remain custom, move them to a project-owned prefix and update downstream consumers. (opentelemetry.io)Confirm this against the semantic-conventions version targeted by the repository.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/sample-app/sample_app/governed_action_span_attributes.py` around lines 14 - 22, The ACTION_ATTRIBUTES mapping uses the gen_ai namespace for application-defined action keys; rename gen_ai.agent.action.ref and gen_ai.agent.action.hash to project-owned attribute prefixes, update downstream consumers of those keys, and verify the chosen names against the repository’s targeted OpenTelemetry semantic-conventions version.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/sample-app/sample_app/governed_action_span_attributes.py`:
- Around line 27-30: Remove the empty instruments argument from the
Traceloop.init call in the governed action span attributes setup, allowing
init_instrumentations() to use its default value and avoid the dict/set
subtraction error.
- Around line 27-31: Update the Traceloop.init call in the standalone sample to
configure a ConsoleSpanExporter imported from opentelemetry.sdk.trace.export,
ensuring spans are emitted locally without requiring TRACELOOP_API_KEY and the
existing tracer flow remains executable.
---
Nitpick comments:
In `@packages/sample-app/sample_app/governed_action_span_attributes.py`:
- Around line 14-22: The ACTION_ATTRIBUTES mapping uses the gen_ai namespace for
application-defined action keys; rename gen_ai.agent.action.ref and
gen_ai.agent.action.hash to project-owned attribute prefixes, update downstream
consumers of those keys, and verify the chosen names against the repository’s
targeted OpenTelemetry semantic-conventions version.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d2c1519e-a188-4d0d-be86-d9e7974b261a
📒 Files selected for processing (2)
packages/sample-app/README.mdpackages/sample-app/sample_app/governed_action_span_attributes.py
| Traceloop.init( | ||
| app_name="governed-action-span-attributes", | ||
| disable_batch=True, | ||
| instruments={}, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n 'traceloop-sdk|Traceloop.init|instruments' . \
--glob 'pyproject.toml' \
--glob 'uv.lock' \
--glob '*.py'
uv run --with traceloop-sdk python - <<'PY'
from inspect import getsource, signature
from traceloop.sdk import Traceloop
from traceloop.sdk.tracing import init_instrumentations
print(signature(Traceloop.init))
print(getsource(init_instrumentations))
PYRepository: traceloop/openllmetry
Length of output: 50379
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "## relevant source files"
wc -l packages/traceloop-sdk/traceloop/sdk/__init__.py packages/traceloop-sdk/traceloop/sdk/instruments.py packages/traceloop-sdk/traceloop/sdk/tracing.py 2>/dev/null || true
echo "## packages/traceloop-sdk/traceloop/sdk/__init__.py lines 1-220"
sed -n '1,220p' packages/traceloop-sdk/traceloop/sdk/__init__.py
echo "## packages/traceloop-sdk/traceloop/sdk/tracing.py lines 1-140"
sed -n '1,140p' packages/traceloop-sdk/traceloop/sdk/tracing.py
echo "## packages/traceloop-sdk/traceloop/sdk/instruments.py"
sed -n '1,80p' packages/traceloop-sdk/traceloop/sdk/instruments.py
echo "## targeted samples with empty dict"
rg -n "Traceloop\.init\([^)]*instruments=\{\}" packages/sample-app/sample_app -g '*.py'Repository: traceloop/openllmetry
Length of output: 9793
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "## tracing package files"
git ls-files packages/traceloop-sdk/traceloop/sdk | sed -n '1,120p'
echo "## init_instrumentations references"
rg -n "init_instrumentations|TracerWrapper|def __init__|instruments|block_instruments" packages/traceloop-sdk/traceloop/sdk -g '*.py'
echo "## candidate files with init_instrumentations"
rg -l "init_instrumentations" packages/traceloop-sdk/traceloop/sdk -g '*.py'Repository: traceloop/openllmetry
Length of output: 12747
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "## packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py lines 140-210"
sed -n '140,210p' packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py
echo "## packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py lines 480-540"
sed -n '480,535p' packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py
echo "## packages/sample-app/sample_app/governed_action_span_attributes.py lines 1-60"
sed -n '1,60p' packages/sample-app/sample_app/governed_action_span_attributes.py
echo "## other empty-instruments samples"
rg -n "Traceloop\.init\([^)]*instruments=\{\}" packages/sample-app/sample_app -g '*.py'
echo "## init_instrumentations import target"
grep -n "from .*init_instrumentations" packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py
sed -n '1,45p' packages/traceloop-sdk/traceloop/sdk/tracing/tracing.pyRepository: traceloop/openllmetry
Length of output: 7234
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "## behavioral probe for empty instruments handling"
python3 - <<'PY'
class Instrument:
pass
Instrument.A = object()
instruments_dict = {}
block_instruments_dict = {}
block_instruments_dict = block_instruments_dict or set()
instruments_after_none = instruments_dict if instruments_dict is not None else {Instrument.A}
try:
instruments_subtracted = instruments_after_none - block_instruments_dict
print("dict_subtraction_result_repr=" + repr(instruments_subtracted))
except Exception as e:
print("dict_subtraction_error_type=" + type(e).__name__)
print("dict_subtraction_error_message=" + str(e))
instruments_set = set()
instruments_subtracted_set = (instruments_set if instruments_set is not None else {Instrument.A}) - (block_instruments_dict or set())
print("set_subtraction_result_repr=" + repr(instruments_subtracted_set))
PY
echo "## uv package availability"
if command -v uv >/dev/null 2>&1; then
echo "uv present"
uv run python3 - <<'PY'
try:
from traceloop.sdk.tracing.tracing import init_instrumentations
from traceloop.sdk.instruments import Instruments
import inspect
print(init_instrumentations.__module__)
print(inspect.signature(init_instrumentations).parameters["instruments"].annotation)
init_instrumentations(False, lambda *a, **k: None, instruments={}, block_instruments={})
print("empty_dict_call=ok")
except Exception as e:
print(type(e).__name__ + ": " + str(e))
PY
else
echo "uv not present"
fi
echo "## local tests mentioning empty instruments"
rg -n "instruments=\{\}|instruments=set\(\)|block_instruments" packages/traceloop-sdk/tests packages/sample-app/sample_app -g '*.py'Repository: traceloop/openllmetry
Length of output: 723
Use no instruments arg to disable instrumentation.
Traceloop.init(instruments={}) passes a dict to init_instrumentations(), which later does instruments = instruments - block_instruments. That raises TypeError: unsupported operand type(s) for -: 'dict' and 'set'. Use the default value instead.
Proposed fix
- Traceloop.init(
- app_name="governed-action-span-attributes",
- disable_batch=True,
- instruments={},
- )
+ Traceloop.init(
+ app_name="governed-action-span-attributes",
+ disable_batch=True,
+ )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/sample-app/sample_app/governed_action_span_attributes.py` around
lines 27 - 30, Remove the empty instruments argument from the Traceloop.init
call in the governed action span attributes setup, allowing
init_instrumentations() to use its default value and avoid the dict/set
subtraction error.
| Traceloop.init( | ||
| app_name="governed-action-span-attributes", | ||
| disable_batch=True, | ||
| instruments={}, | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)governed_action_span_attributes\.py$|pyproject\.toml$|Traceloop' || true
echo
echo "== target file =="
if [ -f packages/sample-app/sample_app/governed_action_span_attributes.py ]; then
nl -ba packages/sample-app/sample-app/governed_action_span_attributes.py 2>/dev/null || nl -ba packages/sample-app/sample_app/governed_action_span_attributes.py
fi
echo
echo "== dependency/version clues =="
rg -n "traceloop|opentelemetry|ConsoleSpanExporter|API_KEY|TRACLOOP" . -g '!**/.git/**' -g '!**/node_modules/**' | head -200
echo
echo "== package metadata =="
fd -a 'pyproject\.toml$' . | while read -r f; do
echo "--- ${f#"$PWD/"}"
sed -n '1,220p' "$f" | rg -n "traceloop|opentelemetry|packages|dependencies|requires-python|python" -C 2 || true
doneRepository: traceloop/openllmetry
Length of output: 2443
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== target file =="
sed -n '1,160p' packages/sample-app/sample_app/governed_action_span_attributes.py | awk '{printf "%6d %s\n", NR, $0}'
echo
echo "== sample pyproject =="
sed -n '1,220p' packages/sample-app/pyproject.toml | awk '{printf "%6d %s\n", NR, $0}'
echo
echo "== traceloop sdk relevant init/export docs in repo =="
python3 - <<'PY'
import pathlib, re
for p in pathlib.Path("packages").rglob("*.py"):
try:
text = p.read_text(encoding="utf-8", errors="ignore")
except Exception:
continue
if "def init" in text and ("Traceloop.init" in text or "class Traceloop" in text):
print("----", p)
lines = text.splitlines()
for i, line in enumerate(lines, start=1):
if "class Traceloop" in line or "def init" in line:
a=max(1,i-12); b=min(len(lines),i+120)
for j in range(a,b+1):
print(f"{j:6d} {lines[j-1]}")
PY
echo
echo "== search ConsoleSpanExporter and API key env usage =="
rg -n "ConsoleSpanExporter|Traceloop\.init|api_key|API_KEY|TRACLOOP|disable_batch" packages -g '*.py' -g '*.toml' | head -300
echo
echo "== deterministic parse probe for target snippet =="
python3 - <<'PY'
import ast, pathlib
p=pathlib.Path("packages/sample-app/sample_app/governed_action_span_attributes.py")
text=p.read_text()
tree=ast.parse(text)
for node in ast.walk(tree):
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Attribute) and node.func.attr == "init":
print("Traceloop.init args:", {kw.arg: ast.unparse(kw.value) for kw in node.keywords})
PYRepository: traceloop/openllmetry
Length of output: 50378
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== target file relevant lines =="
awk '{printf "%6d %s\n", NR, $0}' packages/sample-app/sample_app/governed_action_span_attributes.py | sed -n '1,120p'
awk '{printf "%6d %s\n", NR, $0}' packages/sample-app/sample_app/governed_action_span_attributes.py | sed -n '120,200p'
echo
echo "== console/exporter imports in target and nearby sample files =="
rg -n "import tr\." packages/sample-app/sample_app packages/sample-app -g '*.py' | head -100
rg -n "ConsoleSpanExporter|import exporter|exporter=" packages/sample-app/sample_app packages/sample-app -g '*.py' | head -150
echo
echo "== parse Traceloop.init call in target =="
python3 - <<'PY'
import ast, pathlib
p=pathlib.Path("packages/sample-app/sample_app/governed_action_span_attributes.py")
text=p.read_text()
tree=ast.parse(text)
for node in ast.walk(tree):
if isinstance(node, ast.Call):
func=node.func
if isinstance(func, ast.Attribute) and func.attr == "init":
if isinstance(func.value, ast.Name) and func.value.id == "Traceloop":
print("Traceloop.init call at line", node.lineno)
print("keywords:", {kw.arg or "<positional>": ast.unparse(kw.value) for kw in node.keywords})
print("has exporter keyword:", any(kw.arg == "exporter" for kw in node.keywords))
PY
echo
echo "== focused search SDK init and API key handling =="
rg -n "def init\(|API_KEY|TRACLOOP_API|tracer_provider|ConsoleSpanExporter|disable_batch|get_tracer_provider|set_tracer_provider" packages -g '*.py' | head -250
echo
echo "== focused python files containing relevant SDK init implementation =="
rg -l "class Traceloop|def init\(" packages -g '*.py' | head -20
for f in $(rg -l "class Traceloop|def init\(" packages -g '*.py' | head -10); do
echo "--- $f"
rg -n "class Traceloop|def init\(|API_KEY|TRACLOOP_API|tracer_provider|ConsoleSpanExporter|disable_batch|get_tracer_provider|set_tracer_provider" "$f" -C 8
doneRepository: traceloop/openllmetry
Length of output: 2120
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== focused list files =="
rg -l "class Traceloop|def init\(" packages -g '*.py' | python3 - <<'PY'
import sys
lines=sys.stdin.read().strip().splitlines()
for line in lines:
if line:
print(line)
PY
echo
echo "== focused implementation excerpts =="
while IFS= read -r f; do
echo "--- $f"
rg -n "class Traceloop|def init\(|API_KEY|TRACLOOP_API|tracer_provider|ConsoleSpanExporter|disable_batch|get_tracer_provider|set_tracer_provider|_init_.*API|exporter" "$f" -C 6 || true
done
echo
echo "== target file parser probe =="
python3 - <<'PY'
import ast, pathlib
p=pathlib.Path("packages/sample-app/sample_app/governed_action_span_attributes.py")
text=p.read_text()
tree=ast.parse(text)
for node in ast.walk(tree):
if isinstance(node, ast.Call):
func=node.func
if isinstance(func, ast.Attribute) and func.attr == "init":
if isinstance(func.value, ast.Name) and func.value.id == "Traceloop":
print("Traceloop.init line", node.lineno)
print("keywords:", [(kw.arg or "<positional>", ast.unparse(kw.value)) for kw in node.keywords])
print("has exporter keyword:", any(kw.arg == "exporter" for kw in node.keywords))
PY
echo
echo "== deterministic env/default init-flow probe from source text =="
python3 - <<'PY'
import re, pathlib, ast
files=[pathlib.Path(p) for p in open("/tmp/focused_files.txt")]
# Fallback list if no temp file, but avoid shell subshell failure above.
PYRepository: traceloop/openllmetry
Length of output: 5011
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
echo "== focused files list =="
rg -l "class Traceloop|def init\(" packages -g '*.py' > "$tmp" || true
cat "$tmp"
echo
echo "== focused implementation excerpts =="
while IFS= read -r f; do
echo "--- $f"
rg -n "class Traceloop|def init\(|API_KEY|TRACLOOP_API|tracer_provider|ConsoleSpanExporter|disable_batch|get_tracer_provider|set_tracer_provider|exporter" "$f" -C 8 || true
done < "$tmp"
echo
echo "== target file parser probe =="
python3 - <<'PY'
import ast, pathlib
p=pathlib.Path("packages/sample-app/sample_app/governed_action_span_attributes.py")
text=p.read_text()
tree=ast.parse(text)
for node in ast.walk(tree):
if isinstance(node, ast.Call):
func=node.func
if isinstance(func, ast.Attribute) and func.attr == "init":
if isinstance(func.value, ast.Name) and func.value.id == "Traceloop":
print("Traceloop.init line", node.lineno)
print("keywords:", [(kw.arg or "<positional>", ast.unparse(kw.value)) for kw in node.keywords])
print("has exporter keyword:", any(kw.arg == "exporter" for kw in node.keywords))
PYRepository: traceloop/openllmetry
Length of output: 11822
Use ConsoleSpanExporter for this standalone sample.
Traceloop.init has no local exporter, and this sample does not set TRACELOOP_API_KEY. That makes the SDK print the default missing-API-key error and return before installing the tracer wrapper, so the local tracer.start_as_current_span(...) call and success message only run when credentials are provided externally.
Proposed fix
from opentelemetry import trace
+from opentelemetry.sdk.trace.export import ConsoleSpanExporter
...
Traceloop.init(
app_name="governed-action-span-attributes",
disable_batch=True,
+ exporter=ConsoleSpanExporter(),
instruments={},
)As required by the Python guidelines, use ConsoleSpanExporter from opentelemetry.sdk.trace.export for debugging OpenTelemetry spans and hierarchy issues.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Traceloop.init( | |
| app_name="governed-action-span-attributes", | |
| disable_batch=True, | |
| instruments={}, | |
| ) | |
| Traceloop.init( | |
| app_name="governed-action-span-attributes", | |
| disable_batch=True, | |
| exporter=ConsoleSpanExporter(), | |
| instruments={}, | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/sample-app/sample_app/governed_action_span_attributes.py` around
lines 27 - 31, Update the Traceloop.init call in the standalone sample to
configure a ConsoleSpanExporter imported from opentelemetry.sdk.trace.export,
ensuring spans are emitted locally without requiring TRACELOOP_API_KEY and the
existing tracer flow remains executable.
Source: Coding guidelines
|
Closing this in favor of #4391. The replacement keeps the same sample intent, incorporates the CodeRabbit feedback ( attributes, no empty , console span processor), and uses commits authored from my GitHub-linked email so the CLA check can evaluate cleanly. |
|
Superseded by #4391. |
|
Superseded by #4391. The replacement keeps the same sample intent, incorporates the CodeRabbit feedback by using app.governance attribute names, removing the empty instruments argument, and using a console span processor. It also uses commits authored from my GitHub-linked email so the CLA check can evaluate cleanly. |
Summary
Validation
Summary by CodeRabbit
New Features
Documentation