|
3 | 3 | # SPDX-License-Identifier: Apache-2.0
|
4 | 4 |
|
5 | 5 | import contextlib
|
6 |
| -import json |
7 | 6 | import os
|
8 | 7 | from abc import ABC, abstractmethod
|
9 | 8 | from collections import Counter
|
|
66 | 65 | tracing_context_var: ContextVar[Dict[Any, Any]] = ContextVar("tracing_context")
|
67 | 66 |
|
68 | 67 |
|
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 |
| - |
124 | 68 | class LangfuseSpan(Span):
|
125 | 69 | """
|
126 | 70 | 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:
|
158 | 102 | return
|
159 | 103 | if key.endswith(".input"):
|
160 | 104 | 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"]] |
162 | 106 | self._span.update(input=messages)
|
163 | 107 | else:
|
164 | 108 | coerced_value = tracing_utils.coerce_tag_value(value)
|
165 | 109 | self._span.update(input=coerced_value)
|
166 | 110 | elif key.endswith(".output"):
|
167 | 111 | if "replies" in value:
|
168 | 112 | 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"]] |
170 | 114 | else:
|
171 | 115 | replies = value["replies"]
|
172 | 116 | self._span.update(output=replies)
|
|
0 commit comments