|
1 | 1 | import logging |
2 | 2 | from pprint import pformat |
| 3 | +from typing import Generator |
3 | 4 |
|
4 | 5 | from hayhooks import BasePipelineWrapper |
5 | 6 | from haystack import Pipeline |
6 | 7 | from haystack.components.builders import ChatPromptBuilder |
7 | | -from openinference.instrumentation import _tracers, using_attributes, using_metadata |
8 | | -from opentelemetry.trace.status import Status, StatusCode |
9 | 8 | from pydantic import BaseModel |
10 | 9 |
|
11 | 10 | from src.app_config import config |
12 | | -from src.common import haystack_utils, phoenix_utils |
| 11 | +from src.common import haystack_utils |
13 | 12 | from src.common.components import ( |
14 | 13 | LlmOutputValidator, |
15 | 14 | OpenAIWebSearchGenerator, |
|
19 | 18 | from src.pipelines.generate_referrals.pipeline_wrapper import Resource |
20 | 19 |
|
21 | 20 | logger = logging.getLogger(__name__) |
22 | | -tracer = phoenix_utils.tracer_provider.get_tracer(__name__) |
23 | 21 |
|
24 | 22 |
|
25 | 23 | class ActionPlan(BaseModel): |
@@ -67,51 +65,94 @@ def setup(self) -> None: |
67 | 65 | pipeline.connect("llm", "logger") |
68 | 66 |
|
69 | 67 | self.pipeline = pipeline |
| 68 | + self.runner = haystack_utils.TracedPipelineRunner(self.name, self.pipeline) |
70 | 69 |
|
71 | 70 | # Called for the `generate-action-plan/run` endpoint |
72 | 71 | def run_api( |
73 | 72 | self, resources: list[Resource] | list[dict], user_email: str, user_query: str |
74 | 73 | ) -> dict: |
75 | 74 | resource_objects = get_resources(resources) |
76 | | - |
77 | | - with using_attributes(user_id=user_email), using_metadata({"user_id": user_email}): |
78 | | - # Must set using_metadata context before calling tracer.start_as_current_span() |
79 | | - assert isinstance(tracer, _tracers.OITracer), f"Got unexpected {type(tracer)}" |
80 | | - with tracer.start_as_current_span( # pylint: disable=not-context-manager,unexpected-keyword-arg |
81 | | - self.name, openinference_span_kind="chain" |
82 | | - ) as span: |
83 | | - result = self._run(resource_objects, user_email, user_query) |
84 | | - span.set_input([r.name for r in resource_objects]) |
85 | | - span.set_output(result["response"]) |
86 | | - span.set_status(Status(StatusCode.OK)) |
87 | | - return result |
88 | | - |
89 | | - def _run(self, resource_objects: list[Resource], user_email: str, user_query: str) -> dict: |
90 | | - response = self.pipeline.run( |
91 | | - { |
92 | | - "logger": { |
93 | | - "messages_list": [ |
94 | | - {"resource_count": len(resource_objects), "user_email": user_email} |
95 | | - ], |
96 | | - }, |
97 | | - "prompt_builder": { |
98 | | - "resources": format_resources(resource_objects), |
99 | | - "action_plan_json": action_plan_as_json, |
100 | | - "user_query": user_query, |
101 | | - }, |
102 | | - "llm": { |
103 | | - "model": config.generate_action_plan_model_version, |
104 | | - "reasoning_effort": config.generate_action_plan_reasoning_level, |
105 | | - }, |
106 | | - }, |
| 75 | + pipeline_run_args = self.create_pipeline_args( |
| 76 | + user_email, |
| 77 | + resource_objects, |
| 78 | + user_query, |
| 79 | + ) |
| 80 | + response = self.runner.return_response( |
| 81 | + pipeline_run_args, |
| 82 | + user_id=user_email, |
| 83 | + metadata={"user_id": user_email}, |
107 | 84 | include_outputs_from={"llm", "save_result"}, |
| 85 | + input_=[r.name for r in resource_objects], |
| 86 | + extract_output=lambda response: response["llm"]["replies"][0]._content[0].text, |
108 | 87 | ) |
109 | 88 | logger.debug("Results: %s", pformat(response, width=160)) |
| 89 | + |
110 | 90 | return { |
111 | 91 | "response": response["llm"]["replies"][0]._content[0].text, |
112 | 92 | "save_result": response["save_result"], |
113 | 93 | } |
114 | 94 |
|
| 95 | + def create_pipeline_args( |
| 96 | + self, |
| 97 | + user_email: str, |
| 98 | + resource_objects: list[Resource], |
| 99 | + user_query: str, |
| 100 | + *, |
| 101 | + llm_model: str | None = None, |
| 102 | + reasoning_effort: str | None = None, |
| 103 | + streaming: bool = False, |
| 104 | + ) -> dict: |
| 105 | + return { |
| 106 | + "logger": { |
| 107 | + "messages_list": [ |
| 108 | + {"resource_count": len(resource_objects), "user_email": user_email} |
| 109 | + ], |
| 110 | + }, |
| 111 | + "prompt_builder": { |
| 112 | + "resources": format_resources(resource_objects), |
| 113 | + "action_plan_json": action_plan_as_json, |
| 114 | + "user_query": user_query, |
| 115 | + }, |
| 116 | + "llm": { |
| 117 | + "model": llm_model or config.generate_action_plan_model_version, |
| 118 | + "reasoning_effort": reasoning_effort or config.generate_action_plan_reasoning_level, |
| 119 | + "streaming": streaming, |
| 120 | + }, |
| 121 | + } |
| 122 | + |
| 123 | + # https://docs.haystack.deepset.ai/docs/hayhooks#openai-compatibility |
| 124 | + # Called for the `{pipeline_name}/chat`, `/chat/completions`, or `/v1/chat/completions` streaming endpoint using Server-Sent Events (SSE) |
| 125 | + def run_chat_completion(self, model: str, messages: list, body: dict) -> Generator: |
| 126 | + # Note: 'model' parameter is the pipeline name, not the LLM model |
| 127 | + assert model == self.name, f"Unexpected model/pipeline name: {model}" |
| 128 | + |
| 129 | + # Extract custom parameters from the body |
| 130 | + resources = body.get("resources", []) |
| 131 | + user_email = body.get("user_email", "") |
| 132 | + user_query = body.get("user_query", "") |
| 133 | + |
| 134 | + if not resources: |
| 135 | + raise ValueError("resources parameter is required") |
| 136 | + if not user_email: |
| 137 | + raise ValueError("user_email parameter is required") |
| 138 | + |
| 139 | + resource_objects = get_resources(resources) |
| 140 | + pipeline_run_args = self.create_pipeline_args( |
| 141 | + user_email, |
| 142 | + resource_objects, |
| 143 | + user_query, |
| 144 | + llm_model=body.get("llm_model", None), |
| 145 | + reasoning_effort=body.get("reasoning_effort", None), |
| 146 | + streaming=True, |
| 147 | + ) |
| 148 | + logger.info("Streaming action plan: %s", pipeline_run_args) |
| 149 | + return self.runner.stream_response( |
| 150 | + pipeline_run_args, |
| 151 | + user_id=user_email, |
| 152 | + metadata={"user_id": user_email}, |
| 153 | + input_=[r.name for r in resource_objects], |
| 154 | + ) |
| 155 | + |
115 | 156 |
|
116 | 157 | def get_resources(resources: list[Resource] | list[dict]) -> list[Resource]: |
117 | 158 | """Ensure we have a list of Resource objects.""" |
|
0 commit comments