Skip to content

Commit 6f246b6

Browse files
authored
feat: Add optional suffix to parent_span_name (#148)
1 parent 2966853 commit 6f246b6

3 files changed

Lines changed: 52 additions & 45 deletions

File tree

app/src/common/haystack_utils.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
from typing import Any, Callable, Generator, Sequence
33

44
import hayhooks
5+
from fastapi import HTTPException
56
from haystack import Pipeline
7+
from haystack.core.errors import PipelineRuntimeError
68
from haystack.dataclasses.chat_message import ChatMessage
79
from openinference.instrumentation import using_attributes
810
from opentelemetry.trace import Span
@@ -81,11 +83,15 @@ def stream_response(
8183
metadata: dict[str, Any],
8284
input_: Any | None = None,
8385
shorten_output: Callable[[str], str] = lambda resp: resp,
86+
parent_span_name_suffix: str | None = None,
8487
) -> Generator:
8588
# Must set using attributes and metadata tracer context before calling tracer.start_as_current_span()
8689
with using_attributes(user_id=user_id, metadata=metadata):
8790
with phoenix_utils.tracer().start_as_current_span( # pylint: disable=not-context-manager,unexpected-keyword-arg
88-
self.parent_span_name, openinference_span_kind="chain"
91+
f"{self.parent_span_name}--{parent_span_name_suffix}"
92+
if parent_span_name_suffix
93+
else self.parent_span_name,
94+
openinference_span_kind="chain",
8995
) as span:
9096
assert isinstance(span, Span), f"Got unexpected {type(span)}"
9197
try:
@@ -114,11 +120,16 @@ def stream_response(
114120
response_text = "".join(full_response) if full_response else ""
115121
span.set_output(shorten_output(response_text))
116122
span.set_status(Status(StatusCode.OK))
123+
except PipelineRuntimeError as e:
124+
logger.error("PipelineRuntimeError: %s", e, exc_info=True)
125+
span.set_status(Status(StatusCode.ERROR, str(e)))
126+
span.record_exception(e)
127+
raise HTTPException(status_code=500, detail=str(e)) from e
117128
except Exception as e:
118129
logger.error("Error during streaming: %s", e, exc_info=True)
119130
span.set_status(Status(StatusCode.ERROR, str(e)))
120131
span.record_exception(e)
121-
raise
132+
raise HTTPException(status_code=500, detail=str(e)) from e
122133

123134
def return_response(
124135
self,
@@ -128,12 +139,16 @@ def return_response(
128139
metadata: dict[str, Any],
129140
input_: Any | None = None,
130141
include_outputs_from: set[str] | None = None,
131-
extract_output: Callable[[Any], Any] = lambda resp: resp,
142+
extract_output: Callable[[dict], Any] = lambda resp: resp,
143+
parent_span_name_suffix: str | None = None,
132144
) -> dict:
133145
# Must set using_metadata context before calling tracer.start_as_current_span()
134146
with using_attributes(user_id=user_id, metadata=metadata):
135147
with phoenix_utils.tracer().start_as_current_span( # pylint: disable=not-context-manager,unexpected-keyword-arg
136-
self.parent_span_name, openinference_span_kind="chain"
148+
f"{self.parent_span_name}--{parent_span_name_suffix}"
149+
if parent_span_name_suffix
150+
else self.parent_span_name,
151+
openinference_span_kind="chain",
137152
) as span:
138153
try:
139154
result = self.pipeline.run(
@@ -145,8 +160,13 @@ def return_response(
145160
span.set_output(extract_output(result))
146161
span.set_status(Status(StatusCode.OK))
147162
return result
163+
except PipelineRuntimeError as e:
164+
logger.error("PipelineRuntimeError: %s", e, exc_info=True)
165+
span.set_status(Status(StatusCode.ERROR, str(e)))
166+
span.record_exception(e)
167+
raise HTTPException(status_code=500, detail=str(e)) from e
148168
except Exception as e:
149169
logger.error("Error during pipeline run: %s", e, exc_info=True)
150170
span.set_status(Status(StatusCode.ERROR, str(e)))
151171
span.record_exception(e)
152-
raise
172+
raise HTTPException(status_code=500, detail=f"Internal error: {str(e)}") from e

app/src/pipelines/generate_referrals/pipeline_wrapper.py

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@
99
from hayhooks import BasePipelineWrapper
1010
from haystack import Pipeline
1111
from haystack.components.builders import ChatPromptBuilder
12-
from haystack.core.errors import PipelineRuntimeError
1312
from haystack.dataclasses.chat_message import ChatMessage
14-
from openinference.instrumentation import _tracers, using_attributes, using_metadata
15-
from opentelemetry.trace.status import Status, StatusCode
1613
from pydantic import BaseModel
1714

1815
from src.app_config import config
@@ -63,6 +60,10 @@ class PipelineWrapper(BasePipelineWrapper):
6360
name = "generate_referrals"
6461

6562
def setup(self) -> None:
63+
self.pipeline = self._create_pipeline()
64+
self.runner = haystack_utils.TracedPipelineRunner(self.name, self.pipeline)
65+
66+
def _create_pipeline(self) -> Pipeline:
6667
# Do not rely on max_runs_per_component strictly, i.e., a component may run max_runs_per_component+1 times.
6768
# The component_visits counter for max_runs_per_component is reset with each call to pipeline.run()
6869
pipeline = Pipeline(max_runs_per_component=3)
@@ -96,31 +97,11 @@ def setup(self) -> None:
9697

9798
pipeline.add_component("logger", components.ReadableLogger())
9899
pipeline.connect("output_validator.valid_replies", "logger")
99-
100-
self.pipeline = pipeline
100+
return pipeline
101101

102102
# Called for the `generate-referrals/run` endpoint
103103
def run_api(
104104
self, query: str, user_email: str, prompt_version_id: str = "", suffix: str = ""
105-
) -> dict:
106-
with using_attributes(user_id=user_email), using_metadata({"user_id": user_email}):
107-
# Must set using_metadata context before calling tracer.start_as_current_span()
108-
assert isinstance(tracer, _tracers.OITracer), f"Got unexpected {type(tracer)}"
109-
with tracer.start_as_current_span( # pylint: disable=not-context-manager,unexpected-keyword-arg
110-
self.name, openinference_span_kind="chain"
111-
) as span:
112-
result = self._run(query, user_email, prompt_version_id, suffix)
113-
span.set_input(query)
114-
try:
115-
resp_obj = json.loads(result["llm"]["replies"][-1].text)
116-
span.set_output([r["name"] for r in resp_obj["resources"]])
117-
except (KeyError, IndexError):
118-
span.set_output(result["llm"]["replies"][-1].text)
119-
span.set_status(Status(StatusCode.OK))
120-
return result
121-
122-
def _run(
123-
self, query: str, user_email: str, prompt_version_id: str = "", suffix: str = ""
124105
) -> dict:
125106
# Retrieve the requested prompt (with optional prompt_version_id and/or suffix)
126107
try:
@@ -132,20 +113,26 @@ def _run(
132113
status_code=422,
133114
detail=f"The requested prompt version '{prompt_version_id}' with suffix '{suffix}' could not be retrieved due to HTTP status {he.response.status_code}",
134115
) from he
135-
136-
try:
137-
response = self.pipeline.run(
138-
self._run_arg_data(query, user_email, prompt_template),
139-
include_outputs_from={"llm", "save_result"},
140-
)
141-
logger.debug("Results: %s", pformat(response, width=160))
142-
return response
143-
except PipelineRuntimeError as re:
144-
logger.error("PipelineRuntimeError: %s", re, exc_info=True)
145-
raise HTTPException(status_code=500, detail=str(re)) from re
146-
except Exception as e:
147-
logger.error("Error %s: %s", type(e), e, exc_info=True)
148-
raise HTTPException(status_code=500, detail=f"Internal error: {str(e)}") from e
116+
pipeline_run_args = self._run_arg_data(query, user_email, prompt_template)
117+
118+
def extract_output(result: dict) -> list | str:
119+
try:
120+
resp_obj = json.loads(result["llm"]["replies"][-1].text)
121+
return [r["name"] for r in resp_obj["resources"]]
122+
except (KeyError, IndexError):
123+
return result["llm"]["replies"][-1].text
124+
125+
response = self.runner.return_response(
126+
pipeline_run_args,
127+
user_id=user_email,
128+
metadata={"user_id": user_email},
129+
include_outputs_from={"llm", "save_result"},
130+
input_=query,
131+
extract_output=extract_output,
132+
parent_span_name_suffix=suffix,
133+
)
134+
logger.debug("Results: %s", pformat(response, width=160))
135+
return response
149136

150137
def _run_arg_data(
151138
self, query: str, user_email: str, prompt_template: list[ChatMessage]

app/src/pipelines/generate_referrals_rag/pipeline_wrapper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
class PipelineWrapper(GenerateReferralsPipelineWrapper):
2222
name = "generate_referrals_rag"
2323

24-
def setup(self) -> None:
24+
def _create_pipeline(self) -> Pipeline:
2525
pipeline = Pipeline(max_runs_per_component=3)
2626

2727
# Replace LoadSupports() with retrieval from vector DB
@@ -75,7 +75,7 @@ def setup(self) -> None:
7575
pipeline.connect("output_validator.valid_replies", "logger")
7676

7777
# pipeline.draw(path="generate_referrals_rag.png")
78-
self.pipeline = pipeline
78+
return pipeline
7979

8080
def _run_arg_data(
8181
self, query: str, user_email: str, prompt_template: list[ChatMessage]

0 commit comments

Comments
 (0)