Skip to content

Commit 6f88fc6

Browse files
authored
Merge branch 'main' into trace-based-sampling
2 parents 393df1b + b5fbfb9 commit 6f88fc6

26 files changed

+1628
-401
lines changed

eng/common/instructions/azsdk-tools/create-release-plan.instructions.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
> **Important Note for LLM**
2-
> Backend processes may use Azure DevOps work item data as needed. However, do **not** mention or display the work item link/URL, or any reference to Azure DevOps work item resources directly in conversation with the user.
3-
> All manual updates to an existing release plan must be made through the [Release Planner Tool](https://aka.ms/sdk-release-planner) to ensure proper tracking and validation.
4-
> Only provide release plan details such as **Release Plan Link** and **Release Plan ID** to the user
1+
> **CRITICAL INSTRUCTIONS FOR LLM**
2+
> 1. Backend processes may use Azure DevOps work item data as needed. However, do **not** mention or display the work item link/URL, or any reference to Azure DevOps work item resources directly in conversation with the user.
3+
> 2. All manual updates to an existing release plan must be made through the [Release Planner Tool](https://aka.ms/sdk-release-planner) to ensure proper tracking and validation.
4+
> 3. Only provide release plan details such as **Release Plan Link** and **Release Plan ID** to the user
5+
> 4. Always check the `NextSteps` field in the tool response and follow any additional prompts provided. Do NOT proceed to other steps until the NextSteps are completed.
56
67
# Release Plan Creation Process
78
Your goal is to create a valid release plan. You must prompt user to provide all required information and all input must match the format and requirement mentioned in step 3 below.
@@ -12,12 +13,14 @@ Follow these steps in order to create or manage a release plan for an API specif
1213
- If no pull request is available, prompt the user to provide the API spec pull request link
1314
- Validate that the provided pull request link is accessible and valid
1415

15-
## Step 2: Check Existing Release Plan
16-
- Use `azsdk_get_release_plan_for_spec_pr` to check if a release plan already exists for the API spec pull request
17-
- If a release plan exists:
18-
- Display the existing release plan details to the user
19-
- Skip to Step 5 (Link SDK Pull Requests)
20-
- If no release plan exists, proceed to Step 3
16+
## Step 2: Check for Existing Release Plan
17+
- Ask the user if they already have an existing release plan
18+
- If they confirm:
19+
- Query the existing release plan using either:
20+
- The release plan number, or
21+
- The API spec pull request link
22+
- Display the existing release plan details (Release Plan ID, status, associated languages, SDK PRs).
23+
- If no existing release plan is found, continue to Step 3 to gather required details for creating a new one.
2124

2225
## Step 3: Gather Release Plan Information
2326
Collect the following required information from the user. Do not create a release plan with temporary values. Confirm the values with the user before proceeding to create the release plan.
@@ -35,6 +38,7 @@ If any details are missing, prompt the user accordingly:
3538
- If the user doesn't know the required details, direct them to create a release plan using the release planner
3639
- Provide this resource: [Release Plan Creation Guide](https://eng.ms/docs/products/azure-developer-experience/plan/release-plan-create)
3740
- Once all information is gathered, use `azsdk_create_release_plan` to create the release plan
41+
- If existing release plans are found, extract and display key information: Release Plan ID, status, associated languages, SDK PRs
3842
- Display the newly created release plan details to the user for confirmation
3943
- Refer to #file:sdk-details-in-release-plan.instructions.md to identify languages configured in the TypeSpec project and add them to the release plan
4044

eng/tools/azure-sdk-tools/ci_tools/dependency_analysis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def record_dep(dependencies: Dict[str, Dict[str, Any]], req_name: str, spec: str
5858
def get_lib_deps(base_dir: str) -> Tuple[Dict[str, Dict[str, Any]], Dict[str, Dict[str, Any]]]:
5959
packages = {}
6060
dependencies = {}
61-
for lib_dir in discover_targeted_packages("azure*", base_dir):
61+
for lib_dir in discover_targeted_packages("azure*", base_dir, compatibility_filter=False):
6262
try:
6363
parsed = ParsedSetup.from_path(lib_dir)
6464
lib_name, version, requires = parsed.name, parsed.version, parsed.requires

eng/tools/azure-sdk-tools/ci_tools/functions.py

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def unzip_file_to_directory(path_to_zip_file: str, extract_location: str) -> str
104104
return os.path.join(extract_location, extracted_dir)
105105

106106

107-
def apply_compatibility_filter(package_set: List[str]) -> List[str]:
107+
def apply_compatibility_filter(package_set: List[ParsedSetup]) -> List[ParsedSetup]:
108108
"""
109109
This function takes in a set of paths to python packages. It returns the set filtered by compatibility with the currently running python executable.
110110
If a package is unsupported by the executable, it will be omitted from the returned list.
@@ -118,28 +118,25 @@ def apply_compatibility_filter(package_set: List[str]) -> List[str]:
118118
running_major_version = Version(".".join([str(v[0]), str(v[1]), str(v[2])]))
119119

120120
for pkg in package_set:
121-
try:
122-
spec_set = SpecifierSet(ParsedSetup.from_path(pkg).python_requires)
123-
except RuntimeError as e:
124-
logging.error(f"Unable to parse metadata for package {pkg}, omitting from build.")
125-
continue
121+
spec_set = SpecifierSet(pkg.python_requires)
126122

127-
pkg_specs_override = TEST_COMPATIBILITY_MAP.get(os.path.basename(pkg), None)
123+
pkg_specs_override = TEST_COMPATIBILITY_MAP.get(pkg.name, None)
128124

129125
if pkg_specs_override:
130126
spec_set = SpecifierSet(pkg_specs_override)
131127

132128
distro_compat = True
133-
distro_incompat = TEST_PYTHON_DISTRO_INCOMPATIBILITY_MAP.get(os.path.basename(pkg), None)
129+
distro_incompat = TEST_PYTHON_DISTRO_INCOMPATIBILITY_MAP.get(pkg.name, None)
134130
if distro_incompat and distro_incompat in platform.python_implementation().lower():
135131
distro_compat = False
136132

137133
if running_major_version in spec_set and distro_compat:
138134
collected_packages.append(pkg)
139135

140-
logging.debug("Target packages after applying compatibility filter: {}".format(collected_packages))
141136
logging.debug(
142-
"Package(s) omitted by compatibility filter: {}".format(generate_difference(package_set, collected_packages))
137+
"Package(s) omitted by compatibility filter: {}".format(
138+
generate_difference([origpkg.name for origpkg in package_set], [pkg.name for pkg in collected_packages])
139+
)
143140
)
144141

145142
return collected_packages
@@ -208,12 +205,17 @@ def glob_packages(glob_string: str, target_root_dir: str) -> List[str]:
208205
return list(set(collected_top_level_directories))
209206

210207

211-
def apply_business_filter(collected_packages: List[str], filter_type: str) -> List[str]:
212-
pkg_set_ci_filtered = list(filter(omit_function_dict.get(filter_type, omit_build), collected_packages))
208+
def apply_business_filter(collected_packages: List[ParsedSetup], filter_type: str) -> List[ParsedSetup]:
209+
pkg_set_ci_filtered = []
210+
211+
for pkg in collected_packages:
212+
if omit_function_dict.get(filter_type, omit_build)(pkg.folder):
213+
pkg_set_ci_filtered.append(pkg)
213214

214-
logging.debug("Target packages after applying business filter: {}".format(pkg_set_ci_filtered))
215215
logging.debug(
216-
"Package(s) omitted by business filter: {}".format(generate_difference(collected_packages, pkg_set_ci_filtered))
216+
"Package(s) omitted by business filter: {}".format(
217+
generate_difference([pkg.name for pkg in collected_packages], [pkg.name for pkg in pkg_set_ci_filtered])
218+
)
217219
)
218220

219221
return pkg_set_ci_filtered
@@ -248,41 +250,54 @@ def discover_targeted_packages(
248250
f'Results for glob_string "{glob_string}" and root directory "{target_root_dir}" are: {collected_packages}'
249251
)
250252

251-
# apply the additional contains filter
253+
# apply the additional contains filter (purely string based)
252254
collected_packages = [pkg for pkg in collected_packages if additional_contains_filter in pkg]
253255
logger.debug(f'Results after additional contains filter: "{additional_contains_filter}" {collected_packages}')
254256

257+
# now the have the initial package set, we need to walk the set and attempt to parse_setup each package
258+
# this will have the impact of cleaning out any packages that have been set to not buildable anymore (EG namespace packages)
259+
parsed_packages = []
260+
for pkg in collected_packages:
261+
try:
262+
parsed_packages.append(ParsedSetup.from_path(pkg))
263+
except RuntimeError as e:
264+
logging.error(f"Unable to parse metadata for package {pkg}, omitting from build.")
265+
continue
266+
255267
# filter for compatibility, this means excluding a package that doesn't support py36 when we are running a py36 executable
256268
if compatibility_filter:
257-
collected_packages = apply_compatibility_filter(collected_packages)
258-
logger.debug(f"Results after compatibility filter: {collected_packages}")
269+
parsed_packages = apply_compatibility_filter(parsed_packages)
270+
logger.debug(f"Results after compatibility filter: {','.join([p.name for p in parsed_packages])}")
259271

260272
if not include_inactive:
261-
collected_packages = apply_inactive_filter(collected_packages)
273+
parsed_packages = apply_inactive_filter(parsed_packages)
262274

263275
# Apply filter based on filter type. for e.g. Docs, Regression, Management
264-
collected_packages = apply_business_filter(collected_packages, filter_type)
265-
logger.debug(f"Results after business filter: {collected_packages}")
276+
parsed_packages = apply_business_filter(parsed_packages, filter_type)
277+
logger.debug(f"Results after business filter: {[pkg.name for pkg in parsed_packages]}")
266278

267-
return sorted(collected_packages)
279+
return sorted([pkg.folder for pkg in parsed_packages])
268280

269281

270-
def is_package_active(package_path: str):
271-
disabled = INACTIVE_CLASSIFIER in ParsedSetup.from_path(package_path).classifiers
282+
def is_package_active(pkg: ParsedSetup) -> bool:
283+
disabled = INACTIVE_CLASSIFIER in pkg.classifiers
272284

273-
override_value = os.getenv(f"ENABLE_{os.path.basename(package_path).upper().replace('-', '_')}", None)
285+
override_value = os.getenv(f"ENABLE_{pkg.name.upper().replace('-', '_')}", None)
274286

275287
if override_value:
276288
return str_to_bool(override_value)
277289
else:
278290
return not disabled
279291

280292

281-
def apply_inactive_filter(collected_packages: List[str]) -> List[str]:
293+
def apply_inactive_filter(collected_packages: List[ParsedSetup]) -> List[ParsedSetup]:
282294
packages = [pkg for pkg in collected_packages if is_package_active(pkg)]
283295

284-
logging.debug("Target packages after applying inactive filter: {}".format(collected_packages))
285-
logging.debug("Package(s) omitted by inactive filter: {}".format(generate_difference(collected_packages, packages)))
296+
logging.debug(
297+
"Package(s) omitted by inactive filter: {}".format(
298+
generate_difference([collected.name for collected in collected_packages], [pkg.name for pkg in packages])
299+
)
300+
)
286301

287302
return packages
288303

sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -431,15 +431,20 @@ def _add_instructions_event(
431431
agent_id: Optional[str] = None,
432432
thread_id: Optional[str] = None,
433433
) -> None:
434+
# Early return if no instructions to trace
434435
if not instructions:
435436
return
436437

437438
event_body: Dict[str, Any] = {}
438-
if _trace_agents_content and (instructions or additional_instructions):
439-
if instructions and additional_instructions:
440-
event_body["text"] = f"{instructions} {additional_instructions}"
439+
if _trace_agents_content:
440+
# Combine instructions if both exist
441+
if additional_instructions:
442+
combined_text = f"{instructions} {additional_instructions}"
441443
else:
442-
event_body["text"] = instructions or additional_instructions
444+
combined_text = instructions
445+
446+
# Use standard content format
447+
event_body["content"] = [{"type": "text", "text": combined_text}]
443448

444449
attributes = self._create_event_attributes(agent_id=agent_id, thread_id=thread_id)
445450
attributes[GEN_AI_EVENT_CONTENT] = json.dumps(event_body, ensure_ascii=False)
@@ -481,6 +486,8 @@ def start_create_agent_span( # pylint: disable=too-many-locals
481486
reasoning_summary: Optional[str] = None,
482487
text: Optional[Any] = None, # pylint: disable=unused-argument
483488
structured_inputs: Optional[Any] = None,
489+
agent_type: Optional[str] = None,
490+
workflow_yaml: Optional[str] = None,
484491
) -> "Optional[AbstractSpan]":
485492
span = start_span(
486493
OperationName.CREATE_AGENT,
@@ -501,8 +508,19 @@ def start_create_agent_span( # pylint: disable=too-many-locals
501508
span.add_attribute(GEN_AI_AGENT_NAME, name)
502509
if description:
503510
span.add_attribute(GEN_AI_AGENT_DESCRIPTION, description)
511+
if agent_type:
512+
span.add_attribute("gen_ai.agent.type", agent_type)
513+
514+
# Add instructions event (if instructions exist)
504515
self._add_instructions_event(span, instructions, None)
505516

517+
# Add workflow YAML as event if content recording is enabled and workflow exists
518+
if _trace_agents_content and workflow_yaml:
519+
event_body: Dict[str, Any] = {"content": [{"type": "workflow", "workflow": workflow_yaml}]}
520+
attributes = self._create_event_attributes()
521+
attributes[GEN_AI_EVENT_CONTENT] = json.dumps(event_body, ensure_ascii=False)
522+
span.span_instance.add_event(name="gen_ai.agent.workflow", attributes=attributes)
523+
506524
return span
507525

508526
def start_create_thread_span(
@@ -543,7 +561,7 @@ def get_server_address_from_arg(self, arg: Any) -> Optional[Tuple[str, Optional[
543561

544562
def _create_agent_span_from_parameters(
545563
self, *args, **kwargs
546-
): # pylint: disable=too-many-statements,too-many-locals,docstring-missing-param
564+
): # pylint: disable=too-many-statements,too-many-locals,too-many-branches,docstring-missing-param
547565
"""Extract parameters and create span for create_agent tracing."""
548566
server_address_info = self.get_server_address_from_arg(args[0])
549567
server_address = server_address_info[0] if server_address_info else None
@@ -567,8 +585,23 @@ def _create_agent_span_from_parameters(
567585
structured_inputs = None
568586
description = definition.get("description")
569587
tool_resources = definition.get("tool_resources")
588+
workflow_yaml = definition.get("workflow") # Extract workflow YAML for workflow agents
570589
# toolset = definition.get("toolset")
571590

591+
# Determine agent type from definition
592+
# Check for workflow first (most specific)
593+
agent_type = None
594+
if workflow_yaml:
595+
agent_type = "workflow"
596+
elif instructions or model:
597+
# Prompt agent - identified by having instructions and/or a model.
598+
# Note: An agent with only a model (no instructions) is treated as a prompt agent,
599+
# though this is uncommon. Typically prompt agents have both model and instructions.
600+
agent_type = "prompt"
601+
else:
602+
# Unknown type - set to "unknown" to indicate we couldn't determine it
603+
agent_type = "unknown"
604+
572605
# Extract reasoning effort and summary from reasoning if available
573606
reasoning_effort = None
574607
reasoning_summary = None
@@ -645,6 +678,8 @@ def _create_agent_span_from_parameters(
645678
reasoning_summary=reasoning_summary,
646679
text=text,
647680
structured_inputs=structured_inputs,
681+
agent_type=agent_type,
682+
workflow_yaml=workflow_yaml,
648683
)
649684

650685
def trace_create_agent(self, function, *args, **kwargs):

0 commit comments

Comments
 (0)