Skip to content

Commit a6c9103

Browse files
vblagojesjrl
andauthored
feat: Simpler generation spans, use Haystack's to_openai_dict_format (#2044)
* DRY: remove custom to_openai_dict_format function and use Haystack's * Lint * Update integrations/langfuse/pyproject.toml Co-authored-by: Sebastian Husch Lee <[email protected]> --------- Co-authored-by: Sebastian Husch Lee <[email protected]>
1 parent 630e524 commit a6c9103

File tree

2 files changed

+3
-59
lines changed

2 files changed

+3
-59
lines changed

integrations/langfuse/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ classifiers = [
2222
"Programming Language :: Python :: Implementation :: CPython",
2323
"Programming Language :: Python :: Implementation :: PyPy",
2424
]
25-
dependencies = ["haystack-ai>=2.13.0", "langfuse>=2.9.0, <3.0.0"]
25+
dependencies = ["haystack-ai>=2.15.1", "langfuse>=2.9.0, <3.0.0"]
2626

2727
[project.urls]
2828
Documentation = "https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langfuse#readme"

integrations/langfuse/src/haystack_integrations/tracing/langfuse/tracer.py

Lines changed: 2 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
import contextlib
6-
import json
76
import os
87
from abc import ABC, abstractmethod
98
from collections import Counter
@@ -66,61 +65,6 @@
6665
tracing_context_var: ContextVar[Dict[Any, Any]] = ContextVar("tracing_context")
6766

6867

69-
def _to_openai_dict_format(chat_message: ChatMessage) -> Dict[str, Any]:
70-
"""
71-
Convert a ChatMessage to the dictionary format expected by OpenAI's chat completion API.
72-
73-
Note: We already have such a method in Haystack's ChatMessage class.
74-
However, the original method doesn't tolerate None values for ids of ToolCall and ToolCallResult.
75-
Some generators, like GoogleGenAIChatGenerator, return None values for ids of ToolCall and ToolCallResult.
76-
To seamlessly support these generators, we use this, Langfuse local, version of the method.
77-
78-
:param chat_message: The ChatMessage instance to convert.
79-
:return: Dictionary in OpenAI Chat API format.
80-
"""
81-
text_contents = chat_message.texts
82-
tool_calls = chat_message.tool_calls
83-
tool_call_results = chat_message.tool_call_results
84-
85-
if not text_contents and not tool_calls and not tool_call_results:
86-
message = "A `ChatMessage` must contain at least one `TextContent`, `ToolCall`, or `ToolCallResult`."
87-
logger.error(message)
88-
raise ValueError(message)
89-
if len(text_contents) + len(tool_call_results) > 1:
90-
message = "A `ChatMessage` can only contain one `TextContent` or one `ToolCallResult`."
91-
logger.error(message)
92-
raise ValueError(message)
93-
94-
openai_msg: Dict[str, Any] = {"role": chat_message._role.value}
95-
96-
# Add name field if present
97-
if chat_message._name is not None:
98-
openai_msg["name"] = chat_message._name
99-
100-
if tool_call_results:
101-
result = tool_call_results[0]
102-
openai_msg["content"] = result.result
103-
openai_msg["tool_call_id"] = result.origin.id
104-
# OpenAI does not provide a way to communicate errors in tool invocations, so we ignore the error field
105-
return openai_msg
106-
107-
if text_contents:
108-
openai_msg["content"] = text_contents[0]
109-
if tool_calls:
110-
openai_tool_calls = []
111-
for tc in tool_calls:
112-
openai_tool_calls.append(
113-
{
114-
"id": tc.id,
115-
"type": "function",
116-
# We disable ensure_ascii so special chars like emojis are not converted
117-
"function": {"name": tc.tool_name, "arguments": json.dumps(tc.arguments, ensure_ascii=False)},
118-
}
119-
)
120-
openai_msg["tool_calls"] = openai_tool_calls
121-
return openai_msg
122-
123-
12468
class LangfuseSpan(Span):
12569
"""
12670
Internal class representing a bridge between the Haystack span tracing API and Langfuse.
@@ -158,15 +102,15 @@ def set_content_tag(self, key: str, value: Any) -> None:
158102
return
159103
if key.endswith(".input"):
160104
if "messages" in value:
161-
messages = [_to_openai_dict_format(m) for m in value["messages"]]
105+
messages = [m.to_openai_dict_format(require_tool_call_ids=False) for m in value["messages"]]
162106
self._span.update(input=messages)
163107
else:
164108
coerced_value = tracing_utils.coerce_tag_value(value)
165109
self._span.update(input=coerced_value)
166110
elif key.endswith(".output"):
167111
if "replies" in value:
168112
if all(isinstance(r, ChatMessage) for r in value["replies"]):
169-
replies = [_to_openai_dict_format(m) for m in value["replies"]]
113+
replies = [m.to_openai_dict_format(require_tool_call_ids=False) for m in value["replies"]]
170114
else:
171115
replies = value["replies"]
172116
self._span.update(output=replies)

0 commit comments

Comments
 (0)