Skip to content
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

feat(llmobs): support in-code config for llmobs #9172

Merged
merged 48 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
1e23c2b
support minimal env var conifg for llmobs
May 6, 2024
2332e99
expose explicit integration
May 7, 2024
a46041f
hide patch methods
May 7, 2024
5bd9768
flip apm_enabled default to true
May 7, 2024
3022d19
comments & renaming
May 7, 2024
adb4957
rev test change
May 7, 2024
80699a3
lint
May 8, 2024
655bed5
make sure config values r updated
May 8, 2024
524dd79
grab dd site and api key from env
May 9, 2024
6688219
fix getenv
May 10, 2024
d0cbb04
update prioriryt of env vs args
May 10, 2024
659d2e0
dd_apm_enabled shouldn't override env vars
May 13, 2024
55eb22b
fix tests
May 13, 2024
6afa4cf
llmobs no apm arg
May 13, 2024
2142be6
fix prio for env and service
May 13, 2024
63994f7
remove toggling stuff
May 13, 2024
ef9b21f
no need to toggle
May 13, 2024
6c33acc
set minimal env vars
May 13, 2024
1342865
Update ddtrace/llmobs/_llmobs.py
lievan May 14, 2024
368bf40
Update ddtrace/llmobs/_llmobs.py
lievan May 14, 2024
381fd37
args > config, simplify patching logic
May 14, 2024
6c42a15
update comment, del integration constants
May 14, 2024
07becc9
fix _llmobs_pc_sampler missing err
May 14, 2024
735d9c2
try catch patch all integrations
May 14, 2024
59c163b
Merge branch 'main' into evan.li/in-app-config
Yun-Kim May 14, 2024
c7c4305
refactor
Yun-Kim May 14, 2024
1573d62
remove LLMObs enabling from integration patch code
Yun-Kim May 14, 2024
d486ad8
disable llmobs
May 15, 2024
4418c73
Merge branch 'main' into evan.li/in-app-config
lievan May 15, 2024
492c09f
disable llmobs after bedrock tests
May 15, 2024
d21b6fe
Merge branch 'evan.li/in-app-config' of github.com:DataDog/dd-trace-p…
May 15, 2024
9ab61e5
disable llmobs after mock llmobs writer
May 15, 2024
39b236e
need to explicitly check for None
May 15, 2024
e981988
Merge branch 'main' into evan.li/in-app-config
lievan May 15, 2024
e50c11d
Merge branch 'evan.li/in-app-config' of github.com:DataDog/dd-trace-p…
May 15, 2024
3a9e810
don't uneessarily patch other integratins
May 15, 2024
db8465a
Merge branch 'main' into evan.li/in-app-config
lievan May 15, 2024
26606af
two more enable fixes
May 15, 2024
9f9adbc
Merge branch 'main' into evan.li/in-app-config
lievan May 16, 2024
af75319
Update ddtrace/llmobs/_llmobs.py
lievan May 16, 2024
45179ad
change arg names, make tracer private
May 16, 2024
d6b35e8
fix test, update comments
May 16, 2024
06963cb
Merge branch 'main' into evan.li/in-app-config
lievan May 17, 2024
c472f95
llmobs agentless enabled
May 17, 2024
90c3ba3
Merge branch 'evan.li/in-app-config' of github.com:DataDog/dd-trace-p…
May 17, 2024
d411fdc
debug log
May 17, 2024
fe73fb6
agentless enabled
May 17, 2024
edb3cd4
Merge branch 'main' into evan.li/in-app-config
Yun-Kim May 17, 2024
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
95 changes: 94 additions & 1 deletion ddtrace/llmobs/_llmobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import ddtrace
from ddtrace import Span
from ddtrace import config
from ddtrace import patch
from ddtrace.ext import SpanTypes
from ddtrace.internal import atexit
from ddtrace.internal.logger import get_logger
Expand Down Expand Up @@ -44,6 +45,25 @@ class LLMObs(Service):
_instance = None
enabled = False

# Support llmobs python integrations
langchain = "langchain"
openai = "openai"
botocore = "botocore"

# APM enabled env var config:
_apm_env_config = {
"DD_LLMOBS_NO_APM": "0",
}

# APM disabled env var config:
_no_apm_env_config = {
"DD_INSTRUMENTATION_TELEMETRY_ENABLED": "0",
"DD_REMOTE_CONFIGURATION_ENABLED": "0",
"DD_OPENAI_METRICS_ENABLED": "0",
"DD_LANGCHAIN_METRICS_ENABLED": "0",
"DD_LLMOBS_NO_APM": "1",
}

def __init__(self, tracer=None):
super(LLMObs, self).__init__()
self.tracer = tracer or ddtrace.tracer
Expand Down Expand Up @@ -75,30 +95,103 @@ def _stop_service(self) -> None:
log.warning("Failed to shutdown tracer", exc_info=True)

@classmethod
def enable(cls, tracer=None):
def enable(
cls,
ml_app: Optional[str] = None,
integrations: Optional[list[str]] = None,
apm_enabled=False,
dd_env: Optional[str] = None,
dd_service: Optional[str] = None,
dd_site: Optional[str] = None,
dd_api_key: Optional[str] = None,
tracer=None,
):
"""
Enable LLM Observability tracing.

:param str ml_app: The name of your ml application.
:param list[str] integrations: A list of integrations to enable auto-tracing for.
:param str dd_site Your datadog site.
:param str dd_api_key: Your datadog api key.
:param str dd_apm_enabled: Whether Datadog Application Performance Monitoring is enabled.
"""
if cls._instance is not None:
log.debug("%s already enabled", cls.__name__)
return

if dd_api_key:
config._dd_api_key = dd_api_key

if not config._dd_api_key:
cls.enabled = False
Yun-Kim marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError(
"DD_API_KEY is required for sending LLMObs data. "
"Ensure this configuration is set before running your application."
)

# update environment config based on APM enabled/disabled
os.environ.update(cls._apm_env_config if apm_enabled else cls._no_apm_env_config)

if ml_app:
config._llmobs_ml_app = ml_app
if dd_site:
config._dd_site = dd_site
if dd_api_key:
config._dd_api_key = dd_api_key
if dd_env:
config.env = dd_env
if dd_service:
config.service = dd_service

if not config._llmobs_ml_app:
cls.enabled = False
raise ValueError(
"DD_LLMOBS_APP_NAME is required for sending LLMObs data. "
"Ensure this configuration is set before running your application."
)

# enable LLMObs integations
llmobs_integrations = {
LLMObs.langchain: lambda: LLMObs._patch_langchain(),
LLMObs.openai: lambda: LLMObs._patch_openai(),
LLMObs.botocore: lambda: LLMObs._patch_bedrock(),
}
if integrations:
for integration in integrations:
if integration in llmobs_integrations:
lievan marked this conversation as resolved.
Show resolved Hide resolved
llmobs_integrations[integration]()
lievan marked this conversation as resolved.
Show resolved Hide resolved
else:
log.warning(
"%s is unsupported - LLMObs currently supports %s", integration, str(llmobs_integrations)
lievan marked this conversation as resolved.
Show resolved Hide resolved
)

cls._instance = cls(tracer=tracer)
cls.enabled = True
cls._instance.start()
atexit.register(cls.disable)
log.debug("%s enabled", cls.__name__)

@classmethod
lievan marked this conversation as resolved.
Show resolved Hide resolved
def _patch_langchain(cls) -> None:
if cls._instance is None:
log.warning("%s not enabled, cannot patch langchain", cls.__name__)
return
patch(langchain=True)

@classmethod
def _patch_openai(cls) -> None:
if cls._instance is None:
log.warning("%s not enabled, cannot patch openai", cls.__name__)
return
patch(openai=True)

@classmethod
def _patch_bedrock(cls) -> None:
if cls._instance is None:
log.warning("%s not enabled, cannot patch bedrock", cls.__name__)
return
patch(bedrock=True)

@classmethod
def disable(cls) -> None:
if cls._instance is None:
Expand Down
2 changes: 1 addition & 1 deletion tests/llmobs/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ def LLMObs(mock_llmobs_span_writer, mock_llmobs_eval_metric_writer, ddtrace_glob
global_config.update(ddtrace_global_config)
with override_global_config(global_config):
dummy_tracer = DummyTracer()
llmobs_service.enable(tracer=dummy_tracer)
llmobs_service.enable(apm_enabled=True, tracer=dummy_tracer)
yield llmobs_service
llmobs_service.disable()
28 changes: 28 additions & 0 deletions tests/llmobs/test_llmobs_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import mock
import pytest

from ddtrace._trace.span import Span
from ddtrace.ext import SpanTypes
from ddtrace.llmobs import LLMObs as llmobs_service
from ddtrace.llmobs._constants import INPUT_DOCUMENTS
from ddtrace.llmobs._constants import INPUT_MESSAGES
Expand Down Expand Up @@ -36,6 +38,16 @@ def mock_logs():
yield mock_logs


def run_llmobs_trace_filter(dummy_tracer):
for trace_filter in dummy_tracer._filters:
if isinstance(trace_filter, LLMObsTraceProcessor):
root_llm_span = Span(name="span1", span_type=SpanTypes.LLM)
root_llm_span.set_tag_str(SPAN_KIND, "llm")
trace1 = [root_llm_span]
return trace_filter.process_trace(trace1)
raise ValueError("LLMObsTraceProcessor not found in tracer filters.")


def test_service_enable():
with override_global_config(dict(_dd_api_key="<not-a-real-api-key>", _llmobs_ml_app="<ml-app-name>")):
dummy_tracer = DummyTracer()
Expand All @@ -45,6 +57,22 @@ def test_service_enable():
assert llmobs_service.enabled
assert llmobs_instance.tracer == dummy_tracer
assert any(isinstance(tracer_filter, LLMObsTraceProcessor) for tracer_filter in dummy_tracer._filters)
assert run_llmobs_trace_filter(dummy_tracer) is None

llmobs_service.disable()


def test_service_enable_with_apm():
with override_global_config(dict(_dd_api_key="<not-a-real-api-key>", _llmobs_ml_app="<ml-app-name>")):
dummy_tracer = DummyTracer()
llmobs_service.enable(tracer=dummy_tracer, apm_enabled=True)
llmobs_instance = llmobs_service._instance
assert llmobs_instance is not None
assert llmobs_service.enabled
assert llmobs_instance.tracer == dummy_tracer
assert any(isinstance(tracer_filter, LLMObsTraceProcessor) for tracer_filter in dummy_tracer._filters)
assert run_llmobs_trace_filter(dummy_tracer) is not None

llmobs_service.disable()


Expand Down
7 changes: 4 additions & 3 deletions tests/llmobs/test_llmobs_trace_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ def mock_logs():
yield mock_logs


def test_processor_returns_all_traces_by_default(monkeypatch):
"""Test that the LLMObsTraceProcessor returns all traces by default."""
def test_processor_does_not_submit_apm_traces_if_no_apm_env_var_is_true(monkeypatch):
"""Test that the LLMObsTraceProcessor does not submit APM traces by default."""
monkeypatch.setenv("DD_LLMOBS_NO_APM", "1")
trace_filter = LLMObsTraceProcessor(llmobs_span_writer=mock.MagicMock())
root_llm_span = Span(name="span1", span_type=SpanTypes.LLM)
root_llm_span.set_tag_str(SPAN_KIND, "llm")
trace1 = [root_llm_span]
assert trace_filter.process_trace(trace1) == trace1
assert trace_filter.process_trace(trace1) is None


def test_processor_returns_all_traces_if_no_apm_env_var_is_false(monkeypatch):
Expand Down
Loading