deepset-ai / haystack

:mag: AI orchestration framework to build customizable, production-ready LLM applications. Connect components (models, vector DBs, file converters) to pipelines or agents that can interact with your data. With advanced retrieval methods, it's best suited for building RAG, question answering, semantic search or conversational agent chatbots.
https://haystack.deepset.ai
Apache License 2.0
16.66k stars 1.83k forks source link

experimental: OpenAIFunctionCaller string parsing #8170

Open matheusfvesco opened 1 month ago

matheusfvesco commented 1 month ago

Describe the bug When using the OpenAIFunctionCaller from haystack experimental, if the return of the function includes special characters, these are not correctly parsed. This is important for some languages and also some models like gemma do enjoy using emojis.

Error message Example chat: Style: ChatMessage.role ChatMessage.content

From: ChatRole.USER
Qual o preço da taça de romã? Pesquise na base de dados, por favor

From: ChatRole.ASSISTANT
[{"id": "call_0rca", "function": {"arguments": "{\"query\":\"pre\\u00e7o da ta\\u00e7a de rom\\u00e3\"}", "name": "consulta_base_de_dados"}, "type": "function"}]

From: ChatRole.FUNCTION
{"reply": "O termo n\u00e3o resultou em nenhum resultado relevante"}

The model calls a RAG pipeline, but the return is incorrectly parsed.

Expected behavior

From: ChatRole.ASSISTANT
[{"id": "call_0rca", "function": {"arguments": "{\"query\":\"preço da taça de romã\"}", "name": "consulta_base_de_dados"}, "type": "function"}]

From: ChatRole.FUNCTION
{"reply": "O termo não resultou em nenhum resultado relevante"}

Additional context I believe the problem is related to the json.dumps inside the OpenAiFunctionCaller class.

                    function_to_call = self.available_functions[function_name]
                    try:
                        function_response = function_to_call(**function_args)
                        messages.append(
                            ChatMessage.from_function(
                                content=json.dumps(function_response),
                                name=function_name,
                            )
                        )

Example:

import json

strings = [
    '{"arguments": "çãõóòàáéêíôû"}',
    '{"arguments": "ßÜüöÖäÄëÉ"}',
    '{"arguments": "¡¿?¡!¿?!"}',
    '{"arguments": "😊👍"}',
]
for string in strings:
    input = json.loads(string)
    print("Input:")
    print(input)  # Output: {'arguments': 'ç\u00e3\u00f4\u00f3\u00f2\u00e0\u00e1'}
    print(input["arguments"])
    output = json.dumps(input)
    print("Output:")
    print(output)

    print()

Output:

Input:
{'arguments': 'çãõóòàáéêíôû'}
çãõóòàáéêíôû
Output:
{"arguments": "\u00e7\u00e3\u00f5\u00f3\u00f2\u00e0\u00e1\u00e9\u00ea\u00ed\u00f4\u00fb"}

Input:
{'arguments': 'ßÜüöÖäÄëÉ'}
ßÜüöÖäÄëÉ
Output:
{"arguments": "\u00df\u00dc\u00fc\u00f6\u00d6\u00e4\u00c4\u00eb\u00c9"}

Input:
{'arguments': '¡¿?¡!¿?!'}
¡¿?¡!¿?!
Output:
{"arguments": "\u00a1\u00bf?\u00a1!\u00bf?!"}

Input:
{'arguments': '😊👍'}
😊👍
Output:
{"arguments": "\ud83d\ude0a\ud83d\udc4d"}

To Reproduce Make any function that returns a string with special characters and use the OpenAiFunctionCaller to handle it, or use the snippet above to test how json.dumps handles special characters. I believe the same thing is happening in this class.

FAQ Check

System:

matheusfvesco commented 1 month ago

This might be relevant for #7674

matheusfvesco commented 1 month ago

Also noticed the current ChatMessage and ChatRole classes (and therefore OpenAiFunctionCaller too) use "function" as the role for returning the function calls, but apparently "function" was deprecated on the openai spec, and will probably not be accepted by some providers

see: https://github.com/ollama/ollama/issues/6213#issuecomment-2273952121

But i also see you guys are already updating it with the tool abstraction :)

vblagoje commented 1 week ago

@matheusfvesco thanks for these detailed reports. Would you perhaps be open taking on implementation of the fix for this issue?