Skip to content

Commit 07fea08

Browse files
authored
Instruction now not provided in test function. (#46028)
* Instruction now not provided in test function. * fix test
1 parent de2928b commit 07fea08

9 files changed

Lines changed: 304 additions & 274 deletions

sdk/ai/azure-ai-projects/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "python",
44
"TagPrefix": "python/ai/azure-ai-projects",
5-
"Tag": "python/ai/azure-ai-projects_8595f6b7bb"
5+
"Tag": "python/ai/azure-ai-projects_7d0cbe6866"
66
}

sdk/ai/azure-ai-projects/tests/samples/README.md

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import pytest
3535
from devtools_testutils import recorded_by_proxy, AzureRecordedTestCase, RecordedTransport
3636
from test_base import servicePreparer
3737
from sample_executor import SyncSampleExecutor, get_sample_paths, SamplePathPasser
38-
from test_samples_helpers import agent_tools_instructions, get_sample_env_vars
38+
from test_samples_helpers import get_sample_env_vars
3939

4040
class TestSamples(AzureRecordedTestCase):
4141
@servicePreparer()
@@ -65,10 +65,7 @@ class TestSamples(AzureRecordedTestCase):
6565
**kwargs,
6666
)
6767
executor.execute()
68-
executor.validate_print_calls_by_llm(
69-
instructions=agent_tools_instructions,
70-
project_endpoint=kwargs["foundry_project_endpoint"],
71-
)
68+
executor.validate_print_calls_by_llm()
7269
```
7370

7471
## Async example
@@ -79,7 +76,7 @@ from devtools_testutils.aio import recorded_by_proxy_async
7976
from devtools_testutils import AzureRecordedTestCase, RecordedTransport
8077
from test_base import servicePreparer
8178
from sample_executor import AsyncSampleExecutor, get_async_sample_paths, SamplePathPasser
82-
from test_samples_helpers import agent_tools_instructions, get_sample_env_vars
79+
from test_samples_helpers import get_sample_env_vars
8380

8481
class TestSamplesAsync(AzureRecordedTestCase):
8582

@@ -104,10 +101,7 @@ class TestSamplesAsync(AzureRecordedTestCase):
104101
**kwargs,
105102
)
106103
await executor.execute_async()
107-
await executor.validate_print_calls_by_llm_async(
108-
instructions=agent_tools_instructions,
109-
project_endpoint=kwargs["foundry_project_endpoint"],
110-
)
104+
await executor.validate_print_calls_by_llm_async()
111105
```
112106

113107
## Key pieces
@@ -145,7 +139,7 @@ servicePreparer = functools.partial(
145139
- `@SamplePathPasser`: Forwards the sample path to the recorder decorators.
146140
- `recorded_by_proxy` / `recorded_by_proxy_async`: Wrap tests for recording/playback. Include `RecordedTransport.HTTPX` when samples use httpx in addition to the default `RecordedTransport.AZURE_CORE`.
147141
- `execute` / `execute_async`: Run the sample; any exception fails the test.
148-
- `validate_print_calls_by_llm` / `validate_print_calls_by_llm_async`: Optionally validate captured print output with LLM instructions and an explicit `project_endpoint` (and optional `model`).
142+
- `validate_print_calls_by_llm` / `validate_print_calls_by_llm_async`: Validate captured print output with LLM instructions resolved automatically from the sample folder. You can still pass an explicit `instructions` override when needed.
149143
- `kwargs` in the test function: A dictionary with environment variables in key and value pairs.
150144

151145
## Optional test environment variables mapping
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# pylint: disable=line-too-long,useless-suppression
2+
# ------------------------------------
3+
# Copyright (c) Microsoft Corporation.
4+
# Licensed under the MIT License.
5+
# ------------------------------------
6+
7+
"""Centralized LLM validation instructions for sample execution.
8+
9+
This module is the single source of truth for:
10+
- instruction strings used by sample validation
11+
- mapping from sample folder -> instructions
12+
13+
The mapper is consumed by `sample_executor.py` to ensure all validation
14+
uses consistent instructions based on sample path.
15+
"""
16+
17+
from __future__ import annotations
18+
19+
from typing import Final
20+
21+
agent_tools_instructions: Final[str] = """
22+
We just ran Python code and captured print/log output in an attached log file (TXT).
23+
Validate whether sample execution/output is correct for a tool-driven assistant workflow.
24+
25+
Prefer matched/substantive data in the final output when available.
26+
27+
Mark `correct = false` for:
28+
- Exceptions, stack traces, explicit error/failure messages.
29+
- Timeout/auth/connection/service errors that prevent normal completion.
30+
- Malformed/corrupted output indicating broken processing.
31+
- Tool invocation failures where the sample cannot proceed as designed.
32+
33+
Follow-up questions are allowed, but only as supplementary behavior.
34+
If the output mainly asks follow-up questions without providing matched/substantive data, mark false.
35+
36+
Important distinction:
37+
- Empty tool payloads by themselves (for example `[]` or `""`) can be valid and should not automatically fail.
38+
- But if the assistant explicitly states tool failure/inability (for example "unable to retrieve ..."),
39+
treat that as a failure signal and mark `correct = false`.
40+
41+
Mark `correct = true` when execution succeeds and the output includes matched/substantive data,
42+
even if it also asks follow-up questions.
43+
44+
Always include `reason` with a concise explanation tied to the observed print output.
45+
""".strip()
46+
47+
48+
memories_instructions: Final[str] = """
49+
We just ran Python code and captured print/log output in an attached log file (TXT).
50+
Validate whether sample execution/output is correct for a memories workflow.
51+
52+
For memories scenarios, successful output typically shows one or more of:
53+
- Memory store/session creation or retrieval steps.
54+
- Memory save/ingest operations.
55+
- Memory query/search output with relevant recalled content.
56+
57+
Mark `correct = false` for:
58+
- Exceptions, stack traces, explicit error/failure messages.
59+
- Timeout/auth/connection/service errors that prevent normal completion.
60+
- Malformed/corrupted output indicating broken processing.
61+
- Memory operation failures where the sample cannot proceed as designed.
62+
63+
Important distinction:
64+
- Empty memory results by themselves (for example no matches for a query) can be valid and should not automatically fail.
65+
- Completed updates with 0 memory operations can still be valid for this test and should not automatically fail.
66+
- But explicit inability/failure to access or process memory should be marked `correct = false`.
67+
68+
Mark `correct = true` when execution succeeds and the output is consistent with the sample's intended
69+
memory behavior, even if no memory matches are found.
70+
71+
Always include `reason` with a concise explanation tied to the observed print output.
72+
""".strip()
73+
74+
75+
agents_instructions: Final[str] = """
76+
We just ran Python code and captured print/log output in an attached log file (TXT).
77+
Validate whether sample execution/output is correct.
78+
79+
For agents scenarios, successful output typically shows one or more of:
80+
- Agent/run/thread creation or execution progress.
81+
- Assistant response content, streamed events, or structured output.
82+
- Retrieval/context-aware response behavior when the sample asks for it.
83+
84+
Check input/output correspondence:
85+
- If the printed output contains a user prompt/request/question, the final assistant output should
86+
clearly address that input and stay on-topic.
87+
- If the sample expects structured output, the printed result should match that structure.
88+
- Minor wording differences are acceptable as long as the response is relevant and logically consistent.
89+
90+
Mark `correct = false` for:
91+
- Exceptions, stack traces, explicit error/failure messages.
92+
- Timeout/auth/connection/service errors that prevent normal completion.
93+
- Malformed/corrupted output indicating broken processing.
94+
- Agent run/tool/retrieval failures where the sample cannot proceed as designed.
95+
96+
Important distinction:
97+
- Intermediate/partial prints during streaming can be valid and should not automatically fail.
98+
- Empty or brief intermediate payloads by themselves can be valid if the run still completes successfully.
99+
- But explicit inability/failure to execute the intended agent workflow should be marked `correct = false`.
100+
101+
Mark `correct = true` when execution succeeds and the output is consistent with the sample's intended
102+
agent behavior, including reasonable correspondence between input prompt(s) and final output.
103+
104+
Always include `reason` with a concise explanation tied to the observed print output.
105+
""".strip()
106+
107+
108+
chat_completions_instructions: Final[str] = """
109+
We just ran Python code and captured print/log output in an attached log file (TXT).
110+
Validate whether sample execution/output is correct for Chat Completions scenarios.
111+
112+
Successful output typically shows one or more of:
113+
- A user prompt and a corresponding assistant response.
114+
- Reasonable, on-topic completion content.
115+
116+
Mark `correct = false` for:
117+
- Exceptions, stack traces, explicit error/failure messages.
118+
- Timeout/auth/connection/service errors that prevent normal completion.
119+
- Malformed/corrupted output indicating broken processing.
120+
- Output that clearly does not answer the prompt in the sample.
121+
122+
Mark `correct = true` when execution succeeds and the assistant output is coherent and
123+
responds to the printed prompt, even if the exact wording varies.
124+
125+
Always include `reason` with a concise explanation tied to the observed print output.
126+
""".strip()
127+
128+
129+
resource_management_instructions: Final[str] = """
130+
We just ran Python code and captured print/log output in an attached log file (TXT).
131+
Validate whether sample execution/output is correct for resource-management samples (for example
132+
connections, files, and deployments).
133+
134+
Successful output typically shows one or more of:
135+
- Create/get/list/update/delete operations completing as expected.
136+
- Returned resource objects/IDs/names/versions or other meaningful operation results.
137+
- Consistent progress from setup to cleanup where applicable.
138+
139+
Mark `correct = false` for:
140+
- Exceptions, stack traces, explicit error/failure messages.
141+
- Timeout/auth/connection/service errors that prevent normal completion.
142+
- Malformed/corrupted output indicating broken processing.
143+
- Operation failures where the sample cannot proceed as designed.
144+
145+
Important distinction:
146+
- Empty list results by themselves can be valid and should not automatically fail.
147+
- Cleanup/delete operations that report not found may still be acceptable if the sample otherwise succeeds.
148+
- But explicit inability/failure for required core operations should be marked `correct = false`.
149+
150+
Mark `correct = true` when execution succeeds and output is consistent with the sample's intended
151+
resource-management behavior.
152+
153+
Always include `reason` with a concise explanation tied to the observed print output.
154+
""".strip()
155+
156+
157+
fine_tuning_instructions: Final[str] = """
158+
We just ran Python code and captured print/log output in an attached log file (TXT).
159+
Validate whether sample execution/output is correct for a fine-tuning workflow.
160+
161+
Successful output typically shows one or more of:
162+
- Training/validation files prepared or uploaded successfully.
163+
- Fine-tuning job creation with a returned job id/name.
164+
- Job details/status output that indicates the request was accepted and processed.
165+
166+
Mark `correct = false` for:
167+
- Exceptions, stack traces, explicit error/failure messages.
168+
- Authentication/authorization/service errors that block job creation.
169+
- File upload/read failures for required training/validation data.
170+
- Fine-tuning job creation failures or malformed output that indicates broken processing.
171+
172+
Important distinction:
173+
- Intermediate/transitional job states (for example queued/running) are valid and should not fail by themselves.
174+
- The output does not need to show completed training unless the sample is explicitly designed to wait for completion.
175+
176+
Mark `correct = true` when execution succeeds and output is consistent with initiating/inspecting
177+
the intended fine-tuning workflow.
178+
179+
Always include `reason` with a concise explanation tied to the observed print output.
180+
""".strip()
181+
182+
183+
evaluations_instructions: Final[str] = """
184+
We just ran Python code for an evaluation sample and captured print/log output in an attached log file (TXT).
185+
Your job: determine if the sample code executed to completion WITHOUT throwing an unhandled exception.
186+
187+
Respond TRUE (correct=true) if:
188+
- The output shows the evaluation was created and produced results (any results, including zeros)
189+
- The sample ran to completion (no unhandled Python exceptions/tracebacks)
190+
- Evaluation metric JSON with fields like "failed": 0, "error": null, "not_applicable": 0 is NORMAL
191+
successful output — these are counters, NOT errors
192+
- Status messages like "in_progress", "Waiting for eval run" are normal polling behavior
193+
- HTTP debug headers (x-stainless-read-timeout, x-ms-client-request-id, etc.) are normal and irrelevant
194+
- "deleted": true/false in cleanup output is normal
195+
- The absence of explicit "success" text is fine — no crash means success
196+
197+
Respond FALSE (correct=false) ONLY if:
198+
- There is an actual Python traceback or unhandled exception
199+
- There is an explicit error message like "Evaluation run failed" or "FAILED_EXECUTION"
200+
- There is an actual timeout error or connection failure (NOT an HTTP header containing "timeout")
201+
- The output shows corrupted or malformed data that prevented completion
202+
203+
Always respond with `reason` indicating the reason for the response.
204+
""".strip()
205+
206+
207+
# Folder (under samples/) -> instructions.
208+
# Keys intentionally mirror the folder names used by sample test discovery.
209+
# Use the most specific key possible (e.g. "agents/tools" should win over "agents").
210+
INSTRUCTIONS_BY_FOLDER: Final[dict[str, str]] = {
211+
"agents/tools": agent_tools_instructions,
212+
"agents": agents_instructions,
213+
"memories": memories_instructions,
214+
"connections": resource_management_instructions,
215+
"files": resource_management_instructions,
216+
"deployments": resource_management_instructions,
217+
"datasets": resource_management_instructions,
218+
"chat_completions": chat_completions_instructions,
219+
"finetuning": fine_tuning_instructions,
220+
"evaluations/agentic_evaluators": evaluations_instructions,
221+
"evaluations": evaluations_instructions,
222+
}
223+
224+
225+
def get_instructions_for_sample_path(sample_path: str) -> str:
226+
"""Return the appropriate instruction string for a given sample path.
227+
228+
The sample path may be absolute or relative and may use either '\\' or '/'.
229+
Matching is done against the path segment under the `samples/` directory.
230+
231+
Falls back to resource_management_instructions when no folder match is found.
232+
"""
233+
234+
normalized = str(sample_path).replace("\\", "/")
235+
236+
# Find the portion after /samples/
237+
marker = "/samples/"
238+
relative = normalized
239+
if marker in normalized:
240+
relative = normalized.split(marker, 1)[1]
241+
242+
# Reduce to folder prefix (everything except filename)
243+
folder = "/".join([p for p in relative.split("/") if p][:-1])
244+
245+
# Longest-prefix match on INSTRUCTIONS_BY_FOLDER keys.
246+
for key in sorted(INSTRUCTIONS_BY_FOLDER.keys(), key=len, reverse=True):
247+
if folder == key or folder.startswith(f"{key}/"):
248+
return INSTRUCTIONS_BY_FOLDER[key]
249+
250+
return resource_management_instructions

sdk/ai/azure-ai-projects/tests/samples/sample_executor.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from devtools_testutils import is_live
3737
from devtools_testutils import add_general_string_sanitizer
3838
from azure.ai.projects import AIProjectClient
39+
from llm_instructions import get_instructions_for_sample_path
3940

4041
# Fixed timestamp for playback mode (Nov 2023).
4142
# Must match the timestamp sanitizers in conftest.py (e.g., `Evaluation -\d{10}`).
@@ -465,6 +466,18 @@ def _build_validation_txt_bytes(self, validation_log_text: str) -> bytes:
465466
validation_log_text = ""
466467
return f"print/log contents from sample execution = {validation_log_text}".encode("utf-8")
467468

469+
def _resolve_validation_instructions(self, instructions: Optional[str] = None) -> str:
470+
"""Resolve validation instructions from an override or the sample folder."""
471+
if instructions is not None:
472+
if not instructions.strip():
473+
raise ValueError("instructions must be a non-empty string")
474+
return instructions
475+
476+
resolved_instructions = get_instructions_for_sample_path(self.sample_path)
477+
if not resolved_instructions.strip():
478+
raise ValueError(f"Could not resolve validation instructions for sample_path={self.sample_path!r}")
479+
return resolved_instructions
480+
468481
def _assert_validation_result(self, test_report: dict) -> None:
469482
"""Assert validation result and print reason."""
470483
sample_filename = os.path.basename(self.sample_path)
@@ -685,10 +698,9 @@ def execute(self, patched_open_fn=None):
685698
print(f"\nSample execution failed! Print statements logged to: {log_file}")
686699
raise
687700

688-
def validate_print_calls_by_llm(self, *, instructions: str):
701+
def validate_print_calls_by_llm(self, *, instructions: Optional[str] = None):
689702
"""Validate captured print output using synchronous OpenAI client."""
690-
if not instructions or not instructions.strip():
691-
raise ValueError("instructions must be a non-empty string")
703+
instructions = self._resolve_validation_instructions(instructions)
692704
if is_live():
693705
endpoint = os.environ["LLM_VALIDATION_PROJECT_ENDPOINT"]
694706
model = "gpt-5.2"
@@ -888,11 +900,10 @@ async def execute_async(self, patched_open_fn=None):
888900
async def validate_print_calls_by_llm_async(
889901
self,
890902
*,
891-
instructions: str,
903+
instructions: Optional[str] = None,
892904
):
893905
"""Validate captured print output using asynchronous OpenAI client."""
894-
if not instructions or not instructions.strip():
895-
raise ValueError("instructions must be a non-empty string")
906+
instructions = self._resolve_validation_instructions(instructions)
896907
if is_live():
897908
endpoint = os.environ["LLM_VALIDATION_PROJECT_ENDPOINT"]
898909
model = "gpt-5.2"

sdk/ai/azure-ai-projects/tests/samples/test_fine_tuning_samples_helpers.py

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,9 @@
1111

1212
from devtools_testutils import add_general_string_sanitizer
1313

14+
from llm_instructions import fine_tuning_instructions
1415
from test_samples_helpers import get_sample_env_vars
1516

16-
fine_tuning_instructions = """
17-
We just ran Python code and captured print/log output in an attached log file (TXT).
18-
Validate whether sample execution/output is correct for a fine-tuning workflow.
19-
20-
Successful output typically shows one or more of:
21-
- Training/validation files prepared or uploaded successfully.
22-
- Fine-tuning job creation with a returned job id/name.
23-
- Job details/status output that indicates the request was accepted and processed.
24-
25-
Mark `correct = false` for:
26-
- Exceptions, stack traces, explicit error/failure messages.
27-
- Authentication/authorization/service errors that block job creation.
28-
- File upload/read failures for required training/validation data.
29-
- Fine-tuning job creation failures or malformed output that indicates broken processing.
30-
31-
Important distinction:
32-
- Intermediate/transitional job states (for example queued/running) are valid and should not fail by themselves.
33-
- The output does not need to show completed training unless the sample is explicitly designed to wait for completion.
34-
35-
Mark `correct = true` when execution succeeds and output is consistent with initiating/inspecting
36-
the intended fine-tuning workflow.
37-
38-
Always include `reason` with a concise explanation tied to the observed print output.
39-
""".strip()
40-
41-
4217
SCRUBBED_FINE_TUNING_CONFIG: dict[str, Any] = {
4318
"sft": {
4419
"openai": {"model_name": "sanitized-sft-openai-model"},

0 commit comments

Comments
 (0)