Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Use prompty to store prompts #2178

Merged
merged 30 commits into from
Jan 14, 2025
Merged

Use prompty to store prompts #2178

merged 30 commits into from
Jan 14, 2025

Conversation

pamelafox
Copy link
Collaborator

@pamelafox pamelafox commented Nov 19, 2024

Purpose

This PR uses Prompty (https://prompty.ai/) to store prompts.

Now, instead of storing parts of the prompt in variables, everything is inside prompty files. You can even use the VS Code Prompty extension to play with the prompts, and you can even upload the prompty files to the Chat playground in Azure AI Foundry.

Screenshot 2025-01-14 at 12 29 58 PM

To abstract on top of the prompty interface for greater flexibility and testing, this PR adds a PromptManager class with load_prompt, load_tools, and render_prompt. The render_prompt function returns a structure that contains the system message, past messages, few shots, and new user_query, which can then be used by either our token-truncating function or the chat completion call.

Does this introduce a breaking change?

When developers merge from main and run the server, azd up, or azd deploy, will this produce an error?
If you're not sure, try it out on an old environment.

[ ] Yes
[X] No

Does this require changes to learn.microsoft.com docs?

This repository is referenced by this tutorial
which includes deployment, settings and usage instructions. If text or screenshot need to change in the tutorial,
check the box below and notify the tutorial author. A Microsoft employee can do this for you if you're an external contributor.

[ ] Yes
[X] No

Type of change

[ ] Bugfix
[X] Feature
[ ] Code style update (formatting, local variables)
[X] Refactoring (no functional changes, no api changes)
[ ] Documentation content changes
[ ] Other... Please describe:

Code quality checklist

See CONTRIBUTING.md for more details.

  • The current tests all pass (python -m pytest).
  • I added tests that prove my fix is effective or that my feature works
  • I ran python -m pytest --cov to verify 100% coverage of added lines
  • I ran python -m mypy to check for type errors
  • I either used the pre-commit hooks or ran ruff and black manually on my code.

@jeannotdamoiseaux
Copy link
Contributor

@pamelafox - The absence of tutorials or documentation at prompty.ai or in their GitHub repo has made it challenging to implement the desired prompt management structure using Prompty at this time.

@pamelafox
Copy link
Collaborator Author

@jeannotdamoiseaux Agreed, they need additional documentation about the format of the prompt itself (the Jinja2 part, under the YAML). I've passed the feedback onto the Prompty creators.

@@ -205,6 +207,10 @@ async def search(
def get_sources_content(
self, results: List[Document], use_semantic_captions: bool, use_image_citation: bool
) -> list[str]:

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this function out of text.py since it was a 2-line file, and the function is only used in this one method.

extra_info = {
"data_points": data_points,
"data_points": {"text": text_sources},
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved data_points into the dict itself, as we weren't using that variable separately anyway.

new_user_content: str


class PromptManager:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made an abstraction for a PromptManager, since some developers weren't as keen on Prompty as others, but I'm not sure if this abstraction would work for other ways of managing prompts anyway, so it might be a premature abstraction? I could remove it and only have PromptyManager for now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I do keep only PromptyManager, then I could also just construct it in the init of each approach, instead of constructing it in app.py.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrapping prompty is all good IMO. Also helps for tests

def load_tools(self, path: str):
return json.loads(open(self.PROMPTS_DIRECTORY / path).read())

def render_prompt(self, prompt, data) -> RenderedPrompt:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was my solution to my conundrum about needing to retrieve the messages back from the rendered prompt using indexes, so that they can then get passed to the token counter.
Now, what I do is that I mark each example with (EXAMPLE), so that I can distinguish examples from actual past messages, and then I can extract them all back out in this function.

So this function extracts:

  • system
  • (EXAMPLE) pairs
  • past messages
  • new user message

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also good feedback for prompty as well, thanks for breaking this out

user_content = q + "\n" + f"Sources:\n {content}"

response_token_limit = 1024
updated_messages = build_messages(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized that we do not need to call build_messages in the ask approaches, since it only truncates past messages, and we don't pass in chat history to these. So we remove the call and assume that the /ask questions always fit in the context window.


response_token_limit = 1024
updated_messages = build_messages(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here, I removed the build_messages call since it isn't useful when there's no history being passed in.

if result.sourcepage:
img = await download_blob_as_base64(blob_container_client, result.sourcepage)
if img:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default is "auto", so Prompty doesn't even output detail, so we only need to return the data URI. If developers do need to customize the detail in the future, that'd require a Prompty change.

pyproject.toml Outdated
@@ -33,5 +33,6 @@ module = [
"azure.cognitiveservices.*",
"azure.cognitiveservices.speech.*",
"pymupdf.*",
"prompty.*",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've filed an issue requesting types for Prompty

@@ -319,7 +319,7 @@ def mock_env(monkeypatch, request):
yield


@pytest_asyncio.fixture()
@pytest_asyncio.fixture(scope="function")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a recent change to the asyncio plugin that requires explicit scopes for async fixtures

@@ -47,19 +47,19 @@
{
"description": [
{
"content": "You are an intelligent assistant helping Contoso Inc employees with their healthcare plan questions and employee handbook questions. Use 'you' to refer to the individual asking the questions even if they ask with 'I'. Answer the following question using only the data provided in the sources below. Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. If you cannot answer using the sources below, say you don't know. Use below example to answer",
"content": "You are an intelligent assistant helping Contoso Inc employees with their healthcare plan questions and employee handbook questions.\nUse 'you' to refer to the individual asking the questions even if they ask with 'I'.\nAnswer the following question using only the data provided in the sources below.\nEach source has a name followed by colon and the actual information, always include the source name for each fact you use in the response.\nIf you cannot answer using the sources below, say you don't know. Use below example to answer",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we use Prompty, we get new lines in the prompt whereveer there are new lines in the template, which makes sense. I would assume the model is unaffected by newlines, but I could make one long line in the prompty files if there's concern about that.

"role": "system"
},
{
"content": "\n'What is the deductible for the employee plan for a visit to Overlake in Bellevue?'\n\nSources:\ninfo1.txt: deductibles depend on whether you are in-network or out-of-network. In-network deductibles are $500 for employee and $1000 for family. Out-of-network deductibles are $1000 for employee and $2000 for family.\ninfo2.pdf: Overlake is in-network for the employee plan.\ninfo3.pdf: Overlake is the name of the area that includes a park and ride near Bellevue.\ninfo4.pdf: In-network institutions include Overlake, Swedish and others in the region\n",
"content": "What is the deductible for the employee plan for a visit to Overlake in Bellevue?\n\nSources:\ninfo1.txt: deductibles depend on whether you are in-network or out-of-network. In-network deductibles are $500 for employee and $1000 for family. Out-of-network deductibles are $1000 for employee and $2000 for family.\ninfo2.pdf: Overlake is in-network for the employee plan.\ninfo3.pdf: Overlake is the name of the area that includes a park and ride near Bellevue.\ninfo4.pdf: In-network institutions include Overlake, Swedish and others in the region.",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an improvement from before - no more newline at the start, and no unnecessary quotes around the user question.

@pamelafox
Copy link
Collaborator Author

Per the discussion on the related PR (#2164), many developers seem interested in using Prompty for prompt management. I have made changes to my original PR to add an abstraction layer to make the Prompty rendering more robust, and also potentially make it flexible for other forms of prompt management, since not everyone is as interested in Prompty.

@mattgotteiner
This cannot yet be merged due to Prompty's lack of 3.9 support, but I am working with the Prompty maintainer on getting that fixed ASAP (just type annotation changes), so all the code changes on our side are done, so it's ready for your review.
There are a lot of changes in the tests snapshots, but they are all whitespace/ordering, and I added inline comments for each instance of a change.

@@ -3,7 +3,7 @@ import { parseSupportingContentItem } from "./SupportingContentParser";
import styles from "./SupportingContent.module.css";

interface Props {
supportingContent: string[] | { text: string[]; images?: { url: string }[] };
supportingContent: string[] | { text: string[]; images?: string[] };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this change?

Copy link
Collaborator Author

@pamelafox pamelafox Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would previously construct and pass down [{url: data:bla..., detail: "auto"}] because that was the format that the OpenAI chat completion request wanted as well. However, the Python now only constructs [url, url] because that's all Prompty needs (and OpenAI assumes "default"), so I propagated that change to the frontend.

Copy link

Check Broken URLs

We have automatically detected the following broken URLs in your files. Review and fix the paths to resolve this issue.

Check the file paths and associated broken URLs inside them. For more details, check our Contributing Guide.

File Full Path Issues
docs/customization.md
#LinkLine Number
1https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_query_rewrite.prompty39
2https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_answer_question.prompty41
3https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_query_rewrite.prompty43
4https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_answer_question.prompty43
5https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_answer_question_vision.prompty53
6https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/ask_answer_question.prompty60
7https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/ask_answer_question.prompty62
8https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/ask_answer_question_vision.prompty71

Copy link

Check Broken URLs

We have automatically detected the following broken URLs in your files. Review and fix the paths to resolve this issue.

Check the file paths and associated broken URLs inside them. For more details, check our Contributing Guide.

File Full Path Issues
docs/customization.md
#LinkLine Number
1https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_query_rewrite.prompty39
2https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_answer_question.prompty41
3https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_query_rewrite.prompty43
4https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_answer_question.prompty43
5https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_answer_question_vision.prompty53
6https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/ask_answer_question.prompty60
7https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/ask_answer_question.prompty62
8https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/ask_answer_question_vision.prompty71

Copy link

Check Broken URLs

We have automatically detected the following broken URLs in your files. Review and fix the paths to resolve this issue.

Check the file paths and associated broken URLs inside them. For more details, check our Contributing Guide.

File Full Path Issues
docs/customization.md
#LinkLine Number
1https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_query_rewrite.prompty39
2https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_answer_question.prompty41
3https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_query_rewrite.prompty43
4https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_answer_question.prompty43
5https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/chat_answer_question_vision.prompty53
6https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/ask_answer_question.prompty60
7https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/ask_answer_question.prompty62
8https://github.com/Azure-Samples/azure-search-openai-demo/blob/main/app/backend/approaches/prompts/ask_answer_question_vision.prompty71

@pamelafox
Copy link
Collaborator Author

Broken URLs should work once this is merged.

@pamelafox pamelafox merged commit 3629df8 into Azure-Samples:main Jan 14, 2025
15 of 16 checks passed
@pamelafox pamelafox deleted the prompty branch January 14, 2025 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants