mirror of
https://github.com/langgenius/dify.git
synced 2026-11-13 06:41:07 +08:00
187 lines
6.6 KiB
Python
187 lines
6.6 KiB
Python
from typing import TYPE_CHECKING, Literal, override
|
|
|
|
from dify_agent.layers.execution_context import ApplicationTracingContext
|
|
from dify_trace_sdk.emitter import TraceScope
|
|
from dify_trace_sdk.events import TraceCorrelation, TraceEvent
|
|
from flask import Flask
|
|
|
|
from core.helper.trace_id_helper import ParentTraceContext
|
|
from extensions.tracing import (
|
|
ApplicationTracingHooks,
|
|
ExecutionTraceScopes,
|
|
application_tracing_hooks,
|
|
bind_tracing,
|
|
correlation_from_extras,
|
|
create_trace_scope,
|
|
create_trace_scope_for_app,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from core.app.apps.agent_app.runtime_request_builder import AgentAppRuntimeBuildContext
|
|
from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity
|
|
from core.workflow.nodes.agent_v2.runtime_request_builder import WorkflowAgentRuntimeBuildContext
|
|
|
|
|
|
class _Recorder:
|
|
def __init__(self) -> None:
|
|
self.events: list[TraceEvent] = []
|
|
|
|
def emit(self, event: TraceEvent) -> None:
|
|
self.events.append(event)
|
|
|
|
|
|
class _Scopes(ExecutionTraceScopes):
|
|
def __init__(self, *, fails: bool) -> None:
|
|
self.fails = fails
|
|
self.policies: list[str | None] = []
|
|
self.correlations: list[TraceCorrelation] = []
|
|
|
|
def _scope(
|
|
self, *, tenant_id: str, app_id: str | None, user_id: str | None, correlation: TraceCorrelation
|
|
) -> TraceScope:
|
|
if self.fails:
|
|
raise RuntimeError("policy lookup failed")
|
|
self.correlations.append(correlation)
|
|
return TraceScope(
|
|
emitter=_Recorder(), tenant_id=tenant_id, app_id=app_id, user_id=user_id, correlation=correlation
|
|
)
|
|
|
|
@override
|
|
def for_execution(
|
|
self,
|
|
*,
|
|
tenant_id: str,
|
|
app_id: str | None,
|
|
app_tracing: str | None,
|
|
user_id: str | None,
|
|
correlation: TraceCorrelation,
|
|
) -> TraceScope | None:
|
|
self.policies.append(app_tracing)
|
|
return self._scope(tenant_id=tenant_id, app_id=app_id, user_id=user_id, correlation=correlation)
|
|
|
|
@override
|
|
def for_app(
|
|
self, *, tenant_id: str, app_id: str | None, user_id: str | None, correlation: TraceCorrelation
|
|
) -> TraceScope | None:
|
|
return self._scope(tenant_id=tenant_id, app_id=app_id, user_id=user_id, correlation=correlation)
|
|
|
|
|
|
class _Hooks(ApplicationTracingHooks):
|
|
@override
|
|
def prepare_agent_app(self, context: "AgentAppRuntimeBuildContext") -> ApplicationTracingContext | None:
|
|
return None
|
|
|
|
@override
|
|
def prepare_workflow_agent(
|
|
self, context: "WorkflowAgentRuntimeBuildContext", conversation_id: str | None
|
|
) -> ApplicationTracingContext | None:
|
|
return None
|
|
|
|
@override
|
|
def freeze_workflow_run(
|
|
self,
|
|
entity: "AdvancedChatAppGenerateEntity | WorkflowAppGenerateEntity",
|
|
*,
|
|
workflow_id: str,
|
|
workflow_run_id: str,
|
|
conversation_id: object,
|
|
graph_data: object,
|
|
) -> None:
|
|
return None
|
|
|
|
@override
|
|
def capture_agent_app_terminal(
|
|
self,
|
|
*,
|
|
tenant_id: str,
|
|
app_id: str,
|
|
message_id: str,
|
|
outcome: Literal["succeeded", "failed", "cancelled"],
|
|
output: str | None,
|
|
) -> None:
|
|
return None
|
|
|
|
|
|
def _app(scopes: _Scopes, hooks: ApplicationTracingHooks | None) -> Flask:
|
|
app = Flask(__name__)
|
|
bind_tracing(app, scopes=scopes, hooks=hooks)
|
|
return app
|
|
|
|
|
|
def test_correlation_reads_the_caller_extras() -> None:
|
|
nested = correlation_from_extras(
|
|
{
|
|
"external_trace_id": "caller-trace",
|
|
"trace_session_id": "session-1",
|
|
"parent_trace_context": {"parent_workflow_run_id": "outer-run", "parent_node_execution_id": "outer-node"},
|
|
}
|
|
)
|
|
typed = correlation_from_extras(
|
|
{"parent_trace_context": ParentTraceContext(parent_workflow_run_id="outer-run", parent_node_execution_id=None)}
|
|
)
|
|
|
|
assert nested == TraceCorrelation(
|
|
external_trace_id="caller-trace",
|
|
session_id="session-1",
|
|
parent_workflow_run_id="outer-run",
|
|
parent_node_execution_id="outer-node",
|
|
)
|
|
assert typed.parent_workflow_run_id == "outer-run"
|
|
assert correlation_from_extras({"external_trace_id": "", "parent_trace_context": {"x": 1}}) == TraceCorrelation(
|
|
external_trace_id=None, session_id=None, parent_workflow_run_id=None, parent_node_execution_id=None
|
|
)
|
|
|
|
|
|
def test_scopes_come_from_the_bound_scopes_with_the_raw_policy_and_correlation() -> None:
|
|
scopes = _Scopes(fails=False)
|
|
with _app(scopes, None).app_context():
|
|
scope = create_trace_scope(
|
|
tenant_id="tenant-1",
|
|
app_id="app-1",
|
|
app_tracing='{"enabled": true}',
|
|
user_id="u",
|
|
extras={"external_trace_id": "caller-trace"},
|
|
)
|
|
app_scope = create_trace_scope_for_app(tenant_id="tenant-1", app_id=None, user_id=None, extras={})
|
|
|
|
assert scope is not None
|
|
assert (scope.tenant_id, scope.app_id, scope.user_id) == ("tenant-1", "app-1", "u")
|
|
assert scopes.policies == ['{"enabled": true}']
|
|
assert scope.correlation.external_trace_id == "caller-trace"
|
|
assert app_scope is not None
|
|
assert app_scope.app_id is None
|
|
|
|
|
|
def test_scopes_fail_open() -> None:
|
|
assert create_trace_scope(tenant_id="tenant-1", app_id="app-1", app_tracing=None, user_id=None, extras={}) is None
|
|
with _app(_Scopes(fails=True), None).app_context():
|
|
assert (
|
|
create_trace_scope(tenant_id="tenant-1", app_id="app-1", app_tracing=None, user_id=None, extras={}) is None
|
|
)
|
|
assert create_trace_scope_for_app(tenant_id="tenant-1", app_id="app-1", user_id=None, extras={}) is None
|
|
with Flask(__name__).app_context():
|
|
assert create_trace_scope_for_app(tenant_id="tenant-1", app_id="app-1", user_id=None, extras={}) is None
|
|
|
|
|
|
def test_agent_hooks_are_the_ones_the_composition_root_bound() -> None:
|
|
hooks = _Hooks()
|
|
|
|
with _app(_Scopes(fails=False), hooks).app_context():
|
|
assert application_tracing_hooks() is hooks
|
|
with _app(_Scopes(fails=False), None).app_context():
|
|
assert application_tracing_hooks() is None
|
|
assert application_tracing_hooks() is None
|
|
|
|
|
|
def test_invalid_extension_bindings_fail_open() -> None:
|
|
app = Flask(__name__)
|
|
app.extensions["trace_scopes"] = object()
|
|
app.extensions["application_tracing_hooks"] = object()
|
|
|
|
with app.app_context():
|
|
assert (
|
|
create_trace_scope(tenant_id="tenant-1", app_id="app-1", app_tracing=None, user_id=None, extras={}) is None
|
|
)
|
|
assert create_trace_scope_for_app(tenant_id="tenant-1", app_id="app-1", user_id=None, extras={}) is None
|
|
assert application_tracing_hooks() is None
|