microsoft / semantic-kernel

Integrate cutting-edge LLM technology quickly and easily into your apps
https://aka.ms/semantic-kernel
MIT License
21.84k stars 3.25k forks source link

Python: Function calling breaks ChatHistory.serialize #7903

Open mlindemu opened 2 months ago

mlindemu commented 2 months ago

Describe the bug Using the new FunctionChoiceBehavior.Auto() class for my execution_settings with GPT-4o. Works great! However, an instance of ChatHistory.serialize() method raises an exception semantic_kernel.exceptions.content_exceptions.ContentSerializationError: Unable to serialize ChatHistory to JSON: Error serializing to JSON: TypeError: 'MockValSer' object cannot be converted to 'SchemaSerializer'

Parsing through each message in the ChatHistory, it looks like it can't serialize any message that relates to calling a Function. Example message extracted from the Debugger:

{'message': StreamingChatMessageContent(choice_index=0, inner_content=[ChatCompletionChunk(id='chatcmpl-9tGtMB4gmtRdR9W9B4xc4rp8Xiexq', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, role='assistant', tool_calls=[ChoiceDeltaToolCall(index=0, id='call_nVgttAq1pU4Tz3zRFEf5kaln', function=ChoiceDeltaToolCallFunction(arguments='', name='hr_policy_plugin-search_hr_policies'), type='function')]), finish_reason=None, index=0, logprobs=None, content_filter_results={})], created=1722960000, model='gpt-4o-2024-05-13', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_abc28019ad', usage=None), ChatCompletionChunk(id='chatcmpl-9tGtMB4gmtRdR9W9B4xc4rp8Xiexq', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='{"', name=None), type=None)]), finish_reason=None, index=0, logprobs=None, content_filter_results={})], created=1722960000, model='gpt-4o-2024-05-13', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_abc28019ad', usage=None), ChatCompletionChunk(id='chatcmpl-9tGtMB4gmtRdR9W9B4xc4rp8Xiexq', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='query', name=None), type=None)]), finish_reason=None, index=0, logprobs=None, content_filter_results={})], created=1722960000, model='gpt-4o-2024-05-13', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_abc28019ad', usage=None), ChatCompletionChunk(id='chatcmpl-9tGtMB4gmtRdR9W9B4xc4rp8Xiexq', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='":"', name=None), type=None)]), finish_reason=None, index=0, logprobs=None, content_filter_results={})], created=1722960000, model='gpt-4o-2024-05-13', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_abc28019ad', usage=None), ChatCompletionChunk(id='chatcmpl-9tGtMB4gmtRdR9W9B4xc4rp8Xiexq', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='short', name=None), type=None)]), finish_reason=None, index=0, logprobs=None, content_filter_results={})], created=1722960000, model='gpt-4o-2024-05-13', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_abc28019ad', usage=None), ChatCompletionChunk(id='chatcmpl-9tGtMB4gmtRdR9W9B4xc4rp8Xiexq', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='s', name=None), type=None)]), finish_reason=None, index=0, logprobs=None, content_filter_results={})], created=1722960000, model='gpt-4o-2024-05-13', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_abc28019ad', usage=None), ChatCompletionChunk(id='chatcmpl-9tGtMB4gmtRdR9W9B4xc4rp8Xiexq', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments=' dress', name=None), type=None)]), finish_reason=None, index=0, logprobs=None, content_filter_results={})], created=1722960000, model='gpt-4o-2024-05-13', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_abc28019ad', usage=None), ChatCompletionChunk(id='chatcmpl-9tGtMB4gmtRdR9W9B4xc4rp8Xiexq', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments=' code', name=None), type=None)]), finish_reason=None, index=0, logprobs=None, content_filter_results={})], created=1722960000, model='gpt-4o-2024-05-13', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_abc28019ad', usage=None), ChatCompletionChunk(id='chatcmpl-9tGtMB4gmtRdR9W9B4xc4rp8Xiexq', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='"}', name=None), type=None)]), finish_reason=None, index=0, logprobs=None, content_filter_results={})], created=1722960000, model='gpt-4o-2024-05-13', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_abc28019ad', usage=None), ChatCompletionChunk(id='chatcmpl-9tGtMB4gmtRdR9W9B4xc4rp8Xiexq', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, role=None, tool_calls=None), finish_reason='tool_calls', index=0, logprobs=None, content_filter_results={})], created=1722960000, model='gpt-4o-2024-05-13', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_abc28019ad', usage=None)], ai_model_id='gpt-4o', metadata={'logprobs': None, 'id': 'chatcmpl-9tGtMB4gmtRdR9W9B4xc4rp8Xiexq', 'created': 1722960000, 'system_fingerprint': 'fp_abc28019ad'}, content_type='message', role=<AuthorRole.ASSISTANT: 'assistant'>, name=None, items=[FunctionCallContent(inner_content=None, ai_model_id=None, metadata={}, content_type=<ContentTypes.FUNCTION_CALL_CONTENT: 'function_call'>, id='call_nVgttAq1pU4Tz3zRFEf5kaln', index=0, name='hr_policy_plugin-search_hr_policies', function_name='search_hr_policies', plugin_name='hr_policy_plugin', arguments='{"query":"shorts dress code"}')], encoding=None, finish_reason=<FinishReason.TOOL_CALLS: 'tool_calls'>),

'exception': PydanticSerializationError(Error serializing to JSON: TypeError: 'MockValSer' object cannot be converted to 'SchemaSerializer')}

To Reproduce Steps to reproduce the behavior:

  1. Initialize a kernel that can automatically select a function if necessary
  2. Initialize an instance of ChatHistory
  3. Pass the ChatHistory to the kernel's chat_completion.get_streaming_chat_message_contents() method
  4. Serialize the ChatHistory instance by calling instance.serialize()

Expected behavior Standard JSON should be returned by the serialize() method

Screenshots N/A

Platform

Additional context Possibly related to Issue #7340

tnaber commented 1 month ago

Also experiencing this, still using work around: [msg.to_dict() for msg in chat_history.messages]

rog555 commented 1 week ago

I think this is an issue with pydantic v2 as per https://github.com/pydantic/pydantic/issues/7713 and https://github.com/openai/openai-python/issues/1306

reproduce below using MathPlugin with some hackery to attempt to fix history so can be dumped (tho i've no idea what i'm doing).

It looks to be messages with inner_content=ChatCompletion() and then a subsequent serialization error with message items of FunctionResultContent and KernelParameterMetadata (depending on plugin)

#!/usr/bin/env python
import asyncio
import os

from semantic_kernel.agents import ChatCompletionAgent
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion
from semantic_kernel.contents import (
    AuthorRole,
    ChatHistory,
    ChatMessageContent,
    FunctionResultContent
)
from semantic_kernel.core_plugins import MathPlugin
from semantic_kernel.kernel import Kernel

def try_dump(history):
    msg = None
    try:
        history.model_dump_json()
        msg = 'succeeded'
    except Exception as e:
        msg = str(e)
    print(f'model_dump_json: {msg}')
    return msg

async def main():
    kernel = Kernel()
    kernel.add_service(AzureChatCompletion(
        service_id='agent',
        deployment_name=os.getenv('AZURE_OPENAI_DEPLOYMENT'),
        api_key=os.getenv('AZURE_OPENAI_API_KEY'),
        endpoint=os.getenv('AZURE_OPENAI_ENDPOINT')
    ))
    kernel.add_plugin(plugin=MathPlugin(), plugin_name='math')
    settings = kernel.get_prompt_execution_settings_from_service_id(
        service_id='agent'
    )
    settings.function_choice_behavior = FunctionChoiceBehavior.Required()
    agent = ChatCompletionAgent(
        service_id='agent',
        kernel=kernel,
        name='agent',
        instructions='you are an agent that can use tools to respond to the prompt',
        execution_settings=settings
    )
    history = ChatHistory()
    user_msg = 'add 1 + 2'
    history.add_message(
        ChatMessageContent(role=AuthorRole.USER, content=user_msg)
    )
    print(f'> user: {user_msg}')
    agent.invoke(history)
    async for content in agent.invoke(history=history):
        if content.role != AuthorRole.TOOL:
            print(f'< {content.role}: {content.content}')

    assert try_dump(history) == (
        "Error serializing to JSON: TypeError: 'MockValSer' "
        + "object cannot be converted to 'SchemaSerializer'"
    )

    # from rich.pretty import pprint; pprint(history)

    # hack inner_content
    for msg in history.messages:
        if hasattr(msg, 'inner_content'):
            msg.inner_content = None

    assert try_dump(history) == "Unable to serialize unknown type: <class 'type'>"

    # hack FunctionResultContent.inner_content
    for msg in history.messages:
        for item in msg.items:
            if isinstance(item, FunctionResultContent):
                item.inner_content = None

    assert try_dump(history) == 'succeeded'

if __name__ == "__main__":
    asyncio.run(main())

output

> user: add 1 + 2
< assistant: 1 + 2 equals 3.
model_dump_json: Error serializing to JSON: TypeError: 'MockValSer' object cannot be converted to 'SchemaSerializer'
model_dump_json: Unable to serialize unknown type: <class 'type'>
model_dump_json: succeeded
labeebee commented 1 day ago

Any work-arounds for this one? I was thinking of using this to store it in a redis cache so that I can manage the memory for each session. Now that this is a known bug, I am thinking of maintaining a custom chat history.