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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:52 +08:00
commit e768098d0e
4004 changed files with 2804145 additions and 0 deletions
@@ -0,0 +1,129 @@
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/Flow.schema.json
environment:
python_requirements_txt: requirements.txt
inputs:
text_chunk:
type: string
is_chat_input: false
default: Prompt flow is a suite of development tools designed to streamline the
end-to-end development cycle of LLM-based AI applications, from ideation,
prototyping, testing, evaluation to production deployment and monitoring.
It makes prompt engineering much easier and enables you to build LLM apps
with production quality.
outputs:
question:
type: string
reference: ${validate_question.output.question}
suggested_answer:
type: string
reference: ${validate_suggested_answer.output.suggested_answer}
debug_info:
type: string
reference: ${generate_debug_info.output}
nodes:
- name: score_text_chunk_prompt
type: prompt
source:
type: code
path: score_text_chunk_prompt.jinja2
inputs:
context: ${inputs.text_chunk}
use_variants: false
- name: validate_question_prompt
type: prompt
source:
type: code
path: validate_question_prompt.jinja2
inputs:
question: ${generate_question.output}
context: ${inputs.text_chunk}
use_variants: false
- name: generate_question_prompt
type: prompt
source:
type: code
path: generate_question_prompt.jinja2
inputs:
context: ${inputs.text_chunk}
use_variants: false
- name: generate_suggested_answer_prompt
type: prompt
source:
type: code
path: generate_suggested_answer_prompt.jinja2
inputs:
context: ${inputs.text_chunk}
question: ${validate_question.output.question}
use_variants: false
- name: generate_question
type: python
source:
type: code
path: generate_question.py
inputs:
connection: ""
context: ${validate_text_chunk.output.context}
temperature: 0.2
generate_question_prompt: ${generate_question_prompt.output}
use_variants: false
- name: validate_question
type: python
source:
type: code
path: validate_question.py
inputs:
connection: ""
temperature: 0.2
generated_question: ${generate_question.output}
validate_question_prompt: ${validate_question_prompt.output}
use_variants: false
- name: generate_suggested_answer
type: python
source:
type: code
path: generate_suggested_answer.py
inputs:
connection: ""
context: ${inputs.text_chunk}
generate_suggested_answer_prompt: ${generate_suggested_answer_prompt.output}
question: ${validate_question.output.question}
temperature: 0.2
use_variants: false
- name: generate_debug_info
type: python
source:
type: code
path: generate_debug_info.py
inputs:
text_chunk: ${inputs.text_chunk}
validate_suggested_answer_output: ${validate_suggested_answer.output}
text_chunk_validation_res: ${validate_text_chunk.output.validation_res}
validate_question_output: ${validate_question.output}
- name: validate_suggested_answer_prompt
type: prompt
source:
type: code
path: validate_suggested_answer_prompt.jinja2
inputs:
answer: ${generate_suggested_answer.output}
- name: validate_suggested_answer
type: python
source:
type: code
path: validate_suggested_answer.py
inputs:
connection: ""
suggested_answer: ${generate_suggested_answer.output}
validate_suggested_answer_prompt: ${validate_suggested_answer_prompt.output}
temperature: 0.2
- name: validate_text_chunk
type: python
source:
type: code
path: validate_text_chunk.py
inputs:
connection: ""
score_text_chunk_prompt: ${score_text_chunk_prompt.output}
context: ${inputs.text_chunk}
score_threshold: 4
temperature: 0.2
@@ -0,0 +1,47 @@
from utils import ValidateObj, ValidationResult
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
@tool
def my_python_tool(
text_chunk: str,
text_chunk_validation_res: ValidationResult = None,
validate_question_output: dict = None,
validate_suggested_answer_output: dict = None,
) -> dict:
question_validation_res = validate_question_output["validation_res"]
generated_suggested_answer = validate_suggested_answer_output["suggested_answer"]
suggested_answer_validation_res = validate_suggested_answer_output["validation_res"]
is_generation_success = generated_suggested_answer != ""
is_text_chunk_valid = text_chunk_validation_res["pass_validation"] if text_chunk_validation_res else None
is_seed_question_valid = question_validation_res["pass_validation"] if question_validation_res else None
is_suggested_answer_valid = (
suggested_answer_validation_res["pass_validation"] if suggested_answer_validation_res else None
)
failed_step = ""
if not is_generation_success:
if is_text_chunk_valid is False:
failed_step = ValidateObj.TEXT_CHUNK
elif is_seed_question_valid is False:
failed_step = ValidateObj.QUESTION
elif is_suggested_answer_valid is False:
failed_step = ValidateObj.SUGGESTED_ANSWER
return {
# TODO: support more question types like multi-context etc.
# "question_type": question_type,
"text_chunk": text_chunk,
"validation_summary": {"success": is_generation_success, "failed_step": failed_step},
"validation_details": {
ValidateObj.TEXT_CHUNK: text_chunk_validation_res,
ValidateObj.QUESTION: question_validation_res,
ValidateObj.SUGGESTED_ANSWER: suggested_answer_validation_res,
},
}
@@ -0,0 +1,39 @@
from typing import Union
from utils import llm_call
from promptflow._core.tool import InputSetting
from promptflow.connections import AzureOpenAIConnection, OpenAIConnection
from promptflow.core import tool
@tool(
input_settings={
"deployment_name": InputSetting(
enabled_by="connection",
enabled_by_type=["AzureOpenAIConnection"],
capabilities={"completion": False, "chat_completion": True, "embeddings": False},
),
"model": InputSetting(enabled_by="connection", enabled_by_type=["OpenAIConnection"]),
}
)
def generate_question(
connection: Union[OpenAIConnection, AzureOpenAIConnection],
generate_question_prompt: str,
deployment_name: str = "",
model: str = "",
context: str = None,
temperature: float = 0.2,
):
"""
Generates a question based on the given context.
Returns:
str: The generated seed question.
"""
# text chunk is not valid, just skip test data gen.
if not context:
return ""
seed_question = llm_call(connection, model, deployment_name, generate_question_prompt, temperature=temperature)
return seed_question
@@ -0,0 +1,17 @@
# system:
Your task is to formulate a question from given context satisfying the rules given below:
1.The question should better be framed from the overall context, serving as a general question, rather than just framed from some details.
2.The question should be specific and answerable from the given context.
3.The question must be reasonable and must be understood and responded by humans.
4.The question should not contain phrases like 'provided' or 'given' in the question.
5.The question should be a question asked by the hypothetical user without any given context.
6.The question should not contain any links.
7.The question should not contain more than 20 words, use abbreviation wherever possible.
# user:
context:
{{context}}
question:
@@ -0,0 +1,44 @@
from typing import Union
from utils import llm_call
from promptflow._core.tool import InputSetting
from promptflow.connections import AzureOpenAIConnection, OpenAIConnection
from promptflow.core import tool
@tool(
input_settings={
"deployment_name": InputSetting(
enabled_by="connection",
enabled_by_type=["AzureOpenAIConnection"],
capabilities={"completion": False, "chat_completion": True, "embeddings": False},
),
"model": InputSetting(enabled_by="connection", enabled_by_type=["OpenAIConnection"]),
}
)
def generate_suggested_answer(
connection: Union[OpenAIConnection, AzureOpenAIConnection],
question: str,
context: str,
generate_suggested_answer_prompt: str,
deployment_name: str = "",
model: str = "",
temperature: float = 0.2,
):
"""
Generates a suggested answer based on the given prompts and context information.
Returns:
str: The generated suggested answer.
"""
if question and context:
return llm_call(
connection,
model,
deployment_name,
generate_suggested_answer_prompt,
temperature=temperature,
)
else:
return ""
@@ -0,0 +1,12 @@
system:
Provide the answer for the question using the information from the given context based on the following criteria:
1. The answer is correct and complete.
2. The answer is derived from the given context.
3. The answer can totally answer the question.
4. The answer should not use the words like "in the context". The answer should be enough to answer the question without the context.
5. If the answer for the question cannot be generated from the given context, just return empty string.
user:
question:{{question}}
context:{{context}}
answer:
@@ -0,0 +1 @@
promptflow[azure]>=1.7.0
@@ -0,0 +1,49 @@
# system:
Given a text chunk from a document as context, perform the following tasks
1. Exclude any references, acknowledgments, personal information, code snippets, or other non-essential elements from the original context.
2. Evaluate the cleaned context against specific criteria for content quality and depth.
3. Assign a numerical score between 0 and 10 based on the following criteria:
- Award a high score (closer to 10) if:
a) cleaned context delves into and explains concepts.
b) cleaned context contains substantial information that could lead to meaningful questions.
- Award a lower score (closer to 0) if:
a) cleaned context is very brief, containing fewer than five words.
b) cleaned context is not meaningful.
4. Output a valid JSON containing the score and a reason. The reason must directly relate to the criteria outlined above, explaining the basis for the given score.
Here are some examples:
example 1:
context:
Albert Einstein (14 March 1879 - 18 April 1955) was a German-born theoretical physicist who is widely held to be one of the greatest and most influential scientists of all time.
output:
{
"score": "8.0",
"reason": "The context provides substantial information that could lead to meaningful questions, hence the high score."
}
example 2:
context:
Next step\n- Open the provided examples.
output:
{
"score": "0.0",
"reason": "The context lacks detailed information about the provided example and previous steps, resulting in a low score."
}
# user:
context:
{{context}}
output:
@@ -0,0 +1,170 @@
import json
import re
from collections import namedtuple
from numpy.random import default_rng
from promptflow.connections import AzureOpenAIConnection, OpenAIConnection
from promptflow.tools.aoai import chat as aoai_chat
from promptflow.tools.openai import chat as openai_chat
class QuestionType:
SIMPLE = "simple"
# MULTI_CONTEXT = "multi_context"
class ValidateObj:
QUESTION = "validate_question"
TEXT_CHUNK = "validate_text_chunk"
SUGGESTED_ANSWER = "validate_suggested_answer"
class ResponseFormat:
TEXT = "text"
JSON = "json_object"
class ErrorMsg:
INVALID_JSON_FORMAT = "Invalid json format. Response: {0}"
INVALID_TEXT_CHUNK = "Skipping generating seed question due to invalid text chunk: {0}"
INVALID_QUESTION = "Invalid seed question: {0}"
INVALID_ANSWER = "Invalid answer: {0}"
ValidationResult = namedtuple("ValidationResult", ["pass_validation", "reason"])
ScoreResult = namedtuple("ScoreResult", ["score", "reason", "pass_validation"])
def llm_call(
connection, model, deployment_name, prompt, response_format=ResponseFormat.TEXT, temperature=1.0, max_tokens=None
):
response_format = "json_object" if response_format.lower() == "json" else response_format
# avoid unnecessary jinja2 template re-rendering and potential error.
prompt = f"{{% raw %}}{prompt}{{% endraw %}}"
if isinstance(connection, AzureOpenAIConnection):
return aoai_chat(
connection=connection,
prompt=prompt,
deployment_name=deployment_name,
temperature=temperature,
max_tokens=max_tokens,
response_format={"type": response_format},
)
elif isinstance(connection, OpenAIConnection):
return openai_chat(
connection=connection,
prompt=prompt,
model=model,
temperature=temperature,
max_tokens=max_tokens,
response_format={"type": response_format},
)
def get_question_type(testset_distribution) -> str:
"""
Decides question evolution type based on probability
"""
rng = default_rng()
prob = rng.uniform(0, 1)
return next((key for key in testset_distribution.keys() if prob <= testset_distribution[key]), QuestionType.SIMPLE)
def get_suggested_answer_validation_res(
connection,
model,
deployment_name,
prompt,
suggested_answer: str,
temperature: float,
max_tokens: int = None,
response_format: ResponseFormat = ResponseFormat.TEXT,
):
rsp = llm_call(
connection,
model,
deployment_name,
prompt,
temperature=temperature,
max_tokens=max_tokens,
response_format=response_format,
)
return retrieve_verdict_and_print_reason(
rsp=rsp, validate_obj_name=ValidateObj.SUGGESTED_ANSWER, validate_obj=suggested_answer
)
def get_question_validation_res(
connection,
model,
deployment_name,
prompt,
question: str,
response_format: ResponseFormat,
temperature: float,
max_tokens: int = None,
):
rsp = llm_call(connection, model, deployment_name, prompt, response_format, temperature, max_tokens)
return retrieve_verdict_and_print_reason(rsp=rsp, validate_obj_name=ValidateObj.QUESTION, validate_obj=question)
def get_text_chunk_score(
connection,
model,
deployment_name,
prompt,
response_format: ResponseFormat,
score_threshold: float,
temperature: float,
max_tokens: int = None,
):
rsp = llm_call(connection, model, deployment_name, prompt, response_format, temperature, max_tokens)
data = _load_json_rsp(rsp)
score_float = 0
reason = ""
if data and isinstance(data, dict) and "score" in data and "reason" in data:
# Extract the verdict and reason
score = data["score"].lower()
reason = data["reason"]
print(f"Score {ValidateObj.TEXT_CHUNK}: {score}\nReason: {reason}")
try:
score_float = float(score)
except ValueError:
reason = ErrorMsg.INVALID_JSON_FORMAT.format(rsp)
else:
reason = ErrorMsg.INVALID_JSON_FORMAT.format(rsp)
pass_validation = score_float >= score_threshold
return ScoreResult(score_float, reason, pass_validation)
def retrieve_verdict_and_print_reason(rsp: str, validate_obj_name: str, validate_obj: str) -> ValidationResult:
data = _load_json_rsp(rsp)
if data and isinstance(data, dict) and "verdict" in data and "reason" in data:
# Extract the verdict and reason
verdict = data["verdict"].lower()
reason = data["reason"]
print(f"Is valid {validate_obj_name}: {verdict}\nReason: {reason}")
if verdict == "yes":
return ValidationResult(True, reason)
elif verdict == "no":
return ValidationResult(False, reason)
else:
print(f"Unexpected llm response to validate {validate_obj_name}: {validate_obj}")
return ValidationResult(False, ErrorMsg.INVALID_JSON_FORMAT.format(rsp))
def _load_json_rsp(rsp: str):
try:
# It is possible that even the response format is required as json, the response still contains ```json\n
rsp = re.sub(r"```json\n?|```", "", rsp)
data = json.loads(rsp)
except json.decoder.JSONDecodeError:
print(ErrorMsg.INVALID_JSON_FORMAT.format(rsp))
data = None
return data
@@ -0,0 +1,60 @@
from typing import Union
from utils import ErrorMsg, QuestionType, ResponseFormat, get_question_validation_res
from promptflow._core.tool import InputSetting
from promptflow.connections import AzureOpenAIConnection, OpenAIConnection
from promptflow.core import tool
@tool(
input_settings={
"deployment_name": InputSetting(
enabled_by="connection",
enabled_by_type=["AzureOpenAIConnection"],
capabilities={"completion": False, "chat_completion": True, "embeddings": False},
),
"model": InputSetting(enabled_by="connection", enabled_by_type=["OpenAIConnection"]),
}
)
def validate_question(
connection: Union[OpenAIConnection, AzureOpenAIConnection],
generated_question: str,
validate_question_prompt: str,
deployment_name: str = "",
model: str = "",
response_format: str = ResponseFormat.TEXT,
temperature: float = 0.2,
):
"""
1. Validates the given seed question.
2. Generates a test question based on the given prompts and distribution ratios.
Returns:
dict: The generated test question and its type.
"""
# text chunk is not valid, seed question not generated.
if not generated_question:
return {"question": "", "question_type": "", "validation_res": None}
validation_res = get_question_validation_res(
connection,
model,
deployment_name,
validate_question_prompt,
generated_question,
response_format,
temperature,
)
is_valid_seed_question = validation_res.pass_validation
question = ""
question_type = ""
failed_reason = ""
if not is_valid_seed_question:
failed_reason = ErrorMsg.INVALID_QUESTION.format(generated_question)
print(failed_reason)
else:
question = generated_question
question_type = QuestionType.SIMPLE
return {"question": question, "question_type": question_type, "validation_res": validation_res._asdict()}
@@ -0,0 +1,64 @@
# system:
Verdict a question based on following rules:
1. If there are acronyms or terms in the question, then please check if they exist in the given context. If no, verdict no. If yes, check if other rules are satisfied.
2. Determine if the given question can be clearly understood and give the reason.
Output a valid json with reason and verdict.
Here are some examples:
question: What is the discovery about space?
answer:
{
"reason":"The question is too vague and does not specify which discovery about space it is referring to."
"verdict":"no"
}
question: What caused the Great Depression?
answer:
{
"reason":"The question is specific and refers to a well-known historical economic event, making it clear and answerable.",
"verdict":"yes"
}
question: What is the keyword that best describes the paper's focus in natural language understanding tasks?
answer:
{
"reason": "The question mentions a 'paper' in it without referring it's name which makes it unclear without it",
"verdict": "no"
}
question: Who wrote 'Romeo and Juliet'?
answer:
{
"reason": "The question is clear and refers to a specific work by name therefore it is clear",
"verdict": "yes"
}
question: What did the study mention?
answer:
{
"reason": "The question is vague and does not specify which study it is referring to",
"verdict": "no"
}
question: What is the focus of the REPLUG paper?
answer:
{
"reason": "The question refers to a specific work by it's name hence can be understood",
"verdict": "yes"
}
question: What is the purpose of the reward-driven stage in the training process?
answer:
{
"reason": "The question lacks specific context regarding the type of training process, making it potentially ambiguous and open to multiple interpretations.",
"verdict": "no"
}
# user:
context: {{context}}
question: {{question}}
answer:
@@ -0,0 +1,55 @@
from typing import Union
from utils import ErrorMsg, get_suggested_answer_validation_res
from promptflow._core.tool import InputSetting
from promptflow.connections import AzureOpenAIConnection, OpenAIConnection
from promptflow.core import tool
@tool(
input_settings={
"deployment_name": InputSetting(
enabled_by="connection",
enabled_by_type=["AzureOpenAIConnection"],
capabilities={"completion": False, "chat_completion": True, "embeddings": False},
),
"model": InputSetting(enabled_by="connection", enabled_by_type=["OpenAIConnection"]),
}
)
@tool
def validate_suggested_answer(
connection: Union[OpenAIConnection, AzureOpenAIConnection],
suggested_answer: str,
validate_suggested_answer_prompt: str,
deployment_name: str = "",
model: str = "",
temperature: float = 0.2,
response_format: str = "text",
):
"""
1. Validates the given suggested answer.
Returns:
dict: The generated suggested answer and its validation result.
"""
if not suggested_answer:
return {"suggested_answer": "", "validation_res": None}
validation_res = get_suggested_answer_validation_res(
connection,
model,
deployment_name,
validate_suggested_answer_prompt,
suggested_answer,
temperature,
response_format=response_format,
)
is_valid_gt = validation_res.pass_validation
failed_reason = ""
if not is_valid_gt:
failed_reason = ErrorMsg.INVALID_ANSWER.format(suggested_answer)
print(failed_reason)
suggested_answer = ""
return {"suggested_answer": suggested_answer, "validation_res": validation_res._asdict()}
@@ -0,0 +1,43 @@
# system:
Given an answer, verdict if the provided answer is valid and provide the reason in valid json format.
The answer is not valid if the answer suggests that the context does not provide information or indicates uncertainty (such as 'I don't know'), it is deemed invalid. For any other case, the answer is considered valid.
# user:
Output a json format with the reason and verdict.
Here are some examples:
answer:
The steps to build and install your tool package for use in VS Code extension are not provided in the context.
output:
{
"reason":"The answer is invalid because it states that the context does not provide the necessary steps.",
"verdict":"no"
}
answer:
The context does not provide specific information on what the possible provider values are in supported configs for a connection provider.
output:
{
"reason":"The answer is invalid as it indicates that the context lacks specific information.",
"verdict":"no"
}
answer:
I don't know.
output:
{
"reason":"The answer is invalid because it conveys don't know.",
"verdict":"no"
}
answer:
The two essential components of an activate config in a node flow are `activate.when` and `activate.is`.
output:
{
"reason":"The answer is valid because it is clear and true.",
"verdict":"yes"
}
answer:{{answer}}
output:
@@ -0,0 +1,49 @@
from typing import Union
from utils import ErrorMsg, ResponseFormat, get_text_chunk_score
from promptflow._core.tool import InputSetting
from promptflow.connections import AzureOpenAIConnection, OpenAIConnection
from promptflow.core import tool
@tool(
input_settings={
"deployment_name": InputSetting(
enabled_by="connection",
enabled_by_type=["AzureOpenAIConnection"],
capabilities={"completion": False, "chat_completion": True, "embeddings": False},
),
"model": InputSetting(enabled_by="connection", enabled_by_type=["OpenAIConnection"]),
}
)
def validate_text_chunk(
connection: Union[OpenAIConnection, AzureOpenAIConnection],
score_text_chunk_prompt: str,
score_threshold: float,
deployment_name: str = "",
model: str = "",
context: str = None,
response_format: str = ResponseFormat.TEXT,
temperature: float = 0.2,
):
"""
Validates the given text chunk. If the validation fails, return an empty context and the validation result.
Returns:
dict: Text chunk context and its validation result.
"""
text_chunk_score_res = get_text_chunk_score(
connection,
model,
deployment_name,
score_text_chunk_prompt,
response_format,
score_threshold,
temperature,
)
if not text_chunk_score_res.pass_validation:
print(ErrorMsg.INVALID_TEXT_CHUNK.format(context))
return {"context": "", "validation_res": text_chunk_score_res._asdict()}
return {"context": context, "validation_res": text_chunk_score_res._asdict()}