Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,28 @@ GitHub Pages build so asset paths resolve correctly.

| Variable | Default | Description |
|----------|---------|-------------|
| `VITE_ENABLE_AI_BUILDER` | `false` | Enable the Azure OpenAI ontology builder |
| `VITE_ENABLE_AI_BUILDER` | `false` | Enable the AI ontology builder (backed by the `api/` function) |
| `VITE_ENABLE_LEGACY_FORMATS` | `false` | Enable JSON/YAML/CSV import/export formats |
| `VITE_BASE_PATH` | `/` | Base path for the app (set automatically for GitHub Pages) |
| `VITE_GITHUB_CLIENT_ID` | *(empty)* | GitHub OAuth App client ID for one-click catalogue PRs ([setup guide](docs/github-oauth-setup.md)) |
| `VITE_GITHUB_OAUTH_BASE` | *(empty)* | External OAuth proxy URL for GitHub Pages deployments (e.g. Cloudflare Worker URL) |

#### AI builder backend (`api/`)

The `generate-ontology` function selects its language model provider from
`AI_PROVIDER` (default `azure`). Configure these in `api/local.settings.json`
(local) or the Function App application settings (deployed).

| Variable | Default | Description |
|----------|---------|-------------|
| `AI_PROVIDER` | `azure` | Provider for the ontology builder: `azure` or `minimax` |
| `AZURE_OPENAI_ENDPOINT` | *(empty)* | Azure OpenAI resource endpoint (required when `AI_PROVIDER=azure`) |
| `AZURE_OPENAI_API_KEY` | *(empty)* | Azure OpenAI API key (required when `AI_PROVIDER=azure`) |
| `AZURE_OPENAI_DEPLOYMENT` | `gpt-4o-mini` | Azure OpenAI deployment name |
| `MINIMAX_BASE_URL` | `https://api.minimax.io/v1` | MiniMax OpenAI-compatible base URL (use `https://api.minimaxi.com/v1` for mainland China) |
| `MINIMAX_API_KEY` | *(empty)* | MiniMax API key (required when `AI_PROVIDER=minimax`) |
| `MINIMAX_MODEL` | `MiniMax-M3` | MiniMax model id |

## Project Structure

```
Expand Down
119 changes: 92 additions & 27 deletions api/generate-ontology/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ interface OpenAIResponse {
}>;
}

// Resolved chat-completions call for the selected provider.
interface ProviderRequest {
url: string;
headers: Record<string, string>;
// Model identifier sent in the request body. Azure OpenAI selects the model
// through the deployment segment of the URL and omits this field.
model?: string;
}

const SYSTEM_PROMPT = `You are an expert ontology extraction system. Given a business scenario description, extract entities, relationships, and properties to create a complete ontology.

Output ONLY valid JSON matching this exact schema:
Expand Down Expand Up @@ -53,6 +62,62 @@ Rules:
7. Assign unique hex colors to each entity (use Microsoft palette: #0078D4, #107C10, #5C2D91, #FFB900, #D83B01, #00A9E0, #8764B8, #00B294)
8. Output ONLY the JSON, no explanations`;

// Build the chat-completions request for the configured provider. Azure OpenAI
// remains the default so existing deployments keep working; set AI_PROVIDER to
// switch to another OpenAI-compatible backend. Returns an error string when the
// selected provider is missing required configuration.
function resolveProvider(): { request?: ProviderRequest; error?: string } {
const provider = (process.env.AI_PROVIDER || "azure").trim().toLowerCase();

if (provider === "minimax") {
// MiniMax exposes an OpenAI-compatible chat-completions API. Use the global
// endpoint by default; set MINIMAX_BASE_URL to https://api.minimaxi.com/v1
// for the mainland China endpoint.
const baseUrl = (process.env.MINIMAX_BASE_URL || "https://api.minimax.io/v1").replace(/\/+$/, "");
const apiKey = process.env.MINIMAX_API_KEY;
const model = process.env.MINIMAX_MODEL || "MiniMax-M3";

if (!apiKey) {
return {
error:
"MiniMax not configured. Set MINIMAX_API_KEY (and optionally MINIMAX_BASE_URL and MINIMAX_MODEL).",
};
}

return {
request: {
url: `${baseUrl}/chat/completions`,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
model,
},
};
}

// Default provider: Azure OpenAI.
const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
const apiKey = process.env.AZURE_OPENAI_API_KEY;
const deployment = process.env.AZURE_OPENAI_DEPLOYMENT || "gpt-4o-mini";

if (!endpoint || !apiKey) {
return {
error: "Azure OpenAI not configured. Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY.",
};
}

return {
request: {
url: `${endpoint}openai/deployments/${deployment}/chat/completions?api-version=2024-02-15-preview`,
headers: {
"Content-Type": "application/json",
"api-key": apiKey,
},
},
};
}

const generateOntology: AzureFunction = async function (
context: Context,
req: HttpRequest
Expand All @@ -67,45 +132,45 @@ const generateOntology: AzureFunction = async function (
return;
}

const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
const apiKey = process.env.AZURE_OPENAI_API_KEY;
const deployment = process.env.AZURE_OPENAI_DEPLOYMENT || "gpt-4o-mini";
const { request: providerRequest, error: providerError } = resolveProvider();

if (!endpoint || !apiKey) {
if (!providerRequest) {
context.res = {
status: 500,
body: { error: "Azure OpenAI not configured. Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY." },
body: { error: providerError },
};
return;
}

try {
const response = await fetch(
`${endpoint}openai/deployments/${deployment}/chat/completions?api-version=2024-02-15-preview`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"api-key": apiKey,
},
body: JSON.stringify({
messages: [
{ role: "system", content: SYSTEM_PROMPT },
{ role: "user", content: description },
],
temperature: 0.3,
max_tokens: 4000,
response_format: { type: "json_object" },
}),
}
);
const requestBody: Record<string, unknown> = {
messages: [
{ role: "system", content: SYSTEM_PROMPT },
{ role: "user", content: description },
],
temperature: 0.3,
max_tokens: 4000,
response_format: { type: "json_object" },
};

// OpenAI-compatible providers select the model in the request body; Azure
// OpenAI encodes it in the deployment URL and leaves this undefined.
if (providerRequest.model) {
requestBody.model = providerRequest.model;
}

const response = await fetch(providerRequest.url, {
method: "POST",
headers: providerRequest.headers,
body: JSON.stringify(requestBody),
});

if (!response.ok) {
const errorText = await response.text();
context.log.error("Azure OpenAI error:", errorText);
context.log.error("Ontology provider error:", errorText);
context.res = {
status: 502,
body: { error: "Failed to generate ontology from Azure OpenAI" },
body: { error: "Failed to generate ontology from the language model provider" },
};
return;
}
Expand All @@ -116,7 +181,7 @@ const generateOntology: AzureFunction = async function (
if (!content) {
context.res = {
status: 500,
body: { error: "No content in Azure OpenAI response" },
body: { error: "No content in the language model response" },
};
return;
}
Expand Down
6 changes: 5 additions & 1 deletion api/local.settings.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsStorage": "",
"AI_PROVIDER": "azure",
"AZURE_OPENAI_ENDPOINT": "https://YOUR-RESOURCE.openai.azure.com/",
"AZURE_OPENAI_API_KEY": "YOUR-API-KEY",
"AZURE_OPENAI_DEPLOYMENT": "gpt-4o-mini"
"AZURE_OPENAI_DEPLOYMENT": "gpt-4o-mini",
"MINIMAX_BASE_URL": "https://api.minimax.io/v1",
"MINIMAX_API_KEY": "YOUR-API-KEY",
"MINIMAX_MODEL": "MiniMax-M3"
}
}