Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 34 additions & 25 deletions python/tracing/cursor-sdk/03_stop_cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,40 @@ def main() -> None:

respan, instrumentor = make_respan(EXAMPLE_NAME, state_file)
print_start(EXAMPLE_NAME, run_id)
with example_attributes(EXAMPLE_NAME, run_id):
for event in [
make_event(
EXAMPLE_NAME,
run_id,
"beforeSubmitPrompt",
prompt="Start a refactor, then cancel the agent turn.",
),
make_event(
EXAMPLE_NAME,
run_id,
"afterAgentThought",
text="I found the affected files and am preparing a minimal change.",
duration_ms=300,
),
make_event(EXAMPLE_NAME, run_id, "stop", status="cancelled", loop_count=1),
]:
result = instrumentor.process_event(event)
print(
f"event={result.event_name} emitted={result.emitted} span={result.span_name}",
flush=True,
)

state = json.loads(state_file.read_text()) if state_file.exists() else {}
print(f"state_after_stop={state}", flush=True)
try:
with example_attributes(EXAMPLE_NAME, run_id):
for event in [
make_event(
EXAMPLE_NAME,
run_id,
"beforeSubmitPrompt",
prompt="Start a refactor, then cancel the agent turn.",
),
make_event(
EXAMPLE_NAME,
run_id,
"afterAgentThought",
text="I found the affected files and am preparing a minimal change.",
duration_ms=300,
),
make_event(
EXAMPLE_NAME,
run_id,
"stop",
status="cancelled",
loop_count=1,
),
]:
result = instrumentor.process_event(event)
print(
f"event={result.event_name} emitted={result.emitted} span={result.span_name}",
flush=True,
)

state = json.loads(state_file.read_text()) if state_file.exists() else {}
print(f"state_after_stop={state}", flush=True)
finally:
respan.shutdown()


if __name__ == "__main__":
Expand Down
30 changes: 18 additions & 12 deletions python/tracing/cursor-sdk/_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ def state_path(run_id: str) -> Path:


def make_custom_identifier(example_name: str) -> str:
return f"cursor-sdk-{example_name}-{uuid4().hex[:8]}"
return os.getenv("RESPAN_EXAMPLE_RUN_ID") or (
f"cursor-sdk-{example_name}-{uuid4().hex[:8]}"
)


def make_event(
Expand All @@ -81,7 +83,9 @@ def make_event(
return event


def make_respan(example_name: str, state_file: Path) -> tuple[Respan, CursorSDKInstrumentor]:
def make_respan(
example_name: str, state_file: Path
) -> tuple[Respan, CursorSDKInstrumentor]:
api_key = require_respan_api_key()
instrumentor = CursorSDKInstrumentor(state_path=state_file)
respan = Respan(
Expand Down Expand Up @@ -126,15 +130,17 @@ def replay_events(
respan, instrumentor = make_respan(example_name, state_file)
print_start(example_name, run_id)
results = []
with example_attributes(example_name, run_id):
for event in events:
result = instrumentor.process_event(event)
results.append(result)
print(
f"event={result.event_name} emitted={result.emitted} span={result.span_name}",
flush=True,
)
finish_respan(respan)
try:
with example_attributes(example_name, run_id):
for event in events:
result = instrumentor.process_event(event)
results.append(result)
print(
f"event={result.event_name} emitted={result.emitted} span={result.span_name}",
flush=True,
)
finally:
finish_respan(respan)
return results


Expand All @@ -152,4 +158,4 @@ def print_json(label: str, value: Any) -> None:


def finish_respan(respan: Respan) -> None:
pass
respan.shutdown()
4 changes: 3 additions & 1 deletion python/tracing/dify/01_chat_blocking.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ def main() -> None:
)
response.raise_for_status()
result = response.json()
print_result("chat_blocking", {"workflow": workflow_name, "answer": result.get("answer")})
summary = {"workflow": workflow_name, "answer": result.get("answer")}
runtime.set_result(summary)
print_result("chat_blocking", summary)


if __name__ == "__main__":
Expand Down
4 changes: 3 additions & 1 deletion python/tracing/dify/02_chat_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ def main() -> None:
)
response.raise_for_status()
answer = collect_stream_answer(response)
print_result("chat_streaming", {"workflow": workflow_name, "answer": answer})
summary = {"workflow": workflow_name, "answer": answer}
runtime.set_result(summary)
print_result("chat_streaming", summary)


if __name__ == "__main__":
Expand Down
4 changes: 3 additions & 1 deletion python/tracing/dify/03_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ def main() -> None:
)
response.raise_for_status()
result = response.json()
print_result("completion", {"workflow": workflow_name, "answer": result.get("answer")})
summary = {"workflow": workflow_name, "answer": result.get("answer")}
runtime.set_result(summary)
print_result("completion", summary)


if __name__ == "__main__":
Expand Down
23 changes: 11 additions & 12 deletions python/tracing/dify/04_workflow_and_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,17 @@ def main() -> None:
)
rename.raise_for_status()

print_result(
"workflow_and_api",
{
"workflow": workflow_name,
"workflow_run_id": workflow_response.json().get("workflow_run_id"),
"parameters_keys": sorted(parameters.json().keys()),
"conversations": len(conversations.json().get("data", [])),
"messages": len(messages.json().get("data", [])),
"feedback": feedback.json().get("result"),
"rename": rename.json().get("result"),
},
)
summary = {
"workflow": workflow_name,
"workflow_run_id": workflow_response.json().get("workflow_run_id"),
"parameters_keys": sorted(parameters.json().keys()),
"conversations": len(conversations.json().get("data", [])),
"messages": len(messages.json().get("data", [])),
"feedback": feedback.json().get("result"),
"rename": rename.json().get("result"),
}
runtime.set_result(summary)
print_result("workflow_and_api", summary)


if __name__ == "__main__":
Expand Down
15 changes: 7 additions & 8 deletions python/tracing/dify/05_respan_context_and_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,13 @@ def main() -> None:
)
response.raise_for_status()

print_result(
"context_and_files",
{
"workflow": workflow_name,
"upload_id": upload_id,
"answer": response.json().get("answer"),
},
)
summary = {
"workflow": workflow_name,
"upload_id": upload_id,
"answer": response.json().get("answer"),
}
runtime.set_result(summary)
print_result("context_and_files", summary)


if __name__ == "__main__":
Expand Down
62 changes: 53 additions & 9 deletions python/tracing/dify/_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from typing import Any

from dotenv import load_dotenv
from opentelemetry.semconv_ai import SpanAttributes

EXAMPLE_DIR = Path(__file__).resolve().parent
REPO_ROOT = EXAMPLE_DIR.parents[2]
Expand Down Expand Up @@ -67,6 +68,7 @@ def _send_sse(self, events: list[dict[str, Any]]) -> None:
@staticmethod
def _usage(prompt_tokens: int = 9, completion_tokens: int = 5) -> dict[str, Any]:
return {
"model": "dify/local-test-model",
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"total_tokens": prompt_tokens + completion_tokens,
Expand Down Expand Up @@ -267,10 +269,16 @@ def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
class DifyExampleRuntime(AbstractContextManager):
def __init__(self, workflow_name: str) -> None:
self.workflow_name = workflow_name
self.run_id = os.getenv("RESPAN_EXAMPLE_RUN_ID") or (
f"dify-{workflow_name}-{uuid.uuid4().hex[:8]}"
)
self.respan = None
self.base_url = ""
self._server_context: LocalDifyServer | None = None
self._attributes_context = None
self._workflow_context = None
self._workflow_span = None
self._result: Any = None

def __enter__(self) -> "DifyExampleRuntime":
load_repo_env()
Expand All @@ -284,25 +292,58 @@ def __enter__(self) -> "DifyExampleRuntime":
base_url=os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api"),
app_name=self.workflow_name,
instrumentations=[DifyInstrumentor()],
metadata={
"integration": "dify",
"run_id": self.run_id,
"workflow_name": self.workflow_name,
},
is_batching_enabled=False,
log_level=os.getenv("RESPAN_LOG_LEVEL", "WARNING"),
)
self._attributes_context = self.respan.propagate_attributes(
custom_identifier=self.run_id,
trace_group_identifier=self.workflow_name,
metadata={
"integration": "dify",
"run_id": self.run_id,
"workflow_name": self.workflow_name,
},
)
self._attributes_context.__enter__()
self._workflow_context = get_client().start_span(
self.workflow_name,
kind="workflow",
)
self._workflow_context.__enter__()
self._workflow_span = self._workflow_context.__enter__()
if self._workflow_span is not None:
self._workflow_span.set_attribute(
SpanAttributes.TRACELOOP_ENTITY_INPUT,
json.dumps({"scenario": self.workflow_name}, separators=(",", ":")),
)
print(f"example_run_id={self.run_id}")
return self

def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
if self._workflow_context is not None:
self._workflow_context.__exit__(exc_type, exc, tb)
if self.respan is not None:
shutdown = getattr(self.respan, "shutdown", None)
if callable(shutdown):
shutdown()
if self._server_context is not None:
self._server_context.__exit__(exc_type, exc, tb)
try:
if self._workflow_span is not None:
output = (
{"error": str(exc)}
if exc is not None
else self._result or {"status": "completed"}
)
self._workflow_span.set_attribute(
SpanAttributes.TRACELOOP_ENTITY_OUTPUT,
json.dumps(output, default=str, separators=(",", ":")),
)
if self._workflow_context is not None:
self._workflow_context.__exit__(exc_type, exc, tb)
finally:
if self._attributes_context is not None:
self._attributes_context.__exit__(exc_type, exc, tb)
if self.respan is not None:
self.respan.shutdown()
if self._server_context is not None:
self._server_context.__exit__(exc_type, exc, tb)

def _configure_dify_endpoint(self) -> None:
base_url = os.getenv("DIFY_BASE_URL")
Expand Down Expand Up @@ -342,6 +383,9 @@ def raw_client(self):
def user(self, suffix: str) -> str:
return f"respan-dify-{suffix}-{uuid.uuid4().hex[:8]}"

def set_result(self, value: Any) -> None:
self._result = value


class sample_file(AbstractContextManager):
def __enter__(self) -> Any:
Expand Down
18 changes: 9 additions & 9 deletions python/tracing/dspy/01_predict_signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import dspy

from _shared import create_respan, print_result, traced_example
from _shared import managed_example, print_result, traced_example


class BasicQuestion(dspy.Signature):
Expand All @@ -15,19 +15,19 @@ class BasicQuestion(dspy.Signature):


def run_predict_signature_example() -> None:
context = create_respan(
with managed_example(
app_name="dspy-01-predict-signature",
example_name="01_predict_signature",
temperature=0.1,
)
predict = dspy.Predict(BasicQuestion)
question = "What does DSPy help developers build?"
) as context:
predict = dspy.Predict(BasicQuestion)
question = "What does DSPy help developers build?"

with traced_example(context, input_data={"question": question}) as span:
prediction = predict(question=question)
span.set_output({"answer": prediction.answer})
with traced_example(context, input_data={"question": question}) as span:
prediction = predict(question=question)
span.set_output({"answer": prediction.answer})

print_result("Answer", prediction.answer)
print_result("Answer", prediction.answer)


if __name__ == "__main__":
Expand Down
Loading