-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Decouple AgentServer responses from generated model internals #47995
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
Open
Shivakishore14
wants to merge
22
commits into
main
Choose a base branch
from
sshiva/agentserver-typeddict-local
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
06da564
Decouple agentserver responses from generated model internals
Shivakishore14 c4e25b2
Regenerate agentserver response models from source emitter
Shivakishore14 2e9a3b1
Reduce redundant agentserver wire conversions
Shivakishore14 a7fe835
Use source-fixed AgentServer TypeDict generation
Shivakishore14 7595462
Update AgentServer TypedDict SDK from spec PR
Shivakishore14 17b1a55
Remove redundant AgentServer wire helpers
Shivakishore14 039fbd2
Merge remote-tracking branch 'origin/main' into sshiva/agentserver-ty…
Shivakishore14 23c9a46
Align AgentServer enum shim with main
Shivakishore14 e97042d
Sync AgentServer spec pointer
Shivakishore14 4263a53
Fix AgentServer Responses CI issues
Shivakishore14 01233dd
Fix AgentServer Responses sample typing
Shivakishore14 6fe830f
Merge branch 'main' into sshiva/agentserver-typeddict-local
Shivakishore14 8ab986f
Sync AgentServer contracts from correct spec branch
Shivakishore14 0e27517
Address AgentServer Responses review comments
Shivakishore14 439f4be
Fix AgentServer Responses docs CI
Shivakishore14 53b220a
Fix AgentServer Analyze mypy failures
Shivakishore14 c8a3d8c
Address AgentServer response model suggestions
Shivakishore14 04dda5e
Fix AgentServer enum fallback spellcheck
Shivakishore14 a6977ef
Fix AgentServer enum and timestamp contracts
Shivakishore14 92db0e3
Address AgentServer review and API consistency
Shivakishore14 1ec49f6
Fix AgentServer stream TTL contract tests
Shivakishore14 34b2061
Hide internal AgentServer message models
Shivakishore14 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
162 changes: 162 additions & 0 deletions
162
sdk/agentserver/azure-ai-agentserver-responses/_scripts/extract_model_contracts.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| #!/usr/bin/env python | ||
| # Copyright (c) Microsoft Corporation. | ||
| # Licensed under the MIT license. | ||
| """Extract local OpenAI Responses model contracts from generated output.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import re | ||
| import shutil | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| ROOT_INIT_PREFIX = ( | ||
| "# coding=utf-8\n" | ||
| "# --------------------------------------------------------------------------\n" | ||
| "# Copyright (c) Microsoft Corporation. All rights reserved.\n" | ||
| "# Licensed under the MIT License. See License.txt in the project root for license information.\n" | ||
| "# Code generated by Microsoft (R) Python Code Generator.\n" | ||
| "# Changes may cause incorrect behavior and will be lost if the code is regenerated.\n" | ||
| "# --------------------------------------------------------------------------\n\n" | ||
| "from ._unions import * # type: ignore # noqa: F401,F403\n" | ||
| "from .types import * # type: ignore # noqa: F401,F403\n" | ||
| ) | ||
|
|
||
| ENUM_FALLBACK_PREFIX = ( | ||
| "# coding=utf-8\n" | ||
| "# --------------------------------------------------------------------------\n" | ||
| "# Copyright (c) Microsoft Corporation. All rights reserved.\n" | ||
| "# Licensed under the MIT License. See License.txt in the project root for license information.\n" | ||
| "# --------------------------------------------------------------------------\n\n" | ||
| "import keyword\n" | ||
| "import re\n" | ||
| "from enum import Enum\n\n" | ||
| "from azure.core import CaseInsensitiveEnumMeta\n\n\n" | ||
| "_ENUM_VALUES: dict[str, dict[str, str]] = " | ||
| ) | ||
|
|
||
| ENUM_FALLBACK_SUFFIX = ( | ||
| "\n\n" | ||
| "def _normalize_enum_value(value: str) -> str:\n" | ||
| " return ''.join(ch for ch in value.upper() if ch.isalnum())\n\n\n" | ||
| "def _enum_member_name(value: str) -> str:\n" | ||
| " name = re.sub(r'[^A-Z0-9]+', '_', value.upper()).strip('_')\n" | ||
| " name = re.sub(r'(?<=\\d)(?=[A-Z])', '_', name)\n" | ||
| " if not name or name[0].isdigit() or keyword.iskeyword(name):\n" | ||
| " name = f'ENUM_{name}'\n" | ||
| " return name\n\n\n" | ||
| "def _build_enum(enum_name: str, enum_values: dict[str, str]) -> type[Enum]:\n" | ||
| " namespace = CaseInsensitiveEnumMeta.__prepare__(enum_name, (str, Enum))\n" | ||
| " namespace['__module__'] = __name__\n" | ||
| " namespace['__doc__'] = f'Type of {enum_name}.'\n" | ||
| " for normalized_name, wire_value in enum_values.items():\n" | ||
| " if normalized_name.isidentifier() and not keyword.iskeyword(normalized_name):\n" | ||
| " namespace[normalized_name] = wire_value\n" | ||
| " safe_name = _enum_member_name(wire_value)\n" | ||
| " if safe_name not in namespace:\n" | ||
| " namespace[safe_name] = wire_value\n" | ||
| " return CaseInsensitiveEnumMeta(enum_name, (str, Enum), namespace)\n\n\n" | ||
| "_ENUM_CLASSES = {name: _build_enum(name, values) for name, values in _ENUM_VALUES.items()}\n" | ||
| "globals().update(_ENUM_CLASSES)\n\n\n" | ||
| "def __getattr__(name: str) -> type[Enum]:\n" | ||
| " try:\n" | ||
| " return _ENUM_CLASSES[name]\n" | ||
| " except KeyError as exc:\n" | ||
| " raise AttributeError(f'module {__name__!r} has no attribute {name!r}') from exc\n" | ||
| ) | ||
|
|
||
|
|
||
| def _remove_pycache(root: Path) -> None: | ||
| for pycache in root.rglob("__pycache__"): | ||
| shutil.rmtree(pycache) | ||
|
|
||
|
|
||
| def _find_emitted_models_root(emitter_output_root: Path) -> Path: | ||
| candidates = sorted( | ||
| path | ||
| for path in emitter_output_root.rglob("types.py") | ||
| if path.parent.name == "models" | ||
| and (path.parent / "_unions.py").exists() | ||
| and (path.parent / "models").is_dir() | ||
| ) | ||
| if not candidates: | ||
| raise FileNotFoundError(f"Could not find emitted TypedDict model package under {emitter_output_root}") | ||
| if len(candidates) > 1: | ||
| formatted = "\n".join(str(path.parent) for path in candidates) | ||
| raise RuntimeError(f"Found multiple emitted model packages:\n{formatted}") | ||
| return candidates[0].parent | ||
|
|
||
|
|
||
| def _normalize_enum_value(value: str) -> str: | ||
| return re.sub(r"[^A-Z0-9]", "", value.upper()) | ||
|
|
||
|
|
||
| def _strip_trailing_whitespace(path: Path) -> None: | ||
| path.write_text( | ||
| "\n".join(line.rstrip() for line in path.read_text(encoding="utf-8").splitlines()) + "\n", | ||
| encoding="utf-8", | ||
| ) | ||
|
|
||
|
|
||
| def _literal_enum_values(types_py: Path) -> dict[str, dict[str, str]]: | ||
| text = types_py.read_text(encoding="utf-8") | ||
| enum_values: dict[str, dict[str, str]] = {} | ||
| for match in re.finditer(r"(?ms)^([A-Za-z][A-Za-z0-9_]*) = Literal\[(.*?)\]\n\"\"\"", text): | ||
| enum_name = match.group(1) | ||
| values = re.findall(r'"([^"]+)"', match.group(2)) | ||
| if values: | ||
| enum_values[enum_name] = {_normalize_enum_value(value): value for value in values} | ||
| return enum_values | ||
|
|
||
|
|
||
| def _build_enum_fallback(types_py: Path) -> str: | ||
| return ENUM_FALLBACK_PREFIX + repr(_literal_enum_values(types_py)) + ENUM_FALLBACK_SUFFIX | ||
|
|
||
|
|
||
| def _build_root_init(types_py: Path) -> str: | ||
| enum_names = sorted(_literal_enum_values(types_py)) | ||
| if not enum_names: | ||
| return ROOT_INIT_PREFIX | ||
| enum_lines = "\n".join(f'{name} = getattr(_generated_enums, "{name}")' for name in enum_names) | ||
| return ( | ||
| ROOT_INIT_PREFIX | ||
| + "from . import _enums as _generated_enums\n\n" | ||
| + enum_lines | ||
| + "\n" | ||
| ) | ||
|
|
||
|
|
||
| def finalize(emitter_output_root: Path, generated_root: Path) -> None: | ||
| """Copy the real emitted TypedDict package into the local generated package boundary.""" | ||
| emitted_root = _find_emitted_models_root(emitter_output_root) | ||
|
|
||
| if generated_root.exists(): | ||
| shutil.rmtree(generated_root) | ||
| generated_root.mkdir(parents=True) | ||
|
|
||
| for file_name in ("types.py", "_unions.py", "py.typed"): | ||
| shutil.copy2(emitted_root / file_name, generated_root / file_name) | ||
| _strip_trailing_whitespace(generated_root / "types.py") | ||
|
|
||
| models_root = generated_root / "models" | ||
| models_root.mkdir() | ||
| for file_name in ("__init__.py", "_patch.py"): | ||
| shutil.copy2(emitted_root / "models" / file_name, models_root / file_name) | ||
|
|
||
| (generated_root / "__init__.py").write_text(_build_root_init(generated_root / "types.py"), encoding="utf-8") | ||
| (generated_root / "_enums.py").write_text(_build_enum_fallback(generated_root / "types.py"), encoding="utf-8") | ||
| _remove_pycache(generated_root) | ||
|
|
||
|
|
||
| def main() -> None: | ||
| import argparse | ||
|
|
||
| parser = argparse.ArgumentParser(description=__doc__) | ||
| parser.add_argument("--emitter-output-root", type=Path, required=True) | ||
| parser.add_argument("--generated-root", type=Path, required=True) | ||
| args = parser.parse_args() | ||
| finalize(args.emitter_output_root, args.generated_root) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
11 changes: 0 additions & 11 deletions
11
sdk/agentserver/azure-ai-agentserver-responses/_scripts/generated_shims/__init__.py
This file was deleted.
Oops, something went wrong.
11 changes: 0 additions & 11 deletions
11
sdk/agentserver/azure-ai-agentserver-responses/_scripts/generated_shims/_enums.py
This file was deleted.
Oops, something went wrong.
11 changes: 0 additions & 11 deletions
11
sdk/agentserver/azure-ai-agentserver-responses/_scripts/generated_shims/_models.py
This file was deleted.
Oops, something went wrong.
11 changes: 0 additions & 11 deletions
11
sdk/agentserver/azure-ai-agentserver-responses/_scripts/generated_shims/_patch.py
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.