e768098d0e
tools_continuous_delivery / Private PyPI non-main branch release (push) Has been skipped
tools_continuous_delivery / Private PyPI main branch release (push) Failing after 2m42s
Publish Promptflow Doc / Build (push) Has been cancelled
Publish Promptflow Doc / Deploy (push) Has been cancelled
Flake8 Lint / flake8 (push) Has been cancelled
Spell check CI / Spell_Check (push) Has been cancelled
202 lines
7.8 KiB
Python
202 lines
7.8 KiB
Python
import asyncio
|
|
import os
|
|
from dataclasses import dataclass, field
|
|
|
|
from dotenv import load_dotenv
|
|
from typing_extensions import Never
|
|
|
|
from agent_framework import Agent, Executor, WorkflowBuilder, WorkflowContext, handler
|
|
from agent_framework.openai import OpenAIChatClient, OpenAIChatOptions
|
|
|
|
load_dotenv()
|
|
|
|
_VERIFY_SYSTEM_PROMPT = """\
|
|
You are an assistant tasked with determining whether a conversation between a human and a bot \
|
|
will continue or not. Your outputs are limited to "[STOP]" or "[CONTINUE]". When you predict \
|
|
that the conversation will go on, you should respond with "[CONTINUE]". If you believe the \
|
|
conversation has come to an end, respond with "[STOP]".
|
|
|
|
Examples:
|
|
|
|
Example 1:
|
|
Conversation:
|
|
Human: Hey Bot, what's your favorite movie?
|
|
Bot: I don't watch movies, but I can help you find information about any movie you like!
|
|
Human: Can you tell me about the latest Marvel movie?
|
|
Bot: The latest Marvel movie is "Spider-Man: No Way Home". It features Peter Parker dealing \
|
|
with the fallout after his identity is revealed. Want to know more about it?
|
|
output: [CONTINUE]
|
|
|
|
Example 2:
|
|
Conversation:
|
|
Human: Hey Bot, do you know any good Italian restaurants nearby?
|
|
Bot: I can't access current location data, but I can suggest looking up Italian restaurants \
|
|
on a local review site like Yelp or Google Reviews.
|
|
Human: Thanks for the tip. I'll check it out.
|
|
Bot: You're welcome! Enjoy your meal. If you need more help, just ask.
|
|
output: [STOP]
|
|
|
|
Instruction:
|
|
A conversation is considered to have ended if:
|
|
1. The Bot's final response only contains polite expressions without substantive content \
|
|
for human to inquire about.
|
|
2. In the last round of the conversation, the Human did not ask the Bot any questions."""
|
|
|
|
_HUMAN_SYSTEM_PROMPT = """\
|
|
You are an assistant playing as a random human engaging in a conversation with a digital \
|
|
companion, Bot. Your task is to follow the instruction below to role-play as a random human \
|
|
in a conversation with Bot, responding to Bot in a manner that a human would say.
|
|
|
|
Example:
|
|
This example illustrates how to generate a conversational response to Bot as a human would:
|
|
Conversation:
|
|
Human: Bot, what's your favorite movie?
|
|
Bot: I don't watch movies, but I can help you find information about any movie you like!
|
|
Human: Can you tell me about the latest Marvel movie?
|
|
Bot: The latest Marvel movie is "Spider-Man: No Way Home". It features Peter Parker dealing \
|
|
with the fallout after his identity is revealed. Want to know more about it?
|
|
Human: Yes, can you suggest where I can watch it?
|
|
|
|
Instruction:
|
|
1. Your reply to the Bot should mimic how a human would typically engage in conversation, \
|
|
asking questions or making statements that a person would naturally say in response.
|
|
2. Do not use interjections.
|
|
3. Provide a straightforward, factual response without expressions of surprise, admiration, \
|
|
or evaluative comments for Bot's response.
|
|
4. Focus on directly asking a question about Bot's response in the last exchange. \
|
|
The question should be concise, and without punctuation marks in the middle.
|
|
5. Avoid creating any messages that appear to come from the Bot. Your response should not \
|
|
contain content that could be mistaken as generated by the Bot, maintaining a clear \
|
|
distinction between your input as the Human and the Bot's contributions to the conversation.
|
|
6. Your reply should not contain "\\n", this is a reserved character."""
|
|
|
|
|
|
@dataclass
|
|
class QuestionSimInput:
|
|
chat_history: list = field(default_factory=list)
|
|
question_count: int = 3
|
|
|
|
|
|
def _format_chat_history(chat_history: list) -> str:
|
|
parts = []
|
|
for item in chat_history:
|
|
parts.append(f"Human: {item['inputs']['question']}")
|
|
parts.append(f"Bot: {item['outputs']['answer']}")
|
|
return "\n".join(parts)
|
|
|
|
|
|
class VerifyAndSimulateExecutor(Executor):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
client = OpenAIChatClient(
|
|
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
|
model=os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4"),
|
|
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
|
)
|
|
self._verify_agent = Agent(
|
|
client=client,
|
|
name="VerifyAgent",
|
|
instructions=_VERIFY_SYSTEM_PROMPT,
|
|
)
|
|
self._human_agent = Agent(
|
|
client=client,
|
|
name="HumanSimAgent",
|
|
instructions=_HUMAN_SYSTEM_PROMPT,
|
|
)
|
|
|
|
@handler
|
|
async def process(self, sim_input: QuestionSimInput, ctx: WorkflowContext[Never, str]) -> None:
|
|
history_text = _format_chat_history(sim_input.chat_history)
|
|
|
|
# Step 1: Verify if conversation should continue
|
|
verify_prompt = (
|
|
f"Read the following conversation and respond:\n"
|
|
f"Conversation:\n{history_text}\noutput:"
|
|
)
|
|
verify_response = await self._verify_agent.run(
|
|
verify_prompt,
|
|
options=OpenAIChatOptions(temperature=0, top_p=1.0),
|
|
)
|
|
stop_or_continue = verify_response.text.strip()
|
|
|
|
# Step 2: Check if we should stop
|
|
if "stop" in stop_or_continue.lower():
|
|
await ctx.yield_output("[STOP]")
|
|
return
|
|
|
|
# Step 3: Generate human-like questions
|
|
human_prompt = (
|
|
f"Read the following conversation and respond:\n"
|
|
f"Conversation:\n{history_text}\nHuman:"
|
|
)
|
|
|
|
# Use the OpenAI client directly for n>1 completions
|
|
from openai import AzureOpenAI
|
|
|
|
client = AzureOpenAI(
|
|
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
|
api_version=os.environ.get("AZURE_OPENAI_API_VERSION", "2024-02-15-preview"),
|
|
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
|
)
|
|
|
|
messages = [
|
|
{"role": "system", "content": _HUMAN_SYSTEM_PROMPT},
|
|
{"role": "user", "content": human_prompt},
|
|
]
|
|
completion = client.chat.completions.create(
|
|
model=os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4"),
|
|
messages=messages,
|
|
temperature=1.0,
|
|
top_p=1.0,
|
|
presence_penalty=0.8,
|
|
frequency_penalty=0.8,
|
|
n=sim_input.question_count,
|
|
stop=["Human:", "Bot:"],
|
|
)
|
|
|
|
questions = []
|
|
for choice in completion.choices:
|
|
response = getattr(choice.message, "content", "")
|
|
if response:
|
|
questions.append(response)
|
|
|
|
await ctx.yield_output("\n".join(questions))
|
|
|
|
|
|
def create_workflow():
|
|
"""Create a fresh workflow instance.
|
|
|
|
MAF workflows do not support concurrent execution, so each
|
|
concurrent caller needs its own workflow instance.
|
|
"""
|
|
_executor = VerifyAndSimulateExecutor(id="verify_and_simulate")
|
|
return (
|
|
WorkflowBuilder(name="QuestionSimulationWorkflow", start_executor=_executor)
|
|
.build()
|
|
)
|
|
|
|
|
|
async def main():
|
|
workflow = create_workflow()
|
|
chat_history = [
|
|
{
|
|
"inputs": {"question": "Can you introduce something about large language model?"},
|
|
"outputs": {
|
|
"answer": (
|
|
"A large language model (LLM) is a type of language model that is distinguished "
|
|
"by its ability to perform general-purpose language generation and understanding. "
|
|
"These models learn statistical relationships from text documents through a "
|
|
"self-supervised and semi-supervised training process that is computationally intensive."
|
|
),
|
|
},
|
|
}
|
|
]
|
|
result = await workflow.run(
|
|
QuestionSimInput(chat_history=chat_history, question_count=3)
|
|
)
|
|
print(f"Output:\n{result.get_outputs()[0]}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|