diff --git a/nodejs/openai/sample-agent/.env.template b/nodejs/openai/sample-agent/.env.template index bcd44e95..b96b68e7 100644 --- a/nodejs/openai/sample-agent/.env.template +++ b/nodejs/openai/sample-agent/.env.template @@ -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= diff --git a/nodejs/openai/sample-agent/src/agent.ts b/nodejs/openai/sample-agent/src/agent.ts index f215cd74..2fec4d9f 100644 --- a/nodejs/openai/sample-agent/src/agent.ts +++ b/nodejs/openai/sample-agent/src/agent.ts @@ -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'; diff --git a/nodejs/openai/sample-agent/src/client.ts b/nodejs/openai/sample-agent/src/client.ts index 21792232..56e05908 100644 --- a/nodejs/openai/sample-agent/src/client.ts +++ b/nodejs/openai/sample-agent/src/client.ts @@ -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, @@ -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; } @@ -58,9 +64,13 @@ openAIAgentsTraceInstrumentor.enable(); const toolService = new McpToolRegistrationService(); export async function getClient(authorization: Authorization, authHandlerName: string, turnContext: TurnContext): Promise { + 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. diff --git a/nodejs/openai/sample-agent/src/index.ts b/nodejs/openai/sample-agent/src/index.ts index db3d1e55..9977ff36 100644 --- a/nodejs/openai/sample-agent/src/index.ts +++ b/nodejs/openai/sample-agent/src/index.ts @@ -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) => { @@ -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) => { diff --git a/nodejs/openai/sample-agent/src/openai-config.ts b/nodejs/openai/sample-agent/src/openai-config.ts new file mode 100644 index 00000000..20fa1c75 --- /dev/null +++ b/nodejs/openai/sample-agent/src/openai-config.ts @@ -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'); + } +}