diff --git a/.github/skills/add-feature/SKILL.md b/.github/skills/add-feature/SKILL.md index 63c1a7b1..8f14430b 100644 --- a/.github/skills/add-feature/SKILL.md +++ b/.github/skills/add-feature/SKILL.md @@ -85,8 +85,9 @@ bug fixes. - Use the **`update-schema-docs` skill** to add examples to `docs/front-matter-spec.md` and review `docs/architecture.md` for consistency - Review the skill's PR checklist and address architectural concerns -3. Update `docs/front-matter-spec.md` and/or `docs/triggers.md` if the authoring - surface changed (for non-schema changes, or to refine schema-generated examples). +3. Update relevant docs under `docs/` (commonly `front-matter-spec.md`, `triggers.md`, + or other files as needed) if the authoring surface changed (for non-schema changes, + or to refine schema-generated examples). 4. Update `README.md` if user-facing behavior changed. 5. Update the FRD index in `docs/frds/README.md`. 6. Verify the `AGENTS.md` ยง8 Definition of Done, then open the PR. diff --git a/docs/AUTO_GENERATION.md b/docs/AUTO_GENERATION.md index ac530f96..19fd2892 100644 --- a/docs/AUTO_GENERATION.md +++ b/docs/AUTO_GENERATION.md @@ -96,7 +96,7 @@ Trigger type documentation is defined in the `TRIGGER_TYPES` dictionary in `src/ ### Field Descriptions -Enhanced descriptions are in `*_DESCRIPTIONS` dictionaries in the script. These complement the Pydantic model docstrings and add links to other docs sections. +Enhanced descriptions are in `*_DESCRIPTIONS` dictionaries in `src/azure_functions_agents/config/schema.py` alongside the Pydantic models. These complement the Pydantic model docstrings and add markdown formatting and links to other docs sections. To update field descriptions, edit the appropriate description dictionary in `schema.py`. ### Static Sections diff --git a/eng/ci/library-release.yml b/eng/ci/library-release.yml index 550233bf..5bfc16b7 100644 --- a/eng/ci/library-release.yml +++ b/eng/ci/library-release.yml @@ -1,6 +1,20 @@ +trigger: + batch: true + branches: + include: + - main + paths: + include: + - src/** + exclude: + - src/azure_functions_agents/__init__.py + +pr: none + parameters: - name: libraryVersion type: string + default: '' displayName: 'Library version to release (e.g., 0.1.0)' - name: projectDisplayName type: string @@ -12,7 +26,7 @@ parameters: displayName: 'Path to version file (e.g., __init__.py or version.py)' - name: versionPattern type: string - default: "__version__ = ['\"]([0-9])+\\.([0-9])+.*['\"]" + default: "__version__ = [\"]([0-9])+\\.([0-9])+.*[\"]" displayName: 'Regex pattern to match version string in version file' - name: versionReplacement type: string @@ -20,11 +34,11 @@ parameters: displayName: 'Replacement pattern for version (use {{VERSION}} as placeholder)' - name: releaseBranchPrefix type: string - default: 'release' + default: 'v' displayName: 'Prefix for release branch (e.g., release or release-v2)' - name: tagPrefix type: string - default: '' + default: 'v' displayName: 'Optional prefix for git tags (e.g., runtimes/v2-)' - name: createPullRequest type: boolean @@ -71,12 +85,71 @@ extends: os: windows stages: + - stage: ResolveVersion + displayName: 'Resolve Library Version' + jobs: + - job: ResolveVersionJob + displayName: 'Compute next beta version' + pool: + name: 1es-pool-azfunc + image: 1es-windows-2022 + os: windows + steps: + - powershell: | + $providedVersion = "${{ parameters.libraryVersion }}".Trim() + if (-not [string]::IsNullOrWhiteSpace($providedVersion)) { + Write-Host "Using provided libraryVersion: $providedVersion" + Write-Host "##vso[task.setvariable variable=resolvedLibraryVersion;isOutput=true]$providedVersion" + exit 0 + } + + $versionFilePath = "${{ parameters.versionFilePath }}" + if (-not (Test-Path -LiteralPath $versionFilePath)) { + Write-Host "Version file not found: $versionFilePath" + exit 1 + } + + $content = Get-Content -LiteralPath $versionFilePath -Raw + $match = [regex]::Match($content, '__version__\s*=\s*"(?[^"]+)"') + if (-not $match.Success) { + $match = [regex]::Match($content, "__version__\s*=\s*'(?[^']+)'") + } + if (-not $match.Success) { + Write-Host "Unable to read __version__ from $versionFilePath" + exit 1 + } + + $currentVersion = $match.Groups['version'].Value + $nextVersion = $null + + $betaMatch = [regex]::Match($currentVersion, "^(?\d+\.\d+\.\d+)b(?\d+)$") + if ($betaMatch.Success) { + $baseVersion = $betaMatch.Groups['base'].Value + $nextBeta = [int]$betaMatch.Groups['beta'].Value + 1 + $nextVersion = "$baseVersion" + "b$nextBeta" + } + elseif ($currentVersion -match '^\d+\.\d+\.\d+$') { + $nextVersion = "$currentVersion" + "b1" + } + else { + Write-Host "Unsupported version format '$currentVersion'. Expected x.y.z or x.y.zbN" + exit 1 + } + + Write-Host "Resolved libraryVersion: $nextVersion (from $currentVersion)" + Write-Host "##vso[task.setvariable variable=resolvedLibraryVersion;isOutput=true]$nextVersion" + displayName: 'Resolve libraryVersion value' + name: setVersion + - stage: BumpVersion displayName: 'Bump Version' + dependsOn: ResolveVersion + variables: + resolvedLibraryVersion: $[ stageDependencies.ResolveVersion.ResolveVersionJob.outputs['setVersion.resolvedLibraryVersion'] ] jobs: - template: /eng/templates/official/jobs/bump-version.yml@self parameters: - libraryVersion: ${{ parameters.libraryVersion }} + libraryVersion: $(resolvedLibraryVersion) projectDisplayName: ${{ parameters.projectDisplayName }} versionFilePath: ${{ parameters.versionFilePath }} versionPattern: ${{ parameters.versionPattern }} @@ -89,24 +162,32 @@ extends: - stage: Build displayName: 'Build ${{ parameters.projectDisplayName }}' - dependsOn: BumpVersion + dependsOn: + - BumpVersion + - ResolveVersion + variables: + resolvedLibraryVersion: $[ stageDependencies.ResolveVersion.ResolveVersionJob.outputs['setVersion.resolvedLibraryVersion'] ] jobs: - template: /eng/templates/official/jobs/build-artifacts.yml@self parameters: artifactName: ${{ parameters.artifactName }} projectDisplayName: ${{ parameters.projectDisplayName }} - libraryVersion: ${{ parameters.libraryVersion }} + libraryVersion: $(resolvedLibraryVersion) releaseBranchPrefix: ${{ parameters.releaseBranchPrefix }} githubOrg: ${{ parameters.githubOrg }} githubRepo: ${{ parameters.githubRepo }} - stage: Release displayName: 'Release ${{ parameters.projectDisplayName }}' - dependsOn: Build + dependsOn: + - Build + - ResolveVersion + variables: + resolvedLibraryVersion: $[ stageDependencies.ResolveVersion.ResolveVersionJob.outputs['setVersion.resolvedLibraryVersion'] ] jobs: - template: /eng/templates/official/jobs/publish-release.yml@self parameters: - libraryVersion: ${{ parameters.libraryVersion }} + libraryVersion: $(resolvedLibraryVersion) projectDisplayName: ${{ parameters.projectDisplayName }} releaseBranchPrefix: ${{ parameters.releaseBranchPrefix }} tagPrefix: ${{ parameters.tagPrefix }} diff --git a/eng/scripts/generate_config_reference.py b/eng/scripts/generate_config_reference.py index 9df5655c..45cdbf30 100644 --- a/eng/scripts/generate_config_reference.py +++ b/eng/scripts/generate_config_reference.py @@ -43,6 +43,22 @@ TRIGGER_TYPES = schema.TRIGGER_TYPES WebRequestConfig = schema.WebRequestConfig +# Extract description and default value dictionaries +GLOBAL_CONFIG_DESCRIPTIONS = schema.GLOBAL_CONFIG_DESCRIPTIONS +GLOBAL_CONFIG_DEFAULTS = schema.GLOBAL_CONFIG_DEFAULTS +SYSTEM_TOOLS_CONFIG_DESCRIPTIONS = schema.SYSTEM_TOOLS_CONFIG_DESCRIPTIONS +DYNAMIC_SESSIONS_DESCRIPTIONS = schema.DYNAMIC_SESSIONS_DESCRIPTIONS +TOOLS_FILTER_DESCRIPTIONS = schema.TOOLS_FILTER_DESCRIPTIONS +AGENT_SPEC_REQUIRED_DESCRIPTIONS = schema.AGENT_SPEC_REQUIRED_DESCRIPTIONS +AGENT_SPEC_OPTIONAL_DESCRIPTIONS = schema.AGENT_SPEC_OPTIONAL_DESCRIPTIONS +TRIGGER_SPEC_DESCRIPTIONS = schema.TRIGGER_SPEC_DESCRIPTIONS +BUILTIN_ENDPOINTS_DESCRIPTIONS = schema.BUILTIN_ENDPOINTS_DESCRIPTIONS +SYSTEM_TOOLS_AGENT_DESCRIPTIONS = schema.SYSTEM_TOOLS_AGENT_DESCRIPTIONS +MCP_FILTER_DESCRIPTIONS = schema.MCP_FILTER_DESCRIPTIONS +SKILLS_FILTER_DESCRIPTIONS = schema.SKILLS_FILTER_DESCRIPTIONS +AGENT_TOOLS_FILTER_DESCRIPTIONS = schema.AGENT_TOOLS_FILTER_DESCRIPTIONS +WEB_REQUEST_DESCRIPTIONS = schema.WEB_REQUEST_DESCRIPTIONS + def format_type(field_info: FieldInfo, field_name: str) -> str: """Format field type annotation as a readable string.""" diff --git a/src/azure_functions_agents/config/schema.py b/src/azure_functions_agents/config/schema.py index 5d09b1d8..43b9721b 100644 --- a/src/azure_functions_agents/config/schema.py +++ b/src/azure_functions_agents/config/schema.py @@ -329,3 +329,96 @@ class ResolvedAgent(BaseModel): "note": "No configuration properties. Receives Connector events.", }, } + + +# Field description metadata for documentation generation +# Used by eng/scripts/generate_config_reference.py to enhance generated docs. +# These descriptions complement or override Pydantic field metadata and may include +# markdown formatting and internal document links. + +GLOBAL_CONFIG_DESCRIPTIONS: dict[str, str] = { + "system_tools": "System-level tools configuration. [Details](#global-system_tools)", + "model": "Default LLM model identifier for all agents", + "timeout": "Default execution timeout in seconds", + "tools": "Global tool filtering configuration. [Details](#global-tools)", +} + +GLOBAL_CONFIG_DEFAULTS: dict[str, str] = { + "system_tools": "`{}`", + "model": "Resolved from env/provider", + "timeout": "`900`", + "tools": "`{}`", +} + +SYSTEM_TOOLS_CONFIG_DESCRIPTIONS: dict[str, str] = { + "dynamic_sessions_code_interpreter": "ACA Dynamic Sessions code interpreter configuration. [Details](#global-system_toolsdynamic_sessions_code_interpreter)", + "web_request": "Outbound HTTP request tool configuration. Enabled by default; set to `false` to disable app-wide. [Details](#global-system_toolsweb_request)", +} + +DYNAMIC_SESSIONS_DESCRIPTIONS: dict[str, str] = { + "endpoint": "ACA session pool endpoint URL. Supports env var substitution.", + "client_id": "Optional managed identity client ID for multi-identity Function Apps", +} + +TOOLS_FILTER_DESCRIPTIONS: dict[str, str] = { + "exclude": "Tool names to exclude globally from all agents", +} + +AGENT_SPEC_REQUIRED_DESCRIPTIONS: dict[str, str] = { + "name": "Display name for the agent. Does not control function name or route.", + "description": "Brief description of the agent's purpose", + "trigger": "Required unless at least one `builtin_endpoints` value is enabled. [Details](#agent-trigger)", +} + +AGENT_SPEC_OPTIONAL_DESCRIPTIONS: dict[str, str] = { + "builtin_endpoints": "Enable built-in chat UI, chat API, and/or MCP tool endpoints. [Details](#agent-builtin_endpoints)", + "model": "Override LLM model for this agent", + "timeout": "Override execution timeout (seconds) for this agent", + "logger": "Enable/disable response logging for triggered agents", + "substitute_variables": "Enable/disable environment variable substitution", + "system_tools": "Opt out of system tools. [Details](#agent-system_tools)", + "mcp": "MCP server filtering. [Details](#agent-mcp)", + "skills": "Skill filtering. [Details](#agent-skills)", + "tools": "Custom tool filtering. [Details](#agent-tools)", + "workflows": "Dynamic Workflow enablement and filtering. [Details](./front-matter-spec.md#workflows)", + "input_schema": "JSON Schema for HTTP request validation", + "response_schema": "JSON Schema for response validation", + "response_example": "Example response structure (multiline string)", + "metadata": "Additional metadata for organization. Free-form.", +} + +TRIGGER_SPEC_DESCRIPTIONS: dict[str, str] = { + "type": "Trigger type identifier. See [Supported Trigger Types](#supported-trigger-types)", + "args": "Type-specific configuration. See [Supported Trigger Types](#supported-trigger-types)", +} + +BUILTIN_ENDPOINTS_DESCRIPTIONS: dict[str, str] = { + "debug_chat_ui": "Enable browser-based chat UI at `/agents/{slug}/` plus backing chat APIs", + "chat_api": "Enable REST API endpoints (`/agents/{slug}/chat`, `/agents/{slug}/chatstream`)", + "mcp": "Expose agent as MCP tool on shared runtime MCP transport", +} + +SYSTEM_TOOLS_AGENT_DESCRIPTIONS: dict[str, str] = { + "dynamic_sessions_code_interpreter": "Set to `false` to opt out of code execution capabilities", + "web_request": "Set to `false` to opt out of the default-on `web_request` tool for this agent", +} + +MCP_FILTER_DESCRIPTIONS: dict[str, str] = { + "exclude": "MCP server names to exclude. Must match servers in `mcp.json`.", +} + +SKILLS_FILTER_DESCRIPTIONS: dict[str, str] = { + "exclude": "Skill names to exclude. Matched against `SKILL.md` `name` field.", +} + +AGENT_TOOLS_FILTER_DESCRIPTIONS: dict[str, str] = { + "exclude": "Tool names to exclude (in addition to global excludes)", +} + +WEB_REQUEST_DESCRIPTIONS: dict[str, str] = { + "allowed_hosts": "Exact-match allowlist of hostnames the tool may call. Omit to allow any public host (still subject to the SSRF floor).", + "require_https": "Require `https://` URLs. Set to `false` to also allow `http://`.", + "timeout_seconds": "Per-request timeout in seconds, clamped to a runtime-defined ceiling (120 s).", + "max_response_bytes": "Maximum response body size read before truncating, clamped to a runtime-defined ceiling (10 MB).", + "max_request_bytes": "Maximum request body size accepted, clamped to a runtime-defined ceiling (10 MB).", +}