-
Notifications
You must be signed in to change notification settings - Fork 171
feat: Unify traces of sub-pipelines within pipelines with Langfuse #1624
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
b2c1cb6
6a36320
ca20ef2
97d4e82
8101333
c3f4027
ad84e5b
9d5649a
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 |
---|---|---|
@@ -0,0 +1,213 @@ | ||
# SPDX-FileCopyrightText: 2023-present deepset GmbH <[email protected]> | ||
# | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
import os | ||
|
||
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true" | ||
|
||
from unittest.mock import Mock | ||
|
||
from haystack import Pipeline | ||
from haystack.components.builders import ChatPromptBuilder | ||
from haystack.components.generators.chat import OpenAIChatGenerator | ||
from haystack.utils import Secret | ||
|
||
from haystack_integrations.components.connectors.langfuse import LangfuseConnector | ||
from haystack_integrations.tracing.langfuse import DefaultSpanHandler | ||
|
||
|
||
class CustomSpanHandler(DefaultSpanHandler): | ||
def handle(self, span, component_type=None): | ||
pass | ||
|
||
|
||
class TestLangfuseConnector: | ||
def test_run(self, monkeypatch): | ||
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "secret") | ||
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "public") | ||
|
||
langfuse_connector = LangfuseConnector( | ||
name="Chat example - OpenAI", | ||
public=True, | ||
secret_key=Secret.from_env_var("LANGFUSE_SECRET_KEY"), | ||
public_key=Secret.from_env_var("LANGFUSE_PUBLIC_KEY"), | ||
) | ||
|
||
mock_tracer = Mock() | ||
mock_tracer.get_trace_url.return_value = "https://example.com/trace" | ||
mock_tracer.get_trace_id.return_value = "12345" | ||
langfuse_connector.tracer = mock_tracer | ||
|
||
response = langfuse_connector.run(invocation_context={"some_key": "some_value"}) | ||
assert response["name"] == "Chat example - OpenAI" | ||
assert response["trace_url"] == "https://example.com/trace" | ||
assert response["trace_id"] == "12345" | ||
|
||
def test_to_dict(self, monkeypatch): | ||
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "secret") | ||
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "public") | ||
|
||
langfuse_connector = LangfuseConnector(name="Chat example - OpenAI") | ||
serialized = langfuse_connector.to_dict() | ||
|
||
assert serialized == { | ||
"type": "haystack_integrations.components.connectors.langfuse.langfuse_connector.LangfuseConnector", | ||
"init_parameters": { | ||
"name": "Chat example - OpenAI", | ||
"public": False, | ||
"secret_key": { | ||
"type": "env_var", | ||
"env_vars": ["LANGFUSE_SECRET_KEY"], | ||
"strict": True, | ||
}, | ||
"public_key": { | ||
"type": "env_var", | ||
"env_vars": ["LANGFUSE_PUBLIC_KEY"], | ||
"strict": True, | ||
}, | ||
"span_handler": None, | ||
}, | ||
} | ||
|
||
def test_to_dict_with_params(self, monkeypatch): | ||
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "secret") | ||
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "public") | ||
|
||
langfuse_connector = LangfuseConnector( | ||
name="Chat example - OpenAI", | ||
public=True, | ||
secret_key=Secret.from_env_var("LANGFUSE_SECRET_KEY"), | ||
public_key=Secret.from_env_var("LANGFUSE_PUBLIC_KEY"), | ||
span_handler=CustomSpanHandler(), | ||
) | ||
|
||
serialized = langfuse_connector.to_dict() | ||
assert serialized == { | ||
"type": "haystack_integrations.components.connectors.langfuse.langfuse_connector.LangfuseConnector", | ||
"init_parameters": { | ||
"name": "Chat example - OpenAI", | ||
"public": True, | ||
"secret_key": { | ||
"type": "env_var", | ||
"env_vars": ["LANGFUSE_SECRET_KEY"], | ||
"strict": True, | ||
}, | ||
"public_key": { | ||
"type": "env_var", | ||
"env_vars": ["LANGFUSE_PUBLIC_KEY"], | ||
"strict": True, | ||
}, | ||
"span_handler": { | ||
"type": "tests.test_langfuse_connector.CustomSpanHandler", | ||
"data": { | ||
"type": "tests.test_langfuse_connector.CustomSpanHandler", | ||
"init_parameters": {}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
def test_from_dict(self, monkeypatch): | ||
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "secret") | ||
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "public") | ||
|
||
data = { | ||
"type": "haystack_integrations.components.connectors.langfuse.langfuse_connector.LangfuseConnector", | ||
"init_parameters": { | ||
"name": "Chat example - OpenAI", | ||
"public": False, | ||
"secret_key": { | ||
"type": "env_var", | ||
"env_vars": ["LANGFUSE_SECRET_KEY"], | ||
"strict": True, | ||
}, | ||
"public_key": { | ||
"type": "env_var", | ||
"env_vars": ["LANGFUSE_PUBLIC_KEY"], | ||
"strict": True, | ||
}, | ||
"span_handler": None, | ||
}, | ||
} | ||
langfuse_connector = LangfuseConnector.from_dict(data) | ||
assert langfuse_connector.name == "Chat example - OpenAI" | ||
assert langfuse_connector.public is False | ||
assert langfuse_connector.secret_key == Secret.from_env_var("LANGFUSE_SECRET_KEY") | ||
assert langfuse_connector.public_key == Secret.from_env_var("LANGFUSE_PUBLIC_KEY") | ||
assert langfuse_connector.span_handler is None | ||
|
||
def test_from_dict_with_params(self, monkeypatch): | ||
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "secret") | ||
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "public") | ||
|
||
data = { | ||
"type": "haystack_integrations.components.connectors.langfuse.langfuse_connector.LangfuseConnector", | ||
"init_parameters": { | ||
"name": "Chat example - OpenAI", | ||
"public": True, | ||
"secret_key": { | ||
"type": "env_var", | ||
"env_vars": ["LANGFUSE_SECRET_KEY"], | ||
"strict": True, | ||
}, | ||
"public_key": { | ||
"type": "env_var", | ||
"env_vars": ["LANGFUSE_PUBLIC_KEY"], | ||
"strict": True, | ||
}, | ||
"span_handler": { | ||
"type": "tests.test_langfuse_connector.CustomSpanHandler", | ||
"data": { | ||
"type": "tests.test_langfuse_connector.CustomSpanHandler", | ||
"init_parameters": {}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
langfuse_connector = LangfuseConnector.from_dict(data) | ||
assert langfuse_connector.name == "Chat example - OpenAI" | ||
assert langfuse_connector.public is True | ||
assert langfuse_connector.secret_key == Secret.from_env_var("LANGFUSE_SECRET_KEY") | ||
assert langfuse_connector.public_key == Secret.from_env_var("LANGFUSE_PUBLIC_KEY") | ||
assert isinstance(langfuse_connector.span_handler, CustomSpanHandler) | ||
|
||
def test_pipeline_serialization(self, monkeypatch): | ||
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. This was moved from the |
||
# Set test env vars | ||
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "secret") | ||
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "public") | ||
monkeypatch.setenv("OPENAI_API_KEY", "openai_api_key") | ||
|
||
# Create pipeline with OpenAI LLM | ||
pipe = Pipeline() | ||
pipe.add_component( | ||
"tracer", | ||
LangfuseConnector( | ||
name="Chat example - OpenAI", | ||
public=True, | ||
secret_key=Secret.from_env_var("LANGFUSE_SECRET_KEY"), | ||
public_key=Secret.from_env_var("LANGFUSE_PUBLIC_KEY"), | ||
), | ||
) | ||
pipe.add_component("prompt_builder", ChatPromptBuilder()) | ||
pipe.add_component("llm", OpenAIChatGenerator()) | ||
pipe.connect("prompt_builder.prompt", "llm.messages") | ||
|
||
# Serialize | ||
serialized = pipe.to_dict() | ||
|
||
# Check serialized secrets | ||
tracer_params = serialized["components"]["tracer"]["init_parameters"] | ||
assert isinstance(tracer_params["secret_key"], dict) | ||
assert tracer_params["secret_key"]["type"] == "env_var" | ||
assert tracer_params["secret_key"]["env_vars"] == ["LANGFUSE_SECRET_KEY"] | ||
assert isinstance(tracer_params["public_key"], dict) | ||
assert tracer_params["public_key"]["type"] == "env_var" | ||
assert tracer_params["public_key"]["env_vars"] == ["LANGFUSE_PUBLIC_KEY"] | ||
|
||
# Deserialize | ||
new_pipe = Pipeline.from_dict(serialized) | ||
|
||
# Verify pipeline is the same | ||
assert new_pipe == pipe | ||
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. Great to see that you've introduced multiple test cases. Maybe in future we can cover some edge cases 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. Good point! I think that is something we can do in the future. |
This file was deleted.
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.
This line is what fixed the issue