Files
wehub-resource-sync e768098d0e
Flake8 Lint / flake8 (push) Waiting to run
Spell check CI / Spell_Check (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:39:52 +08:00

215 lines
8.7 KiB
Python

"""Tests for question-simulation MAF workflow.
Runs the workflow end-to-end against Azure OpenAI and verifies output format
and behaviour for both STOP and CONTINUE conversation scenarios.
Requires AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_OPENAI_DEPLOYMENT
to be set (via .env or environment).
"""
import asyncio
from workflow import QuestionSimInput, create_workflow
# ---------------------------------------------------------------------------
# Sample inputs and expected outputs
# ---------------------------------------------------------------------------
# Case 1: Multi-turn conversation that should CONTINUE — the human keeps asking
# substantive follow-up questions, the bot gives rich answers with more to explore.
CONTINUE_INPUT = QuestionSimInput(
chat_history=[
{
"inputs": {"question": "Can you introduce something about large language model?"},
"outputs": {
"answer": (
"A large language model (LLM) is a type of language model that is distinguished "
"by its ability to perform general-purpose language generation and understanding. "
"These models learn statistical relationships from text documents through a "
"self-supervised and semi-supervised training process that is computationally "
"intensive. LLMs are artificial neural networks, the largest and most capable "
"of which are built with a transformer-based architecture. Some recent "
"implementations are based on other architectures, such as recurrent neural "
"network variants and Mamba. LLMs can be used for text generation, a form of "
"generative AI, by taking an input text and repeatedly predicting the next "
"token or word. Notable examples include OpenAI's GPT series, Google's PaLM "
"and Gemini, Meta's LLaMA family, and Anthropic's Claude models."
),
},
},
{
"inputs": {"question": "What is the transformer architecture you mentioned?"},
"outputs": {
"answer": (
"The transformer is a deep learning architecture introduced in the 2017 "
"paper 'Attention Is All You Need' by Google researchers. It uses self-attention "
"mechanisms to process sequences in parallel, making it much faster to train "
"than previous recurrent models. The key innovation is multi-head attention, "
"which allows the model to focus on different parts of the input simultaneously."
),
},
},
],
question_count=3,
)
# Case 2: Conversation that should STOP — the human said thanks with no question,
# the bot replied with just a polite closing.
STOP_INPUT = QuestionSimInput(
chat_history=[
{
"inputs": {"question": "Thanks for the info. I'll look into it."},
"outputs": {
"answer": "You're welcome! If you need anything else, feel free to ask.",
},
}
],
question_count=3,
)
# Case 3: Multi-turn conversation that should CONTINUE.
MULTI_TURN_INPUT = QuestionSimInput(
chat_history=[
{
"inputs": {"question": "What is machine learning?"},
"outputs": {
"answer": (
"Machine learning is a subset of artificial intelligence that enables "
"systems to learn and improve from experience without being explicitly "
"programmed. It focuses on developing algorithms that can access data "
"and use it to learn for themselves."
),
},
},
{
"inputs": {"question": "What are the main types of machine learning?"},
"outputs": {
"answer": (
"There are three main types: supervised learning, unsupervised learning, "
"and reinforcement learning. Supervised learning uses labeled data, "
"unsupervised learning finds patterns in unlabeled data, and reinforcement "
"learning learns through trial and error with rewards."
),
},
},
],
question_count=2,
)
# Case 4: Single question requested — multi-turn to ensure CONTINUE.
SINGLE_Q_INPUT = QuestionSimInput(
chat_history=[
{
"inputs": {"question": "What is the transformer architecture?"},
"outputs": {
"answer": (
"The transformer is a deep learning architecture introduced in the 2017 "
"paper 'Attention Is All You Need' by Google researchers at NeurIPS. It "
"uses self-attention mechanisms to process sequences in parallel, making "
"it much faster to train than previous recurrent models like LSTMs."
),
},
},
{
"inputs": {"question": "How does self-attention work in transformers?"},
"outputs": {
"answer": (
"Self-attention computes a weighted sum of all positions in a sequence. "
"For each token, it creates query, key, and value vectors. The attention "
"score between two tokens is the dot product of the query of one with the "
"key of the other. These scores determine how much each token attends to "
"every other token, enabling the model to capture long-range dependencies."
),
},
},
],
question_count=1,
)
# ---------------------------------------------------------------------------
# Helper: run a single test case
# ---------------------------------------------------------------------------
async def run_test(name: str, sim_input: QuestionSimInput, expect_stop: bool):
"""Run the workflow and verify the output."""
print(f"\n{'='*60}")
print(f"TEST: {name}")
print(f" chat_history turns: {len(sim_input.chat_history)}")
print(f" question_count: {sim_input.question_count}")
print(f" expect_stop: {expect_stop}")
print(f"{'='*60}")
workflow = create_workflow()
result = await workflow.run(sim_input)
output = result.get_outputs()[0]
print(f" Output:\n {output!r}")
errors = []
# Basic type check
if not isinstance(output, str):
errors.append(f"Expected str output, got {type(output).__name__}")
if expect_stop:
# STOP case: output must be exactly "[STOP]"
if output != "[STOP]":
errors.append(f"Expected '[STOP]', got: {output!r}")
else:
# CONTINUE case: output must NOT be "[STOP]"
if output == "[STOP]":
errors.append("Expected generated questions but got '[STOP]'")
# Should contain at least one non-empty line
lines = [line.strip() for line in output.split("\n") if line.strip()]
if len(lines) < 1:
errors.append("Expected at least 1 generated question, got empty output")
# Number of questions should match question_count
if len(lines) != sim_input.question_count:
# This is a soft check — LLM may occasionally return fewer
print(f" WARNING: Expected {sim_input.question_count} questions, got {len(lines)}")
if errors:
for e in errors:
print(f" FAIL: {e}")
return False
else:
print(" PASS")
return True
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
async def main():
test_cases = [
("continue_single_turn", CONTINUE_INPUT, False),
("stop_polite_close", STOP_INPUT, True),
("continue_multi_turn", MULTI_TURN_INPUT, False),
("continue_single_question", SINGLE_Q_INPUT, False),
]
results = {}
for name, sim_input, expect_stop in test_cases:
passed = await run_test(name, sim_input, expect_stop)
results[name] = passed
# Summary
print(f"\n{'='*60}")
print("SUMMARY")
print(f"{'='*60}")
total = len(results)
passed = sum(1 for v in results.values() if v)
for name, ok in results.items():
status = "PASS" if ok else "FAIL"
print(f" [{status}] {name}")
print(f"\n {passed}/{total} passed")
if passed < total:
raise SystemExit(1)
if __name__ == "__main__":
asyncio.run(main())