chore: import upstream snapshot with attribution
Code Quality / Python Lint & Format (push) Has been cancelled
Code Quality / Python Tests (push) Has been cancelled
Code Quality / JavaScript/TypeScript Lint (advisory) (push) Has been cancelled
Security Scan / CodeQL Analysis (python) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript-typescript) (push) Has been cancelled
Code Quality / Python Lint & Format (push) Has been cancelled
Code Quality / Python Tests (push) Has been cancelled
Code Quality / JavaScript/TypeScript Lint (advisory) (push) Has been cancelled
Security Scan / CodeQL Analysis (python) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript-typescript) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,19 @@
|
||||
version: 2
|
||||
updates:
|
||||
# For Python dependencies
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/" # Location of the requirements.txt file
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# For Node.js dependencies
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/" # Location of the package.json file
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# Optional: For GitHub Actions dependencies
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/" # Location of the .github/workflows directory
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -0,0 +1,161 @@
|
||||
---
|
||||
name: azure-openai-to-responses
|
||||
description: >-
|
||||
Migrate Python apps from Azure OpenAI Chat Completions to the Responses API.
|
||||
Covers AzureOpenAI/AsyncAzureOpenAI client migration to the v1 endpoint,
|
||||
streaming, tools, structured output, multi-turn, EntraID auth, and model
|
||||
compatibility checks. Python-focused, Azure OpenAI-specific.
|
||||
USE FOR: migrate to responses API, switch from chat completions, openai responses,
|
||||
upgrade openai SDK, responses API migration, move from completions to responses,
|
||||
gpt-5 migration, azure openai python migration, chat completions to responses,
|
||||
AzureOpenAI to OpenAI client, python azure openai upgrade.
|
||||
DO NOT USE FOR: building new apps from scratch (start with responses directly),
|
||||
Node/TypeScript/C#/Java/Go migrations (this skill is Python-only),
|
||||
Azure infrastructure setup (use azure-prepare), deploying models (use microsoft-foundry).
|
||||
license: MIT
|
||||
source: https://github.com/Azure-Samples/azure-openai-to-responses
|
||||
---
|
||||
|
||||
# Migrate Python Apps from Azure OpenAI Chat Completions to Responses API
|
||||
|
||||
> **AUTHORITATIVE GUIDANCE — FOLLOW EXACTLY**
|
||||
>
|
||||
> This skill migrates Python codebases using Azure OpenAI Chat Completions
|
||||
> to the unified Responses API. Follow these instructions precisely.
|
||||
> Do not improvise parameter mappings or invent API shapes.
|
||||
|
||||
Installed from [Azure-Samples/azure-openai-to-responses](https://github.com/Azure-Samples/azure-openai-to-responses) (MIT).
|
||||
|
||||
## Triggers
|
||||
|
||||
Activate this skill when the user wants to:
|
||||
- Migrate a Python app from Azure OpenAI Chat Completions to Responses API
|
||||
- Upgrade Python OpenAI SDK usage to the latest API shape against Azure OpenAI
|
||||
- Prepare Python code for GPT-5 or newer models that require Responses on Azure
|
||||
- Switch from `AzureOpenAI`/`AsyncAzureOpenAI` to standard `OpenAI`/`AsyncOpenAI` client with the v1 endpoint
|
||||
- Fix deprecation warnings related to `AzureOpenAI` constructors or `api_version`
|
||||
|
||||
## Why migrate
|
||||
|
||||
GPT-5 and newer models require the Responses API. The new `/openai/v1/` endpoint
|
||||
uses the standard `OpenAI()` client instead of `AzureOpenAI()`, requires no
|
||||
`api_version` parameter, and works identically across OpenAI and Azure OpenAI.
|
||||
|
||||
## What changes
|
||||
|
||||
| Chat Completions (before) | Responses API (after) |
|
||||
| --- | --- |
|
||||
| `AzureOpenAI()` / `AsyncAzureOpenAI()` | `OpenAI(base_url=...)` / `AsyncOpenAI(base_url=...)` |
|
||||
| `azure_endpoint=...` | `base_url=f"{endpoint.rstrip('/')}/openai/v1/"` |
|
||||
| `api_version="2024-..."` | Remove entirely — `/openai/v1/` is stable |
|
||||
| `azure_ad_token_provider=...` | `api_key=token_provider` |
|
||||
| `client.chat.completions.create(messages=...)` | `client.responses.create(input=...)` |
|
||||
| `resp.choices[0].message.content` | `resp.output_text` |
|
||||
| `max_tokens` | `max_output_tokens` (min 16 on Azure) |
|
||||
| `response_format` | `text={"format": {...}}` |
|
||||
| `seed` | **Remove** (not supported) |
|
||||
| tools nested `{"type":"function","function":{...}}` | flat `{"type":"function","name":...}` |
|
||||
| tool result `{"role":"tool","tool_call_id":...}` | `{"type":"function_call_output","call_id":...,"output":...}` |
|
||||
| `content[].type: "text"` | `content[].type: "input_text"` |
|
||||
| `content[].type: "image_url"` + `{"url": "..."}` | `content[].type: "input_image"` + flat `"image_url": "..."` |
|
||||
| streaming `chunk.choices[0].delta.content` | `event.type == "response.output_text.delta"` → `event.delta` |
|
||||
|
||||
## Model compatibility — CHECK FIRST
|
||||
|
||||
Verify the deployed model supports the Responses API before migrating. GPT-4o and
|
||||
GPT-4 support Responses for basic text/chat/streaming/tools but not all features.
|
||||
Newer models (gpt-4.1+, gpt-5.x) have full support. **GitHub Models
|
||||
(`models.github.ai`, `models.inference.ai.azure.com`) do NOT support the Responses
|
||||
API** — remove those code paths and switch to Azure OpenAI, OpenAI, or a compatible
|
||||
local endpoint.
|
||||
|
||||
Smoke test:
|
||||
|
||||
```python
|
||||
import os
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
|
||||
)
|
||||
resp = client.responses.create(
|
||||
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
|
||||
input="ping",
|
||||
max_output_tokens=50,
|
||||
store=False,
|
||||
)
|
||||
print(resp.output_text)
|
||||
```
|
||||
|
||||
## Step 0: Client migration (prerequisite)
|
||||
|
||||
`AzureOpenAI`/`AsyncAzureOpenAI` constructors are deprecated in `openai>=1.108.1`.
|
||||
|
||||
Before:
|
||||
```python
|
||||
from openai import AzureOpenAI
|
||||
client = AzureOpenAI(
|
||||
api_version=os.environ["AZURE_OPENAI_API_VERSION"],
|
||||
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
)
|
||||
```
|
||||
|
||||
After:
|
||||
```python
|
||||
from openai import OpenAI
|
||||
client = OpenAI(
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
|
||||
)
|
||||
```
|
||||
|
||||
Cleanup: remove `api_version` args, remove `AZURE_OPENAI_API_VERSION` /
|
||||
`AZURE_OPENAI_VERSION` from `.env`/infra, rename `AZURE_OPENAI_CLIENT_ID` →
|
||||
`AZURE_CLIENT_ID`, ensure `openai>=1.108.1`.
|
||||
|
||||
## Step 1: Detect legacy call sites
|
||||
|
||||
```bash
|
||||
rg "chat\.completions\.create" # legacy API calls
|
||||
rg "ChatCompletion\.create|Completion\.create"
|
||||
rg "AzureOpenAI\(|AsyncAzureOpenAI\(" # deprecated constructors
|
||||
rg "choices\[0\]\.message\.content" # response access
|
||||
rg "choices\[0\]\.delta\.content" # streaming access
|
||||
rg "max_tokens\b" # rename to max_output_tokens
|
||||
rg "['\"]seed['\"]" # remove entirely
|
||||
rg "response_format" # → text.format
|
||||
rg "AZURE_OPENAI_API_VERSION|AZURE_OPENAI_VERSION"
|
||||
rg "models\.github\.ai|models\.inference\.ai\.azure" # GitHub Models: remove
|
||||
```
|
||||
|
||||
## Step 2: Apply migration
|
||||
|
||||
- `client.chat.completions.create(messages=...)` → `client.responses.create(input=...)`
|
||||
- `resp.choices[0].message.content` → `resp.output_text`
|
||||
- `max_tokens` → `max_output_tokens` (min 16); remove `seed`
|
||||
- `response_format` → `text={"format": {"type": "json_schema", "name": "Output", "strict": True, "schema": {...}}}`
|
||||
- Set `store=False` on every request (client-managed state)
|
||||
- Streaming: iterate events, handle `event.type == "response.output_text.delta"` (use `event.delta`) and `response.completed`
|
||||
- Tools: flat format, `tool_choice`, return results as `function_call_output` items; append `response.output` items for round-trips
|
||||
- Multi-turn: maintain conversation in an `input` array, or use `previous_response_id` (requires `store=True`)
|
||||
- O-series: `max_completion_tokens` → `max_output_tokens` (4096+), `reasoning_effort` → `reasoning={"effort": ...}`, `temperature` must be 1/omitted, remove `top_p`
|
||||
|
||||
## Acceptance criteria (all must pass)
|
||||
|
||||
- Zero matches for `chat.completions.create|ChatCompletion.create|Completion.create`
|
||||
- Zero matches for `AzureOpenAI(|AsyncAzureOpenAI(` — all use `OpenAI`/`AsyncOpenAI` + v1 endpoint
|
||||
- Zero matches for `models.github.ai|models.inference.ai.azure`
|
||||
- Zero matches for `choices[0]` — all access uses `resp.output_text` / Responses schema
|
||||
- No top-level `response_format`; structured output uses `text={"format": {...}}`
|
||||
- `openai>=1.108.1` in requirements; `store=False` on every call; no `api_version` in client construction
|
||||
- Tests updated (mocks use `kwargs.get("input")`, snapshots use Responses shape); `pytest` passes
|
||||
|
||||
See [references/cheat-sheet.md](./references/cheat-sheet.md) for complete before/after code examples.
|
||||
|
||||
## References
|
||||
|
||||
- [Azure OpenAI Responses API docs](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/responses)
|
||||
- [OpenAI Responses API reference](https://platform.openai.com/docs/api-reference/responses)
|
||||
- [Source skill: Azure-Samples/azure-openai-to-responses](https://github.com/Azure-Samples/azure-openai-to-responses)
|
||||
@@ -0,0 +1,246 @@
|
||||
# Responses API Cheat Sheet (Python + Azure OpenAI)
|
||||
|
||||
> All snippets assume `deployment = os.environ["AZURE_OPENAI_DEPLOYMENT"]` and `client` is already initialized.
|
||||
|
||||
Installed from [Azure-Samples/azure-openai-to-responses](https://github.com/Azure-Samples/azure-openai-to-responses) (MIT).
|
||||
|
||||
## Client setup — API key
|
||||
|
||||
```python
|
||||
import os
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
|
||||
)
|
||||
```
|
||||
|
||||
## Client setup — EntraID (recommended)
|
||||
|
||||
```python
|
||||
import os
|
||||
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
|
||||
from openai import OpenAI
|
||||
|
||||
token_provider = get_bearer_token_provider(
|
||||
DefaultAzureCredential(),
|
||||
"https://cognitiveservices.azure.com/.default",
|
||||
)
|
||||
client = OpenAI(
|
||||
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
|
||||
api_key=token_provider,
|
||||
)
|
||||
```
|
||||
|
||||
## Async client setup
|
||||
|
||||
```python
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
client = AsyncOpenAI(
|
||||
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
|
||||
api_key=token_provider, # or api_key=os.environ["AZURE_OPENAI_API_KEY"]
|
||||
)
|
||||
```
|
||||
|
||||
## Basic request
|
||||
|
||||
```python
|
||||
resp = client.responses.create(
|
||||
model=deployment,
|
||||
input="Hello",
|
||||
max_output_tokens=1000,
|
||||
store=False,
|
||||
)
|
||||
print(resp.output_text)
|
||||
```
|
||||
|
||||
## Full sync migration — before/after
|
||||
|
||||
Before (Chat Completions):
|
||||
```python
|
||||
from openai import AzureOpenAI
|
||||
client = AzureOpenAI(
|
||||
api_version=os.environ["AZURE_OPENAI_API_VERSION"],
|
||||
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
)
|
||||
resp = client.chat.completions.create(
|
||||
model="gpt-4o",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
max_tokens=500,
|
||||
)
|
||||
print(resp.choices[0].message.content)
|
||||
```
|
||||
|
||||
After (Responses API):
|
||||
```python
|
||||
from openai import OpenAI
|
||||
deployment = os.environ["AZURE_OPENAI_DEPLOYMENT"]
|
||||
client = OpenAI(
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
|
||||
)
|
||||
resp = client.responses.create(
|
||||
model=deployment,
|
||||
input="Hello",
|
||||
max_output_tokens=1000,
|
||||
store=False,
|
||||
)
|
||||
print(resp.output_text)
|
||||
```
|
||||
|
||||
## Multi-turn conversation
|
||||
|
||||
```python
|
||||
messages = [
|
||||
{"role": "system", "content": "You are a helpful coding assistant."},
|
||||
{"role": "user", "content": "Write a Python function to calculate factorial"},
|
||||
]
|
||||
response = client.responses.create(model=deployment, input=messages, max_output_tokens=400)
|
||||
messages.append({"role": "assistant", "content": response.output_text})
|
||||
messages.append({"role": "user", "content": "Now optimize it with memoization"})
|
||||
response2 = client.responses.create(model=deployment, input=messages, max_output_tokens=400)
|
||||
print(response2.output_text)
|
||||
```
|
||||
|
||||
## Streaming (sync)
|
||||
|
||||
```python
|
||||
stream = client.responses.create(
|
||||
model=deployment,
|
||||
input="Explain streaming in simple terms",
|
||||
max_output_tokens=1000,
|
||||
stream=True,
|
||||
)
|
||||
for event in stream:
|
||||
if event.type == "response.output_text.delta":
|
||||
print(event.delta, end="", flush=True)
|
||||
elif event.type == "response.completed":
|
||||
print()
|
||||
```
|
||||
|
||||
## Streaming (async)
|
||||
|
||||
```python
|
||||
stream = await client.responses.create(
|
||||
model=deployment, input="...", max_output_tokens=1000, stream=True,
|
||||
)
|
||||
async for event in stream:
|
||||
if event.type == "response.output_text.delta":
|
||||
print(event.delta, end="", flush=True)
|
||||
elif event.type == "response.completed":
|
||||
print()
|
||||
```
|
||||
|
||||
## Structured output — JSON Schema
|
||||
|
||||
```python
|
||||
resp = client.responses.create(
|
||||
model=deployment,
|
||||
input="What is the capital of France?",
|
||||
max_output_tokens=500,
|
||||
text={
|
||||
"format": {
|
||||
"type": "json_schema",
|
||||
"name": "Output",
|
||||
"strict": True,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"answer": {"type": "string"}},
|
||||
"required": ["answer"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
}
|
||||
},
|
||||
store=False,
|
||||
)
|
||||
import json
|
||||
data = json.loads(resp.output_text)
|
||||
```
|
||||
|
||||
## Tools (flat Responses format)
|
||||
|
||||
```python
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"name": "lookup_weather",
|
||||
"description": "Lookup the weather for a given city name.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"city_name": {"type": "string", "description": "The city name"}},
|
||||
"required": ["city_name"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
}
|
||||
]
|
||||
response = client.responses.create(
|
||||
model=deployment,
|
||||
input=[
|
||||
{"role": "system", "content": "You are a weather chatbot."},
|
||||
{"role": "user", "content": "What's the weather in Berkeley?"},
|
||||
],
|
||||
tools=tools,
|
||||
tool_choice="auto",
|
||||
store=False,
|
||||
)
|
||||
```
|
||||
|
||||
Tool call round-trip:
|
||||
```python
|
||||
import json
|
||||
messages = [
|
||||
{"role": "system", "content": "You are a weather chatbot."},
|
||||
{"role": "user", "content": "Is it sunny in Berkeley?"},
|
||||
]
|
||||
response = client.responses.create(model=deployment, input=messages, tools=tools, store=False)
|
||||
tool_calls = [item for item in response.output if item.type == "function_call"]
|
||||
if tool_calls:
|
||||
messages.extend(response.output) # append the model's function_call items
|
||||
for tc in tool_calls:
|
||||
result = execute_tool(tc.name, json.loads(tc.arguments))
|
||||
messages.append({
|
||||
"type": "function_call_output",
|
||||
"call_id": tc.call_id,
|
||||
"output": json.dumps(result),
|
||||
})
|
||||
response = client.responses.create(model=deployment, input=messages, tools=tools, store=False)
|
||||
print(response.output_text)
|
||||
```
|
||||
|
||||
> `openai.pydantic_function_tool()` still generates the old nested format and is **not** compatible with `responses.create()`. Define tool schemas manually.
|
||||
|
||||
## Image input
|
||||
|
||||
Before (Chat): `{"type": "image_url", "image_url": {"url": "..."}}`
|
||||
After (Responses): `{"type": "input_image", "image_url": "..."}` (flat string — HTTPS URL or `data:image/...;base64,...`). Also `{"type": "text"}` → `{"type": "input_text"}`.
|
||||
|
||||
```python
|
||||
resp = client.responses.create(
|
||||
model=deployment,
|
||||
input=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "input_text", "text": "What's in this image?"},
|
||||
{"type": "input_image", "image_url": "https://example.com/image.jpg"},
|
||||
],
|
||||
}
|
||||
],
|
||||
max_output_tokens=500,
|
||||
store=False,
|
||||
)
|
||||
print(resp.output_text)
|
||||
```
|
||||
|
||||
## O-series (o1, o3-mini, o3, o4-mini)
|
||||
|
||||
| Chat Completions | Responses API | Notes |
|
||||
|---|---|---|
|
||||
| `max_completion_tokens` | `max_output_tokens` | Set high (4096+) |
|
||||
| `reasoning_effort` | `reasoning={"effort": ...}` | keep low/medium/high |
|
||||
| `temperature` | remove or set to `1` | o-series only accepts 1 |
|
||||
| `top_p` | remove | not supported |
|
||||
| `seed` | remove | not supported |
|
||||
@@ -0,0 +1,94 @@
|
||||
name: Code Quality
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '**.py'
|
||||
- '**.ts'
|
||||
- '**.js'
|
||||
- 'pyproject.toml'
|
||||
- '.eslintrc.json'
|
||||
- '.github/workflows/code-quality.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '**.py'
|
||||
- '**.ts'
|
||||
- '**.js'
|
||||
- 'pyproject.toml'
|
||||
- '.eslintrc.json'
|
||||
- '.github/workflows/code-quality.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
python-quality:
|
||||
name: Python Lint & Format
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install linters
|
||||
run: python -m pip install --upgrade ruff black
|
||||
|
||||
# Enforced: the maintained shared utilities module must stay clean.
|
||||
- name: Ruff lint (shared utilities)
|
||||
run: ruff check shared/
|
||||
|
||||
- name: Black format check (shared utilities)
|
||||
run: black --check shared/
|
||||
|
||||
# Advisory: surface issues across the rest of the curriculum without
|
||||
# failing the build (lesson samples are intentionally kept simple).
|
||||
- name: Ruff lint (full repository, advisory)
|
||||
continue-on-error: true
|
||||
run: ruff check .
|
||||
|
||||
python-tests:
|
||||
name: Python Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install test dependencies
|
||||
run: python -m pip install pytest openai requests python-dotenv
|
||||
|
||||
- name: Run pytest (shared utilities)
|
||||
run: pytest tests/
|
||||
|
||||
|
||||
js-quality:
|
||||
name: JavaScript/TypeScript Lint (advisory)
|
||||
runs-on: ubuntu-latest
|
||||
# Advisory only: educational samples are not held to strict lint rules.
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install ESLint
|
||||
run: npm install --no-save eslint@8 @typescript-eslint/parser @typescript-eslint/eslint-plugin
|
||||
|
||||
- name: Run ESLint
|
||||
run: npx eslint . --ext .js,.ts
|
||||
@@ -0,0 +1,17 @@
|
||||
name: Lock closed issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: OSDKDev/lock-issues@v1.2
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -0,0 +1,60 @@
|
||||
name: Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
# Weekly scan every Monday at 06:00 UTC
|
||||
- cron: '0 6 * * 1'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
codeql:
|
||||
name: CodeQL Analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
actions: read
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language:
|
||||
- javascript-typescript
|
||||
- python
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
|
||||
dependency-review:
|
||||
name: Dependency Review
|
||||
runs-on: ubuntu-latest
|
||||
# Dependency review only runs on pull requests.
|
||||
if: github.event_name == 'pull_request'
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
comment-summary-in-pr: on-failure
|
||||
@@ -0,0 +1,30 @@
|
||||
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
|
||||
#
|
||||
# You can adjust the behavior by modifying this file.
|
||||
# For more information, see:
|
||||
# https://github.com/actions/stale
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '35 8 * * *'
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'This issue has not seen any action for a while! Closing for now, but it can be reopened at a later date.'
|
||||
stale-pr-message: 'This PR has not seen any action for a while! Closing for now, but it can be reopened at a later date.'
|
||||
stale-issue-label: 'no-issue-activity'
|
||||
stale-pr-label: 'no-pr-activity'
|
||||
days-before-stale: 30 # Added parameter to control inactivity period
|
||||
days-before-close: 7 # Optional: Time after marking stale before closing
|
||||
@@ -0,0 +1,106 @@
|
||||
name: Validate Markdown
|
||||
|
||||
on:
|
||||
# Trigger the workflow on pull request
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '**.md'
|
||||
- '**.ipynb'
|
||||
- '!translations/**'
|
||||
- '!translated_images/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
check-broken-paths:
|
||||
name: Check Broken Relative Paths
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v7
|
||||
- name: Check broken Paths
|
||||
id: check-broken-paths
|
||||
uses: john0isaac/action-check-markdown@v1.3.1
|
||||
with:
|
||||
command: check_broken_paths
|
||||
directory: ./
|
||||
guide-url: 'https://github.com/microsoft/generative-ai-for-beginners/blob/main/CONTRIBUTING.md'
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
check-paths-tracking:
|
||||
if: ${{ always() }}
|
||||
needs: check-broken-paths
|
||||
name: Check Paths Have Tracking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v7
|
||||
- name: Run Check paths tracking
|
||||
id: check-paths-tracking
|
||||
uses: john0isaac/action-check-markdown@v1.3.1
|
||||
with:
|
||||
command: check_paths_tracking
|
||||
directory: ./
|
||||
guide-url: 'https://github.com/microsoft/generative-ai-for-beginners/blob/main/CONTRIBUTING.md'
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
check-urls-tracking:
|
||||
if: ${{ always() }}
|
||||
needs: check-paths-tracking
|
||||
name: Check URLs Have Tracking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Collect changed markdown files
|
||||
id: changed-markdown-files
|
||||
run: |
|
||||
mapfile -d '' -t files < <(git diff --name-only -z --diff-filter=ACMR "${{ github.event.pull_request.base.sha }}"..."${{ github.event.pull_request.head.sha }}" -- '**.md' '**.ipynb' ':(exclude)translations/**' ':(exclude)translated_images/**')
|
||||
if [ ${#files[@]} -eq 0 ]; then
|
||||
echo "has_files=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
printf '%s\0' "${files[@]}" > changed_markdown_files.txt
|
||||
echo "has_files=true" >> "$GITHUB_OUTPUT"
|
||||
- name: Run Check URLs tracking
|
||||
if: steps.changed-markdown-files.outputs.has_files == 'true'
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install markdown-checker
|
||||
mapfile -d '' -t files < changed_markdown_files.txt
|
||||
markdown-checker -f check_urls_tracking --guide-url 'https://github.com/microsoft/generative-ai-for-beginners/blob/main/CONTRIBUTING.md' "${files[@]}"
|
||||
check-urls-locale:
|
||||
if: ${{ always() }}
|
||||
needs: check-urls-tracking
|
||||
name: Check URLs Don't Have Locale
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v7
|
||||
- name: Run Check URLs Country Locale
|
||||
id: check-urls-locale
|
||||
uses: john0isaac/action-check-markdown@v1.3.1
|
||||
with:
|
||||
command: check_urls_locale
|
||||
directory: ./
|
||||
guide-url: 'https://github.com/microsoft/generative-ai-for-beginners/blob/main/CONTRIBUTING.md'
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
check-broken-urls:
|
||||
if: ${{ always() }}
|
||||
name: Check Broken URLs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v7
|
||||
- name: Run Check Broken URLs
|
||||
id: check-broken-urls
|
||||
uses: john0isaac/action-check-markdown@v1.3.1
|
||||
with:
|
||||
command: check_broken_urls
|
||||
directory: ./
|
||||
guide-url: 'https://github.com/microsoft/generative-ai-for-beginners/blob/main/CONTRIBUTING.md'
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Welcome to the Microsoft Generative AI
|
||||
on:
|
||||
# Trigger the workflow on new issue
|
||||
issues:
|
||||
types: [opened]
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
jobs:
|
||||
assess-issue:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add Label and thanks comment to Issue
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const issueAuthor = context.payload.sender.login
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['needs-review']
|
||||
})
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `👋 Thanks for contributing @${ issueAuthor }! We will review the issue and get back to you soon.`
|
||||
})
|
||||
- name: Auto-assign issue
|
||||
uses: pozil/auto-assign-issue@v4
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
assignees: koreyspace
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Welcome to the Microsoft Generative AI
|
||||
on:
|
||||
# Trigger the workflow on pull request
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
jobs:
|
||||
assess-pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add Label and thanks comment to Pull request
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const issueAuthor = context.payload.sender.login
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['needs-review']
|
||||
})
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `👋 Thanks for contributing @${ issueAuthor }! We will review the pull request and get back to you soon.`
|
||||
})
|
||||
- name: Auto-assign pull request
|
||||
uses: pozil/auto-assign-issue@v4
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
assignees: koreyspace
|
||||
Reference in New Issue
Block a user