Skip to content

Python: improved chat history samples #10737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions python/samples/SAMPLE_GUIDELINES.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,20 @@ Try to do a best effort to make sure that the samples are incremental in complex

### **Documentation**

Try to over-document the samples. This includes comments in the code, README.md files, and any other documentation that is necessary to understand the sample.
Try to over-document the samples. This includes comments in the code, README.md files, and any other documentation that is necessary to understand the sample. We use the guidance from [PEP8](https://peps.python.org/pep-0008/#comments) for comments in the code, with a deviation for the initial summary comment in samples and the output of the samples.

For the getting started samples and the concept samples, we should have the following:

1. A README.md file is included in each set of samples that explains the purpose of the samples and the setup required to run them.
2. A summary should be included at the top of the file that explains the purpose of the sample and required components/concepts to understand the sample. For example:

```python
# This sample shows how to create a chatbot. This sample uses the following two main components:
# - a ChatCompletionService: This component is responsible for generating responses to user messages.
# - a ChatHistory: This component is responsible for keeping track of the chat history.
# The chatbot in this sample is called Mosscap, who responds to user messages with long flowery prose.
'''
This sample shows how to create a chatbot. This sample uses the following two main components:
- a ChatCompletionService: This component is responsible for generating responses to user messages.
- a ChatHistory: This component is responsible for keeping track of the chat history.
The chatbot in this sample is called Mosscap, who responds to user messages with long flowery prose.
'''
```

3. Mark the code with comments to explain the purpose of each section of the code. For example:
Expand All @@ -64,12 +66,11 @@ For the getting started samples and the concept samples, we should have the foll
```python
'''
Sample output:
# User:> Why is the sky blue in one sentence?
# Mosscap:> The sky is blue due to the scattering of sunlight by the molecules in the Earth's atmosphere,
# a phenomenon known as Rayleigh scattering, which causes shorter blue wavelengths to become more
# prominent in our visual perception.
User:> Why is the sky blue in one sentence?
Mosscap:> The sky is blue due to the scattering of sunlight by the molecules in the Earth's atmosphere,
a phenomenon known as Rayleigh scattering, which causes shorter blue wavelengths to become more
prominent in our visual perception.
'''

```

For the demos, a README.md file must be included that explains the purpose of the demo and how to run it. The README.md file should include the following:
Expand Down
6 changes: 3 additions & 3 deletions python/samples/concepts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
- [Chat Completion Truncate History Reducer Agent Chat](./agents/chat_completion_agent/chat_completion_truncate_history_reducer_agent_chat.py)
- [Chat Completion Truncate History Reducer Single Agent](./agents/chat_completion_agent/chat_completion_truncate_history_reducer_single_agent.py)


#### [Mixed Agent Group Chat](../../semantic_kernel/agents/group_chat/agent_group_chat.py)

- [Mixed Chat Agents Plugins](./agents/mixed_chat/mixed_chat_agents_plugins.py)
Expand Down Expand Up @@ -90,6 +89,7 @@
### ChatHistory - Using and serializing the [`ChatHistory`](https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/contents/chat_history.py)

- [Serialize Chat History](./chat_history/serialize_chat_history.py)
- [Store Chat History in CosmosDB](./chat_history/store_chat_history_in_cosmosdb.py)

### Filtering - Creating and using Filters

Expand Down Expand Up @@ -202,7 +202,7 @@ In Semantic Kernel for Python, we leverage Pydantic Settings to manage configura

1. **Reading Environment Variables:**
- **Primary Source:** Pydantic first attempts to read the required settings from environment variables.

2. **Using a .env File:**
- **Fallback Source:** If the required environment variables are not set, Pydantic will look for a `.env` file in the current working directory.
- **Custom Path (Optional):** You can specify an alternative path for the `.env` file via `env_file_path`. This can be either a relative or an absolute path.
Expand All @@ -220,4 +220,4 @@ To successfully retrieve and use the Entra Auth Token, you need the `Cognitive S

- **.env File Placement:** We highly recommend placing the `.env` file in the `semantic-kernel/python` root directory. This is a common practice when developing in the Semantic Kernel repository.

By following these guidelines, you can ensure that your settings for various components are configured correctly, enabling seamless functionality and integration of Semantic Kernel in your Python projects.
By following these guidelines, you can ensure that your settings for various components are configured correctly, enabling seamless functionality and integration of Semantic Kernel in your Python projects.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio
from typing import TYPE_CHECKING

from samples.concepts.setup.chat_completion_services import Services, get_chat_completion_service_and_request_settings
from semantic_kernel import Kernel
Expand All @@ -11,9 +10,6 @@
from semantic_kernel.core_plugins.time_plugin import TimePlugin
from semantic_kernel.functions import KernelArguments

if TYPE_CHECKING:
pass

#####################################################################
# This sample demonstrates how to build a conversational chatbot #
# using Semantic Kernel, featuring auto function calling, #
Expand Down
17 changes: 17 additions & 0 deletions python/samples/concepts/chat_history/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Chat History manipulation samples

This folder contains samples that demonstrate how to manipulate chat history in Semantic Kernel.

## [Serialize Chat History](./serialize_chat_history.py)

This sample demonstrates how to build a conversational chatbot using Semantic Kernel, it features auto function calling, but with file-based serialization of the chat history. This sample stores and reads the chat history at every turn. This is not the best way to do it, but clearly demonstrates the mechanics.

To run this sample a environment with keys for the chosen chat service is required. In line 61 you can change the model used. This sample uses a temporary file to store the chat history, so no additional setup is required.

## [Store Chat History in Cosmos DB](./store_chat_history_in_cosmosdb.py)

This a more complex version of the sample above, it uses Azure CosmosDB NoSQL to store the chat messages.

In order to do that a simple datamodel is defined. And then a class is created that extends ChatHistory, this class adds `store` and `read` methods, as well as a `create_collection` method that creates a collection in CosmosDB.

This samples further uses the same chat service setup as the sample above, so the keys and other parameters for the chosen model should be in the environment. Next to that a AZURE_COSMOS_DB_NO_SQL_URL and optionally a AZURE_COSMOS_DB_NO_SQL_KEY should be set in the environment, you can also rely on Entra ID Auth instead of the key. The database name can also be put in the environment.
178 changes: 98 additions & 80 deletions python/samples/concepts/chat_history/serialize_chat_history.py
Original file line number Diff line number Diff line change
@@ -1,94 +1,112 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio
import os
from typing import TYPE_CHECKING

from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
AzureChatPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion
import tempfile

from samples.concepts.setup.chat_completion_services import Services, get_chat_completion_service_and_request_settings
from semantic_kernel.contents import ChatHistory
from semantic_kernel.core_plugins.math_plugin import MathPlugin
from semantic_kernel.core_plugins.time_plugin import TimePlugin
from semantic_kernel.functions import KernelArguments

if TYPE_CHECKING:
pass


system_message = """
You are a chat bot. Your name is Mosscap and
you have one goal: figure out what people need.
Your full name, should you need to know it, is
Splendid Speckled Mosscap. You communicate
effectively, but you tend to answer with long
flowery prose. You are also a math wizard,
especially for adding and subtracting.
You also excel at joke telling, where your tone is often sarcastic.
Once you have the answer I am looking for,
you will return a full answer to me as soon as possible.

"""
This sample demonstrates how to build a conversational chatbot
using Semantic Kernel, it features auto function calling,
but with file-based serialization of the chat history.
This sample stores and reads the chat history at every turn.
This is not the best way to do it, but clearly demonstrates the mechanics.
More optimal would for instance be to only write once when a conversation is done.
And writing to something other then a file is also usually better.
"""

kernel = Kernel()

# Note: the underlying gpt-35/gpt-4 model version needs to be at least version 0613 to support tools.
kernel.add_service(AzureChatCompletion(service_id="chat"))

plugins_directory = os.path.join(__file__, "../../../../../prompt_template_samples/")
# adding plugins to the kernel
kernel.add_plugin(MathPlugin(), plugin_name="math")
kernel.add_plugin(TimePlugin(), plugin_name="time")

# Enabling or disabling function calling is done by setting the `function_choice_behavior` attribute for the
# prompt execution settings. When the function_call parameter is set to "auto" the model will decide which
# function to use, if any.
#
# There are two ways to define the `function_choice_behavior` parameter:
# 1. Using the type string as `"auto"`, `"required"`, or `"none"`. For example:
# configure `function_choice_behavior="auto"` parameter directly in the execution settings.
# 2. Using the FunctionChoiceBehavior class. For example:
# `function_choice_behavior=FunctionChoiceBehavior.Auto()`.
# Both of these configure the `auto` tool_choice and all of the available plugins/functions
# registered on the kernel. If you want to limit the available plugins/functions, you must
# configure the `filters` dictionary attribute for each type of function choice behavior.
# For example:
#
# from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior

# function_choice_behavior = FunctionChoiceBehavior.Auto(
# filters={"included_functions": ["time-date", "time-time", "math-Add"]}
# )
#
# The filters attribute allows you to specify either: `included_functions`, `excluded_functions`,
# `included_plugins`, or `excluded_plugins`.

# Note: the number of responses for auto invoking tool calls is limited to 1.
# If configured to be greater than one, this value will be overridden to 1.
execution_settings = AzureChatPromptExecutionSettings(
service_id="chat",
max_tokens=2000,
temperature=0.7,
top_p=0.8,
function_choice_behavior=FunctionChoiceBehavior.Auto(),
)

arguments = KernelArguments(settings=execution_settings)

# You can select from the following chat completion services that support function calling:
# - Services.OPENAI
# - Services.AZURE_OPENAI
# - Services.AZURE_AI_INFERENCE
# - Services.ANTHROPIC
# - Services.BEDROCK
# - Services.GOOGLE_AI
# - Services.MISTRAL_AI
# - Services.OLLAMA
# - Services.ONNX
# - Services.VERTEX_AI
# - Services.DEEPSEEK
# Please make sure you have configured your environment correctly for the selected chat completion service.
chat_completion_service, request_settings = get_chat_completion_service_and_request_settings(Services.OPENAI)


async def chat(file) -> bool:
"""
Continuously prompt the user for input and show the assistant's response.
Type 'exit' to exit.
"""
try:
# Try to load the chat history from a file.
history = ChatHistory.load_chat_history_from_file(file_path=file)
print(f"Chat history successfully loaded {len(history.messages)} messages.")
except Exception:
# Create a new chat history to store the system message, initial messages, and the conversation.
print("Chat history file not found. Starting a new conversation.")
history = ChatHistory()
history.add_system_message(
"You are a chat bot. Your name is Mosscap and you have one goal: figure out what people need."
)

try:
# Get the user input
user_input = input("User:> ")
except (KeyboardInterrupt, EOFError):
print("\n\nExiting chat...")
return False

if user_input.lower().strip() == "exit":
print("\n\nExiting chat...")
return False

# Add the user input to the chat history
history.add_user_message(user_input)
# Get a response from the chat completion service
result = await chat_completion_service.get_chat_message_content(history, request_settings)

# Update the chat history with the user's input and the assistant's response
if result:
print(f"Mosscap:> {result}")
history.add_message(result)

# Save the chat history to a file.
print(f"Saving {len(history.messages)} messages to the file.")
history.store_chat_history_to_file(file_path=file)
return True

async def main() -> None:
user_input = "What is the current hour plus 10?"
print(f"User:> {user_input}")

result = await kernel.invoke_prompt(prompt=user_input, arguments=arguments)
"""
Sample output:

Welcome to the chat bot!
Type 'exit' to exit.
Try a math question to see function calling in action (e.g. 'what is 3+3?').
Your chat history will be saved in: <local working directory>/tmpq1n1f6qk.json
Chat history file not found. Starting a new conversation.
User:> Hello, how are you?
Mosscap:> Hello! I'm here and ready to help. What do you need today?
Saving 3 messages to the file.
Chat history successfully loaded 3 messages.
User:> exit
"""

print(f"Mosscap:> {result}")

print("\nChat history:")
chat_history: ChatHistory = result.metadata["messages"]
print(chat_history.serialize())
async def main() -> None:
chatting = True
with tempfile.NamedTemporaryFile(mode="w+", dir=".", suffix=".json", delete=True) as file:
print(
"Welcome to the chat bot!\n"
" Type 'exit' to exit.\n"
" Try a math question to see function calling in action (e.g. 'what is 3+3?')."
f" Your chat history will be saved in: {file.name}"
)
try:
while chatting:
chatting = await chat(file.name)
except Exception:
print("Closing and removing the file.")


if __name__ == "__main__":
Expand Down
Loading
Loading