chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
|
||||
AZURE_OPENAI_API_KEY=your-api-key
|
||||
AZURE_OPENAI_DEPLOYMENT=gpt-4
|
||||
@@ -0,0 +1 @@
|
||||
{"name": "FilmTriviaGPT", "role": "an AI specialized in film trivia that provides accurate and up-to-date information about movies, directors, actors, and more.", "goals": ["Introduce 'Lord of the Rings' film trilogy including the film title, release year, director, current age of the director, production company and a brief summary of the film."]}
|
||||
@@ -0,0 +1,127 @@
|
||||
import sys
|
||||
from io import StringIO
|
||||
import functools
|
||||
import logging
|
||||
import ast
|
||||
from typing import Dict, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def warn_once() -> None:
|
||||
# Warn that the PythonREPL
|
||||
logger.warning("Python REPL can execute arbitrary code. Use with caution.")
|
||||
|
||||
|
||||
COMMAND_EXECUTION_FUNCTIONS = ["system", "exec", "execfile", "eval"]
|
||||
|
||||
|
||||
class PythonValidation:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
allow_imports: bool = False,
|
||||
allow_command_exec: bool = False,
|
||||
):
|
||||
"""Initialize a PALValidation instance.
|
||||
|
||||
Args:
|
||||
allow_imports (bool): Allow import statements.
|
||||
allow_command_exec (bool): Allow using known command execution functions.
|
||||
"""
|
||||
self.allow_imports = allow_imports
|
||||
self.allow_command_exec = allow_command_exec
|
||||
|
||||
def validate_code(self, code: str) -> None:
|
||||
try:
|
||||
code_tree = ast.parse(code)
|
||||
except (SyntaxError, UnicodeDecodeError):
|
||||
raise ValueError(f"Generated code is not valid python code: {code}")
|
||||
except TypeError:
|
||||
raise ValueError(
|
||||
f"Generated code is expected to be a string, "
|
||||
f"instead found {type(code)}"
|
||||
)
|
||||
except OverflowError:
|
||||
raise ValueError(
|
||||
f"Generated code too long / complex to be parsed by ast: {code}"
|
||||
)
|
||||
|
||||
has_imports = False
|
||||
top_level_nodes = list(ast.iter_child_nodes(code_tree))
|
||||
for node in top_level_nodes:
|
||||
if isinstance(node, ast.Import) or isinstance(node, ast.ImportFrom):
|
||||
has_imports = True
|
||||
|
||||
if not self.allow_imports and has_imports:
|
||||
raise ValueError(f"Generated code has disallowed imports: {code}")
|
||||
|
||||
if (
|
||||
not self.allow_command_exec
|
||||
or not self.allow_imports
|
||||
):
|
||||
for node in ast.walk(code_tree):
|
||||
if (
|
||||
(not self.allow_command_exec)
|
||||
and isinstance(node, ast.Call)
|
||||
and (
|
||||
(
|
||||
hasattr(node.func, "id")
|
||||
and node.func.id in COMMAND_EXECUTION_FUNCTIONS
|
||||
)
|
||||
or (
|
||||
isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr in COMMAND_EXECUTION_FUNCTIONS
|
||||
)
|
||||
)
|
||||
):
|
||||
raise ValueError(
|
||||
f"Found illegal command execution function "
|
||||
f"{node.func.id} in code {code}"
|
||||
)
|
||||
|
||||
if (not self.allow_imports) and (
|
||||
isinstance(node, ast.Import) or isinstance(node, ast.ImportFrom)
|
||||
):
|
||||
raise ValueError(f"Generated code has disallowed imports: {code}")
|
||||
|
||||
|
||||
class PythonREPL:
|
||||
"""Simulates a standalone Python REPL."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.globals: Optional[Dict] = globals()
|
||||
self.locals: Optional[Dict] = None
|
||||
self.code_validations = PythonValidation(allow_imports=True)
|
||||
|
||||
def run(self, command: str) -> str:
|
||||
"""Run command with own globals/locals and returns anything printed."""
|
||||
|
||||
# Warn against dangers of PythonREPL
|
||||
warn_once()
|
||||
self.code_validations.validate_code(command)
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = my_stdout = StringIO()
|
||||
try:
|
||||
exec(command, self.globals, self.locals)
|
||||
sys.stdout = old_stdout
|
||||
output = my_stdout.getvalue()
|
||||
except Exception as e:
|
||||
sys.stdout = old_stdout
|
||||
output = repr(e)
|
||||
print(output)
|
||||
return output
|
||||
|
||||
|
||||
python_repl = PythonREPL()
|
||||
|
||||
|
||||
def python(command: str):
|
||||
"""
|
||||
A Python shell. Use this to execute python commands. Input should be a valid python command.
|
||||
If you want to see the output of a value, you should print it out with `print(...)`.
|
||||
"""
|
||||
|
||||
command = command.strip().strip("```")
|
||||
return python_repl.run(command)
|
||||
@@ -0,0 +1,5 @@
|
||||
agent-framework>=1.0.1
|
||||
agent-framework-openai>=1.0.1
|
||||
python-dotenv
|
||||
beautifulsoup4
|
||||
requests
|
||||
@@ -0,0 +1,27 @@
|
||||
import asyncio
|
||||
|
||||
from workflow import AutoGPTInput, create_workflow
|
||||
|
||||
|
||||
async def main():
|
||||
print("--- Running autonomous agent ---")
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run(
|
||||
AutoGPTInput(
|
||||
name="FilmTriviaGPT",
|
||||
goals=[
|
||||
"Introduce 'Lord of the Rings' film trilogy including the film title, "
|
||||
"release year, director, current age of the director, production company "
|
||||
"and a brief summary of the film."
|
||||
],
|
||||
role=(
|
||||
"an AI specialized in film trivia that provides accurate and up-to-date "
|
||||
"information about movies, directors, actors, and more."
|
||||
),
|
||||
)
|
||||
)
|
||||
print(f"Output:\n{result.get_outputs()[0]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,62 @@
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
import requests
|
||||
|
||||
|
||||
def decode_str(string):
|
||||
return string.encode().decode("unicode-escape").encode("latin1").decode("utf-8")
|
||||
|
||||
|
||||
def get_page_sentence(page, count: int = 10):
|
||||
# find all paragraphs
|
||||
paragraphs = page.split("\n")
|
||||
paragraphs = [p.strip() for p in paragraphs if p.strip()]
|
||||
|
||||
# find all sentence
|
||||
sentences = []
|
||||
for p in paragraphs:
|
||||
sentences += p.split('. ')
|
||||
sentences = [s.strip() + '.' for s in sentences if s.strip()]
|
||||
# get first `count` number of sentences
|
||||
return ' '.join(sentences[:count])
|
||||
|
||||
|
||||
def remove_nested_parentheses(string):
|
||||
pattern = r'\([^()]+\)'
|
||||
while re.search(pattern, string):
|
||||
string = re.sub(pattern, '', string)
|
||||
return string
|
||||
|
||||
|
||||
def search(entity: str, count: int = 10):
|
||||
"""
|
||||
The input is an exact entity name. The action will search this entity name on Wikipedia and returns the first
|
||||
count sentences if it exists. If not, it will return some related entities to search next.
|
||||
"""
|
||||
|
||||
entity_ = entity.replace(" ", "+")
|
||||
search_url = f"https://en.wikipedia.org/w/index.php?search={entity_}"
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35"
|
||||
}
|
||||
response_text = requests.get(search_url, headers=headers).text
|
||||
soup = BeautifulSoup(response_text, features="html.parser")
|
||||
result_divs = soup.find_all("div", {"class": "mw-search-result-heading"})
|
||||
if result_divs: # mismatch
|
||||
result_titles = [decode_str(div.get_text().strip()) for div in result_divs]
|
||||
result_titles = [remove_nested_parentheses(result_title) for result_title in result_titles]
|
||||
obs = f"Could not find {entity}. Similar: {result_titles[:5]}."
|
||||
else:
|
||||
page_content = [p_ul.get_text().strip() for p_ul in soup.find_all("p") + soup.find_all("ul")]
|
||||
if any("may refer to:" in p for p in page_content):
|
||||
obs = search("[" + entity + "]")
|
||||
else:
|
||||
page = ""
|
||||
for content in page_content:
|
||||
if len(content.split(" ")) > 2:
|
||||
page += decode_str(content)
|
||||
if not content.endswith("\n"):
|
||||
page += "\n"
|
||||
obs = get_page_sentence(page, count=count)
|
||||
return obs
|
||||
@@ -0,0 +1,127 @@
|
||||
import asyncio
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
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
|
||||
from wiki_search import search
|
||||
from python_repl import python
|
||||
|
||||
load_dotenv()
|
||||
|
||||
_TRIGGERING_PROMPT = (
|
||||
"Determine which next function to use, and respond using stringfield JSON object.\n"
|
||||
"If you have completed all your tasks, make sure to use the 'finish' function to signal "
|
||||
"and remember show your results."
|
||||
)
|
||||
|
||||
|
||||
def _search_tool(entity: str, count: int = 10) -> str:
|
||||
"""Search Wikipedia for an entity and return the first sentences."""
|
||||
return search(entity, count)
|
||||
|
||||
|
||||
def _python_tool(command: str) -> str:
|
||||
"""Execute a Python command and return the output."""
|
||||
return python(command)
|
||||
|
||||
|
||||
def _finish_tool(response: str) -> str:
|
||||
"""Signal that all goals are completed and show results."""
|
||||
return response
|
||||
|
||||
|
||||
@dataclass
|
||||
class AutoGPTInput:
|
||||
name: str = "FilmTriviaGPT"
|
||||
goals: List[str] = field(default_factory=lambda: [
|
||||
"Introduce 'Lord of the Rings' film trilogy including the film title, "
|
||||
"release year, director, current age of the director, production company "
|
||||
"and a brief summary of the film."
|
||||
])
|
||||
role: str = (
|
||||
"an AI specialized in film trivia that provides accurate and up-to-date "
|
||||
"information about movies, directors, actors, and more."
|
||||
)
|
||||
|
||||
|
||||
class AutoGPTExecutor(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._agent = Agent(
|
||||
client=client,
|
||||
name="AutoGPTAgent",
|
||||
instructions="", # will be set dynamically per run
|
||||
tools=[_search_tool, _python_tool, _finish_tool],
|
||||
)
|
||||
|
||||
@handler
|
||||
async def process(self, gpt_input: AutoGPTInput, ctx: WorkflowContext[Never, str]) -> None:
|
||||
# Build system prompt
|
||||
system_prompt = (
|
||||
f"You are {gpt_input.name}, {gpt_input.role}\n"
|
||||
"Play to your strengths as an LLM and pursue simple strategies "
|
||||
"with no legal complications to complete all goals.\n"
|
||||
"Your decisions must always be made independently without seeking "
|
||||
"user assistance.\n\n"
|
||||
"Performance Evaluation:\n"
|
||||
"1. Continuously review and analyze your actions to ensure you are "
|
||||
"performing to the best of your abilities.\n"
|
||||
"2. Constructively self-criticize your big-picture behavior constantly.\n"
|
||||
"3. Reflect on past decisions and strategies to refine your approach.\n"
|
||||
"4. Every command has a cost, so be smart and efficient. "
|
||||
"Aim to complete tasks in the least number of steps.\n"
|
||||
)
|
||||
|
||||
goals_text = "\n".join(f"{i + 1}. {g}" for i, g in enumerate(gpt_input.goals))
|
||||
user_prompt = f"Goals:\n\n{goals_text}\n\n{_TRIGGERING_PROMPT}"
|
||||
|
||||
self._agent._instructions = system_prompt
|
||||
|
||||
response = await self._agent.run(user_prompt)
|
||||
await ctx.yield_output(response.text)
|
||||
|
||||
|
||||
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 = AutoGPTExecutor(id="autogpt")
|
||||
return (
|
||||
WorkflowBuilder(name="AutonomousAgentWorkflow", start_executor=_executor)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run(
|
||||
AutoGPTInput(
|
||||
name="FilmTriviaGPT",
|
||||
goals=[
|
||||
"Introduce 'Lord of the Rings' film trilogy including the film title, "
|
||||
"release year, director, current age of the director, production company "
|
||||
"and a brief summary of the film."
|
||||
],
|
||||
role=(
|
||||
"an AI specialized in film trivia that provides accurate and up-to-date "
|
||||
"information about movies, directors, actors, and more."
|
||||
),
|
||||
)
|
||||
)
|
||||
print(f"Output:\n{result.get_outputs()[0]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,66 @@
|
||||
# Autonomous Agent
|
||||
|
||||
This is a flow showcasing how to construct a AutoGPT agent with promptflow to autonomously figures out how to apply the given functions
|
||||
to solve the goal, which is film trivia that provides accurate and up-to-date information about movies, directors,
|
||||
actors, and more in this sample.
|
||||
|
||||
It involves inferring next executed function and user intent with LLM, and then use the function to generate
|
||||
observation. The observation above will be used as augmented prompt which is the input of next LLM inferring loop
|
||||
until the inferred function is the signal that you have finished all your objectives. The functions set used in the
|
||||
flow contains Wikipedia search function that can search the web to find answer about current events and PythonREPL
|
||||
python function that can run python code in a REPL.
|
||||
|
||||
For the sample input about movie introduction, the AutoGPT usually runs 4 rounds to finish the task. The first round
|
||||
is searching for the movie name, the second round is searching for the movie director, the third round is calculating
|
||||
director age, and the last round is outputting finishing signal. It takes 30s~40s to finish the task, but may take
|
||||
longer time if you use "gpt-3.5" or encounter Azure OpenAI rate limit. You could use "gpt-4" or go to
|
||||
https://aka.ms/oai/quotaincrease if you would like to further increase the default rate limit.
|
||||
|
||||
Note: This is just a sample introducing how to use promptflow to build a simple AutoGPT. You can go to
|
||||
https://github.com/Significant-Gravitas/Auto-GPT to get more concepts about AutoGPT.
|
||||
|
||||
## What you will learn
|
||||
|
||||
In this flow, you will learn
|
||||
- how to use prompt tool.
|
||||
- how to compose an AutoGPT flow using functions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install prompt-flow sdk and other dependencies:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 1 Create Azure OpenAI or OpenAI connection
|
||||
```bash
|
||||
# Override keys with --set to avoid yaml file changes
|
||||
pf connection create --file ../../../connections/azure_openai.yml --set api_key=<your_api_key> api_base=<your_api_base>
|
||||
```
|
||||
Note that you need to use "2023-07-01-preview" as Azure OpenAI connection API version when using function calling.
|
||||
See <a href='https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/function-calling' target='_blank'>How to use function calling with Azure OpenAI Service</a> for more details.
|
||||
|
||||
### 2. Configure the flow with your connection
|
||||
`flow.dag.yaml` is already configured with connection named `open_ai_connection`. It is recommended to use "gpt-4" model for stable performance. Using "gpt-3.5-turbo" may lead to the model getting stuck in the agent inner loop due to its suboptimal and unstable performance.
|
||||
|
||||
### 3. Test flow with single line data
|
||||
|
||||
```bash
|
||||
# test with default input value in flow.dag.yaml
|
||||
pf flow test --flow .
|
||||
```
|
||||
|
||||
### 4. Run with multi-line data
|
||||
|
||||
```bash
|
||||
# create run using command line args
|
||||
pf run create --flow . --data ./data.jsonl --column-mapping name='${data.name}' role='${data.role}' goals='${data.goals}' --stream
|
||||
```
|
||||
|
||||
You can also skip providing `column-mapping` if provided data has same column name as the flow.
|
||||
Reference [here](https://aka.ms/pf/column-mapping) for default behavior when `column-mapping` not provided in CLI.
|
||||
|
||||
## Disclaimer
|
||||
LLM systems are susceptible to prompt injection, and you can gain a deeper understanding of this issue in the [technical blog](https://developer.nvidia.com/blog/securing-llm-systems-against-prompt-injection/). As an illustration, the PythonREPL function might execute harmful code if provided with a malicious prompt within the provided sample. Furthermore, we cannot guarantee that implementing AST validations solely within the PythonREPL function will reliably elevate the sample's security to an enterprise level. We kindly remind you to refrain from utilizing this in a production environment.
|
||||
@@ -0,0 +1,151 @@
|
||||
from promptflow.tools.aoai import chat as aoai_chat
|
||||
from promptflow.tools.openai import chat as openai_chat
|
||||
from promptflow.connections import AzureOpenAIConnection, OpenAIConnection
|
||||
from util import count_message_tokens, count_string_tokens, create_chat_message, generate_context, get_logger, \
|
||||
parse_reply, construct_prompt
|
||||
|
||||
autogpt_logger = get_logger("autogpt_agent")
|
||||
|
||||
|
||||
class AutoGPT:
|
||||
def __init__(
|
||||
self,
|
||||
connection,
|
||||
tools,
|
||||
full_message_history,
|
||||
functions,
|
||||
tokens_per_message,
|
||||
tokens_per_name,
|
||||
system_prompt=None,
|
||||
triggering_prompt=None,
|
||||
user_prompt=None,
|
||||
model_or_deployment_name=None
|
||||
):
|
||||
self.tools = tools
|
||||
self.full_message_history = full_message_history
|
||||
self.functions = functions
|
||||
self.system_prompt = system_prompt
|
||||
self.connection = connection
|
||||
self.model_or_deployment_name = model_or_deployment_name
|
||||
self.triggering_prompt = triggering_prompt
|
||||
self.user_prompt = user_prompt
|
||||
self.tokens_per_message = tokens_per_message
|
||||
self.tokens_per_name = tokens_per_name
|
||||
|
||||
def chat_with_ai(self, token_limit):
|
||||
"""Interact with the OpenAI API, sending the prompt, message history and functions."""
|
||||
|
||||
# Reserve 1000 tokens for the response
|
||||
send_token_limit = token_limit - 1000
|
||||
(
|
||||
next_message_to_add_index,
|
||||
current_tokens_used,
|
||||
insertion_index,
|
||||
current_context,
|
||||
) = generate_context(self.system_prompt, self.full_message_history, self.user_prompt, self.tokens_per_message,
|
||||
self.tokens_per_name)
|
||||
# Account for user input (appended later)
|
||||
current_tokens_used += count_message_tokens([create_chat_message("user", self.triggering_prompt)],
|
||||
self.tokens_per_message, self.tokens_per_name)
|
||||
current_tokens_used += 500 # Account for memory (appended later)
|
||||
# Add Messages until the token limit is reached or there are no more messages to add.
|
||||
while next_message_to_add_index >= 0:
|
||||
message_to_add = self.full_message_history[next_message_to_add_index]
|
||||
|
||||
tokens_to_add = count_message_tokens([message_to_add], self.tokens_per_message,
|
||||
self.tokens_per_name)
|
||||
if current_tokens_used + tokens_to_add > send_token_limit:
|
||||
break
|
||||
|
||||
# Add the most recent message to the start of the current context, after the two system prompts.
|
||||
current_context.insert(
|
||||
insertion_index, self.full_message_history[next_message_to_add_index]
|
||||
)
|
||||
|
||||
# Count the currently used tokens
|
||||
current_tokens_used += tokens_to_add
|
||||
# Move to the next most recent message in the full message history
|
||||
next_message_to_add_index -= 1
|
||||
|
||||
# Append user input, the length of this is accounted for above
|
||||
current_context.extend([create_chat_message("user", self.triggering_prompt)])
|
||||
# Calculate remaining tokens
|
||||
tokens_remaining = token_limit - current_tokens_used
|
||||
|
||||
current_context = construct_prompt(current_context)
|
||||
if isinstance(self.connection, AzureOpenAIConnection):
|
||||
try:
|
||||
response = aoai_chat(
|
||||
connection=self.connection,
|
||||
prompt=current_context,
|
||||
deployment_name=self.model_or_deployment_name,
|
||||
max_tokens=tokens_remaining,
|
||||
functions=self.functions)
|
||||
return response
|
||||
except Exception as e:
|
||||
if "The API deployment for this resource does not exist" in str(e):
|
||||
raise Exception(
|
||||
"Please fill in the deployment name of your Azure OpenAI resource gpt-4 model.")
|
||||
|
||||
elif isinstance(self.connection, OpenAIConnection):
|
||||
response = openai_chat(
|
||||
connection=self.connection,
|
||||
prompt=current_context,
|
||||
model=self.model_or_deployment_name,
|
||||
max_tokens=tokens_remaining,
|
||||
functions=self.functions)
|
||||
return response
|
||||
else:
|
||||
raise ValueError("Connection must be an instance of AzureOpenAIConnection or OpenAIConnection")
|
||||
|
||||
def run(self):
|
||||
tools = {t.__name__: t for t in self.tools}
|
||||
while True:
|
||||
# Send message to AI, get response
|
||||
response = self.chat_with_ai(token_limit=4000)
|
||||
if "function_call" in response:
|
||||
# Update full message history
|
||||
function_name = response["function_call"]["name"]
|
||||
parsed_output = parse_reply(response["function_call"]["arguments"])
|
||||
if "Error" in parsed_output:
|
||||
error_message = parsed_output["Error"]
|
||||
autogpt_logger.info(f"Error: {error_message}")
|
||||
command_result = f"Error: {error_message}"
|
||||
else:
|
||||
autogpt_logger.info(f"Function generation requested, function = {function_name}, args = "
|
||||
f"{parsed_output}")
|
||||
self.full_message_history.append(
|
||||
create_chat_message("assistant", f"Function generation requested, function = {function_name}, "
|
||||
f"args = {parsed_output}")
|
||||
)
|
||||
if function_name == "finish":
|
||||
response = parsed_output["response"]
|
||||
autogpt_logger.info(f"Responding to user: {response}")
|
||||
return response
|
||||
if function_name in tools:
|
||||
tool = tools[function_name]
|
||||
try:
|
||||
autogpt_logger.info(f"Next function = {function_name}, arguments = {parsed_output}")
|
||||
result = tool(**parsed_output)
|
||||
command_result = f"Executed function {function_name} and returned: {result}"
|
||||
except Exception as e:
|
||||
command_result = (
|
||||
f"Error: {str(e)}, {type(e).__name__}"
|
||||
)
|
||||
result_length = count_string_tokens(command_result)
|
||||
|
||||
if result_length + 600 > 4000:
|
||||
command_result = f"Failure: function {function_name} returned too much output. Do not " \
|
||||
f"execute this function again with the same arguments."
|
||||
else:
|
||||
command_result = f"Unknown function '{function_name}'. Please refer to available functions " \
|
||||
f"defined in functions parameter."
|
||||
|
||||
# Append command result to the message history
|
||||
self.full_message_history.append(create_chat_message("function", str(command_result), function_name))
|
||||
autogpt_logger.info(f"function: {command_result}")
|
||||
else:
|
||||
autogpt_logger.info(f"No function generated, returned: {response['content']}")
|
||||
self.full_message_history.append(
|
||||
create_chat_message("assistant", f"No function generated, returned: {response['content']}")
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
from typing import Union
|
||||
|
||||
from promptflow.core import tool
|
||||
from promptflow.connections import AzureOpenAIConnection, OpenAIConnection
|
||||
|
||||
|
||||
@tool
|
||||
def autogpt_easy_start(connection: Union[AzureOpenAIConnection, OpenAIConnection], system_prompt: str, user_prompt: str,
|
||||
triggering_prompt: str, functions: list, model_or_deployment_name: str, tokens_per_message: int,
|
||||
tokens_per_name: int):
|
||||
from wiki_search import search
|
||||
from python_repl import python
|
||||
from autogpt_class import AutoGPT
|
||||
|
||||
full_message_history = []
|
||||
tools = [
|
||||
search,
|
||||
python
|
||||
]
|
||||
agent = AutoGPT(
|
||||
full_message_history=full_message_history,
|
||||
tools=tools,
|
||||
system_prompt=system_prompt,
|
||||
connection=connection,
|
||||
model_or_deployment_name=model_or_deployment_name,
|
||||
functions=functions,
|
||||
user_prompt=user_prompt,
|
||||
triggering_prompt=triggering_prompt,
|
||||
tokens_per_message=tokens_per_message,
|
||||
tokens_per_name=tokens_per_name,
|
||||
)
|
||||
result = agent.run()
|
||||
return result
|
||||
@@ -0,0 +1 @@
|
||||
{"name": "FilmTriviaGPT", "role": "an AI specialized in film trivia that provides accurate and up-to-date information about movies, directors, actors, and more.", "goals": ["Introduce 'Lord of the Rings' film trilogy including the film title, release year, director, current age of the director, production company and a brief summary of the film."]}
|
||||
@@ -0,0 +1,72 @@
|
||||
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/Flow.schema.json
|
||||
inputs:
|
||||
name:
|
||||
type: string
|
||||
default: "FilmTriviaGPT"
|
||||
goals:
|
||||
type: list
|
||||
default: ["Introduce 'Lord of the Rings' film trilogy including the film title, release year, director, current age of the director, production company and a brief summary of the film."]
|
||||
role:
|
||||
type: string
|
||||
default: "an AI specialized in film trivia that provides accurate and up-to-date information about movies, directors, actors, and more."
|
||||
tokens_per_message:
|
||||
type: int
|
||||
default: 3
|
||||
description: "This parameter is related with the model_or_deployment_name used in this flow, such as gpt-4 in this sample."
|
||||
tokens_per_name:
|
||||
type: int
|
||||
default: 1
|
||||
description: "This parameter is related with the model_or_deployment_name used in this flow, such as gpt-4 in this sample."
|
||||
outputs:
|
||||
output:
|
||||
type: string
|
||||
reference: ${autogpt_easy_start.output}
|
||||
nodes:
|
||||
- name: autogpt_easy_start
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: autogpt_easy_start.py
|
||||
inputs:
|
||||
connection: open_ai_connection
|
||||
functions: ${functions.output}
|
||||
model_or_deployment_name: gpt-4
|
||||
system_prompt: ${system_prompt.output}
|
||||
triggering_prompt: ${triggering_prompt.output}
|
||||
user_prompt: ${user_prompt.output}
|
||||
tokens_per_message: ${inputs.tokens_per_message}
|
||||
tokens_per_name: ${inputs.tokens_per_name}
|
||||
- name: system_prompt
|
||||
type: prompt
|
||||
source:
|
||||
type: code
|
||||
path: system_prompt.jinja2
|
||||
inputs:
|
||||
name: ${inputs.name}
|
||||
role: ${inputs.role}
|
||||
- name: user_prompt
|
||||
type: prompt
|
||||
source:
|
||||
type: code
|
||||
path: user_prompt.jinja2
|
||||
inputs:
|
||||
goals: ${generate_goal.output}
|
||||
- name: triggering_prompt
|
||||
type: prompt
|
||||
source:
|
||||
type: code
|
||||
path: triggering_prompt.jinja2
|
||||
- name: functions
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: functions.py
|
||||
- name: generate_goal
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: generate_goal.py
|
||||
inputs:
|
||||
items: ${inputs.goals}
|
||||
environment:
|
||||
python_requirements_txt: requirements.txt
|
||||
@@ -0,0 +1,59 @@
|
||||
from promptflow.core import tool
|
||||
|
||||
|
||||
@tool
|
||||
def functions_format() -> list:
|
||||
functions = [
|
||||
{
|
||||
"name": "search",
|
||||
"description": """The action will search this entity name on Wikipedia and returns the first {count}
|
||||
sentences if it exists. If not, it will return some related entities to search next.""",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entity": {
|
||||
"type": "string",
|
||||
"description": "Entity name which is used for Wikipedia search.",
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Returned sentences count if entity name exists Wikipedia.",
|
||||
},
|
||||
},
|
||||
"required": ["entity"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "python",
|
||||
"description": """A Python shell. Use this to execute python commands. Input should be a valid python
|
||||
command and you should print result with `print(...)` to see the output.""",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "The command you want to execute in python",
|
||||
}
|
||||
},
|
||||
"required": ["command"]
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "finish",
|
||||
"description": """use this to signal that you have finished all your goals and remember show your
|
||||
results""",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"response": {
|
||||
"type": "string",
|
||||
"description": "final response to let people know you have finished your goals and remember "
|
||||
"show your results",
|
||||
},
|
||||
},
|
||||
"required": ["response"],
|
||||
},
|
||||
},
|
||||
]
|
||||
return functions
|
||||
@@ -0,0 +1,15 @@
|
||||
from promptflow.core import tool
|
||||
|
||||
|
||||
@tool
|
||||
def generate_goal(items: list = []) -> str:
|
||||
"""
|
||||
Generate a numbered list from given items based on the item_type.
|
||||
|
||||
Args:
|
||||
items (list): A list of items to be numbered.
|
||||
|
||||
Returns:
|
||||
str: The formatted numbered list.
|
||||
"""
|
||||
return "\n".join(f"{i + 1}. {item}" for i, item in enumerate(items))
|
||||
@@ -0,0 +1,127 @@
|
||||
import sys
|
||||
from io import StringIO
|
||||
import functools
|
||||
import logging
|
||||
import ast
|
||||
from typing import Dict, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def warn_once() -> None:
|
||||
# Warn that the PythonREPL
|
||||
logger.warning("Python REPL can execute arbitrary code. Use with caution.")
|
||||
|
||||
|
||||
COMMAND_EXECUTION_FUNCTIONS = ["system", "exec", "execfile", "eval"]
|
||||
|
||||
|
||||
class PythonValidation:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
allow_imports: bool = False,
|
||||
allow_command_exec: bool = False,
|
||||
):
|
||||
"""Initialize a PALValidation instance.
|
||||
|
||||
Args:
|
||||
allow_imports (bool): Allow import statements.
|
||||
allow_command_exec (bool): Allow using known command execution functions.
|
||||
"""
|
||||
self.allow_imports = allow_imports
|
||||
self.allow_command_exec = allow_command_exec
|
||||
|
||||
def validate_code(self, code: str) -> None:
|
||||
try:
|
||||
code_tree = ast.parse(code)
|
||||
except (SyntaxError, UnicodeDecodeError):
|
||||
raise ValueError(f"Generated code is not valid python code: {code}")
|
||||
except TypeError:
|
||||
raise ValueError(
|
||||
f"Generated code is expected to be a string, "
|
||||
f"instead found {type(code)}"
|
||||
)
|
||||
except OverflowError:
|
||||
raise ValueError(
|
||||
f"Generated code too long / complex to be parsed by ast: {code}"
|
||||
)
|
||||
|
||||
has_imports = False
|
||||
top_level_nodes = list(ast.iter_child_nodes(code_tree))
|
||||
for node in top_level_nodes:
|
||||
if isinstance(node, ast.Import) or isinstance(node, ast.ImportFrom):
|
||||
has_imports = True
|
||||
|
||||
if not self.allow_imports and has_imports:
|
||||
raise ValueError(f"Generated code has disallowed imports: {code}")
|
||||
|
||||
if (
|
||||
not self.allow_command_exec
|
||||
or not self.allow_imports
|
||||
):
|
||||
for node in ast.walk(code_tree):
|
||||
if (
|
||||
(not self.allow_command_exec)
|
||||
and isinstance(node, ast.Call)
|
||||
and (
|
||||
(
|
||||
hasattr(node.func, "id")
|
||||
and node.func.id in COMMAND_EXECUTION_FUNCTIONS
|
||||
)
|
||||
or (
|
||||
isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr in COMMAND_EXECUTION_FUNCTIONS
|
||||
)
|
||||
)
|
||||
):
|
||||
raise ValueError(
|
||||
f"Found illegal command execution function "
|
||||
f"{node.func.id} in code {code}"
|
||||
)
|
||||
|
||||
if (not self.allow_imports) and (
|
||||
isinstance(node, ast.Import) or isinstance(node, ast.ImportFrom)
|
||||
):
|
||||
raise ValueError(f"Generated code has disallowed imports: {code}")
|
||||
|
||||
|
||||
class PythonREPL:
|
||||
"""Simulates a standalone Python REPL."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.globals: Optional[Dict] = globals()
|
||||
self.locals: Optional[Dict] = None
|
||||
self.code_validations = PythonValidation(allow_imports=True)
|
||||
|
||||
def run(self, command: str) -> str:
|
||||
"""Run command with own globals/locals and returns anything printed."""
|
||||
|
||||
# Warn against dangers of PythonREPL
|
||||
warn_once()
|
||||
self.code_validations.validate_code(command)
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = my_stdout = StringIO()
|
||||
try:
|
||||
exec(command, self.globals, self.locals)
|
||||
sys.stdout = old_stdout
|
||||
output = my_stdout.getvalue()
|
||||
except Exception as e:
|
||||
sys.stdout = old_stdout
|
||||
output = repr(e)
|
||||
print(output)
|
||||
return output
|
||||
|
||||
|
||||
python_repl = PythonREPL()
|
||||
|
||||
|
||||
def python(command: str):
|
||||
"""
|
||||
A Python shell. Use this to execute python commands. Input should be a valid python command.
|
||||
If you want to see the output of a value, you should print it out with `print(...)`.
|
||||
"""
|
||||
|
||||
command = command.strip().strip("```")
|
||||
return python_repl.run(command)
|
||||
@@ -0,0 +1,4 @@
|
||||
promptflow
|
||||
promptflow-tools
|
||||
tiktoken
|
||||
bs4
|
||||
@@ -0,0 +1 @@
|
||||
You are {{name}}, {{role}}
|
||||
@@ -0,0 +1 @@
|
||||
Determine which next function to use, and respond using stringfield JSON object.
|
||||
@@ -0,0 +1 @@
|
||||
Goals:
|
||||
@@ -0,0 +1,166 @@
|
||||
import time
|
||||
from typing import List
|
||||
import re
|
||||
import tiktoken
|
||||
import logging
|
||||
import sys
|
||||
import json
|
||||
|
||||
FORMATTER = logging.Formatter(
|
||||
fmt="[%(asctime)s] %(name)-8s %(levelname)-8s %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S %z",
|
||||
)
|
||||
|
||||
|
||||
def get_logger(name: str, level=logging.INFO) -> logging.Logger:
|
||||
logger = logging.Logger(name)
|
||||
# log to sys.stdout for backward compatibility.
|
||||
# TODO: May need to be removed in the future, after local/blob file stream are fully supported.
|
||||
stdout_handler = logging.StreamHandler(sys.stdout)
|
||||
stdout_handler.setFormatter(FORMATTER)
|
||||
logger.addHandler(stdout_handler)
|
||||
logger.setLevel(level)
|
||||
return logger
|
||||
|
||||
|
||||
def parse_reply(text: str):
|
||||
try:
|
||||
parsed = json.loads(text, strict=False)
|
||||
except json.JSONDecodeError:
|
||||
preprocessed_text = preprocess_json_input(text)
|
||||
try:
|
||||
parsed = json.loads(preprocessed_text, strict=False)
|
||||
except Exception:
|
||||
return {"Error": f"Could not parse invalid json: {text}"}
|
||||
except TypeError:
|
||||
return {"Error": f"the JSON object must be str, bytes or bytearray not {type(text)}"}
|
||||
return parsed
|
||||
|
||||
|
||||
def count_message_tokens(
|
||||
messages: List, tokens_per_message: int, tokens_per_name: int, model: str = "gpt-3.5-turbo-0301"
|
||||
) -> int:
|
||||
"""
|
||||
Returns the number of tokens used by a list of messages.
|
||||
|
||||
Args:
|
||||
messages (list): A list of messages, each of which is a dictionary
|
||||
containing the role and content of the message.
|
||||
model (str): The name of the model to use for tokenization.
|
||||
Defaults to "gpt-3.5-turbo-0301".
|
||||
|
||||
Returns:
|
||||
int: The number of tokens used by the list of messages.
|
||||
"""
|
||||
tokens_per_message = tokens_per_message
|
||||
tokens_per_name = tokens_per_name
|
||||
try:
|
||||
encoding = tiktoken.encoding_for_model(model)
|
||||
except KeyError:
|
||||
encoding = tiktoken.get_encoding("cl100k_base")
|
||||
if model == "gpt-3.5-turbo":
|
||||
# !Note: gpt-3.5-turbo may change over time.
|
||||
# Returning num tokens assuming gpt-3.5-turbo-0301.")
|
||||
return count_message_tokens(messages, tokens_per_message, tokens_per_name, model="gpt-3.5-turbo-0301")
|
||||
elif model == "gpt-4":
|
||||
# !Note: gpt-4 may change over time. Returning num tokens assuming gpt-4-0314.")
|
||||
return count_message_tokens(messages, tokens_per_message, tokens_per_name, model="gpt-4-0314")
|
||||
elif model == "gpt-3.5-turbo-0301":
|
||||
tokens_per_message = (
|
||||
4 # every message follows <|start|>{role/name}\n{content}<|end|>\n
|
||||
)
|
||||
tokens_per_name = -1 # if there's a name, the role is omitted
|
||||
elif model == "gpt-4-0314":
|
||||
tokens_per_message = 3
|
||||
tokens_per_name = 1
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"num_tokens_from_messages() is not implemented for model {model}.\n"
|
||||
" See https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken for"
|
||||
" information on how messages are converted to tokens."
|
||||
)
|
||||
num_tokens = 0
|
||||
for message in messages:
|
||||
num_tokens += tokens_per_message
|
||||
for key, value in message.items():
|
||||
num_tokens += len(encoding.encode(value))
|
||||
if key == "name":
|
||||
num_tokens += tokens_per_name
|
||||
num_tokens += 3 # every reply is primed with <|start|>assistant<|message|>
|
||||
return num_tokens
|
||||
|
||||
|
||||
def count_string_tokens(string: str, model_name="gpt-3.5-turbo") -> int:
|
||||
"""
|
||||
Returns the number of tokens in a text string.
|
||||
|
||||
Args:
|
||||
string (str): The text string.
|
||||
model_name (str): The name of the encoding to use. (e.g., "gpt-3.5-turbo")
|
||||
|
||||
Returns:
|
||||
int: The number of tokens in the text string.
|
||||
"""
|
||||
encoding = tiktoken.encoding_for_model(model_name)
|
||||
return len(encoding.encode(string))
|
||||
|
||||
|
||||
def create_chat_message(role, content, name=None):
|
||||
"""
|
||||
Create a chat message with the given role and content.
|
||||
|
||||
Args:
|
||||
role (str): The role of the message sender, e.g., "system", "user", or "assistant".
|
||||
content (str): The content of the message.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the role and content of the message.
|
||||
"""
|
||||
if name is None:
|
||||
return {"role": role, "content": content}
|
||||
else:
|
||||
return {"role": role, "name": name, "content": content}
|
||||
|
||||
|
||||
def generate_context(prompt, full_message_history, user_prompt, tokens_per_message, tokens_per_name,
|
||||
model="gpt-3.5-turbo"):
|
||||
current_context = [
|
||||
create_chat_message("system", prompt),
|
||||
create_chat_message(
|
||||
"system", f"The current time and date is {time.strftime('%c')}"
|
||||
),
|
||||
create_chat_message("user", user_prompt),
|
||||
]
|
||||
|
||||
# Add messages from the full message history until we reach the token limit
|
||||
next_message_to_add_index = len(full_message_history) - 1
|
||||
insertion_index = len(current_context)
|
||||
# Count the currently used tokens
|
||||
current_tokens_used = count_message_tokens(current_context, tokens_per_message, tokens_per_name, model)
|
||||
return (
|
||||
next_message_to_add_index,
|
||||
current_tokens_used,
|
||||
insertion_index,
|
||||
current_context,
|
||||
)
|
||||
|
||||
|
||||
def preprocess_json_input(input_str: str) -> str:
|
||||
# Replace single backslashes with double backslashes, while leaving already escaped ones intact
|
||||
corrected_str = re.sub(r'(?<!\\)\\(?!["\\/bfnrt]|u[0-9a-fA-F]{4})', r"\\\\", input_str)
|
||||
return corrected_str
|
||||
|
||||
|
||||
def construct_prompt(current_context):
|
||||
update_current_context = []
|
||||
for item in current_context:
|
||||
role = item.get("role", None)
|
||||
content = item.get("content", None)
|
||||
name = item.get("name", None)
|
||||
|
||||
if name is not None:
|
||||
update_current_context.append(":\n".join([role, "name", name]) + "\n" + ":\n".join(["content", content]))
|
||||
else:
|
||||
update_current_context.append(":\n".join([role, content]))
|
||||
update_current_context = "\n".join(update_current_context)
|
||||
return update_current_context
|
||||
@@ -0,0 +1,62 @@
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
import requests
|
||||
|
||||
|
||||
def decode_str(string):
|
||||
return string.encode().decode("unicode-escape").encode("latin1").decode("utf-8")
|
||||
|
||||
|
||||
def get_page_sentence(page, count: int = 10):
|
||||
# find all paragraphs
|
||||
paragraphs = page.split("\n")
|
||||
paragraphs = [p.strip() for p in paragraphs if p.strip()]
|
||||
|
||||
# find all sentence
|
||||
sentences = []
|
||||
for p in paragraphs:
|
||||
sentences += p.split('. ')
|
||||
sentences = [s.strip() + '.' for s in sentences if s.strip()]
|
||||
# get first `count` number of sentences
|
||||
return ' '.join(sentences[:count])
|
||||
|
||||
|
||||
def remove_nested_parentheses(string):
|
||||
pattern = r'\([^()]+\)'
|
||||
while re.search(pattern, string):
|
||||
string = re.sub(pattern, '', string)
|
||||
return string
|
||||
|
||||
|
||||
def search(entity: str, count: int = 10):
|
||||
"""
|
||||
The input is an exact entity name. The action will search this entity name on Wikipedia and returns the first
|
||||
count sentences if it exists. If not, it will return some related entities to search next.
|
||||
"""
|
||||
|
||||
entity_ = entity.replace(" ", "+")
|
||||
search_url = f"https://en.wikipedia.org/w/index.php?search={entity_}"
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35"
|
||||
}
|
||||
response_text = requests.get(search_url, headers=headers).text
|
||||
soup = BeautifulSoup(response_text, features="html.parser")
|
||||
result_divs = soup.find_all("div", {"class": "mw-search-result-heading"})
|
||||
if result_divs: # mismatch
|
||||
result_titles = [decode_str(div.get_text().strip()) for div in result_divs]
|
||||
result_titles = [remove_nested_parentheses(result_title) for result_title in result_titles]
|
||||
obs = f"Could not find {entity}. Similar: {result_titles[:5]}."
|
||||
else:
|
||||
page_content = [p_ul.get_text().strip() for p_ul in soup.find_all("p") + soup.find_all("ul")]
|
||||
if any("may refer to:" in p for p in page_content):
|
||||
obs = search("[" + entity + "]")
|
||||
else:
|
||||
page = ""
|
||||
for content in page_content:
|
||||
if len(content.split(" ")) > 2:
|
||||
page += decode_str(content)
|
||||
if not content.endswith("\n"):
|
||||
page += "\n"
|
||||
obs = get_page_sentence(page, count=count)
|
||||
return obs
|
||||
@@ -0,0 +1,3 @@
|
||||
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
|
||||
AZURE_OPENAI_API_KEY=your-api-key
|
||||
AZURE_OPENAI_DEPLOYMENT=gpt-35-turbo-instruct
|
||||
@@ -0,0 +1,3 @@
|
||||
agent-framework>=1.0.1
|
||||
agent-framework-openai>=1.0.1
|
||||
python-dotenv
|
||||
@@ -0,0 +1,14 @@
|
||||
import asyncio
|
||||
|
||||
from workflow import create_workflow
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run("Hello World!")
|
||||
output = result.get_outputs()[0]
|
||||
print(f"Output: {output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,64 @@
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
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
|
||||
|
||||
load_dotenv()
|
||||
|
||||
_PROMPT_TEMPLATE = "Write a simple {text} program that displays the greeting message."
|
||||
|
||||
|
||||
class PromptExecutor(Executor):
|
||||
@handler
|
||||
async def receive(self, text: str, ctx: WorkflowContext[str]) -> None:
|
||||
prompt = _PROMPT_TEMPLATE.format(text=text)
|
||||
await ctx.send_message(prompt)
|
||||
|
||||
|
||||
class LLMExecutor(Executor):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
client = OpenAIChatClient(
|
||||
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
||||
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
)
|
||||
self._agent = Agent(
|
||||
client=client,
|
||||
name="BasicAgent",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
@handler
|
||||
async def call_llm(self, prompt: str, ctx: WorkflowContext[Never, str]) -> None:
|
||||
response = await self._agent.run(prompt)
|
||||
await ctx.yield_output(response.text)
|
||||
|
||||
|
||||
def create_workflow():
|
||||
"""Create a fresh workflow instance.
|
||||
|
||||
MAF workflows do not support concurrent execution, so each
|
||||
concurrent caller needs its own workflow instance.
|
||||
"""
|
||||
_prompt = PromptExecutor(id="hello_prompt")
|
||||
_llm = LLMExecutor(id="llm")
|
||||
return (
|
||||
WorkflowBuilder(name="BasicWorkflow", start_executor=_prompt)
|
||||
.add_edge(_prompt, _llm)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run("Hello World!")
|
||||
print(result.get_outputs()[0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,3 @@
|
||||
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
|
||||
AZURE_OPENAI_API_KEY=your-api-key
|
||||
AZURE_OPENAI_DEPLOYMENT=gpt-35-turbo
|
||||
@@ -0,0 +1,3 @@
|
||||
agent-framework>=1.0.1
|
||||
agent-framework-openai>=1.0.1
|
||||
python-dotenv
|
||||
@@ -0,0 +1,14 @@
|
||||
import asyncio
|
||||
|
||||
from workflow import create_workflow
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run("Python Hello World!")
|
||||
output = result.get_outputs()[0]
|
||||
print(f"Output: {output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,67 @@
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
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
|
||||
|
||||
load_dotenv()
|
||||
|
||||
_SYSTEM_PROMPT = (
|
||||
"You are a assistant which can write code. Response should only contain code."
|
||||
)
|
||||
_USER_TEMPLATE = "Write a simple {text} program that displays the greeting message."
|
||||
|
||||
|
||||
class PromptExecutor(Executor):
|
||||
@handler
|
||||
async def receive(self, text: str, ctx: WorkflowContext[str]) -> None:
|
||||
prompt = _USER_TEMPLATE.format(text=text)
|
||||
await ctx.send_message(prompt)
|
||||
|
||||
|
||||
class LLMExecutor(Executor):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
client = OpenAIChatClient(
|
||||
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
||||
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
)
|
||||
self._agent = Agent(
|
||||
client=client,
|
||||
name="CodeAgent",
|
||||
instructions=_SYSTEM_PROMPT,
|
||||
)
|
||||
|
||||
@handler
|
||||
async def call_llm(self, prompt: str, ctx: WorkflowContext[Never, str]) -> None:
|
||||
response = await self._agent.run(prompt)
|
||||
await ctx.yield_output(response.text)
|
||||
|
||||
|
||||
def create_workflow():
|
||||
"""Create a fresh workflow instance.
|
||||
|
||||
MAF workflows do not support concurrent execution, so each
|
||||
concurrent caller needs its own workflow instance.
|
||||
"""
|
||||
_prompt = PromptExecutor(id="hello_prompt")
|
||||
_llm = LLMExecutor(id="llm")
|
||||
return (
|
||||
WorkflowBuilder(name="BasicBuiltinLLMWorkflow", start_executor=_prompt)
|
||||
.add_edge(_prompt, _llm)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run("Python Hello World!")
|
||||
print(result.get_outputs()[0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,74 @@
|
||||
# Basic flow with builtin llm tool
|
||||
A basic standard flow that calls Azure OpenAI with builtin llm tool.
|
||||
|
||||
Tools used in this flow:
|
||||
- `prompt` tool
|
||||
- built-in `llm` tool
|
||||
|
||||
Connections used in this flow:
|
||||
- `azure_open_ai` connection
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install promptflow sdk and other dependencies:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Setup connection
|
||||
Prepare your Azure OpenAI resource follow this [instruction](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal) and get your `api_key` if you don't have one.
|
||||
|
||||
Note in this example, we are using [chat api](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/chatgpt?pivots=programming-language-chat-completions), please use `gpt-35-turbo` or `gpt-4` model deployment.
|
||||
|
||||
Ensure you have created `open_ai_connection` connection before.
|
||||
```bash
|
||||
pf connection show -n open_ai_connection
|
||||
```
|
||||
|
||||
Create connection if you haven't done that. Ensure you have put your azure OpenAI endpoint key in [azure_openai.yml](../../../connections/azure_openai.yml) file.
|
||||
```bash
|
||||
# Override keys with --set to avoid yaml file changes
|
||||
pf connection create -f ../../../connections/azure_openai.yml --name open_ai_connection --set api_key=<your_api_key> api_base=<your_api_base>
|
||||
```
|
||||
|
||||
|
||||
## Run flow
|
||||
|
||||
### Run with single line input
|
||||
|
||||
```bash
|
||||
# test with default input value in flow.dag.yaml
|
||||
pf flow test --flow .
|
||||
|
||||
# test with inputs
|
||||
pf flow test --flow . --inputs text="Python Hello World!"
|
||||
```
|
||||
|
||||
### run with multiple lines data
|
||||
|
||||
- create run
|
||||
```bash
|
||||
pf run create --flow . --data ./data.jsonl --column-mapping text='${data.text}' --stream
|
||||
```
|
||||
|
||||
You can also skip providing `column-mapping` if provided data has same column name as the flow.
|
||||
Reference [here](https://aka.ms/pf/column-mapping) for default behavior when `column-mapping` not provided in CLI.
|
||||
|
||||
- list and show run meta
|
||||
```bash
|
||||
# list created run
|
||||
pf run list
|
||||
|
||||
# get a sample run name
|
||||
name=$(pf run list -r 10 | jq '.[] | select(.name | contains("basic_with_builtin_llm")) | .name'| head -n 1 | tr -d '"')
|
||||
|
||||
# show specific run detail
|
||||
pf run show --name $name
|
||||
|
||||
# show output
|
||||
pf run show-details --name $name
|
||||
|
||||
# visualize run in browser
|
||||
pf run visualize --name $name
|
||||
```
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{"text": "Python Hello World!"}
|
||||
{"text": "C Hello World!"}
|
||||
{"text": "C# Hello World!"}
|
||||
@@ -0,0 +1,34 @@
|
||||
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/Flow.schema.json
|
||||
inputs:
|
||||
text:
|
||||
type: string
|
||||
default: Python Hello World!
|
||||
outputs:
|
||||
output:
|
||||
type: string
|
||||
reference: ${llm.output}
|
||||
nodes:
|
||||
- name: hello_prompt
|
||||
type: prompt
|
||||
inputs:
|
||||
text: ${inputs.text}
|
||||
source:
|
||||
type: code
|
||||
path: hello.jinja2
|
||||
- name: llm
|
||||
type: llm
|
||||
inputs:
|
||||
prompt: ${hello_prompt.output}
|
||||
# This is to easily switch between openai and azure openai.
|
||||
# deployment_name is required by azure openai, model is required by openai.
|
||||
deployment_name: gpt-35-turbo
|
||||
model: gpt-3.5-turbo
|
||||
max_tokens: '120'
|
||||
source:
|
||||
type: code
|
||||
path: hello.jinja2
|
||||
connection: open_ai_connection
|
||||
api: chat
|
||||
node_variants: {}
|
||||
environment:
|
||||
python_requirements_txt: requirements.txt
|
||||
@@ -0,0 +1,5 @@
|
||||
# system:
|
||||
You are a assistant which can write code. Response should only contain code.
|
||||
|
||||
# user:
|
||||
Write a simple {{text}} program that displays the greeting message.
|
||||
@@ -0,0 +1,3 @@
|
||||
promptflow
|
||||
promptflow-tools
|
||||
python-dotenv
|
||||
@@ -0,0 +1,3 @@
|
||||
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
|
||||
AZURE_OPENAI_API_KEY=your-api-key
|
||||
AZURE_OPENAI_DEPLOYMENT=gpt-35-turbo-instruct
|
||||
@@ -0,0 +1,3 @@
|
||||
agent-framework>=1.0.1
|
||||
agent-framework-openai>=1.0.1
|
||||
python-dotenv
|
||||
@@ -0,0 +1,14 @@
|
||||
import asyncio
|
||||
|
||||
from workflow import create_workflow
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run("Hello World!")
|
||||
output = result.get_outputs()[0]
|
||||
print(f"Output: {output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,64 @@
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
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
|
||||
|
||||
load_dotenv()
|
||||
|
||||
_PROMPT_TEMPLATE = "Write a simple {text} program that displays the greeting message."
|
||||
|
||||
|
||||
class PromptExecutor(Executor):
|
||||
@handler
|
||||
async def receive(self, text: str, ctx: WorkflowContext[str]) -> None:
|
||||
prompt = _PROMPT_TEMPLATE.format(text=text)
|
||||
await ctx.send_message(prompt)
|
||||
|
||||
|
||||
class LLMExecutor(Executor):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
client = OpenAIChatClient(
|
||||
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
||||
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
)
|
||||
self._agent = Agent(
|
||||
client=client,
|
||||
name="BasicConnectionAgent",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
@handler
|
||||
async def call_llm(self, prompt: str, ctx: WorkflowContext[Never, str]) -> None:
|
||||
response = await self._agent.run(prompt)
|
||||
await ctx.yield_output(response.text)
|
||||
|
||||
|
||||
def create_workflow():
|
||||
"""Create a fresh workflow instance.
|
||||
|
||||
MAF workflows do not support concurrent execution, so each
|
||||
concurrent caller needs its own workflow instance.
|
||||
"""
|
||||
_prompt = PromptExecutor(id="hello_prompt")
|
||||
_llm = LLMExecutor(id="llm")
|
||||
return (
|
||||
WorkflowBuilder(name="BasicWithConnectionWorkflow", start_executor=_prompt)
|
||||
.add_edge(_prompt, _llm)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run("Hello World!")
|
||||
print(result.get_outputs()[0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,108 @@
|
||||
# Basic flow with custom connection
|
||||
A basic standard flow that using custom python tool calls Azure OpenAI with connection info stored in custom connection.
|
||||
|
||||
Tools used in this flow:
|
||||
- `prompt` tool
|
||||
- custom `python` Tool
|
||||
|
||||
Connections used in this flow:
|
||||
- None
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install promptflow sdk and other dependencies:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Setup connection
|
||||
Prepare your Azure OpenAI resource follow this [instruction](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal) and get your `api_key` if you don't have one.
|
||||
|
||||
Create connection if you haven't done that.
|
||||
```bash
|
||||
# Override keys with --set to avoid yaml file changes
|
||||
pf connection create -f custom.yml --set secrets.api_key=<your_api_key> configs.api_base=<your_api_base>
|
||||
```
|
||||
|
||||
Ensure you have created `basic_custom_connection` connection.
|
||||
```bash
|
||||
pf connection show -n basic_custom_connection
|
||||
```
|
||||
|
||||
## Run flow
|
||||
|
||||
### Run with single line input
|
||||
|
||||
```bash
|
||||
# test with default input value in flow.dag.yaml
|
||||
pf flow test --flow .
|
||||
|
||||
# test with flow inputs
|
||||
pf flow test --flow . --inputs text="Hello World!"
|
||||
|
||||
# test node with inputs
|
||||
pf flow test --flow . --node llm --inputs prompt="Write a simple Hello World! program that displays the greeting message."
|
||||
|
||||
```
|
||||
|
||||
### Run with multiple lines data
|
||||
|
||||
- create run
|
||||
```bash
|
||||
pf run create --flow . --data ./data.jsonl --column-mapping text='${data.text}' --stream
|
||||
```
|
||||
|
||||
You can also skip providing `column-mapping` if provided data has same column name as the flow.
|
||||
Reference [here](https://aka.ms/pf/column-mapping) for default behavior when `column-mapping` not provided in CLI.
|
||||
|
||||
- list and show run meta
|
||||
```bash
|
||||
# list created run
|
||||
pf run list -r 3
|
||||
|
||||
# get a sample run name
|
||||
name=$(pf run list -r 10 | jq '.[] | select(.name | contains("basic_with_connection")) | .name'| head -n 1 | tr -d '"')
|
||||
|
||||
# show specific run detail
|
||||
pf run show --name $name
|
||||
|
||||
# show output
|
||||
pf run show-details --name $name
|
||||
|
||||
# visualize run in browser
|
||||
pf run visualize --name $name
|
||||
```
|
||||
|
||||
### Run with connection override
|
||||
|
||||
Ensure you have created `open_ai_connection` connection before.
|
||||
|
||||
```bash
|
||||
pf connection show -n open_ai_connection
|
||||
```
|
||||
|
||||
Create connection if you haven't done that.
|
||||
```bash
|
||||
# Override keys with --set to avoid yaml file changes
|
||||
pf connection create --file ../../../connections/azure_openai.yml --set api_key=<your_api_key> api_base=<your_api_base>
|
||||
```
|
||||
|
||||
Run flow with newly created connection.
|
||||
|
||||
```bash
|
||||
pf run create --flow . --data ./data.jsonl --connections llm.connection=open_ai_connection --column-mapping text='${data.text}' --stream
|
||||
```
|
||||
|
||||
### Run in cloud with connection override
|
||||
|
||||
Ensure you have created `open_ai_connection` connection in cloud. Reference [this notebook](../../../tutorials/get-started/quickstart-azure.ipynb) on how to create connections in cloud with UI.
|
||||
|
||||
Run flow with connection `open_ai_connection`.
|
||||
|
||||
```bash
|
||||
# set default workspace
|
||||
az account set -s <your_subscription_id>
|
||||
az configure --defaults group=<your_resource_group_name> workspace=<your_workspace_name>
|
||||
|
||||
pfazure run create --flow . --data ./data.jsonl --connections llm.connection=open_ai_connection --column-mapping text='${data.text}' --stream
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/CustomConnection.schema.json
|
||||
name: basic_custom_connection
|
||||
type: custom
|
||||
configs:
|
||||
api_type: azure
|
||||
api_version: 2023-03-15-preview
|
||||
api_base: https://<to-be-replaced>.openai.azure.com/
|
||||
secrets: # must-have
|
||||
api_key: <to-be-replaced>
|
||||
@@ -0,0 +1,3 @@
|
||||
{"text": "Python Hello World!"}
|
||||
{"text": "C Hello World!"}
|
||||
{"text": "C# Hello World!"}
|
||||
@@ -0,0 +1,29 @@
|
||||
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/Flow.schema.json
|
||||
inputs:
|
||||
text:
|
||||
type: string
|
||||
default: Hello World!
|
||||
outputs:
|
||||
output:
|
||||
type: string
|
||||
reference: ${llm.output}
|
||||
nodes:
|
||||
- name: hello_prompt
|
||||
type: prompt
|
||||
source:
|
||||
type: code
|
||||
path: hello.jinja2
|
||||
inputs:
|
||||
text: ${inputs.text}
|
||||
- name: llm
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: hello.py
|
||||
inputs:
|
||||
connection: basic_custom_connection
|
||||
deployment_name: gpt-35-turbo-instruct
|
||||
max_tokens: "120"
|
||||
prompt: ${hello_prompt.output}
|
||||
environment:
|
||||
python_requirements_txt: requirements.txt
|
||||
@@ -0,0 +1,2 @@
|
||||
{# Please replace the template with your own prompt. #}
|
||||
Write a simple {{text}} program that displays the greeting message.
|
||||
@@ -0,0 +1,85 @@
|
||||
from typing import Union
|
||||
from openai.version import VERSION as OPENAI_VERSION
|
||||
|
||||
from promptflow.core import tool
|
||||
from promptflow.connections import CustomConnection, AzureOpenAIConnection
|
||||
|
||||
# The inputs section will change based on the arguments of the tool function, after you save the code
|
||||
# Adding type to arguments and return value will help the system show the types properly
|
||||
# Please update the function name/signature per need
|
||||
|
||||
|
||||
def to_bool(value) -> bool:
|
||||
return str(value).lower() == "true"
|
||||
|
||||
|
||||
def get_client(connection: Union[CustomConnection, AzureOpenAIConnection]):
|
||||
if OPENAI_VERSION.startswith("0."):
|
||||
raise Exception(
|
||||
"Please upgrade your OpenAI package to version >= 1.0.0 or using the command: pip install --upgrade openai."
|
||||
)
|
||||
# connection can be extract as a dict object contains the configs and secrets
|
||||
connection_dict = dict(connection)
|
||||
api_key = connection_dict.get("api_key")
|
||||
conn = dict(
|
||||
api_key=api_key,
|
||||
)
|
||||
if api_key.startswith("sk-"):
|
||||
from openai import OpenAI as Client
|
||||
else:
|
||||
from openai import AzureOpenAI as Client
|
||||
conn.update(
|
||||
azure_endpoint=connection_dict.get("api_base"),
|
||||
api_version=connection_dict.get("api_version", "2023-07-01-preview"),
|
||||
)
|
||||
return Client(**conn)
|
||||
|
||||
|
||||
@tool
|
||||
def my_python_tool(
|
||||
prompt: str,
|
||||
# for AOAI, deployment name is customized by user, not model name.
|
||||
deployment_name: str,
|
||||
suffix: str = None,
|
||||
max_tokens: int = 120,
|
||||
temperature: float = 1.0,
|
||||
top_p: float = 1.0,
|
||||
n: int = 1,
|
||||
logprobs: int = None,
|
||||
echo: bool = False,
|
||||
stop: list = None,
|
||||
presence_penalty: float = 0,
|
||||
frequency_penalty: float = 0,
|
||||
best_of: int = 1,
|
||||
logit_bias: dict = {},
|
||||
user: str = "",
|
||||
connection: Union[CustomConnection, AzureOpenAIConnection] = None,
|
||||
**kwargs,
|
||||
) -> str:
|
||||
|
||||
# TODO: remove below type conversion after client can pass json rather than string.
|
||||
echo = to_bool(echo)
|
||||
|
||||
response = get_client(connection).completions.create(
|
||||
prompt=prompt,
|
||||
model=deployment_name,
|
||||
# empty string suffix should be treated as None.
|
||||
suffix=suffix if suffix else None,
|
||||
max_tokens=int(max_tokens),
|
||||
temperature=float(temperature),
|
||||
top_p=float(top_p),
|
||||
n=int(n),
|
||||
logprobs=int(logprobs) if logprobs else None,
|
||||
echo=echo,
|
||||
# fix bug "[] is not valid under any of the given schemas-'stop'"
|
||||
stop=stop if stop else None,
|
||||
presence_penalty=float(presence_penalty),
|
||||
frequency_penalty=float(frequency_penalty),
|
||||
best_of=int(best_of),
|
||||
# Logit bias must be a dict if we passed it to openai api.
|
||||
logit_bias=logit_bias if logit_bias else {},
|
||||
user=user,
|
||||
)
|
||||
|
||||
# get first element because prompt is single.
|
||||
return response.choices[0].text
|
||||
@@ -0,0 +1,3 @@
|
||||
promptflow[azure]
|
||||
promptflow-tools
|
||||
python-dotenv
|
||||
@@ -0,0 +1,3 @@
|
||||
AZURE_OPENAI_API_KEY=<your_AOAI_key>
|
||||
AZURE_OPENAI_API_BASE=<your_AOAI_endpoint>
|
||||
AZURE_OPENAI_API_TYPE=azure
|
||||
@@ -0,0 +1,134 @@
|
||||
# Basic standard flow
|
||||
A basic standard flow using custom python tool that calls Azure OpenAI with connection info stored in environment variables.
|
||||
|
||||
Tools used in this flow:
|
||||
- `prompt` tool
|
||||
- custom `python` Tool
|
||||
|
||||
Connections used in this flow:
|
||||
- None
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install promptflow sdk and other dependencies:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Run flow
|
||||
|
||||
- Prepare your Azure OpenAI resource follow this [instruction](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal) and get your `api_key` if you don't have one.
|
||||
|
||||
- Setup environment variables
|
||||
|
||||
Ensure you have put your azure OpenAI endpoint key in [.env](.env) file. You can create one refer to this [example file](.env.example).
|
||||
|
||||
```bash
|
||||
cat .env
|
||||
```
|
||||
|
||||
- Test flow/node
|
||||
```bash
|
||||
# test with default input value in flow.dag.yaml
|
||||
pf flow test --flow .
|
||||
|
||||
# test with flow inputs
|
||||
pf flow test --flow . --inputs text="Java Hello World!"
|
||||
|
||||
# test node with inputs
|
||||
pf flow test --flow . --node llm --inputs prompt="Write a simple Hello World program that displays the greeting message."
|
||||
```
|
||||
|
||||
- Create run with multiple lines data
|
||||
```bash
|
||||
# using environment from .env file (loaded in user code: hello.py)
|
||||
pf run create --flow . --data ./data.jsonl --column-mapping text='${data.text}' --stream
|
||||
```
|
||||
|
||||
You can also skip providing `column-mapping` if provided data has same column name as the flow.
|
||||
Reference [here](https://aka.ms/pf/column-mapping) for default behavior when `column-mapping` not provided in CLI.
|
||||
|
||||
- List and show run meta
|
||||
```bash
|
||||
# list created run
|
||||
pf run list
|
||||
|
||||
# get a sample run name
|
||||
name=$(pf run list -r 10 | jq '.[] | select(.name | contains("basic_variant_0")) | .name'| head -n 1 | tr -d '"')
|
||||
|
||||
# show specific run detail
|
||||
pf run show --name $name
|
||||
|
||||
# show output
|
||||
pf run show-details --name $name
|
||||
|
||||
# visualize run in browser
|
||||
pf run visualize --name $name
|
||||
```
|
||||
|
||||
## Run flow with connection
|
||||
Storing connection info in .env with plaintext is not safe. We recommend to use `pf connection` to guard secrets like `api_key` from leak.
|
||||
|
||||
- Show or create `open_ai_connection`
|
||||
```bash
|
||||
# create connection from `azure_openai.yml` file
|
||||
# Override keys with --set to avoid yaml file changes
|
||||
pf connection create --file ../../../connections/azure_openai.yml --set api_key=<your_api_key> api_base=<your_api_base>
|
||||
|
||||
# check if connection exists
|
||||
pf connection show -n open_ai_connection
|
||||
```
|
||||
|
||||
- Test using connection secret specified in environment variables
|
||||
**Note**: we used `'` to wrap value since it supports raw value without escape in powershell & bash. For windows command prompt, you may remove the `'` to avoid it become part of the value.
|
||||
|
||||
```bash
|
||||
# test with default input value in flow.dag.yaml
|
||||
pf flow test --flow . --environment-variables AZURE_OPENAI_API_KEY='${open_ai_connection.api_key}' AZURE_OPENAI_API_BASE='${open_ai_connection.api_base}'
|
||||
```
|
||||
|
||||
- Create run using connection secret binding specified in environment variables, see [run.yml](run.yml)
|
||||
```bash
|
||||
# create run
|
||||
pf run create --flow . --data ./data.jsonl --stream --environment-variables AZURE_OPENAI_API_KEY='${open_ai_connection.api_key}' AZURE_OPENAI_API_BASE='${open_ai_connection.api_base}' --column-mapping text='${data.text}'
|
||||
# create run using yaml file
|
||||
pf run create --file run.yml --stream
|
||||
|
||||
# show outputs
|
||||
name=$(pf run list -r 10 | jq '.[] | select(.name | contains("basic_variant_0")) | .name'| head -n 1 | tr -d '"')
|
||||
pf run show-details --name $name
|
||||
```
|
||||
|
||||
## Run flow in cloud with connection
|
||||
- Assume we already have a connection named `open_ai_connection` in workspace.
|
||||
```bash
|
||||
# set default workspace
|
||||
az account set -s <your_subscription_id>
|
||||
az configure --defaults group=<your_resource_group_name> workspace=<your_workspace_name>
|
||||
```
|
||||
|
||||
- Create run
|
||||
```bash
|
||||
# run with environment variable reference connection in azureml workspace
|
||||
pfazure run create --flow . --data ./data.jsonl --environment-variables AZURE_OPENAI_API_KEY='${open_ai_connection.api_key}' AZURE_OPENAI_API_BASE='${open_ai_connection.api_base}' --column-mapping text='${data.text}' --stream
|
||||
# run using yaml file
|
||||
pfazure run create --file run.yml --stream
|
||||
```
|
||||
|
||||
- List and show run meta
|
||||
```bash
|
||||
# list created run
|
||||
pfazure run list -r 3
|
||||
|
||||
# get a sample run name
|
||||
name=$(pfazure run list -r 100 | jq '.[] | select(.name | contains("basic_variant_0")) | .name'| head -n 1 | tr -d '"')
|
||||
|
||||
# show specific run detail
|
||||
pfazure run show --name $name
|
||||
|
||||
# show output
|
||||
pfazure run show-details --name $name
|
||||
|
||||
# visualize run in browser
|
||||
pfazure run visualize --name $name
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
{"text": "Python Hello World!"}
|
||||
{"text": "C Hello World!"}
|
||||
{"text": "C# Hello World!"}
|
||||
@@ -0,0 +1,28 @@
|
||||
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/Flow.schema.json
|
||||
environment:
|
||||
python_requirements_txt: requirements.txt
|
||||
inputs:
|
||||
text:
|
||||
type: string
|
||||
default: Hello World!
|
||||
outputs:
|
||||
output:
|
||||
type: string
|
||||
reference: ${llm.output}
|
||||
nodes:
|
||||
- name: hello_prompt
|
||||
type: prompt
|
||||
source:
|
||||
type: code
|
||||
path: hello.jinja2
|
||||
inputs:
|
||||
text: ${inputs.text}
|
||||
- name: llm
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: hello.py
|
||||
inputs:
|
||||
prompt: ${hello_prompt.output}
|
||||
deployment_name: gpt-35-turbo-instruct
|
||||
max_tokens: "120"
|
||||
@@ -0,0 +1,2 @@
|
||||
{# Please replace the template with your own prompt. #}
|
||||
Write a simple {{text}} program that displays the greeting message.
|
||||
@@ -0,0 +1,88 @@
|
||||
import os
|
||||
from openai.version import VERSION as OPENAI_VERSION
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from promptflow.core import tool
|
||||
|
||||
# The inputs section will change based on the arguments of the tool function, after you save the code
|
||||
# Adding type to arguments and return value will help the system show the types properly
|
||||
# Please update the function name/signature per need
|
||||
|
||||
|
||||
def to_bool(value) -> bool:
|
||||
return str(value).lower() == "true"
|
||||
|
||||
|
||||
def get_client():
|
||||
if OPENAI_VERSION.startswith("0."):
|
||||
raise Exception(
|
||||
"Please upgrade your OpenAI package to version >= 1.0.0 or using the command: pip install --upgrade openai."
|
||||
)
|
||||
api_key = os.environ["AZURE_OPENAI_API_KEY"]
|
||||
conn = dict(
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
)
|
||||
if api_key.startswith("sk-"):
|
||||
from openai import OpenAI as Client
|
||||
else:
|
||||
from openai import AzureOpenAI as Client
|
||||
conn.update(
|
||||
azure_endpoint=os.environ.get("AZURE_OPENAI_API_BASE", "azure"),
|
||||
api_version=os.environ.get("OPENAI_API_VERSION", "2023-07-01-preview"),
|
||||
)
|
||||
return Client(**conn)
|
||||
|
||||
|
||||
@tool
|
||||
def my_python_tool(
|
||||
prompt: str,
|
||||
# for AOAI, deployment name is customized by user, not model name.
|
||||
deployment_name: str,
|
||||
suffix: str = None,
|
||||
max_tokens: int = 120,
|
||||
temperature: float = 1.0,
|
||||
top_p: float = 1.0,
|
||||
n: int = 1,
|
||||
logprobs: int = None,
|
||||
echo: bool = False,
|
||||
stop: list = None,
|
||||
presence_penalty: float = 0,
|
||||
frequency_penalty: float = 0,
|
||||
best_of: int = 1,
|
||||
logit_bias: dict = {},
|
||||
user: str = "",
|
||||
**kwargs,
|
||||
) -> str:
|
||||
if "AZURE_OPENAI_API_KEY" not in os.environ or "AZURE_OPENAI_API_BASE" not in os.environ:
|
||||
# load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
if "AZURE_OPENAI_API_KEY" not in os.environ:
|
||||
raise Exception("Please specify environment variables: AZURE_OPENAI_API_KEY")
|
||||
|
||||
# TODO: remove below type conversion after client can pass json rather than string.
|
||||
echo = to_bool(echo)
|
||||
|
||||
response = get_client().completions.create(
|
||||
prompt=prompt,
|
||||
model=deployment_name,
|
||||
# empty string suffix should be treated as None.
|
||||
suffix=suffix if suffix else None,
|
||||
max_tokens=int(max_tokens),
|
||||
temperature=float(temperature),
|
||||
top_p=float(top_p),
|
||||
n=int(n),
|
||||
logprobs=int(logprobs) if logprobs else None,
|
||||
echo=echo,
|
||||
# fix bug "[] is not valid under any of the given schemas-'stop'"
|
||||
stop=stop if stop else None,
|
||||
presence_penalty=float(presence_penalty),
|
||||
frequency_penalty=float(frequency_penalty),
|
||||
best_of=int(best_of),
|
||||
# Logit bias must be a dict if we passed it to openai api.
|
||||
logit_bias=logit_bias if logit_bias else {},
|
||||
user=user,
|
||||
)
|
||||
|
||||
# get first element because prompt is single.
|
||||
return response.choices[0].text
|
||||
@@ -0,0 +1,3 @@
|
||||
promptflow[azure]
|
||||
promptflow-tools
|
||||
python-dotenv
|
||||
@@ -0,0 +1,10 @@
|
||||
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/Run.schema.json
|
||||
flow: .
|
||||
data: data.jsonl
|
||||
environment_variables:
|
||||
# environment variables from connection
|
||||
AZURE_OPENAI_API_KEY: ${open_ai_connection.api_key}
|
||||
AZURE_OPENAI_API_BASE: ${open_ai_connection.api_base}
|
||||
AZURE_OPENAI_API_TYPE: azure
|
||||
column_mapping:
|
||||
text: ${data.text}
|
||||
@@ -0,0 +1 @@
|
||||
agent-framework>=1.0.1
|
||||
@@ -0,0 +1,50 @@
|
||||
import asyncio
|
||||
from workflow import create_workflow
|
||||
|
||||
|
||||
async def main():
|
||||
"""Run the workflow with multiple inputs to demonstrate random conditions."""
|
||||
|
||||
test_questions = [
|
||||
"What is Prompt flow?",
|
||||
"How does it work?",
|
||||
"Tell me more about the features",
|
||||
"What is machine learning?",
|
||||
]
|
||||
|
||||
print("=" * 70)
|
||||
print("Testing Conditional Workflow with Random Conditions")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
safe_count = 0
|
||||
unsafe_count = 0
|
||||
|
||||
for i, question in enumerate(test_questions, 1):
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run(question)
|
||||
output = result.get_outputs()[0]
|
||||
|
||||
# Determine which path was taken based on the output
|
||||
is_safe_path = "Prompt flow is a suite" in output
|
||||
|
||||
if is_safe_path:
|
||||
safe_count += 1
|
||||
status = "✓ SAFE"
|
||||
else:
|
||||
unsafe_count += 1
|
||||
status = "✗ UNSAFE"
|
||||
|
||||
print(f"Test {i:2d} [{status}]: {question}")
|
||||
print(f" → {output}")
|
||||
print()
|
||||
|
||||
print("=" * 70)
|
||||
print("Results Summary:")
|
||||
print(f" Safe paths: {safe_count}/{len(test_questions)}")
|
||||
print(f" Unsafe paths: {unsafe_count}/{len(test_questions)}")
|
||||
print("=" * 70)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,65 @@
|
||||
import asyncio
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
|
||||
from typing_extensions import Never
|
||||
|
||||
from agent_framework import Executor, WorkflowBuilder, WorkflowContext, handler
|
||||
|
||||
|
||||
@dataclass
|
||||
class SafetyResult:
|
||||
question: str
|
||||
is_safe: bool
|
||||
|
||||
|
||||
class ContentSafetyExecutor(Executor):
|
||||
@handler
|
||||
async def check(self, question: str, ctx: WorkflowContext[SafetyResult]) -> None:
|
||||
# Placeholder: replace with a real content safety check.
|
||||
is_safe = random.choice([True, False])
|
||||
await ctx.send_message(SafetyResult(question=question, is_safe=is_safe))
|
||||
|
||||
|
||||
class LLMResultExecutor(Executor):
|
||||
@handler
|
||||
async def run_llm(self, msg: SafetyResult, ctx: WorkflowContext[Never, str]) -> None:
|
||||
# Placeholder: replace with a real LLM call.
|
||||
answer = (
|
||||
"Prompt flow is a suite of development tools designed to streamline "
|
||||
"the end-to-end development cycle of LLM-based AI applications."
|
||||
)
|
||||
await ctx.yield_output(answer)
|
||||
|
||||
|
||||
class DefaultResultExecutor(Executor):
|
||||
@handler
|
||||
async def default(self, msg: SafetyResult, ctx: WorkflowContext[Never, str]) -> None:
|
||||
await ctx.yield_output(f"I'm not familiar with your query: {msg.question}.")
|
||||
|
||||
|
||||
def create_workflow():
|
||||
"""Create a fresh workflow instance.
|
||||
|
||||
MAF workflows do not support concurrent execution, so each
|
||||
concurrent caller needs its own workflow instance.
|
||||
"""
|
||||
_safety = ContentSafetyExecutor(id="content_safety_check")
|
||||
_llm = LLMResultExecutor(id="llm_result")
|
||||
_default = DefaultResultExecutor(id="default_result")
|
||||
return (
|
||||
WorkflowBuilder(name="ConditionalIfElseWorkflow", start_executor=_safety)
|
||||
.add_edge(_safety, _llm, condition=lambda msg: msg.is_safe)
|
||||
.add_edge(_safety, _default, condition=lambda msg: not msg.is_safe)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run("What is Prompt flow?")
|
||||
print(f"Answer: {result.get_outputs()[0]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,61 @@
|
||||
# Conditional flow for if-else scenario
|
||||
|
||||
This example is a conditional flow for if-else scenario.
|
||||
|
||||
By following this example, you will learn how to create a conditional flow using the `activate config`.
|
||||
|
||||
## Flow description
|
||||
|
||||
In this flow, it checks if an input query passes content safety check. If it's denied, we'll return a default response; otherwise, we'll call LLM to get a response and then summarize the final results.
|
||||
|
||||
The following are two execution situations of this flow:
|
||||
- if input query passes content safety check:
|
||||
|
||||

|
||||
- else:
|
||||
|
||||

|
||||
|
||||
**Notice**: The `content_safety_check` and `llm_result` node in this flow are dummy nodes that do not actually use the conten safety tool and LLM tool. You can replace them with the real ones. Learn more: [LLM Tool](https://microsoft.github.io/promptflow/reference/tools-reference/llm-tool.html)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install promptflow sdk and other dependencies:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Run flow
|
||||
|
||||
- Test flow
|
||||
```bash
|
||||
# test with default input value in flow.dag.yaml
|
||||
pf flow test --flow .
|
||||
|
||||
# test with flow inputs
|
||||
pf flow test --flow . --inputs question="What is Prompt flow?"
|
||||
```
|
||||
|
||||
- Create run with multiple lines of data
|
||||
```bash
|
||||
# create a random run name
|
||||
run_name="conditional_flow_for_if_else_"$(openssl rand -hex 12)
|
||||
|
||||
# create run
|
||||
pf run create --flow . --data ./data.jsonl --column-mapping question='${data.question}' --stream --name $run_name
|
||||
```
|
||||
|
||||
- List and show run metadata
|
||||
```bash
|
||||
# list created run
|
||||
pf run list
|
||||
|
||||
# show specific run detail
|
||||
pf run show --name $run_name
|
||||
|
||||
# show output
|
||||
pf run show-details --name $run_name
|
||||
|
||||
# visualize run in browser
|
||||
pf run visualize --name $run_name
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
from promptflow.core import tool
|
||||
import random
|
||||
|
||||
|
||||
@tool
|
||||
def content_safety_check(text: str) -> str:
|
||||
# You can use a content safety node to replace this tool.
|
||||
return random.choice([True, False])
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@@ -0,0 +1,2 @@
|
||||
{"question": "What is Prompt flow?"}
|
||||
{"question": "What is ChatGPT?"}
|
||||
@@ -0,0 +1,6 @@
|
||||
from promptflow.core import tool
|
||||
|
||||
|
||||
@tool
|
||||
def default_result(question: str) -> str:
|
||||
return f"I'm not familiar with your query: {question}."
|
||||
@@ -0,0 +1,47 @@
|
||||
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/Flow.schema.json
|
||||
inputs:
|
||||
question:
|
||||
type: string
|
||||
default: What is Prompt flow?
|
||||
outputs:
|
||||
answer:
|
||||
type: string
|
||||
reference: ${generate_result.output}
|
||||
nodes:
|
||||
- name: content_safety_check
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: content_safety_check.py
|
||||
inputs:
|
||||
text: ${inputs.question}
|
||||
- name: llm_result
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: llm_result.py
|
||||
inputs:
|
||||
question: ${inputs.question}
|
||||
activate:
|
||||
when: ${content_safety_check.output}
|
||||
is: true
|
||||
- name: default_result
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: default_result.py
|
||||
inputs:
|
||||
question: ${inputs.question}
|
||||
activate:
|
||||
when: ${content_safety_check.output}
|
||||
is: false
|
||||
- name: generate_result
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: generate_result.py
|
||||
inputs:
|
||||
llm_result: ${llm_result.output}
|
||||
default_result: ${default_result.output}
|
||||
environment:
|
||||
python_requirements_txt: requirements.txt
|
||||
@@ -0,0 +1,9 @@
|
||||
from promptflow.core import tool
|
||||
|
||||
|
||||
@tool
|
||||
def generate_result(llm_result="", default_result="") -> str:
|
||||
if llm_result:
|
||||
return llm_result
|
||||
else:
|
||||
return default_result
|
||||
@@ -0,0 +1,10 @@
|
||||
from promptflow.core import tool
|
||||
|
||||
|
||||
@tool
|
||||
def llm_result(question: str) -> str:
|
||||
# You can use an LLM node to replace this tool.
|
||||
return (
|
||||
"Prompt flow is a suite of development tools designed to streamline "
|
||||
"the end-to-end development cycle of LLM-based AI applications."
|
||||
)
|
||||
@@ -0,0 +1,2 @@
|
||||
promptflow
|
||||
promptflow-tools
|
||||
@@ -0,0 +1,3 @@
|
||||
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
|
||||
AZURE_OPENAI_API_KEY=your-api-key
|
||||
AZURE_OPENAI_DEPLOYMENT=gpt-35-turbo
|
||||
@@ -0,0 +1,3 @@
|
||||
agent-framework>=1.0.1
|
||||
agent-framework-openai>=1.0.1
|
||||
python-dotenv
|
||||
@@ -0,0 +1,69 @@
|
||||
import asyncio
|
||||
from workflow import create_workflow
|
||||
|
||||
|
||||
async def main():
|
||||
"""Run the workflow with multiple queries to demonstrate the switch logic."""
|
||||
|
||||
# Test cases targeting each switch condition
|
||||
test_cases = [
|
||||
("When will my order be shipped?", "order_search"),
|
||||
("Where is my order?", "order_search"),
|
||||
("Can you track my package?", "order_search"),
|
||||
("Tell me about this product", "product_info"),
|
||||
("What is the specification?", "product_info"),
|
||||
("Who manufactures this?", "product_info"),
|
||||
("Recommend a good product", "product_recommendation"),
|
||||
("What products do you suggest?", "product_recommendation"),
|
||||
("Give me product recommendations", "product_recommendation"),
|
||||
("Something completely random query", "default/unknown"),
|
||||
]
|
||||
|
||||
print("=" * 80)
|
||||
print("Testing Conditional Switch Workflow")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
results_by_intention = {}
|
||||
|
||||
for i, (query, expected_intention) in enumerate(test_cases, 1):
|
||||
print(f"Test {i:2d}: {query}")
|
||||
print(f" Expected intention: {expected_intention}")
|
||||
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run(query)
|
||||
response = result.get_outputs()[0]
|
||||
print(f" Response: {response}")
|
||||
|
||||
# Track results
|
||||
if expected_intention not in results_by_intention:
|
||||
results_by_intention[expected_intention] = []
|
||||
results_by_intention[expected_intention].append(response)
|
||||
|
||||
print()
|
||||
|
||||
print("=" * 80)
|
||||
print("Summary of Switch Paths:")
|
||||
print("=" * 80)
|
||||
|
||||
intention_labels = {
|
||||
"order_search": "📦 Order Search",
|
||||
"product_info": "ℹ️ Product Info",
|
||||
"product_recommendation": "⭐ Product Recommendation",
|
||||
"default/unknown": "❓ Default/Unknown",
|
||||
}
|
||||
|
||||
for intention, label in intention_labels.items():
|
||||
if intention in results_by_intention:
|
||||
count = len(results_by_intention[intention])
|
||||
print(f"{label}: {count} matched")
|
||||
for response in results_by_intention[intention]:
|
||||
print(f" → {response}")
|
||||
else:
|
||||
print(f"{label}: 0 matched")
|
||||
|
||||
print("=" * 80)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,108 @@
|
||||
import asyncio
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
|
||||
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
|
||||
|
||||
load_dotenv()
|
||||
|
||||
_CLASSIFY_SYSTEM_PROMPT = """\
|
||||
There is a search bar in the mall APP and users can enter any query in the search bar.
|
||||
|
||||
The user may want to search for orders, view product information, or seek recommended products.
|
||||
|
||||
Therefore, please classify user intentions into the following three types \
|
||||
according to the query: product_recommendation, order_search, product_info
|
||||
|
||||
Please note that only the above three situations can be returned, and try not to include other return values."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class ClassifiedQuery:
|
||||
query: str
|
||||
intention: str
|
||||
|
||||
|
||||
class ClassifyExecutor(Executor):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
client = OpenAIChatClient(
|
||||
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
||||
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
)
|
||||
self._agent = Agent(
|
||||
client=client,
|
||||
name="ClassifyAgent",
|
||||
instructions=_CLASSIFY_SYSTEM_PROMPT,
|
||||
)
|
||||
|
||||
@handler
|
||||
async def classify(self, query: str, ctx: WorkflowContext[ClassifiedQuery]) -> None:
|
||||
response = await self._agent.run(f"The user's query is {query}")
|
||||
llm_result = response.text
|
||||
intentions_list = ["order_search", "product_info", "product_recommendation"]
|
||||
matches = [i for i in intentions_list if i in llm_result.lower()]
|
||||
intention = matches[0] if matches else "unknown"
|
||||
await ctx.send_message(ClassifiedQuery(query=query, intention=intention))
|
||||
|
||||
|
||||
class OrderSearchExecutor(Executor):
|
||||
@handler
|
||||
async def search(self, msg: ClassifiedQuery, ctx: WorkflowContext[Never, str]) -> None:
|
||||
await ctx.yield_output("Your order is being mailed, please wait patiently.")
|
||||
|
||||
|
||||
class ProductInfoExecutor(Executor):
|
||||
@handler
|
||||
async def info(self, msg: ClassifiedQuery, ctx: WorkflowContext[Never, str]) -> None:
|
||||
await ctx.yield_output("This product is produced by Microsoft.")
|
||||
|
||||
|
||||
class ProductRecommendationExecutor(Executor):
|
||||
@handler
|
||||
async def recommend(self, msg: ClassifiedQuery, ctx: WorkflowContext[Never, str]) -> None:
|
||||
await ctx.yield_output(
|
||||
"I recommend promptflow to you, which can solve your problem very well."
|
||||
)
|
||||
|
||||
|
||||
class DefaultExecutor(Executor):
|
||||
@handler
|
||||
async def default(self, msg: ClassifiedQuery, ctx: WorkflowContext[Never, str]) -> None:
|
||||
await ctx.yield_output("Sorry, no results matching your search were found.")
|
||||
|
||||
|
||||
def create_workflow():
|
||||
"""Create a fresh workflow instance.
|
||||
|
||||
MAF workflows do not support concurrent execution, so each
|
||||
concurrent caller needs its own workflow instance.
|
||||
"""
|
||||
_classify = ClassifyExecutor(id="classify")
|
||||
_order = OrderSearchExecutor(id="order_search")
|
||||
_product = ProductInfoExecutor(id="product_info")
|
||||
_recommend = ProductRecommendationExecutor(id="product_recommendation")
|
||||
_default = DefaultExecutor(id="default")
|
||||
return (
|
||||
WorkflowBuilder(name="ConditionalSwitchWorkflow", start_executor=_classify)
|
||||
.add_edge(_classify, _order, condition=lambda m: m.intention == "order_search")
|
||||
.add_edge(_classify, _product, condition=lambda m: m.intention == "product_info")
|
||||
.add_edge(_classify, _recommend, condition=lambda m: m.intention == "product_recommendation")
|
||||
.add_edge(_classify, _default, condition=lambda m: m.intention == "unknown")
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run("When will my order be shipped?")
|
||||
print(f"Response: {result.get_outputs()[0]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,78 @@
|
||||
# Conditional flow for switch scenario
|
||||
|
||||
This example is a conditional flow for switch scenario.
|
||||
|
||||
By following this example, you will learn how to create a conditional flow using the `activate config`.
|
||||
|
||||
## Flow description
|
||||
|
||||
In this flow, we set the background to the search function of a certain mall, use `activate config` to implement switch logic and determine user intent based on the input queries to achieve dynamic processing and generate user-oriented output.
|
||||
|
||||
- The `classify_with_llm` node analyzes user intent based on input query and provides one of the following results: "product_recommendation," "order_search," or "product_info".
|
||||
- The `class_check` node generates the correctly formatted user intent.
|
||||
- The `product_recommendation`, `order_search`, and `product_info` nodes are configured with activate config and are only executed when the output from `class_check` meets the specified conditions.
|
||||
- The `generate_response` node generates user-facing output.
|
||||
|
||||
For example, as the shown below, the input query is "When will my order be shipped" and the LLM node classifies the user intent as "order_search", resulting in both the `product_info` and `product_recommendation` nodes being bypassed and only the `order_search` node being executed, and then generating the outputs.
|
||||
|
||||

|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install promptflow sdk and other dependencies:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Setup connection
|
||||
|
||||
Prepare your Azure OpenAI resource follow this [instruction](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal) and get your `api_key` if you don't have one.
|
||||
|
||||
Note in this example, we are using [chat api](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/chatgpt?pivots=programming-language-chat-completions), please use `gpt-35-turbo` or `gpt-4` model deployment.
|
||||
|
||||
Create connection if you haven't done that. Ensure you have put your azure OpenAI endpoint key in [azure_openai.yml](../../../connections/azure_openai.yml) file.
|
||||
```bash
|
||||
# Override keys with --set to avoid yaml file changes
|
||||
pf connection create -f ../../../connections/azure_openai.yml --name open_ai_connection --set api_key=<your_api_key> api_base=<your_api_base>
|
||||
```
|
||||
|
||||
Note in [flow.dag.yaml](flow.dag.yaml) we are using connection named `open_ai_connection`.
|
||||
```bash
|
||||
# show registered connection
|
||||
pf connection show --name open_ai_connection
|
||||
```
|
||||
|
||||
## Run flow
|
||||
|
||||
- Test flow
|
||||
```bash
|
||||
# test with default input value in flow.dag.yaml
|
||||
pf flow test --flow .
|
||||
|
||||
# test with flow inputs
|
||||
pf flow test --flow . --inputs query="When will my order be shipped?"
|
||||
```
|
||||
|
||||
- Create run with multiple lines of data
|
||||
```bash
|
||||
# create a random run name
|
||||
run_name="conditional_flow_for_switch_"$(openssl rand -hex 12)
|
||||
|
||||
# create run
|
||||
pf run create --flow . --data ./data.jsonl --column-mapping query='${data.query}' --stream --name $run_name
|
||||
```
|
||||
|
||||
- List and show run metadata
|
||||
```bash
|
||||
# list created run
|
||||
pf run list
|
||||
|
||||
# show specific run detail
|
||||
pf run show --name $run_name
|
||||
|
||||
# show output
|
||||
pf run show-details --name $run_name
|
||||
|
||||
# visualize run in browser
|
||||
pf run visualize --name $run_name
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
from promptflow.core import tool
|
||||
|
||||
|
||||
@tool
|
||||
def class_check(llm_result: str) -> str:
|
||||
intentions_list = ["order_search", "product_info", "product_recommendation"]
|
||||
matches = [intention for intention in intentions_list if intention in llm_result.lower()]
|
||||
return matches[0] if matches else "unknown"
|
||||
@@ -0,0 +1,11 @@
|
||||
# system:
|
||||
There is a search bar in the mall APP and users can enter any query in the search bar.
|
||||
|
||||
The user may want to search for orders, view product information, or seek recommended products.
|
||||
|
||||
Therefore, please classify user intentions into the following three types according to the query: product_recommendation, order_search, product_info
|
||||
|
||||
Please note that only the above three situations can be returned, and try not to include other return values.
|
||||
|
||||
# user:
|
||||
The user's query is {{query}}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
@@ -0,0 +1,3 @@
|
||||
{"query": "When will my order be shipped?"}
|
||||
{"query": "Can you help me find information about this T-shirt?"}
|
||||
{"query": "Can you recommend me a useful prompt tool?"}
|
||||
@@ -0,0 +1,69 @@
|
||||
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/Flow.schema.json
|
||||
inputs:
|
||||
query:
|
||||
type: string
|
||||
default: When will my order be shipped?
|
||||
outputs:
|
||||
response:
|
||||
type: string
|
||||
reference: ${generate_response.output}
|
||||
nodes:
|
||||
- name: classify_with_llm
|
||||
type: llm
|
||||
source:
|
||||
type: code
|
||||
path: classify_with_llm.jinja2
|
||||
inputs:
|
||||
deployment_name: gpt-35-turbo
|
||||
max_tokens: 128
|
||||
query: ${inputs.query}
|
||||
connection: open_ai_connection
|
||||
api: chat
|
||||
- name: class_check
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: class_check.py
|
||||
inputs:
|
||||
llm_result: ${classify_with_llm.output}
|
||||
- name: order_search
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: order_search.py
|
||||
inputs:
|
||||
query: ${inputs.query}
|
||||
activate:
|
||||
when: ${class_check.output}
|
||||
is: order_search
|
||||
- name: product_info
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: product_info.py
|
||||
inputs:
|
||||
query: ${inputs.query}
|
||||
activate:
|
||||
when: ${class_check.output}
|
||||
is: product_info
|
||||
- name: product_recommendation
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: product_recommendation.py
|
||||
inputs:
|
||||
query: ${inputs.query}
|
||||
activate:
|
||||
when: ${class_check.output}
|
||||
is: product_recommendation
|
||||
- name: generate_response
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: generate_response.py
|
||||
inputs:
|
||||
order_search: ${order_search.output}
|
||||
product_info: ${product_info.output}
|
||||
product_recommendation: ${product_recommendation.output}
|
||||
environment:
|
||||
python_requirements_txt: requirements.txt
|
||||
@@ -0,0 +1,8 @@
|
||||
from promptflow.core import tool
|
||||
|
||||
|
||||
@tool
|
||||
def generate_response(order_search="", product_info="", product_recommendation="") -> str:
|
||||
default_response = "Sorry, no results matching your search were found."
|
||||
responses = [order_search, product_info, product_recommendation]
|
||||
return next((response for response in responses if response), default_response)
|
||||
@@ -0,0 +1,7 @@
|
||||
from promptflow.core import tool
|
||||
|
||||
|
||||
@tool
|
||||
def order_search(query: str) -> str:
|
||||
print(f"Your query is {query}.\nSearching for order...")
|
||||
return "Your order is being mailed, please wait patiently."
|
||||
@@ -0,0 +1,7 @@
|
||||
from promptflow.core import tool
|
||||
|
||||
|
||||
@tool
|
||||
def product_info(query: str) -> str:
|
||||
print(f"Your query is {query}.\nLooking for product information...")
|
||||
return "This product is produced by Microsoft."
|
||||
@@ -0,0 +1,7 @@
|
||||
from promptflow.core import tool
|
||||
|
||||
|
||||
@tool
|
||||
def product_recommendation(query: str) -> str:
|
||||
print(f"Your query is {query}.\nRecommending products...")
|
||||
return "I recommend promptflow to you, which can solve your problem very well."
|
||||
@@ -0,0 +1,2 @@
|
||||
promptflow
|
||||
promptflow-tools
|
||||
@@ -0,0 +1,3 @@
|
||||
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
|
||||
AZURE_OPENAI_API_KEY=your-api-key
|
||||
AZURE_OPENAI_DEPLOYMENT=gpt-35-turbo
|
||||
@@ -0,0 +1,3 @@
|
||||
agent-framework>=1.0.1
|
||||
agent-framework-openai>=1.0.1
|
||||
python-dotenv
|
||||
@@ -0,0 +1,18 @@
|
||||
import asyncio
|
||||
|
||||
from workflow import IntentInput, create_workflow
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run(
|
||||
IntentInput(
|
||||
history="Customer: I want to return my order\nAgent: Sure, I can help with that.",
|
||||
customer_info="Name: John Doe\nOrder: #12345 - Widget A",
|
||||
)
|
||||
)
|
||||
print(f"Intent: {result.get_outputs()[0]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,102 @@
|
||||
import asyncio
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
|
||||
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
|
||||
|
||||
load_dotenv()
|
||||
|
||||
_INTENT_SYSTEM_PROMPT = """\
|
||||
You are given a list of orders with item_numbers from a customer and a statement from the customer. \
|
||||
It is your job to identify the intent that the customer has with their statement. \
|
||||
Possible intents can be: "product return", "product exchange", "general question", "product question", "other"."""
|
||||
|
||||
_INTENT_USER_TEMPLATE = """\
|
||||
In triple backticks below is the customer information and a list of orders.
|
||||
|
||||
```
|
||||
{customer_info}
|
||||
```
|
||||
|
||||
In triple backticks below are the is the chat history with customer \
|
||||
statements and replies from the customer service agent:
|
||||
|
||||
```
|
||||
{history}
|
||||
```
|
||||
|
||||
What is the customer's `intent:` here?
|
||||
|
||||
"product return", "exchange product", "general question", "product question" or "other"?
|
||||
|
||||
Reply with only the intent string."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class IntentInput:
|
||||
history: str
|
||||
customer_info: str
|
||||
|
||||
|
||||
class PromptExecutor(Executor):
|
||||
@handler
|
||||
async def receive(self, intent_input: IntentInput, ctx: WorkflowContext[str]) -> None:
|
||||
prompt = _INTENT_USER_TEMPLATE.format(
|
||||
customer_info=intent_input.customer_info,
|
||||
history=intent_input.history,
|
||||
)
|
||||
await ctx.send_message(prompt)
|
||||
|
||||
|
||||
class ExtractIntentExecutor(Executor):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
client = OpenAIChatClient(
|
||||
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
||||
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
)
|
||||
self._agent = Agent(
|
||||
client=client,
|
||||
name="IntentAgent",
|
||||
instructions=_INTENT_SYSTEM_PROMPT,
|
||||
)
|
||||
|
||||
@handler
|
||||
async def extract(self, prompt: str, ctx: WorkflowContext[Never, str]) -> None:
|
||||
response = await self._agent.run(prompt)
|
||||
await ctx.yield_output(response.text)
|
||||
|
||||
|
||||
def create_workflow():
|
||||
"""Create a fresh workflow instance.
|
||||
|
||||
MAF workflows do not support concurrent execution, so each
|
||||
concurrent caller needs its own workflow instance.
|
||||
"""
|
||||
_prompt = PromptExecutor(id="chat_prompt")
|
||||
_extract = ExtractIntentExecutor(id="extract_intent")
|
||||
return (
|
||||
WorkflowBuilder(name="CustomerIntentWorkflow", start_executor=_prompt)
|
||||
.add_edge(_prompt, _extract)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run(
|
||||
IntentInput(
|
||||
history="Customer: I want to return my order\nAgent: Sure, I can help with that.",
|
||||
customer_info="Name: John Doe\nOrder: #12345 - Widget A",
|
||||
)
|
||||
)
|
||||
print(f"Intent: {result.get_outputs()[0]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,7 @@
|
||||
*.ipynb
|
||||
.venv/
|
||||
.data/
|
||||
.env
|
||||
.vscode/
|
||||
outputs/
|
||||
connection.json
|
||||
@@ -0,0 +1,3 @@
|
||||
CHAT_DEPLOYMENT_NAME=gpt-35-turbo
|
||||
AZURE_OPENAI_API_KEY=<your_AOAI_key>
|
||||
AZURE_OPENAI_API_BASE=<your_AOAI_endpoint>
|
||||
@@ -0,0 +1,100 @@
|
||||
# Customer Intent Extraction
|
||||
|
||||
This sample is using OpenAI chat model(ChatGPT/GPT4) to identify customer intent from customer's question.
|
||||
|
||||
By going through this sample you will learn how to create a flow from existing working code (written in LangChain in this case).
|
||||
|
||||
This is the [existing code](./intent.py).
|
||||
|
||||
## Prerequisites
|
||||
Install promptflow sdk and other dependencies:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Ensure you have put your azure OpenAI endpoint key in .env file.
|
||||
|
||||
```bash
|
||||
cat .env
|
||||
```
|
||||
|
||||
## Run flow
|
||||
|
||||
1. init flow directory - create promptflow folder from existing python file
|
||||
```bash
|
||||
pf flow init --flow . --entry intent.py --function extract_intent --prompt-template chat_prompt=user_intent_zero_shot.jinja2
|
||||
```
|
||||
The generated files:
|
||||
- extract_intent_tool.py: Wrap the func `extract_intent` in the `intent.py` script into a [Python Tool](https://promptflow.azurewebsites.net/tools-reference/python-tool.html).
|
||||
- flow.dag.yaml: Describes the DAG(Directed Acyclic Graph) of this flow.
|
||||
- .gitignore: File/folder in the flow to be ignored.
|
||||
|
||||
2. create needed custom connection
|
||||
```bash
|
||||
pf connection create -f .env --name custom_connection
|
||||
```
|
||||
|
||||
3. test flow with single line input
|
||||
```bash
|
||||
pf flow test --flow . --inputs ./data/sample.json
|
||||
```
|
||||
|
||||
4. run with multiple lines input
|
||||
```bash
|
||||
pf run create --flow . --data ./data --column-mapping history='${data.history}' customer_info='${data.customer_info}'
|
||||
```
|
||||
|
||||
You can also skip providing `column-mapping` if provided data has same column name as the flow.
|
||||
Reference [here](https://aka.ms/pf/column-mapping) for default behavior when `column-mapping` not provided in CLI.
|
||||
|
||||
5. list/show
|
||||
|
||||
```bash
|
||||
# list created run
|
||||
pf run list
|
||||
# get a sample completed run name
|
||||
name=$(pf run list | jq '.[] | select(.name | contains("customer_intent_extraction")) | .name'| head -n 1 | tr -d '"')
|
||||
# show run
|
||||
pf run show --name $name
|
||||
# show specific run detail, top 3 lines
|
||||
pf run show-details --name $name -r 3
|
||||
```
|
||||
|
||||
6. evaluation
|
||||
|
||||
```bash
|
||||
# create evaluation run
|
||||
pf run create --flow ../../evaluation/eval-classification-accuracy --data ./data --column-mapping groundtruth='${data.intent}' prediction='${run.outputs.output}' --run $name
|
||||
```
|
||||
|
||||
```bash
|
||||
# get the evaluation run in previous step
|
||||
eval_run_name=$(pf run list | jq '.[] | select(.name | contains("eval_classification_accuracy")) | .name'| head -n 1 | tr -d '"')
|
||||
# show run
|
||||
pf run show --name $eval_run_name
|
||||
# show run output
|
||||
pf run show-details --name $eval_run_name -r 3
|
||||
```
|
||||
|
||||
6. visualize
|
||||
```bash
|
||||
# visualize in browser
|
||||
pf run visualize --name $eval_run_name # your evaluation run name
|
||||
```
|
||||
|
||||
## Deploy
|
||||
|
||||
### Serve as a local test app
|
||||
|
||||
```bash
|
||||
pf flow serve --source . --port 5123 --host localhost
|
||||
```
|
||||
Visit http://localhost:5213 to access the test app.
|
||||
|
||||
### Export
|
||||
|
||||
#### Export as docker
|
||||
```bash
|
||||
# pf flow export --source . --format docker --output ./package
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Sarah \nLast Name: Lee \nAge: 38 \nEmail Address: sarahlee@example.com \nPhone Number: 555-867-5309 \nShipping Address: 321 Maple St, Bigtown USA, 90123 \nMembership: Platinum \n\n## Recent_Purchases\n\norder_number: 2 \ndate: 2023-02-10 \nitem:\n- description: TrailMaster X4 Tent, quantity 1, price $250 \n\u00a0 item_number: 1 \n\norder_number: 26 \ndate: 2023-02-05 \nitem:\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \n\u00a0 item_number: 7 \n\norder_number: 35 \ndate: 2023-02-20 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \n\u00a0 item_number: 10 \n\norder_number: 42 \ndate: 2023-04-06 \nitem:\n- description: TrekMaster Camping Chair, quantity 2, price $100 \n\u00a0 item_number: 12 \n\norder_number: 51 \ndate: 2023-04-21 \nitem:\n- description: SkyView 2-Person Tent, quantity 1, price $200 \n\u00a0 item_number: 15 \n\norder_number: 56 \ndate: 2023-03-26 \nitem:\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \n\u00a0 item_number: 17 \n\norder_number: 65 \ndate: 2023-04-11 \nitem:\n- description: CompactCook Camping Stove, quantity 1, price $60 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I recently bought the TrailMaster X4 Tent, and it leaked during a light rain. This is unacceptable! I expected a reliable and waterproof tent, but it failed to deliver. I'm extremely disappointed in the quality."}], "item_number": 1, "order_number": 2, "description": "TrailMaster X4 Tent, quantity 1, price $250", "intent": "product return"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: John \nLast Name: Smith \nAge: 35 \nEmail Address: johnsmith@example.com \nPhone Number: 555-123-4567 \nShipping Address: 123 Main St, Anytown USA, 12345 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 1 \ndate: 2023-01-05 \nitem:\n- description: TrailMaster X4 Tent, quantity 2, price $500 \n\u00a0 item_number: 1 \n\norder_number: 19 \ndate: 2023-01-25 \nitem:\n- description: BaseCamp Folding Table, quantity 1, price $60 \n\u00a0 item_number: 5 \n\norder_number: 29 \ndate: 2023-02-10 \nitem:\n- description: Alpine Explorer Tent, quantity 2, price $700 \n\u00a0 item_number: 8 \n\norder_number: 41 \ndate: 2023-03-01 \nitem:\n- description: TrekMaster Camping Chair, quantity 1, price $50 \n\u00a0 item_number: 12 \n\norder_number: 50 \ndate: 2023-03-16 \nitem:\n- description: SkyView 2-Person Tent, quantity 2, price $400 \n\u00a0 item_number: 15 \n\norder_number: 59 \ndate: 2023-04-01 \nitem:\n- description: TrekStar Hiking Sandals, quantity 1, price $70 \n\u00a0 item_number: 18 \n\n", "history": [{"role": "customer", "content": "I recently purchased two TrailMaster X4 Tents, and while the overall quality is good, I noticed a minor issue. One of the tent poles arrived slightly bent, making it challenging to assemble the tent properly. I'm concerned that this may affect the stability of the tent. Can you provide any guidance on how to address this?"}], "item_number": 1, "order_number": 1, "description": "TrailMaster X4 Tent, quantity 2, price $500", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Jason \nLast Name: Brown \nAge: 50 \nEmail Address: jasonbrown@example.com \nPhone Number: 555-222-3333 \nShipping Address: 456 Cedar Rd, Anytown USA, 12345 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 8 \ndate: 2023-03-20 \nitem:\n- description: Adventurer Pro Backpack, quantity 1, price $90 \n\u00a0 item_number: 2 \n\norder_number: 27 \ndate: 2023-03-10 \nitem:\n- description: CozyNights Sleeping Bag, quantity 2, price $200 \n\u00a0 item_number: 7 \n\norder_number: 36 \ndate: 2023-03-25 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 2, price $150 \n\u00a0 item_number: 10 \n\norder_number: 43 \ndate: 2023-05-11 \nitem:\n- description: TrekMaster Camping Chair, quantity 1, price $50 \n\u00a0 item_number: 12 \n\norder_number: 52 \ndate: 2023-05-26 \nitem:\n- description: SkyView 2-Person Tent, quantity 1, price $200 \n\u00a0 item_number: 15 \n\norder_number: 57 \ndate: 2023-05-01 \nitem:\n- description: RainGuard Hiking Jacket, quantity 2, price $220 \n\u00a0 item_number: 17 \n\norder_number: 66 \ndate: 2023-05-16 \nitem:\n- description: CompactCook Camping Stove, quantity 2, price $120 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I recently purchased the Adventurer Pro Backpack, and I'm excited to use it for my upcoming camping trip. Can you provide some guidance on the backpack's capacity and any special features I should be aware of? I want to make sure I utilize it to its fullest potential."}], "item_number": 2, "order_number": 8, "description": "Adventurer Pro Backpack, quantity 3, price $270", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Daniel \nLast Name: Wilson \nAge: 47 \nEmail Address: danielw@example.com \nPhone Number: 555-444-5555 \nShipping Address: 321 Birch Ln, Smallville USA, 34567 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 9 \ndate: 2023-04-25 \nitem:\n- description: Adventurer Pro Backpack, quantity 3, price $270 \n\u00a0 item_number: 2 \n\norder_number: 13 \ndate: 2023-03-25 \nitem:\n- description: Summit Breeze Jacket, quantity 1, price $120 \n\u00a0 item_number: 3 \n\norder_number: 22 \ndate: 2023-05-07 \nitem:\n- description: BaseCamp Folding Table, quantity 3, price $180 \n\u00a0 item_number: 5 \n\norder_number: 40 \ndate: 2023-04-05 \nitem:\n- description: TrailWalker Hiking Shoes, quantity 1, price $110 \n\u00a0 item_number: 11 \n\norder_number: 49 \ndate: 2023-05-21 \nitem:\n- description: MountainDream Sleeping Bag, quantity 1, price $130 \n\u00a0 item_number: 14 \n\n", "history": [{"role": "customer", "content": "I recently received the Adventurer Pro Backpack I ordered, but there seems to be a problem. One of the zippers on the main compartment is jammed, making it difficult to open and close. This is quite frustrating, as I was looking forward to using it on my upcoming hiking trip."}], "item_number": 2, "order_number": 9, "description": "Adventurer Pro Backpack, quantity 2, price $180", "intent": "product return"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Robert \nLast Name: Johnson \nAge: 36 \nEmail Address: robertj@example.com \nPhone Number: 555-555-1212 \nShipping Address: 123 Main St, Anytown USA, 12345 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 10 \ndate: 2023-05-05 \nitem:\n- description: Adventurer Pro Backpack, quantity 2, price $180 \n\u00a0 item_number: 2 \n\n", "history": [{"role": "customer", "content": "I recently purchased two Adventurer Pro Backpacks, and I'm curious to know if they are waterproof. I'm planning to go on a camping trip where we might encounter some rain, and I want to make sure my belongings stay dry."}], "item_number": 2, "order_number": 10, "description": "Adventurer Pro Backpack, quantity 2, price $180", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Michael \nLast Name: Johnson \nAge: 45 \nEmail Address: michaelj@example.com \nPhone Number: 555-555-1212 \nShipping Address: 789 Elm St, Smallville USA, 34567 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 11 \ndate: 2023-01-15 \nitem:\n- description: Summit Breeze Jacket, quantity 1, price $120 \n\u00a0 item_number: 3 \n\norder_number: 20 \ndate: 2023-02-28 \nitem:\n- description: BaseCamp Folding Table, quantity 2, price $120 \n\u00a0 item_number: 5 \n\norder_number: 30 \ndate: 2023-03-15 \nitem:\n- description: Alpine Explorer Tent, quantity 1, price $350 \n\u00a0 item_number: 8 \n\norder_number: 38 \ndate: 2023-02-25 \nitem:\n- description: TrailWalker Hiking Shoes, quantity 1, price $110 \n\u00a0 item_number: 11 \n\norder_number: 47 \ndate: 2023-03-11 \nitem:\n- description: MountainDream Sleeping Bag, quantity 1, price $130 \n\u00a0 item_number: 14 \n\norder_number: 60 \ndate: 2023-05-06 \nitem:\n- description: TrekStar Hiking Sandals, quantity 2, price $140 \n\u00a0 item_number: 18 \n\n", "history": [{"role": "customer", "content": "I recently purchased the Summit Breeze Jacket, and I'm extremely disappointed. The jacket doesn't provide the protection it claims. I wore it during a light rain, and it soaked through within minutes. This is completely unacceptable!"}], "item_number": 3, "order_number": 11, "description": "Summit Breeze Jacket, quantity 1, price $120", "intent": "product return"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Melissa \nLast Name: Davis \nAge: 31 \nEmail Address: melissad@example.com \nPhone Number: 555-333-4444 \nShipping Address: 789 Ash St, Another City USA, 67890 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 4 \ndate: 2023-04-22 \nitem:\n- description: TrailMaster X4 Tent, quantity 2, price $500 \n\u00a0 item_number: 1 \n\norder_number: 17 \ndate: 2023-03-30 \nitem:\n- description: TrekReady Hiking Boots, quantity 1, price $140 \n\u00a0 item_number: 4 \n\norder_number: 25 \ndate: 2023-04-10 \nitem:\n- description: EcoFire Camping Stove, quantity 1, price $80 \n\u00a0 item_number: 6 \n\norder_number: 34 \ndate: 2023-04-25 \nitem:\n- description: SummitClimber Backpack, quantity 1, price $120 \n\u00a0 item_number: 9 \n\norder_number: 46 \ndate: 2023-05-16 \nitem:\n- description: PowerBurner Camping Stove, quantity 1, price $100 \n\u00a0 item_number: 13 \n\norder_number: 55 \ndate: 2023-05-31 \nitem:\n- description: TrailLite Daypack, quantity 1, price $60 \n\u00a0 item_number: 16 \n\norder_number: 64 \ndate: 2023-06-16 \nitem:\n- description: Adventure Dining Table, quantity 1, price $90 \n\u00a0 item_number: 19 \n\n", "history": [{"role": "customer", "content": "I recently purchased the TrekReady Hiking Boots, and I must say I'm disappointed. The boots started falling apart after just a few uses. The stitching came undone, and the sole started detaching. I expected better quality and durability from these boots. I'm not satisfied with my purchase."}], "item_number": 4, "order_number": 17, "description": "TrekReady Hiking Boots, quantity 1, price $140", "intent": "product return"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Emily \nLast Name: Rodriguez \nAge: 29 \nEmail Address: emilyr@example.com \nPhone Number: 555-111-2222 \nShipping Address: 987 Oak Ave, Cityville USA, 56789 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 3 \ndate: 2023-03-18 \nitem:\n- description: TrailMaster X4 Tent, quantity 3, price $750 \n\u00a0 item_number: 1 \n\norder_number: 12 \ndate: 2023-02-20 \nitem:\n- description: Summit Breeze Jacket, quantity 2, price $240 \n\u00a0 item_number: 3 \n\norder_number: 21 \ndate: 2023-04-02 \nitem:\n- description: BaseCamp Folding Table, quantity 1, price $60 \n\u00a0 item_number: 5 \n\norder_number: 31 \ndate: 2023-04-20 \nitem:\n- description: Alpine Explorer Tent, quantity 1, price $350 \n\u00a0 item_number: 8 \n\norder_number: 39 \ndate: 2023-03-30 \nitem:\n- description: TrailWalker Hiking Shoes, quantity 2, price $220 \n\u00a0 item_number: 11 \n\norder_number: 48 \ndate: 2023-04-16 \nitem:\n- description: MountainDream Sleeping Bag, quantity 2, price $260 \n\u00a0 item_number: 14 \n\norder_number: 61 \ndate: 2023-06-11 \nitem:\n- description: TrekStar Hiking Sandals, quantity 1, price $70 \n\u00a0 item_number: 18 \n\n", "history": [{"role": "customer", "content": "I'm interested in purchasing the BaseCamp Folding Table. Can you provide me with more details about its dimensions and weight? I want to make sure it will fit well in my camping gear."}], "item_number": 5, "order_number": 21, "description": "BaseCamp Folding Table, quantity 1, price $60", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Jane \nLast Name: Doe \nAge: 28 \nEmail Address: janedoe@example.com \nPhone Number: 555-987-6543 \nShipping Address: 456 Oak St, Another City USA, 67890 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 6 \ndate: 2023-01-10 \nitem:\n- description: Adventurer Pro Backpack, quantity 1, price $90 \n\u00a0 item_number: 2 \n\norder_number: 15 \ndate: 2023-01-20 \nitem:\n- description: TrekReady Hiking Boots, quantity 1, price $140 \n\u00a0 item_number: 4 \n\norder_number: 23 \ndate: 2023-01-30 \nitem:\n- description: EcoFire Camping Stove, quantity 1, price $80 \n\u00a0 item_number: 6 \n\norder_number: 32 \ndate: 2023-02-15 \nitem:\n- description: SummitClimber Backpack, quantity 1, price $120 \n\u00a0 item_number: 9 \n\norder_number: 44 \ndate: 2023-03-06 \nitem:\n- description: PowerBurner Camping Stove, quantity 1, price $100 \n\u00a0 item_number: 13 \n\norder_number: 53 \ndate: 2023-03-21 \nitem:\n- description: TrailLite Daypack, quantity 1, price $60 \n\u00a0 item_number: 16 \n\norder_number: 62 \ndate: 2023-04-06 \nitem:\n- description: Adventure Dining Table, quantity 1, price $90 \n\u00a0 item_number: 19 \n\n", "history": [{"role": "customer", "content": "I recently purchased a camping cooker, but I'm disappointed with its performance. It takes too long to heat up, and the flames seem to be uneven. I expected better quality from this stove."}], "item_number": 6, "order_number": 23, "description": "EcoFire Camping Stove, quantity 1, price $80", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Melissa \nLast Name: Davis \nAge: 31 \nEmail Address: melissad@example.com \nPhone Number: 555-333-4444 \nShipping Address: 789 Ash St, Another City USA, 67890 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 4 \ndate: 2023-04-22 \nitem:\n- description: TrailMaster X4 Tent, quantity 2, price $500 \n\u00a0 item_number: 1 \n\norder_number: 17 \ndate: 2023-03-30 \nitem:\n- description: TrekReady Hiking Boots, quantity 1, price $140 \n\u00a0 item_number: 4 \n\norder_number: 25 \ndate: 2023-04-10 \nitem:\n- description: EcoFire Camping Stove, quantity 1, price $80 \n\u00a0 item_number: 6 \n\norder_number: 34 \ndate: 2023-04-25 \nitem:\n- description: SummitClimber Backpack, quantity 1, price $120 \n\u00a0 item_number: 9 \n\norder_number: 46 \ndate: 2023-05-16 \nitem:\n- description: PowerBurner Camping Stove, quantity 1, price $100 \n\u00a0 item_number: 13 \n\norder_number: 55 \ndate: 2023-05-31 \nitem:\n- description: TrailLite Daypack, quantity 1, price $60 \n\u00a0 item_number: 16 \n\norder_number: 64 \ndate: 2023-06-16 \nitem:\n- description: Adventure Dining Table, quantity 1, price $90 \n\u00a0 item_number: 19 \n\n", "history": [{"role": "customer", "content": "I'm interested in purchasing the a camping cooker. Can you tell me more about its fuel efficiency and cooking capacity? I want to make sure it will suit my camping needs."}], "item_number": 6, "order_number": 25, "description": "EcoFire Camping Stove, quantity 1, price $80", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Sarah \nLast Name: Lee \nAge: 38 \nEmail Address: sarahlee@example.com \nPhone Number: 555-867-5309 \nShipping Address: 321 Maple St, Bigtown USA, 90123 \nMembership: Platinum \n\n## Recent_Purchases\n\norder_number: 2 \ndate: 2023-02-10 \nitem:\n- description: TrailMaster X4 Tent, quantity 1, price $250 \n\u00a0 item_number: 1 \n\norder_number: 26 \ndate: 2023-02-05 \nitem:\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \n\u00a0 item_number: 7 \n\norder_number: 35 \ndate: 2023-02-20 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \n\u00a0 item_number: 10 \n\norder_number: 42 \ndate: 2023-04-06 \nitem:\n- description: TrekMaster Camping Chair, quantity 2, price $100 \n\u00a0 item_number: 12 \n\norder_number: 51 \ndate: 2023-04-21 \nitem:\n- description: SkyView 2-Person Tent, quantity 1, price $200 \n\u00a0 item_number: 15 \n\norder_number: 56 \ndate: 2023-03-26 \nitem:\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \n\u00a0 item_number: 17 \n\norder_number: 65 \ndate: 2023-04-11 \nitem:\n- description: CompactCook Camping Stove, quantity 1, price $60 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "Hi, I recently bought a sleeping Bag, and I'm having some trouble with the zipper. It seems to get stuck every time I try to close it. What should I do?"}], "item_number": 7, "order_number": 26, "description": "CozyNights Sleeping Bag, quantity 1, price $100", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Amanda \nLast Name: Perez \nAge: 26 \nEmail Address: amandap@example.com \nPhone Number: 555-123-4567 \nShipping Address: 654 Pine St, Suburbia USA, 23456 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 5 \ndate: 2023-05-01 \nitem:\n- description: TrailMaster X4 Tent, quantity 1, price $250 \n\u00a0 item_number: 1 \n\norder_number: 18 \ndate: 2023-05-04 \nitem:\n- description: TrekReady Hiking Boots, quantity 3, price $420 \n\u00a0 item_number: 4 \n\norder_number: 28 \ndate: 2023-04-15 \nitem:\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \n\u00a0 item_number: 7 \n\norder_number: 37 \ndate: 2023-04-30 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \n\u00a0 item_number: 10 \n\norder_number: 58 \ndate: 2023-06-06 \nitem:\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \n\u00a0 item_number: 17 \n\norder_number: 67 \ndate: 2023-06-21 \nitem:\n- description: CompactCook Camping Stove, quantity 1, price $60 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I received the sleeping bag I ordered, but it's not the color I chose. I specifically selected the blue one, but I got a green one instead. This is really disappointing!"}], "item_number": 7, "order_number": 28, "description": "CozyNights Sleeping Bag, quantity 2, price $100", "intent": "product exchange"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: John \nLast Name: Smith \nAge: 35 \nEmail Address: johnsmith@example.com \nPhone Number: 555-123-4567 \nShipping Address: 123 Main St, Anytown USA, 12345 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 1 \ndate: 2023-01-05 \nitem:\n- description: TrailMaster X4 Tent, quantity 2, price $500 \n\u00a0 item_number: 1 \n\norder_number: 19 \ndate: 2023-01-25 \nitem:\n- description: BaseCamp Folding Table, quantity 1, price $60 \n\u00a0 item_number: 5 \n\norder_number: 29 \ndate: 2023-02-10 \nitem:\n- description: Alpine Explorer Tent, quantity 2, price $700 \n\u00a0 item_number: 8 \n\norder_number: 41 \ndate: 2023-03-01 \nitem:\n- description: TrekMaster Camping Chair, quantity 1, price $50 \n\u00a0 item_number: 12 \n\norder_number: 50 \ndate: 2023-03-16 \nitem:\n- description: SkyView 2-Person Tent, quantity 2, price $400 \n\u00a0 item_number: 15 \n\norder_number: 59 \ndate: 2023-04-01 \nitem:\n- description: TrekStar Hiking Sandals, quantity 1, price $70 \n\u00a0 item_number: 18 \n\n", "history": [{"role": "customer", "content": "Hi there! I recently purchased two Tents from your store. They look great, but I wanted to know if they come with any additional accessories like stakes or a rainfly?"}], "item_number": 8, "order_number": 29, "description": "Alpine Explorer Tents, quantity 2, price $700", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Michael \nLast Name: Johnson \nAge: 45 \nEmail Address: michaelj@example.com \nPhone Number: 555-555-1212 \nShipping Address: 789 Elm St, Smallville USA, 34567 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 11 \ndate: 2023-01-15 \nitem:\n- description: Summit Breeze Jacket, quantity 1, price $120 \n\u00a0 item_number: 3 \n\norder_number: 20 \ndate: 2023-02-28 \nitem:\n- description: BaseCamp Folding Table, quantity 2, price $120 \n\u00a0 item_number: 5 \n\norder_number: 30 \ndate: 2023-03-15 \nitem:\n- description: Alpine Explorer Tent, quantity 1, price $350 \n\u00a0 item_number: 8 \n\norder_number: 38 \ndate: 2023-02-25 \nitem:\n- description: TrailWalker Hiking Shoes, quantity 1, price $110 \n\u00a0 item_number: 11 \n\norder_number: 47 \ndate: 2023-03-11 \nitem:\n- description: MountainDream Sleeping Bag, quantity 1, price $130 \n\u00a0 item_number: 14 \n\norder_number: 60 \ndate: 2023-05-06 \nitem:\n- description: TrekStar Hiking Sandals, quantity 2, price $140 \n\u00a0 item_number: 18 \n\n", "history": [{"role": "customer", "content": "I recently bought a Tent, and I have to say, I'm really disappointed. The tent poles seem flimsy, and the zippers are constantly getting stuck. It's not what I expected from a high-end tent."}], "item_number": 8, "order_number": 30, "description": "Alpine Explorer Tents, quantity 1, price $350", "intent": "product return"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Emily \nLast Name: Rodriguez \nAge: 29 \nEmail Address: emilyr@example.com \nPhone Number: 555-111-2222 \nShipping Address: 987 Oak Ave, Cityville USA, 56789 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 3 \ndate: 2023-03-18 \nitem:\n- description: TrailMaster X4 Tent, quantity 3, price $750 \n\u00a0 item_number: 1 \n\norder_number: 12 \ndate: 2023-02-20 \nitem:\n- description: Summit Breeze Jacket, quantity 2, price $240 \n\u00a0 item_number: 3 \n\norder_number: 21 \ndate: 2023-04-02 \nitem:\n- description: BaseCamp Folding Table, quantity 1, price $60 \n\u00a0 item_number: 5 \n\norder_number: 31 \ndate: 2023-04-20 \nitem:\n- description: Alpine Explorer Tent, quantity 1, price $350 \n\u00a0 item_number: 8 \n\norder_number: 39 \ndate: 2023-03-30 \nitem:\n- description: TrailWalker Hiking Shoes, quantity 2, price $220 \n\u00a0 item_number: 11 \n\norder_number: 48 \ndate: 2023-04-16 \nitem:\n- description: MountainDream Sleeping Bag, quantity 2, price $260 \n\u00a0 item_number: 14 \n\norder_number: 61 \ndate: 2023-06-11 \nitem:\n- description: TrekStar Hiking Sandals, quantity 1, price $70 \n\u00a0 item_number: 18 \n\n", "history": [{"role": "customer", "content": "I recently received the tent I ordered, but I'm having trouble setting it up. The instructions provided are a bit unclear. Can you guide me through the setup process?"}], "item_number": 8, "order_number": 31, "description": "Alpine Explorer Tents, quantity 1, price $350", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Jane \nLast Name: Doe \nAge: 28 \nEmail Address: janedoe@example.com \nPhone Number: 555-987-6543 \nShipping Address: 456 Oak St, Another City USA, 67890 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 6 \ndate: 2023-01-10 \nitem:\n- description: Adventurer Pro Backpack, quantity 1, price $90 \n\u00a0 item_number: 2 \n\norder_number: 15 \ndate: 2023-01-20 \nitem:\n- description: TrekReady Hiking Boots, quantity 1, price $140 \n\u00a0 item_number: 4 \n\norder_number: 23 \ndate: 2023-01-30 \nitem:\n- description: EcoFire Camping Stove, quantity 1, price $80 \n\u00a0 item_number: 6 \n\norder_number: 32 \ndate: 2023-02-15 \nitem:\n- description: SummitClimber Backpack, quantity 1, price $120 \n\u00a0 item_number: 9 \n\norder_number: 44 \ndate: 2023-03-06 \nitem:\n- description: PowerBurner Camping Stove, quantity 1, price $100 \n\u00a0 item_number: 13 \n\norder_number: 53 \ndate: 2023-03-21 \nitem:\n- description: TrailLite Daypack, quantity 1, price $60 \n\u00a0 item_number: 16 \n\norder_number: 62 \ndate: 2023-04-06 \nitem:\n- description: Adventure Dining Table, quantity 1, price $90 \n\u00a0 item_number: 19 \n\n", "history": [{"role": "customer", "content": "Hi, I recently purchased the a backpack from your store. It looks great, but I'm wondering if it has a separate compartment for a hydration bladder?"}], "item_number": 9, "order_number": 32, "description": "SummitClimber Backpack, quantity 1, price $120", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Melissa \nLast Name: Davis \nAge: 31 \nEmail Address: melissad@example.com \nPhone Number: 555-333-4444 \nShipping Address: 789 Ash St, Another City USA, 67890 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 4 \ndate: 2023-04-22 \nitem:\n- description: TrailMaster X4 Tent, quantity 2, price $500 \n\u00a0 item_number: 1 \n\norder_number: 17 \ndate: 2023-03-30 \nitem:\n- description: TrekReady Hiking Boots, quantity 1, price $140 \n\u00a0 item_number: 4 \n\norder_number: 25 \ndate: 2023-04-10 \nitem:\n- description: EcoFire Camping Stove, quantity 1, price $80 \n\u00a0 item_number: 6 \n\norder_number: 34 \ndate: 2023-04-25 \nitem:\n- description: SummitClimber Backpack, quantity 1, price $120 \n\u00a0 item_number: 9 \n\norder_number: 46 \ndate: 2023-05-16 \nitem:\n- description: PowerBurner Camping Stove, quantity 1, price $100 \n\u00a0 item_number: 13 \n\norder_number: 55 \ndate: 2023-05-31 \nitem:\n- description: TrailLite Daypack, quantity 1, price $60 \n\u00a0 item_number: 16 \n\norder_number: 64 \ndate: 2023-06-16 \nitem:\n- description: Adventure Dining Table, quantity 1, price $90 \n\u00a0 item_number: 19 \n\n", "history": [{"role": "customer", "content": "I recently received the Backpack I ordered, and I noticed that one of the zippers is not closing smoothly. It seems to get stuck halfway. Is there anything I can do to fix it?"}], "item_number": 9, "order_number": 34, "description": "SummitClimber Backpack, quantity 1, price $120", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Sarah \nLast Name: Lee \nAge: 38 \nEmail Address: sarahlee@example.com \nPhone Number: 555-867-5309 \nShipping Address: 321 Maple St, Bigtown USA, 90123 \nMembership: Platinum \n\n## Recent_Purchases\n\norder_number: 2 \ndate: 2023-02-10 \nitem:\n- description: TrailMaster X4 Tent, quantity 1, price $250 \n\u00a0 item_number: 1 \n\norder_number: 26 \ndate: 2023-02-05 \nitem:\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \n\u00a0 item_number: 7 \n\norder_number: 35 \ndate: 2023-02-20 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \n\u00a0 item_number: 10 \n\norder_number: 42 \ndate: 2023-04-06 \nitem:\n- description: TrekMaster Camping Chair, quantity 2, price $100 \n\u00a0 item_number: 12 \n\norder_number: 51 \ndate: 2023-04-21 \nitem:\n- description: SkyView 2-Person Tent, quantity 1, price $200 \n\u00a0 item_number: 15 \n\norder_number: 56 \ndate: 2023-03-26 \nitem:\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \n\u00a0 item_number: 17 \n\norder_number: 65 \ndate: 2023-04-11 \nitem:\n- description: CompactCook Camping Stove, quantity 1, price $60 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I recently purchased the Pants from your store, but I'm disappointed with the fit. They're too tight around the waist, and the length is too short. Can I exchange them for a different size?"}], "item_number": 10, "order_number": 35, "description": "TrailBlaze Hiking Pants, quantity 1, price $75", "intent": "product return"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Amanda \nLast Name: Perez \nAge: 26 \nEmail Address: amandap@example.com \nPhone Number: 555-123-4567 \nShipping Address: 654 Pine St, Suburbia USA, 23456 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 5 \ndate: 2023-05-01 \nitem:\n- description: TrailMaster X4 Tent, quantity 1, price $250 \n\u00a0 item_number: 1 \n\norder_number: 18 \ndate: 2023-05-04 \nitem:\n- description: TrekReady Hiking Boots, quantity 3, price $420 \n\u00a0 item_number: 4 \n\norder_number: 28 \ndate: 2023-04-15 \nitem:\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \n\u00a0 item_number: 7 \n\norder_number: 37 \ndate: 2023-04-30 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \n\u00a0 item_number: 10 \n\norder_number: 58 \ndate: 2023-06-06 \nitem:\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \n\u00a0 item_number: 17 \n\norder_number: 67 \ndate: 2023-06-21 \nitem:\n- description: CompactCook Camping Stove, quantity 1, price $60 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I recently received the Pants I ordered, and I noticed that they have several pockets. Can you provide some information on the pocket layout and their intended purposes?"}], "item_number": 10, "order_number": 37, "description": "TrailBlaze Hiking Pants, quantity 1, price $75", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Emily \nLast Name: Rodriguez \nAge: 29 \nEmail Address: emilyr@example.com \nPhone Number: 555-111-2222 \nShipping Address: 987 Oak Ave, Cityville USA, 56789 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 3 \ndate: 2023-03-18 \nitem:\n- description: TrailMaster X4 Tent, quantity 3, price $750 \n\u00a0 item_number: 1 \n\norder_number: 12 \ndate: 2023-02-20 \nitem:\n- description: Summit Breeze Jacket, quantity 2, price $240 \n\u00a0 item_number: 3 \n\norder_number: 21 \ndate: 2023-04-02 \nitem:\n- description: BaseCamp Folding Table, quantity 1, price $60 \n\u00a0 item_number: 5 \n\norder_number: 31 \ndate: 2023-04-20 \nitem:\n- description: Alpine Explorer Tent, quantity 1, price $350 \n\u00a0 item_number: 8 \n\norder_number: 39 \ndate: 2023-03-30 \nitem:\n- description: TrailWalker Hiking Shoes, quantity 2, price $220 \n\u00a0 item_number: 11 \n\norder_number: 48 \ndate: 2023-04-16 \nitem:\n- description: MountainDream Sleeping Bag, quantity 2, price $260 \n\u00a0 item_number: 14 \n\norder_number: 61 \ndate: 2023-06-11 \nitem:\n- description: TrekStar Hiking Sandals, quantity 1, price $70 \n\u00a0 item_number: 18 \n\n", "history": [{"role": "customer", "content": "I purchased two pairs of Hiking Shoes for myself and my husband last month. While my pair is great, my husband's pair seems to be slightly small. Is it possible to exchange them for a larger size?"}], "item_number": 11, "order_number": 39, "description": "TrailWalker Hiking Shoes, quantity 2, price $220", "intent": "product exchange"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Daniel \nLast Name: Wilson \nAge: 47 \nEmail Address: danielw@example.com \nPhone Number: 555-444-5555 \nShipping Address: 321 Birch Ln, Smallville USA, 34567 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 9 \ndate: 2023-04-25 \nitem:\n- description: Adventurer Pro Backpack, quantity 3, price $270 \n\u00a0 item_number: 2 \n\norder_number: 13 \ndate: 2023-03-25 \nitem:\n- description: Summit Breeze Jacket, quantity 1, price $120 \n\u00a0 item_number: 3 \n\norder_number: 22 \ndate: 2023-05-07 \nitem:\n- description: BaseCamp Folding Table, quantity 3, price $180 \n\u00a0 item_number: 5 \n\norder_number: 40 \ndate: 2023-04-05 \nitem:\n- description: TrailWalker Hiking Shoes, quantity 1, price $110 \n\u00a0 item_number: 11 \n\norder_number: 49 \ndate: 2023-05-21 \nitem:\n- description: MountainDream Sleeping Bag, quantity 1, price $130 \n\u00a0 item_number: 14 \n\n", "history": [{"role": "customer", "content": "I just bought a pair of Hiking Shoes, and I'm planning a hiking trip soon. Do you have any recommendations for maintaining the shoes and increasing their lifespan?"}], "item_number": 11, "order_number": 40, "description": "TrailWalker Hiking Shoes, quantity 1, price $110", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Sarah \nLast Name: Lee \nAge: 38 \nEmail Address: sarahlee@example.com \nPhone Number: 555-867-5309 \nShipping Address: 321 Maple St, Bigtown USA, 90123 \nMembership: Platinum \n\n## Recent_Purchases\n\norder_number: 2 \ndate: 2023-02-10 \nitem:\n- description: TrailMaster X4 Tent, quantity 1, price $250 \n\u00a0 item_number: 1 \n\norder_number: 26 \ndate: 2023-02-05 \nitem:\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \n\u00a0 item_number: 7 \n\norder_number: 35 \ndate: 2023-02-20 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \n\u00a0 item_number: 10 \n\norder_number: 42 \ndate: 2023-04-06 \nitem:\n- description: TrekMaster Camping Chair, quantity 2, price $100 \n\u00a0 item_number: 12 \n\norder_number: 51 \ndate: 2023-04-21 \nitem:\n- description: SkyView 2-Person Tent, quantity 1, price $200 \n\u00a0 item_number: 15 \n\norder_number: 56 \ndate: 2023-03-26 \nitem:\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \n\u00a0 item_number: 17 \n\norder_number: 65 \ndate: 2023-04-11 \nitem:\n- description: CompactCook Camping Stove, quantity 1, price $60 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I'm a Platinum member, and I just ordered two outdoor seats. I was wondering if there is any assembly required and if any tools are needed for that?"}], "item_number": 12, "order_number": 42, "description": "TrekMaster Camping Chair, quantity 2, price $100", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Jason \nLast Name: Brown \nAge: 50 \nEmail Address: jasonbrown@example.com \nPhone Number: 555-222-3333 \nShipping Address: 456 Cedar Rd, Anytown USA, 12345 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 8 \ndate: 2023-03-20 \nitem:\n- description: Adventurer Pro Backpack, quantity 1, price $90 \n\u00a0 item_number: 2 \n\norder_number: 27 \ndate: 2023-03-10 \nitem:\n- description: CozyNights Sleeping Bag, quantity 2, price $200 \n\u00a0 item_number: 7 \n\norder_number: 36 \ndate: 2023-03-25 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 2, price $150 \n\u00a0 item_number: 10 \n\norder_number: 43 \ndate: 2023-05-11 \nitem:\n- description: TrekMaster Camping Chair, quantity 1, price $50 \n\u00a0 item_number: 12 \n\norder_number: 52 \ndate: 2023-05-26 \nitem:\n- description: SkyView 2-Person Tent, quantity 1, price $200 \n\u00a0 item_number: 15 \n\norder_number: 57 \ndate: 2023-05-01 \nitem:\n- description: RainGuard Hiking Jacket, quantity 2, price $220 \n\u00a0 item_number: 17 \n\norder_number: 66 \ndate: 2023-05-16 \nitem:\n- description: CompactCook Camping Stove, quantity 2, price $120 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I bought a camping Chair last month, and it seems to be slightly wobbly when I sit on it. Is there any way to fix this issue?"}], "item_number": 12, "order_number": 43, "description": "TrekMaster Camping Chair, quantity 1, price $50", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: David \nLast Name: Kim \nAge: 42 \nEmail Address: davidkim@example.com \nPhone Number: 555-555-5555 \nShipping Address: 654 Pine St, Suburbia USA, 23456 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 7 \ndate: 2023-02-15 \nitem:\n- description: Adventurer Pro Backpack, quantity 2, price $180 \n\u00a0 item_number: 2 \n\norder_number: 16 \ndate: 2023-02-25 \nitem:\n- description: TrekReady Hiking Boots, quantity 2, price $280 \n\u00a0 item_number: 4 \n\norder_number: 24 \ndate: 2023-03-05 \nitem:\n- description: EcoFire Camping Stove, quantity 2, price $160 \n\u00a0 item_number: 6 \n\norder_number: 33 \ndate: 2023-03-20 \nitem:\n- description: SummitClimber Backpack, quantity 2, price $240 \n\u00a0 item_number: 9 \n\norder_number: 45 \ndate: 2023-04-11 \nitem:\n- description: PowerBurner Camping Stove, quantity 2, price $200 \n\u00a0 item_number: 13 \n\norder_number: 54 \ndate: 2023-04-26 \nitem:\n- description: TrailLite Daypack, quantity 2, price $120 \n\u00a0 item_number: 16 \n\norder_number: 63 \ndate: 2023-05-11 \nitem:\n- description: Adventure Dining Table, quantity 2, price $180 \n\u00a0 item_number: 19 \n\n", "history": [{"role": "customer", "content": "I just bought an outdoor stove but I'm not sure how to attach the fuel canister. Can you please guide me through the process?"}], "item_number": 13, "order_number": 45, "description": "PowerBurner Camping Stove, quantity 2, price $200", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Emily \nLast Name: Rodriguez \nAge: 29 \nEmail Address: emilyr@example.com \nPhone Number: 555-111-2222 \nShipping Address: 987 Oak Ave, Cityville USA, 56789 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 3 \ndate: 2023-03-18 \nitem:\n- description: TrailMaster X4 Tent, quantity 3, price $750 \n\u00a0 item_number: 1 \n\norder_number: 12 \ndate: 2023-02-20 \nitem:\n- description: Summit Breeze Jacket, quantity 2, price $240 \n\u00a0 item_number: 3 \n\norder_number: 21 \ndate: 2023-04-02 \nitem:\n- description: BaseCamp Folding Table, quantity 1, price $60 \n\u00a0 item_number: 5 \n\norder_number: 31 \ndate: 2023-04-20 \nitem:\n- description: Alpine Explorer Tent, quantity 1, price $350 \n\u00a0 item_number: 8 \n\norder_number: 39 \ndate: 2023-03-30 \nitem:\n- description: TrailWalker Hiking Shoes, quantity 2, price $220 \n\u00a0 item_number: 11 \n\norder_number: 48 \ndate: 2023-04-16 \nitem:\n- description: MountainDream Sleeping Bag, quantity 2, price $260 \n\u00a0 item_number: 14 \n\norder_number: 61 \ndate: 2023-06-11 \nitem:\n- description: TrekStar Hiking Sandals, quantity 1, price $70 \n\u00a0 item_number: 18 \n\n", "history": [{"role": "customer", "content": "I've ordered two Sleeping Bags for my upcoming camping trip. I was wondering if they can be zipped together to create a double sleeping bag?"}], "item_number": 14, "order_number": 48, "description": "MountainDream Sleeping Bag, quantity 2, price $260", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Daniel \nLast Name: Wilson \nAge: 47 \nEmail Address: danielw@example.com \nPhone Number: 555-444-5555 \nShipping Address: 321 Birch Ln, Smallville USA, 34567 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 9 \ndate: 2023-04-25 \nitem:\n- description: Adventurer Pro Backpack, quantity 3, price $270 \n\u00a0 item_number: 2 \n\norder_number: 13 \ndate: 2023-03-25 \nitem:\n- description: Summit Breeze Jacket, quantity 1, price $120 \n\u00a0 item_number: 3 \n\norder_number: 22 \ndate: 2023-05-07 \nitem:\n- description: BaseCamp Folding Table, quantity 3, price $180 \n\u00a0 item_number: 5 \n\norder_number: 40 \ndate: 2023-04-05 \nitem:\n- description: TrailWalker Hiking Shoes, quantity 1, price $110 \n\u00a0 item_number: 11 \n\norder_number: 49 \ndate: 2023-05-21 \nitem:\n- description: MountainDream Sleeping Bag, quantity 1, price $130 \n\u00a0 item_number: 14 \n\n", "history": [{"role": "customer", "content": "I recently purchased a Sleeping Bag, and I'm wondering how to properly clean and store it to ensure it lasts for a long time."}], "item_number": 14, "order_number": 49, "description": "MountainDream Sleeping Bag, quantity 1, price $130", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: John \nLast Name: Smith \nAge: 35 \nEmail Address: johnsmith@example.com \nPhone Number: 555-123-4567 \nShipping Address: 123 Main St, Anytown USA, 12345 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 1 \ndate: 2023-01-05 \nitem:\n- description: TrailMaster X4 Tent, quantity 2, price $500 \n\u00a0 item_number: 1 \n\norder_number: 19 \ndate: 2023-01-25 \nitem:\n- description: BaseCamp Folding Table, quantity 1, price $60 \n\u00a0 item_number: 5 \n\norder_number: 29 \ndate: 2023-02-10 \nitem:\n- description: Alpine Explorer Tent, quantity 2, price $700 \n\u00a0 item_number: 8 \n\norder_number: 41 \ndate: 2023-03-01 \nitem:\n- description: TrekMaster Camping Chair, quantity 1, price $50 \n\u00a0 item_number: 12 \n\norder_number: 50 \ndate: 2023-03-16 \nitem:\n- description: SkyView 2-Person Tent, quantity 2, price $400 \n\u00a0 item_number: 15 \n\norder_number: 59 \ndate: 2023-04-01 \nitem:\n- description: TrekStar Hiking Sandals, quantity 1, price $70 \n\u00a0 item_number: 18 \n\n", "history": [{"role": "customer", "content": "I just received my Tents, and they look amazing! I can't wait to use them on our next camping trip. Quick question, though - what's the best way to set up the tent?"}], "item_number": 15, "order_number": 50, "description": "SkyView 2-Person Tent, quantity 2, price $400", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Sarah \nLast Name: Lee \nAge: 38 \nEmail Address: sarahlee@example.com \nPhone Number: 555-867-5309 \nShipping Address: 321 Maple St, Bigtown USA, 90123 \nMembership: Platinum \n\n## Recent_Purchases\n\norder_number: 2 \ndate: 2023-02-10 \nitem:\n- description: TrailMaster X4 Tent, quantity 1, price $250 \n\u00a0 item_number: 1 \n\norder_number: 26 \ndate: 2023-02-05 \nitem:\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \n\u00a0 item_number: 7 \n\norder_number: 35 \ndate: 2023-02-20 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \n\u00a0 item_number: 10 \n\norder_number: 42 \ndate: 2023-04-06 \nitem:\n- description: TrekMaster Camping Chair, quantity 2, price $100 \n\u00a0 item_number: 12 \n\norder_number: 51 \ndate: 2023-04-21 \nitem:\n- description: SkyView 2-Person Tent, quantity 1, price $200 \n\u00a0 item_number: 15 \n\norder_number: 56 \ndate: 2023-03-26 \nitem:\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \n\u00a0 item_number: 17 \n\norder_number: 65 \ndate: 2023-04-11 \nitem:\n- description: CompactCook Camping Stove, quantity 1, price $60 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I ordered a Tent, and it arrived yesterday. However, I noticed that one of the tent poles is damaged. How can I get a replacement for the damaged pole?"}], "item_number": 15, "order_number": 51, "description": "SkyView 2-Person Tent, quantity 1, price $200", "intent": "product return"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Jason \nLast Name: Brown \nAge: 50 \nEmail Address: jasonbrown@example.com \nPhone Number: 555-222-3333 \nShipping Address: 456 Cedar Rd, Anytown USA, 12345 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 8 \ndate: 2023-03-20 \nitem:\n- description: Adventurer Pro Backpack, quantity 1, price $90 \n\u00a0 item_number: 2 \n\norder_number: 27 \ndate: 2023-03-10 \nitem:\n- description: CozyNights Sleeping Bag, quantity 2, price $200 \n\u00a0 item_number: 7 \n\norder_number: 36 \ndate: 2023-03-25 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 2, price $150 \n\u00a0 item_number: 10 \n\norder_number: 43 \ndate: 2023-05-11 \nitem:\n- description: TrekMaster Camping Chair, quantity 1, price $50 \n\u00a0 item_number: 12 \n\norder_number: 52 \ndate: 2023-05-26 \nitem:\n- description: SkyView 2-Person Tent, quantity 1, price $200 \n\u00a0 item_number: 15 \n\norder_number: 57 \ndate: 2023-05-01 \nitem:\n- description: RainGuard Hiking Jacket, quantity 2, price $220 \n\u00a0 item_number: 17 \n\norder_number: 66 \ndate: 2023-05-16 \nitem:\n- description: CompactCook Camping Stove, quantity 2, price $120 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I'm considering buying the SkyView 2-Person Tent for an upcoming camping trip. Can you please tell me more about the tent's ventilation and how it performs in rainy conditions?"}], "item_number": 15, "order_number": 52, "description": "SkyView 2-Person Tent, quantity 1, price $200", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: David \nLast Name: Kim \nAge: 42 \nEmail Address: davidkim@example.com \nPhone Number: 555-555-5555 \nShipping Address: 654 Pine St, Suburbia USA, 23456 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 7 \ndate: 2023-02-15 \nitem:\n- description: Adventurer Pro Backpack, quantity 2, price $180 \n\u00a0 item_number: 2 \n\norder_number: 16 \ndate: 2023-02-25 \nitem:\n- description: TrekReady Hiking Boots, quantity 2, price $280 \n\u00a0 item_number: 4 \n\norder_number: 24 \ndate: 2023-03-05 \nitem:\n- description: EcoFire Camping Stove, quantity 2, price $160 \n\u00a0 item_number: 6 \n\norder_number: 33 \ndate: 2023-03-20 \nitem:\n- description: SummitClimber Backpack, quantity 2, price $240 \n\u00a0 item_number: 9 \n\norder_number: 45 \ndate: 2023-04-11 \nitem:\n- description: PowerBurner Camping Stove, quantity 2, price $200 \n\u00a0 item_number: 13 \n\norder_number: 54 \ndate: 2023-04-26 \nitem:\n- description: TrailLite Daypack, quantity 2, price $120 \n\u00a0 item_number: 16 \n\norder_number: 63 \ndate: 2023-05-11 \nitem:\n- description: Adventure Dining Table, quantity 2, price $180 \n\u00a0 item_number: 19 \n\n", "history": [{"role": "customer", "content": "I purchased two Daypacks for my kids, and while one is perfect, the other has a zipper issue that makes it difficult to open and close. Can I get a replacement for the faulty daypack?"}], "item_number": 16, "order_number": 54, "description": "TrailLite Daypack, quantity 2, price $120", "intent": "product return"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Melissa \nLast Name: Davis \nAge: 31 \nEmail Address: melissad@example.com \nPhone Number: 555-333-4444 \nShipping Address: 789 Ash St, Another City USA, 67890 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 4 \ndate: 2023-04-22 \nitem:\n- description: TrailMaster X4 Tent, quantity 2, price $500 \n\u00a0 item_number: 1 \n\norder_number: 17 \ndate: 2023-03-30 \nitem:\n- description: TrekReady Hiking Boots, quantity 1, price $140 \n\u00a0 item_number: 4 \n\norder_number: 25 \ndate: 2023-04-10 \nitem:\n- description: EcoFire Camping Stove, quantity 1, price $80 \n\u00a0 item_number: 6 \n\norder_number: 34 \ndate: 2023-04-25 \nitem:\n- description: SummitClimber Backpack, quantity 1, price $120 \n\u00a0 item_number: 9 \n\norder_number: 46 \ndate: 2023-05-16 \nitem:\n- description: PowerBurner Camping Stove, quantity 1, price $100 \n\u00a0 item_number: 13 \n\norder_number: 55 \ndate: 2023-05-31 \nitem:\n- description: TrailLite Daypack, quantity 1, price $60 \n\u00a0 item_number: 16 \n\norder_number: 64 \ndate: 2023-06-16 \nitem:\n- description: Adventure Dining Table, quantity 1, price $90 \n\u00a0 item_number: 19 \n\n", "history": [{"role": "customer", "content": "I just bought a TrailLite Daypack for my day hikes, and I was wondering if it's water-resistant. Should I be concerned about my belongings getting wet if it rains?"}], "item_number": 16, "order_number": 55, "description": "TrailLite Daypack, quantity 1, price $60", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Jason \nLast Name: Brown \nAge: 50 \nEmail Address: jasonbrown@example.com \nPhone Number: 555-222-3333 \nShipping Address: 456 Cedar Rd, Anytown USA, 12345 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 8 \ndate: 2023-03-20 \nitem:\n- description: Adventurer Pro Backpack, quantity 1, price $90 \n\u00a0 item_number: 2 \n\norder_number: 27 \ndate: 2023-03-10 \nitem:\n- description: CozyNights Sleeping Bag, quantity 2, price $200 \n\u00a0 item_number: 7 \n\norder_number: 36 \ndate: 2023-03-25 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 2, price $150 \n\u00a0 item_number: 10 \n\norder_number: 43 \ndate: 2023-05-11 \nitem:\n- description: TrekMaster Camping Chair, quantity 1, price $50 \n\u00a0 item_number: 12 \n\norder_number: 52 \ndate: 2023-05-26 \nitem:\n- description: SkyView 2-Person Tent, quantity 1, price $200 \n\u00a0 item_number: 15 \n\norder_number: 57 \ndate: 2023-05-01 \nitem:\n- description: RainGuard Hiking Jacket, quantity 2, price $220 \n\u00a0 item_number: 17 \n\norder_number: 66 \ndate: 2023-05-16 \nitem:\n- description: CompactCook Camping Stove, quantity 2, price $120 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I just bought two RainGuard Hiking Jackets for my wife and me. Can you please provide some care instructions to ensure the jackets maintain their water resistance and durability?"}], "item_number": 17, "order_number": 57, "description": "RainGuard Hiking Jacket, quantity 2, price $220", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Amanda \nLast Name: Perez \nAge: 26 \nEmail Address: amandap@example.com \nPhone Number: 555-123-4567 \nShipping Address: 654 Pine St, Suburbia USA, 23456 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 5 \ndate: 2023-05-01 \nitem:\n- description: TrailMaster X4 Tent, quantity 1, price $250 \n\u00a0 item_number: 1 \n\norder_number: 18 \ndate: 2023-05-04 \nitem:\n- description: TrekReady Hiking Boots, quantity 3, price $420 \n\u00a0 item_number: 4 \n\norder_number: 28 \ndate: 2023-04-15 \nitem:\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \n\u00a0 item_number: 7 \n\norder_number: 37 \ndate: 2023-04-30 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \n\u00a0 item_number: 10 \n\norder_number: 58 \ndate: 2023-06-06 \nitem:\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \n\u00a0 item_number: 17 \n\norder_number: 67 \ndate: 2023-06-21 \nitem:\n- description: CompactCook Camping Stove, quantity 1, price $60 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I bought the RainGuard Hiking Jacket a few weeks ago, but I noticed that the seam tape on the inside is starting to peel off. Is there anything I can do to fix this issue?"}], "item_number": 17, "order_number": 58, "description": "RainGuard Hiking Jacket, quantity 1, price $110", "intent": "product return"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: John \nLast Name: Smith \nAge: 35 \nEmail Address: johnsmith@example.com \nPhone Number: 555-123-4567 \nShipping Address: 123 Main St, Anytown USA, 12345 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 1 \ndate: 2023-01-05 \nitem:\n- description: TrailMaster X4 Tent, quantity 2, price $500 \n\u00a0 item_number: 1 \n\norder_number: 19 \ndate: 2023-01-25 \nitem:\n- description: BaseCamp Folding Table, quantity 1, price $60 \n\u00a0 item_number: 5 \n\norder_number: 29 \ndate: 2023-02-10 \nitem:\n- description: Alpine Explorer Tent, quantity 2, price $700 \n\u00a0 item_number: 8 \n\norder_number: 41 \ndate: 2023-03-01 \nitem:\n- description: TrekMaster Camping Chair, quantity 1, price $50 \n\u00a0 item_number: 12 \n\norder_number: 50 \ndate: 2023-03-16 \nitem:\n- description: SkyView 2-Person Tent, quantity 2, price $400 \n\u00a0 item_number: 15 \n\norder_number: 59 \ndate: 2023-04-01 \nitem:\n- description: TrekStar Hiking Sandals, quantity 1, price $70 \n\u00a0 item_number: 18 \n\n", "history": [{"role": "customer", "content": "Hi, I purchased the TrekStar Hiking Sandals a few weeks ago, and they feel a bit tight. Is there a break-in period for these sandals, or should I exchange them for a larger size?"}], "item_number": 18, "order_number": 59, "description": "TrekStar Hiking Sandals, quantity 1, price $70", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Emily \nLast Name: Rodriguez \nAge: 29 \nEmail Address: emilyr@example.com \nPhone Number: 555-111-2222 \nShipping Address: 987 Oak Ave, Cityville USA, 56789 \nMembership: None \n\n## Recent_Purchases\n\norder_number: 3 \ndate: 2023-03-18 \nitem:\n- description: TrailMaster X4 Tent, quantity 3, price $750 \n\u00a0 item_number: 1 \n\norder_number: 12 \ndate: 2023-02-20 \nitem:\n- description: Summit Breeze Jacket, quantity 2, price $240 \n\u00a0 item_number: 3 \n\norder_number: 21 \ndate: 2023-04-02 \nitem:\n- description: BaseCamp Folding Table, quantity 1, price $60 \n\u00a0 item_number: 5 \n\norder_number: 31 \ndate: 2023-04-20 \nitem:\n- description: Alpine Explorer Tent, quantity 1, price $350 \n\u00a0 item_number: 8 \n\norder_number: 39 \ndate: 2023-03-30 \nitem:\n- description: TrailWalker Hiking Shoes, quantity 2, price $220 \n\u00a0 item_number: 11 \n\norder_number: 48 \ndate: 2023-04-16 \nitem:\n- description: MountainDream Sleeping Bag, quantity 2, price $260 \n\u00a0 item_number: 14 \n\norder_number: 61 \ndate: 2023-06-11 \nitem:\n- description: TrekStar Hiking Sandals, quantity 1, price $70 \n\u00a0 item_number: 18 \n\n", "history": [{"role": "customer", "content": "Hi there, I'm interested in purchasing the TrekStar Hiking Sandals. Can you tell me more about their features?"}], "item_number": 18, "order_number": 61, "description": "TrekStar Hiking Sandals, quantity 1, price $70", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Jane \nLast Name: Doe \nAge: 28 \nEmail Address: janedoe@example.com \nPhone Number: 555-987-6543 \nShipping Address: 456 Oak St, Another City USA, 67890 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 6 \ndate: 2023-01-10 \nitem:\n- description: Adventurer Pro Backpack, quantity 1, price $90 \n\u00a0 item_number: 2 \n\norder_number: 15 \ndate: 2023-01-20 \nitem:\n- description: TrekReady Hiking Boots, quantity 1, price $140 \n\u00a0 item_number: 4 \n\norder_number: 23 \ndate: 2023-01-30 \nitem:\n- description: EcoFire Camping Stove, quantity 1, price $80 \n\u00a0 item_number: 6 \n\norder_number: 32 \ndate: 2023-02-15 \nitem:\n- description: SummitClimber Backpack, quantity 1, price $120 \n\u00a0 item_number: 9 \n\norder_number: 44 \ndate: 2023-03-06 \nitem:\n- description: PowerBurner Camping Stove, quantity 1, price $100 \n\u00a0 item_number: 13 \n\norder_number: 53 \ndate: 2023-03-21 \nitem:\n- description: TrailLite Daypack, quantity 1, price $60 \n\u00a0 item_number: 16 \n\norder_number: 62 \ndate: 2023-04-06 \nitem:\n- description: Adventure Dining Table, quantity 1, price $90 \n\u00a0 item_number: 19 \n\n", "history": [{"role": "customer", "content": "I recently purchased the Adventure Dining Table, but I'm not happy with its quality. The table arrived with scratches on the surface, and one of the legs seems wobbly. I expected better craftsmanship from your brand. This is disappointing."}], "item_number": 19, "order_number": 62, "description": "Adventure Dining Table, quantity 1, price $90", "intent": "product return"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Melissa \nLast Name: Davis \nAge: 31 \nEmail Address: melissad@example.com \nPhone Number: 555-333-4444 \nShipping Address: 789 Ash St, Another City USA, 67890 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 4 \ndate: 2023-04-22 \nitem:\n- description: TrailMaster X4 Tent, quantity 2, price $500 \n\u00a0 item_number: 1 \n\norder_number: 17 \ndate: 2023-03-30 \nitem:\n- description: TrekReady Hiking Boots, quantity 1, price $140 \n\u00a0 item_number: 4 \n\norder_number: 25 \ndate: 2023-04-10 \nitem:\n- description: EcoFire Camping Stove, quantity 1, price $80 \n\u00a0 item_number: 6 \n\norder_number: 34 \ndate: 2023-04-25 \nitem:\n- description: SummitClimber Backpack, quantity 1, price $120 \n\u00a0 item_number: 9 \n\norder_number: 46 \ndate: 2023-05-16 \nitem:\n- description: PowerBurner Camping Stove, quantity 1, price $100 \n\u00a0 item_number: 13 \n\norder_number: 55 \ndate: 2023-05-31 \nitem:\n- description: TrailLite Daypack, quantity 1, price $60 \n\u00a0 item_number: 16 \n\norder_number: 64 \ndate: 2023-06-16 \nitem:\n- description: Adventure Dining Table, quantity 1, price $90 \n\u00a0 item_number: 19 \n\n", "history": [{"role": "customer", "content": "I recently purchased the Adventure Dining Table, and I have a question about its setup. The instructions provided are not very clear, and I'm having trouble assembling the table correctly. Can you provide me with some guidance or more detailed instructions?"}], "item_number": 19, "order_number": 64, "description": "Adventure Dining Table, quantity 2, price $180", "intent": "product question"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Sarah \nLast Name: Lee \nAge: 38 \nEmail Address: sarahlee@example.com \nPhone Number: 555-867-5309 \nShipping Address: 321 Maple St, Bigtown USA, 90123 \nMembership: Platinum \n\n## Recent_Purchases\n\norder_number: 2 \ndate: 2023-02-10 \nitem:\n- description: TrailMaster X4 Tent, quantity 1, price $250 \n\u00a0 item_number: 1 \n\norder_number: 26 \ndate: 2023-02-05 \nitem:\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \n\u00a0 item_number: 7 \n\norder_number: 35 \ndate: 2023-02-20 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \n\u00a0 item_number: 10 \n\norder_number: 42 \ndate: 2023-04-06 \nitem:\n- description: TrekMaster Camping Chair, quantity 2, price $100 \n\u00a0 item_number: 12 \n\norder_number: 51 \ndate: 2023-04-21 \nitem:\n- description: SkyView 2-Person Tent, quantity 1, price $200 \n\u00a0 item_number: 15 \n\norder_number: 56 \ndate: 2023-03-26 \nitem:\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \n\u00a0 item_number: 17 \n\norder_number: 65 \ndate: 2023-04-11 \nitem:\n- description: CompactCook Camping Stove, quantity 1, price $60 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I recently purchased the CompactCook Camping Stove, and I'm quite disappointed with its performance. The flame doesn't seem to stay consistent, and it takes forever to boil water. This is not what I expected from a camping stove. Can you help me with this issue?"}], "item_number": 20, "order_number": 65, "description": "CompactCook Camping Stove, quantity 1, price $60", "intent": "product return"}
|
||||
{"customer_info": "## Customer_Info\n\nFirst Name: Amanda \nLast Name: Perez \nAge: 26 \nEmail Address: amandap@example.com \nPhone Number: 555-123-4567 \nShipping Address: 654 Pine St, Suburbia USA, 23456 \nMembership: Gold \n\n## Recent_Purchases\n\norder_number: 5 \ndate: 2023-05-01 \nitem:\n- description: TrailMaster X4 Tent, quantity 1, price $250 \n\u00a0 item_number: 1 \n\norder_number: 18 \ndate: 2023-05-04 \nitem:\n- description: TrekReady Hiking Boots, quantity 3, price $420 \n\u00a0 item_number: 4 \n\norder_number: 28 \ndate: 2023-04-15 \nitem:\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \n\u00a0 item_number: 7 \n\norder_number: 37 \ndate: 2023-04-30 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \n\u00a0 item_number: 10 \n\norder_number: 58 \ndate: 2023-06-06 \nitem:\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \n\u00a0 item_number: 17 \n\norder_number: 67 \ndate: 2023-06-21 \nitem:\n- description: CompactCook Camping Stove, quantity 1, price $60 \n\u00a0 item_number: 20 \n\n", "history": [{"role": "customer", "content": "I recently received the CompactCook Camping Stove, and I'm not sure about its maintenance requirements. Are there any specific cleaning or storage instructions I should follow to ensure the stove's longevity?"}], "item_number": 20, "order_number": 67, "description": "CompactCook Camping Stove, quantity 1, price $60", "intent": "product question"}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"customer_info": "## Customer_Info\n\nFirst Name: Sarah \nLast Name: Lee \nAge: 38 \nEmail Address: sarahlee@example.com \nPhone Number: 555-867-5309 \nShipping Address: 321 Maple St, Bigtown USA, 90123 \nMembership: Platinum \n\n## Recent_Purchases\n\norder_number: 2 \ndate: 2023-02-10 \nitem:\n- description: TrailMaster X4 Tent, quantity 1, price $250 \n\u00a0 item_number: 1 \n\norder_number: 26 \ndate: 2023-02-05 \nitem:\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \n\u00a0 item_number: 7 \n\norder_number: 35 \ndate: 2023-02-20 \nitem:\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \n\u00a0 item_number: 10 \n\norder_number: 42 \ndate: 2023-04-06 \nitem:\n- description: TrekMaster Camping Chair, quantity 2, price $100 \n\u00a0 item_number: 12 \n\norder_number: 51 \ndate: 2023-04-21 \nitem:\n- description: SkyView 2-Person Tent, quantity 1, price $200 \n\u00a0 item_number: 15 \n\norder_number: 56 \ndate: 2023-03-26 \nitem:\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \n\u00a0 item_number: 17 \n\norder_number: 65 \ndate: 2023-04-11 \nitem:\n- description: CompactCook Camping Stove, quantity 1, price $60 \n\u00a0 item_number: 20 \n\n",
|
||||
"history": [
|
||||
{
|
||||
"role": "customer",
|
||||
"content": "I recently bought the TrailMaster X4 Tent, and it leaked during a light rain. This is unacceptable! I expected a reliable and waterproof tent, but it failed to deliver. I'm extremely disappointed in the quality."
|
||||
}
|
||||
],
|
||||
"item_number": 1,
|
||||
"order_number": 2,
|
||||
"description": "TrailMaster X4 Tent, quantity 1, price $250",
|
||||
"intent": "product return"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
|
||||
from promptflow.core import tool
|
||||
from promptflow.connections import CustomConnection
|
||||
|
||||
from intent import extract_intent
|
||||
|
||||
|
||||
@tool
|
||||
def extract_intent_tool(chat_prompt, connection: CustomConnection) -> str:
|
||||
|
||||
# set environment variables
|
||||
for key, value in dict(connection).items():
|
||||
os.environ[key] = value
|
||||
|
||||
# call the entry function
|
||||
return extract_intent(
|
||||
chat_prompt=chat_prompt,
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/Flow.schema.json
|
||||
inputs:
|
||||
history:
|
||||
type: string
|
||||
customer_info:
|
||||
type: string
|
||||
outputs:
|
||||
output:
|
||||
type: string
|
||||
reference: ${extract_intent.output}
|
||||
nodes:
|
||||
- name: chat_prompt
|
||||
type: prompt
|
||||
source:
|
||||
type: code
|
||||
path: user_intent_zero_shot.jinja2
|
||||
inputs: # Please check the generated prompt inputs
|
||||
history: ${inputs.history}
|
||||
customer_info: ${inputs.customer_info}
|
||||
- name: extract_intent
|
||||
type: python
|
||||
source:
|
||||
type: code
|
||||
path: extract_intent_tool.py
|
||||
inputs:
|
||||
chat_prompt: ${chat_prompt.output}
|
||||
connection: custom_connection
|
||||
environment:
|
||||
python_requirements_txt: requirements.txt
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"customer_info": "## Customer_Info\\n\\nFirst Name: Sarah \\nLast Name: Lee \\nAge: 38 \\nEmail Address: sarahlee@example.com \\nPhone Number: 555-867-5309 \\nShipping Address: 321 Maple St, Bigtown USA, 90123 \\nMembership: Platinum \\n\\n## Recent_Purchases\\n\\norder_number: 2 \\ndate: 2023-02-10 \\nitem:\\n- description: TrailMaster X4 Tent, quantity 1, price $250 \\n\\u00a0 item_number: 1 \\n\\norder_number: 26 \\ndate: 2023-02-05 \\nitem:\\n- description: CozyNights Sleeping Bag, quantity 1, price $100 \\n\\u00a0 item_number: 7 \\n\\norder_number: 35 \\ndate: 2023-02-20 \\nitem:\\n- description: TrailBlaze Hiking Pants, quantity 1, price $75 \\n\\u00a0 item_number: 10 \\n\\norder_number: 42 \\ndate: 2023-04-06 \\nitem:\\n- description: TrekMaster Camping Chair, quantity 2, price $100 \\n\\u00a0 item_number: 12 \\n\\norder_number: 51 \\ndate: 2023-04-21 \\nitem:\\n- description: SkyView 2-Person Tent, quantity 1, price $200 \\n\\u00a0 item_number: 15 \\n\\norder_number: 56 \\ndate: 2023-03-26 \\nitem:\\n- description: RainGuard Hiking Jacket, quantity 1, price $110 \\n\\u00a0 item_number: 17 \\n\\norder_number: 65 \\ndate: 2023-04-11 \\nitem:\\n- description: CompactCook Camping Stove, quantity 1, price $60 \\n\\u00a0 item_number: 20 \\n\\n",
|
||||
"history": "[ { \"role\": \"customer\", \"content\": \"I recently bought the TrailMaster X4 Tent, and it leaked during a light rain. This is unacceptable! I expected a reliable and waterproof tent, but it failed to deliver. I'm extremely disappointed in the quality.\" } ]"
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import os
|
||||
import pip
|
||||
from langchain.chat_models import AzureChatOpenAI
|
||||
from langchain.prompts.chat import ChatPromptTemplate, HumanMessagePromptTemplate
|
||||
from langchain.prompts.prompt import PromptTemplate
|
||||
from langchain.schema import HumanMessage
|
||||
|
||||
|
||||
def extract_intent(chat_prompt: str):
|
||||
if (
|
||||
"AZURE_OPENAI_API_KEY" not in os.environ
|
||||
or "AZURE_OPENAI_API_BASE" not in os.environ
|
||||
):
|
||||
# load environment variables from .env file
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
# This can be removed if user using custom image.
|
||||
pip.main(["install", "python-dotenv"])
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# AZURE_OPENAI_ENDPOINT conflict with AZURE_OPENAI_API_BASE when use with langchain
|
||||
if "AZURE_OPENAI_ENDPOINT" in os.environ:
|
||||
os.environ.pop("AZURE_OPENAI_ENDPOINT")
|
||||
chat = AzureChatOpenAI(
|
||||
deployment_name=os.environ.get("CHAT_DEPLOYMENT_NAME", "gpt-35-turbo"),
|
||||
openai_api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
openai_api_base=os.environ["AZURE_OPENAI_API_BASE"],
|
||||
openai_api_type="azure",
|
||||
openai_api_version="2023-07-01-preview",
|
||||
temperature=0,
|
||||
)
|
||||
reply_message = chat([HumanMessage(content=chat_prompt)])
|
||||
return reply_message.content
|
||||
|
||||
|
||||
def generate_prompt(customer_info: str, history: list, user_prompt_template: str):
|
||||
|
||||
chat_history_text = "\n".join(
|
||||
[message["role"] + ": " + message["content"] for message in history]
|
||||
)
|
||||
|
||||
prompt_template = PromptTemplate.from_template(user_prompt_template)
|
||||
chat_prompt_template = ChatPromptTemplate.from_messages(
|
||||
[HumanMessagePromptTemplate(prompt=prompt_template)]
|
||||
)
|
||||
return chat_prompt_template.format_prompt(
|
||||
customer_info=customer_info, chat_history=chat_history_text
|
||||
).to_string()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
with open("./data/denormalized-flat.jsonl", "r") as f:
|
||||
data = [json.loads(line) for line in f.readlines()]
|
||||
|
||||
# only ten samples
|
||||
data = data[:10]
|
||||
|
||||
# load template from file
|
||||
with open("user_intent_zero_shot.jinja2", "r") as f:
|
||||
user_prompt_template = f.read()
|
||||
|
||||
# each test
|
||||
for item in data:
|
||||
chat_prompt = generate_prompt(
|
||||
item["customer_info"], item["history"], user_prompt_template
|
||||
)
|
||||
reply = extract_intent(chat_prompt)
|
||||
print("=====================================")
|
||||
# print("Customer info: ", item["customer_info"])
|
||||
# print("+++++++++++++++++++++++++++++++++++++")
|
||||
print("Chat history: ", item["history"])
|
||||
print("+++++++++++++++++++++++++++++++++++++")
|
||||
print(reply)
|
||||
print("+++++++++++++++++++++++++++++++++++++")
|
||||
print(f"Ground Truth: {item['intent']}")
|
||||
print("=====================================")
|
||||
@@ -0,0 +1,5 @@
|
||||
promptflow
|
||||
promptflow-tools
|
||||
python-dotenv
|
||||
langchain<0.2.0
|
||||
jinja2
|
||||
@@ -0,0 +1,43 @@
|
||||
You are given a list of orders with item_numbers from a customer and a statement from the customer. It is your job to identify
|
||||
the intent that the customer has with their statement. Possible intents can be:
|
||||
"product return", "product exchange", "general question", "product question", "other".
|
||||
|
||||
If the intent is product related ("product return", "product exchange", "product question"), then you should also
|
||||
provide the order id and item that the customer is referring to in their statement.
|
||||
|
||||
For instance if you are give the following list of orders:
|
||||
|
||||
order_number: 2020230
|
||||
date: 2023-04-23
|
||||
store_location: SeattleStore
|
||||
items:
|
||||
- description: Roof Rack, color black, price $199.99
|
||||
item_number: 101010
|
||||
- description: Running Shoes, size 10, color blue, price $99.99
|
||||
item_number: 202020
|
||||
|
||||
You are given the following customer statements:
|
||||
- I am having issues with the jobbing shoes I bought.
|
||||
|
||||
Then you should answer with in valid yaml format with the fields intent, order_number, item, and item_number like so:
|
||||
intent: product question
|
||||
order_number: 2020230
|
||||
descrption: Running Shoes, size 10, color blue, price $99.99
|
||||
item_number: 202020
|
||||
|
||||
Here is the actual problem you need to solve:
|
||||
|
||||
In triple backticks below is the customer information and a list of orders.
|
||||
```
|
||||
{{customer_info}}
|
||||
```
|
||||
|
||||
In triple backticks below are the is the chat history with customer statements and replies from the customer service agent:
|
||||
```
|
||||
{{chat_history}}
|
||||
```
|
||||
|
||||
What is the customer's `intent:` here?
|
||||
"product return", "exchange product", "general question", "product question" or "other"?
|
||||
|
||||
Reply with only the intent string.
|
||||
@@ -0,0 +1,23 @@
|
||||
You are given a list of orders with item_numbers from a customer and a statement from the customer. It is your job to identify the intent that the customer has with their statement. Possible intents can be: "product return", "product exchange", "general question", "product question", "other".
|
||||
|
||||
In triple backticks below is the customer information and a list of orders.
|
||||
|
||||
```
|
||||
|
||||
{{customer_info}}
|
||||
|
||||
```
|
||||
|
||||
In triple backticks below are the is the chat history with customer statements and replies from the customer service agent:
|
||||
|
||||
```
|
||||
|
||||
{{history}}
|
||||
|
||||
```
|
||||
|
||||
What is the customer's `intent:` here?
|
||||
|
||||
"product return", "exchange product", "general question", "product question" or "other"?
|
||||
|
||||
Reply with only the intent string.
|
||||
@@ -0,0 +1,3 @@
|
||||
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
|
||||
AZURE_OPENAI_API_KEY=your-api-key
|
||||
AZURE_OPENAI_DEPLOYMENT=gpt-4o
|
||||
@@ -0,0 +1,2 @@
|
||||
{"question": "How many colors are there in the image?", "input_image": {"data:image/png;url": "https://developer.microsoft.com/_devcom/images/logo-ms-social.png"}}
|
||||
{"question": "What's this image about?", "input_image": {"data:image/png;url": "https://developer.microsoft.com/_devcom/images/404.png"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
agent-framework>=1.0.1
|
||||
agent-framework-openai>=1.0.1
|
||||
python-dotenv
|
||||
Pillow
|
||||
requests
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user