Skip to content

Python: Fix Chat History Agent format instructions regression. #10512

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
merged 3 commits into from
Feb 13, 2025
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
182 changes: 97 additions & 85 deletions python/samples/learn_resources/agent_docs/agent_collaboration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,16 @@
import asyncio
import os

from semantic_kernel import Kernel
from semantic_kernel.agents import AgentGroupChat, ChatCompletionAgent
from semantic_kernel.agents.strategies.selection.kernel_function_selection_strategy import (
from semantic_kernel.agents.strategies import (
KernelFunctionSelectionStrategy,
)
from semantic_kernel.agents.strategies.termination.kernel_function_termination_strategy import (
KernelFunctionTerminationStrategy,
)
from semantic_kernel.agents.strategies.termination.termination_strategy import TerminationStrategy
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.contents import ChatHistoryTruncationReducer, ChatMessageContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
from semantic_kernel.kernel import Kernel

###################################################################
# The following sample demonstrates how to create a simple, #
Expand All @@ -24,117 +21,123 @@
# complete a user's task. #
###################################################################


class ApprovalTerminationStrategy(TerminationStrategy):
"""A strategy for determining when an agent should terminate."""

async def should_agent_terminate(self, agent, history):
"""Check if the agent should terminate."""
return "approved" in history[-1].content.lower()


# Define agent names
REVIEWER_NAME = "Reviewer"
COPYWRITER_NAME = "Writer"
WRITER_NAME = "Writer"


def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
def create_kernel() -> Kernel:
"""Creates a Kernel instance with an Azure OpenAI ChatCompletion service."""
kernel = Kernel()
kernel.add_service(AzureChatCompletion(service_id=service_id))
kernel.add_service(service=AzureChatCompletion())
return kernel


async def main():
# Create a single kernel instance for all agents.
kernel = create_kernel()

# Create ChatCompletionAgents using the same kernel.
agent_reviewer = ChatCompletionAgent(
service_id=REVIEWER_NAME,
kernel=_create_kernel_with_chat_completion(REVIEWER_NAME),
kernel=kernel,
name=REVIEWER_NAME,
instructions="""
Your responsibility is to review and identify how to improve user provided content.
If the user has providing input or direction for content already provided, specify how to
address this input.
Never directly perform the correction or provide example.
Once the content has been updated in a subsequent response, you will review the content
again until satisfactory.
Always copy satisfactory content to the clipboard using available tools and inform user.

RULES:
- Only identify suggestions that are specific and actionable.
- Verify previous suggestions have been addressed.
- Never repeat previous suggestions.
""",
Your responsibility is to review and identify how to improve user provided content.
If the user has provided input or direction for content already provided, specify how to address this input.
Never directly perform the correction or provide an example.
Once the content has been updated in a subsequent response, review it again until it is satisfactory.

RULES:
- Only identify suggestions that are specific and actionable.
- Verify previous suggestions have been addressed.
- Never repeat previous suggestions.
""",
)

agent_writer = ChatCompletionAgent(
service_id=COPYWRITER_NAME,
kernel=_create_kernel_with_chat_completion(COPYWRITER_NAME),
name=COPYWRITER_NAME,
service_id=WRITER_NAME,
kernel=kernel,
name=WRITER_NAME,
instructions="""
Your sole responsibility is to rewrite content according to review suggestions.

- Always apply all review direction.
- Always revise the content in its entirety without explanation.
- Never address the user.
""",
Your sole responsibility is to rewrite content according to review suggestions.
- Always apply all review directions.
- Always revise the content in its entirety without explanation.
- Never address the user.
""",
)

# Define a selection function to determine which agent should take the next turn.
selection_function = KernelFunctionFromPrompt(
function_name="selection",
prompt=f"""
Determine which participant takes the next turn in a conversation based on the the most recent participant.
State only the name of the participant to take the next turn.
No participant should take more than one turn in a row.
Choose only from these participants:
- {REVIEWER_NAME}
- {COPYWRITER_NAME}
Always follow these rules when selecting the next participant:
- After user input, it is {COPYWRITER_NAME}'s turn.
- After {COPYWRITER_NAME} replies, it is {REVIEWER_NAME}'s turn.
- After {REVIEWER_NAME} provides feedback, it is {COPYWRITER_NAME}'s turn.

History:
{{{{$history}}}}
""",
Examine the provided RESPONSE and choose the next participant.
State only the name of the chosen participant without explanation.
Never choose the participant named in the RESPONSE.

Choose only from these participants:
- {REVIEWER_NAME}
- {WRITER_NAME}

Rules:
- If RESPONSE is user input, it is {REVIEWER_NAME}'s turn.
- If RESPONSE is by {REVIEWER_NAME}, it is {WRITER_NAME}'s turn.
- If RESPONSE is by {WRITER_NAME}, it is {REVIEWER_NAME}'s turn.

RESPONSE:
{{{{$lastmessage}}}}
""",
)

TERMINATION_KEYWORD = "yes"
# Define a termination function where the reviewer signals completion with "yes".
termination_keyword = "yes"

termination_function = KernelFunctionFromPrompt(
function_name="termination",
prompt=f"""
Examine the RESPONSE and determine whether the content has been deemed satisfactory.
If content is satisfactory, respond with a single word without explanation: {TERMINATION_KEYWORD}.
If specific suggestions are being provided, it is not satisfactory.
If no correction is suggested, it is satisfactory.

RESPONSE:
{{{{$history}}}}
""",
Examine the RESPONSE and determine whether the content has been deemed satisfactory.
If the content is satisfactory, respond with a single word without explanation: {termination_keyword}.
If specific suggestions are being provided, it is not satisfactory.
If no correction is suggested, it is satisfactory.

RESPONSE:
{{{{$lastmessage}}}}
""",
)

history_reducer = ChatHistoryTruncationReducer(target_count=5)

# Create the AgentGroupChat with selection and termination strategies.
chat = AgentGroupChat(
agents=[agent_writer, agent_reviewer],
agents=[agent_reviewer, agent_writer],
selection_strategy=KernelFunctionSelectionStrategy(
initial_agent=agent_reviewer,
function=selection_function,
kernel=_create_kernel_with_chat_completion("selection"),
result_parser=lambda result: str(result.value[0]) if result.value is not None else COPYWRITER_NAME,
agent_variable_name="agents",
history_variable_name="history",
kernel=kernel,
result_parser=lambda result: str(result.value[0]).strip() if result.value[0] is not None else WRITER_NAME,
history_variable_name="lastmessage",
history_reducer=history_reducer,
),
termination_strategy=KernelFunctionTerminationStrategy(
agents=[agent_reviewer],
function=termination_function,
kernel=_create_kernel_with_chat_completion("termination"),
result_parser=lambda result: TERMINATION_KEYWORD in str(result.value[0]).lower(),
history_variable_name="history",
kernel=kernel,
result_parser=lambda result: termination_keyword in str(result.value[0]).lower(),
history_variable_name="lastmessage",
maximum_iterations=10,
history_reducer=history_reducer,
),
)

is_complete: bool = False
print(
"Ready! Type your input, or 'exit' to quit, 'reset' to restart the conversation. "
"You may pass in a file path using @<path_to_file>."
)

is_complete = False
while not is_complete:
user_input = input("User:> ")
print()
user_input = input("User > ").strip()
if not user_input:
continue

Expand All @@ -147,26 +150,35 @@ async def main():
print("[Conversation has been reset]")
continue

if user_input.startswith("@") and len(input) > 1:
file_path = input[1:]
# Try to grab files from the script's current directory
if user_input.startswith("@") and len(user_input) > 1:
file_name = user_input[1:]
script_dir = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(script_dir, file_name)
try:
if not os.path.exists(file_path):
print(f"Unable to access file: {file_path}")
continue
with open(file_path) as file:
with open(file_path, encoding="utf-8") as file:
user_input = file.read()
except Exception:
print(f"Unable to access file: {file_path}")
continue

# Add the current user_input to the chat
await chat.add_chat_message(ChatMessageContent(role=AuthorRole.USER, content=user_input))

async for response in chat.invoke():
print(f"# {response.role} - {response.name or '*'}: '{response.content}'")
try:
async for response in chat.invoke():
if response is None or not response.name:
continue
print()
print(f"# {response.name.upper()}:\n{response.content}")
except Exception as e:
print(f"Error during chat invocation: {e}")

if chat.is_complete:
is_complete = True
break
# Reset the chat's complete flag for the new conversation round.
chat.is_complete = False


if __name__ == "__main__":
Expand Down
6 changes: 2 additions & 4 deletions python/samples/learn_resources/agent_docs/assistant_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
import logging
import os

from semantic_kernel.agents.open_ai.azure_assistant_agent import AzureAssistantAgent
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.streaming_file_reference_content import StreamingFileReferenceContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.agents.open_ai import AzureAssistantAgent
from semantic_kernel.contents import AuthorRole, ChatMessageContent, StreamingFileReferenceContent
from semantic_kernel.kernel import Kernel

logging.basicConfig(level=logging.ERROR)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
import asyncio
import os

from semantic_kernel.agents.open_ai.azure_assistant_agent import AzureAssistantAgent
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.streaming_annotation_content import StreamingAnnotationContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.agents.open_ai import AzureAssistantAgent
from semantic_kernel.contents import AuthorRole, ChatMessageContent, StreamingAnnotationContent
from semantic_kernel.kernel import Kernel

###################################################################
Expand Down
4 changes: 1 addition & 3 deletions python/samples/learn_resources/agent_docs/chat_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
from semantic_kernel.agents import ChatCompletionAgent
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.contents import AuthorRole, ChatHistory, ChatMessageContent
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel

Expand Down
9 changes: 9 additions & 0 deletions python/samples/learn_resources/resources/WomensSuffrage.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Women's suffrage is when women got the right to vote. A long time ago, only men could vote and make decisions. This was not fair because women should have the same rights as men. Women wanted to vote too, so they started asking for it. It took a long time, and they had to work very hard to make people listen to them. Many men did not think women should vote, and this made it very hard for the women.

The women who fought for voting were called suffragets. They did many things to show they wanted the right to vote. Some gave speeches, others made signs and marched in the streets. Some even went to jail because they refused to stop fighting for what they believed was right. It was scary for some of the women, but they knew how important it was to keep trying. They wanted to change the world so that it was more fair for everyone.

One of the most important suffragets was Susan B. Anthony. She worked very hard to help women get the right to vote. She gave speeches and wrote letters to the goverment to make them change the laws. Susan never gave up, even when people said mean things to her. Another important person was Elizabeth Cady Stanton. She also helped fight for women's rights and was friends with Susan B. Anthony. Together, they made a great team and helped make big changes.

Finally, in 1920, the 19th amendment was passed in the United States. This law gave women the right to vote. It was a huge victory for the suffragets, and they were very happy. Many women went to vote for the first time, and it felt like they were finally equal with men. It took many years and a lot of hard work, but the women never gave up. They kept fighting until they won.

Women's suffrage is very important because it shows that if you work hard and believe in something, you can make a change. The women who fought for the right to vote showed bravery and strengh, and they helped make the world a better place. Today, women can vote because of them, and it's important to remember their hard work. We should always stand up for what is right, just like the suffragets did.
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,8 @@ def generate_streaming_code_interpreter_content(

metadata: dict[str, bool] = {}
for index, tool in enumerate(step_details.tool_calls):
if isinstance(tool.type, RunStepDeltaCodeInterpreterToolCall):
code_interpreter_tool_call: RunStepDeltaCodeInterpreterDetailItemObject = tool
if isinstance(tool, RunStepDeltaCodeInterpreterDetailItemObject):
code_interpreter_tool_call = tool
if code_interpreter_tool_call.input:
items.append(
StreamingTextContent(
Expand All @@ -349,7 +349,11 @@ def generate_streaming_code_interpreter_content(
metadata["code"] = True
if code_interpreter_tool_call.outputs:
for output in code_interpreter_tool_call.outputs:
if isinstance(output, RunStepDeltaCodeInterpreterImageOutput) and output.image.file_id:
if (
isinstance(output, RunStepDeltaCodeInterpreterImageOutput)
and output.image is not None
and output.image.file_id
):
items.append(
StreamingFileReferenceContent(
file_id=output.image.file_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,8 @@ async def _stream_tool_outputs(
if sub_event_type == AgentStreamEvent.THREAD_MESSAGE_DELTA:
yield generate_streaming_message_content(agent.name, sub_event_data)
elif sub_event_type == AgentStreamEvent.THREAD_RUN_COMPLETED:
logger.info(f"Run completed with ID: {sub_event_data.id}")
thread_run = cast(ThreadRun, sub_event_data)
logger.info(f"Run completed with ID: {thread_run.id}")
if active_messages:
for msg_id, step in active_messages.items():
message = await cls._retrieve_message(agent=agent, thread_id=thread_id, message_id=msg_id)
Expand All @@ -551,7 +552,7 @@ async def _stream_tool_outputs(
messages.append(final_content)
return
elif sub_event_type == AgentStreamEvent.THREAD_RUN_FAILED:
run_failed: ThreadRun = sub_event_data
run_failed = cast(ThreadRun, sub_event_data)
error_message = (
run_failed.last_error.message if run_failed.last_error and run_failed.last_error.message else ""
)
Expand Down
9 changes: 5 additions & 4 deletions python/semantic_kernel/agents/bedrock/bedrock_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from semantic_kernel.agents.bedrock.bedrock_agent_settings import BedrockAgentSettings
from semantic_kernel.agents.bedrock.models.bedrock_agent_event_type import BedrockAgentEventType
from semantic_kernel.agents.bedrock.models.bedrock_agent_model import BedrockAgentModel
from semantic_kernel.agents.channels.agent_channel import AgentChannel
from semantic_kernel.agents.channels.bedrock_agent_channel import BedrockAgentChannel
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.contents.binary_content import BinaryContent
Expand All @@ -44,7 +45,7 @@ class BedrockAgent(BedrockAgentBase, Agent):
Manages the interaction with Amazon Bedrock Agent Service.
"""

channel_type: ClassVar[type[BedrockAgentChannel]] = BedrockAgentChannel
channel_type: ClassVar[type[AgentChannel]] = BedrockAgentChannel

def __init__(
self,
Expand Down Expand Up @@ -93,9 +94,9 @@ def __init__(
raise AgentInitializationException("Failed to initialize the Amazon Bedrock Agent settings.") from e

bedrock_agent_model = BedrockAgentModel(
agent_id=id,
agent_name=name,
foundation_model=bedrock_agent_settings.foundation_model,
agentId=id,
agentName=name,
foundationModel=bedrock_agent_settings.foundation_model,
)

prompt_template: PromptTemplateBase | None = None
Expand Down
Loading
Loading