-
Notifications
You must be signed in to change notification settings - Fork 1k
docs: add governed action span metadata example #4390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,10 @@ | ||
| # sample-app | ||
|
|
||
| Project description here. | ||
|
|
||
| ## Governed agent action metadata | ||
|
|
||
| `sample_app/governed_action_span_attributes.py` shows how to attach | ||
| vendor-neutral governance metadata to an OpenTelemetry span for an agent action. | ||
| The example includes an action reference and hash, governance verdict, approval | ||
| status, proof URL, and external verifier reference as custom span attributes. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,45 @@ | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
| Example: attach governed agent action metadata to OpenTelemetry spans. | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| These attributes are illustrative custom span attributes. They can help connect | ||||||||||||||||||||||||
| an agent's proposed action to the governance decision, approval state, proof | ||||||||||||||||||||||||
| record, and an external verifier reference without requiring a vendor-specific | ||||||||||||||||||||||||
| collector or backend. | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| from opentelemetry import trace | ||||||||||||||||||||||||
| from traceloop.sdk import Traceloop | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ACTION_ATTRIBUTES = { | ||||||||||||||||||||||||
| "gen_ai.agent.action.ref": "tool:crm.update_customer", | ||||||||||||||||||||||||
| "gen_ai.agent.action.hash": ( | ||||||||||||||||||||||||
| "sha256:6f1f2d8a3c8c9e7a0b4d5e6f7890abcd1234567890abcdef1234567890abcdef" | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| "governance.verdict": "allow", | ||||||||||||||||||||||||
| "governance.approval.status": "approved", | ||||||||||||||||||||||||
| "governance.proof.url": "https://governance.example/proofs/run-9b7c/action-42", | ||||||||||||||||||||||||
| "governance.external_verifier.ref": "verifier://policy-engine/prod/decision-42", | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def main(): | ||||||||||||||||||||||||
| Traceloop.init( | ||||||||||||||||||||||||
| app_name="governed-action-span-attributes", | ||||||||||||||||||||||||
| disable_batch=True, | ||||||||||||||||||||||||
| instruments={}, | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
Comment on lines
+27
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 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
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 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| tracer = trace.get_tracer("sample_app.governed_action_span_attributes") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| with tracer.start_as_current_span("agent.action") as span: | ||||||||||||||||||||||||
| for key, value in ACTION_ATTRIBUTES.items(): | ||||||||||||||||||||||||
| span.set_attribute(key, value) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # Run the approved action here. OSuite or another governance emitter can | ||||||||||||||||||||||||
| # provide these values before the action is executed. | ||||||||||||||||||||||||
| print("Recorded governed action metadata on the current span.") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||||
| main() | ||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: traceloop/openllmetry
Length of output: 50379
🏁 Script executed:
Repository: traceloop/openllmetry
Length of output: 9793
🏁 Script executed:
Repository: traceloop/openllmetry
Length of output: 12747
🏁 Script executed:
Repository: traceloop/openllmetry
Length of output: 7234
🏁 Script executed:
Repository: traceloop/openllmetry
Length of output: 723
Use no
instrumentsarg to disable instrumentation.Traceloop.init(instruments={})passes a dict toinit_instrumentations(), which later doesinstruments = instruments - block_instruments. That raisesTypeError: unsupported operand type(s) for -: 'dict' and 'set'. Use the default value instead.Proposed fix
🤖 Prompt for AI Agents