-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
Conversation
@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. |
@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]: | |||
|
There was a problem hiding this comment.
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}, |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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.*", |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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.", |
There was a problem hiding this comment.
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.
tests/snapshots/test_app/test_chat_with_history/client1/result.json
Outdated
Show resolved
Hide resolved
tests/snapshots/test_app/test_chat_with_history/client0/result.json
Outdated
Show resolved
Hide resolved
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 |
@@ -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[] }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did this change?
There was a problem hiding this comment.
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.
Check Broken URLsWe 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.
|
Check Broken URLsWe 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.
|
Check Broken URLsWe 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.
|
Broken URLs should work once this is merged. |
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.
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.
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.
Type of change
Code quality checklist
See CONTRIBUTING.md for more details.
python -m pytest
).python -m pytest --cov
to verify 100% coverage of added linespython -m mypy
to check for type errorsruff
andblack
manually on my code.