Skip to content
Merged
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
10 changes: 10 additions & 0 deletions nodejs/openai/sample-agent/.env.template
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# OpenAI Configuration
# Use EITHER standard OpenAI OR Azure OpenAI (not both)

# Option 1: Standard OpenAI API
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4o

# Option 2: Azure OpenAI (takes precedence if AZURE_OPENAI_API_KEY is set)
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_ENDPOINT=
AZURE_OPENAI_DEPLOYMENT=
AZURE_OPENAI_API_VERSION=2024-10-21

# MCP Tooling Configuration
BEARER_TOKEN=
Expand Down
5 changes: 5 additions & 0 deletions nodejs/openai/sample-agent/src/agent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

// IMPORTANT: Load environment variables FIRST before any other imports
// This ensures NODE_ENV and other config is available when AgentApplication initializes
import { configDotenv } from 'dotenv';
configDotenv();

import { TurnState, AgentApplication, TurnContext, MemoryStorage } from '@microsoft/agents-hosting';
import { ActivityTypes } from '@microsoft/agents-activity';
import { BaggageBuilder } from '@microsoft/agents-a365-observability';
Expand Down
10 changes: 10 additions & 0 deletions nodejs/openai/sample-agent/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { Authorization, TurnContext } from '@microsoft/agents-hosting';
import { McpToolRegistrationService } from '@microsoft/agents-a365-tooling-extensions-openai';
import { AgenticTokenCacheInstance} from '@microsoft/agents-a365-observability-hosting'

// OpenAI/Azure OpenAI Configuration
import { configureOpenAIClient, getModelName, isAzureOpenAI } from './openai-config';

// Observability Imports
import {
ObservabilityManager,
Expand All @@ -21,6 +24,9 @@ import {
import { OpenAIAgentsTraceInstrumentor } from '@microsoft/agents-a365-observability-extensions-openai';
import { tokenResolver } from './token-cache';

// Configure OpenAI/Azure OpenAI client before any agent operations
configureOpenAIClient();

export interface Client {
invokeAgentWithScope(prompt: string): Promise<string>;
}
Expand Down Expand Up @@ -58,9 +64,13 @@ openAIAgentsTraceInstrumentor.enable();
const toolService = new McpToolRegistrationService();

export async function getClient(authorization: Authorization, authHandlerName: string, turnContext: TurnContext): Promise<Client> {
const modelName = getModelName();
console.log(`[Client] Creating agent with model: ${modelName} (Azure: ${isAzureOpenAI()})`);

const agent = new Agent({
// You can customize the agent configuration here if needed
name: 'OpenAI Agent',
model: modelName,
instructions: `You are a helpful assistant with access to tools provided by MCP (Model Context Protocol) servers.

When users ask about your MCP servers, tools, or capabilities, use introspection to list the tools you have available. You can see all the tools registered to you and should report them accurately when asked.
Expand Down
26 changes: 18 additions & 8 deletions nodejs/openai/sample-agent/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

// It is important to load environment variables before importing other modules
import { configDotenv } from 'dotenv';

configDotenv();
// Note: dotenv is loaded in agent.ts before AgentApplication is instantiated

import { AuthConfiguration, authorizeJWT, CloudAdapter, loadAuthConfigFromEnv, Request } from '@microsoft/agents-hosting';
import express, { Response } from 'express'
import { agentApplication } from './agent';

// Use request validation middleware only if hosting publicly
const isProduction = Boolean(process.env.WEBSITE_SITE_NAME) || process.env.NODE_ENV === 'production';
const authConfig: AuthConfiguration = isProduction ? loadAuthConfigFromEnv() : {};
// Only NODE_ENV=development explicitly disables authentication
// All other cases (production, test, unset, etc.) require authentication
const isDevelopment = process.env.NODE_ENV === 'development';
const authConfig: AuthConfiguration = isDevelopment ? {} : loadAuthConfigFromEnv();

console.log(`Environment: NODE_ENV=${process.env.NODE_ENV}, isDevelopment=${isDevelopment}`);

const server = express()
server.use(express.json())

// Health endpoint - placed BEFORE auth middleware so it doesn't require authentication
server.get('/api/health', (req, res: Response) => {
res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString()
});
});

server.use(authorizeJWT(authConfig))

server.post('/api/messages', (req: Request, res: Response) => {
Expand All @@ -26,7 +35,8 @@ server.post('/api/messages', (req: Request, res: Response) => {
})

const port = Number(process.env.PORT) || 3978
const host = isProduction ? '0.0.0.0' : '127.0.0.1';
// Host is configurable; default to localhost for development, 0.0.0.0 for everything else
const host = process.env.HOST ?? (isDevelopment ? 'localhost' : '0.0.0.0');
server.listen(port, host, async () => {
console.log(`\nServer listening on ${host}:${port} for appId ${authConfig.clientId} debug ${process.env.DEBUG}`)
}).on('error', async (err: unknown) => {
Expand Down
80 changes: 80 additions & 0 deletions nodejs/openai/sample-agent/src/openai-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/**
* OpenAI/Azure OpenAI Configuration
*
* This module configures the OpenAI SDK to work with either:
* - Standard OpenAI API (using OPENAI_API_KEY)
* - Azure OpenAI (using AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT)
*
* Azure OpenAI takes precedence if AZURE_OPENAI_API_KEY is set.
*/

// Note: We import AzureOpenAI from 'openai' which is a transitive dependency of @openai/agents
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { AzureOpenAI } = require('openai');
import { setDefaultOpenAIClient, setOpenAIAPI } from '@openai/agents';

/**
* Determines if Azure OpenAI should be used based on environment variables.
* All three variables (API_KEY, ENDPOINT, DEPLOYMENT) must be set.
*/
export function isAzureOpenAI(): boolean {
return Boolean(
process.env.AZURE_OPENAI_API_KEY &&
process.env.AZURE_OPENAI_ENDPOINT &&
process.env.AZURE_OPENAI_DEPLOYMENT
);
}

/**
* Gets the model/deployment name to use.
* For Azure OpenAI, this is the deployment name (required).
* For standard OpenAI, this is the model name.
*/
export function getModelName(): string {
if (isAzureOpenAI()) {
const deployment = process.env.AZURE_OPENAI_DEPLOYMENT;
if (!deployment) {
throw new Error('AZURE_OPENAI_DEPLOYMENT is required when using Azure OpenAI');
}
return deployment;
}
return process.env.OPENAI_MODEL || 'gpt-4o';
}

/**
* Configures the OpenAI SDK with the appropriate client.
* Call this function early in your application startup.
*/
export function configureOpenAIClient(): void {
if (isAzureOpenAI()) {
console.log('[OpenAI Config] Using Azure OpenAI');
console.log(`[OpenAI Config] Endpoint: ${process.env.AZURE_OPENAI_ENDPOINT}`);
console.log(`[OpenAI Config] Deployment: ${process.env.AZURE_OPENAI_DEPLOYMENT}`);

const azureClient = new AzureOpenAI({
apiKey: process.env.AZURE_OPENAI_API_KEY,
endpoint: process.env.AZURE_OPENAI_ENDPOINT,
apiVersion: process.env.AZURE_OPENAI_API_VERSION || '2024-10-21',
deployment: process.env.AZURE_OPENAI_DEPLOYMENT,
});

// Set the Azure client as the default for @openai/agents
// Using 'any' to bypass type version mismatch between openai package versions
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setDefaultOpenAIClient(azureClient as any);

// Azure OpenAI requires Chat Completions API (not Responses API)
setOpenAIAPI('chat_completions');
} else if (process.env.OPENAI_API_KEY) {
console.log('[OpenAI Config] Using standard OpenAI API');
// Standard OpenAI uses OPENAI_API_KEY automatically
// No need to set client explicitly
} else {
console.warn('[OpenAI Config] WARNING: No OpenAI or Azure OpenAI credentials found!');
console.warn('[OpenAI Config] Set OPENAI_API_KEY for standard OpenAI');
console.warn('[OpenAI Config] Or set AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT for Azure OpenAI');
}
}