Skip to content
Merged
21 changes: 15 additions & 6 deletions nodejs/perplexity/sample-agent/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ PERPLEXITY_API_KEY=your_perplexity_api_key_here
PERPLEXITY_MODEL=sonar

# Agent 365 Configuration
AGENT_ID=perplexity-agent
AGENT_ID=perplexity-agent-id
PORT=3978

# Microsoft Bot Framework Authentication
Expand All @@ -12,12 +12,21 @@ CLIENT_ID=
CLIENT_SECRET=
TENANT_ID=

# MCP Tools Configuration (optional - for M365 integration)
AGENTIC_USER_ID=
MCP_AUTH_TOKEN=
# Agent Hosting Environment Configuration
connections__serviceConnection__settings__clientId=blueprint_id
connections__serviceConnection__settings__clientSecret=blueprint_secret
connections__serviceConnection__settings__tenantId=your-tenant-id

# Observability (optional - Azure Application Insights)
CONNECTION_STRING=
connectionsMap__0__connection=serviceConnection
connectionsMap__0__serviceUrl=*

agentic_type=agentic
agentic_scopes=https://graph.microsoft.com/.default

# Agent 365 observability Environment Configuration
ENABLE_OBSERVABILITY=true
ENABLE_A365_OBSERVABILITY_EXPORTER=true
A365_OBSERVABILITY_LOG_LEVEL=info # optional - set to enable observability logs, value can be 'info', 'warn', or 'error', default to 'none' if not set

# Debug Mode
DEBUG=false
117 changes: 66 additions & 51 deletions nodejs/perplexity/sample-agent/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,7 @@ import {
import { ActivityTypes } from "@microsoft/agents-activity";
import { AgentNotificationActivity } from "@microsoft/agents-a365-notifications";
import { PerplexityAgent } from "./perplexityAgent.js";
import {
MentionInWordValue,
PlaygroundActivityTypes,
SendEmailActivity,
SendTeamsMessageActivity,
} from "./playgroundActivityTypes.js";
import { PlaygroundActivityTypes } from "./playgroundActivityTypes.js";

import {
BaggageBuilder,
Expand Down Expand Up @@ -78,7 +73,7 @@ async function runWithTelemetry(
executionType: ExecutionType;
requestContent?: string;
},
handler: () => Promise<void>
handler: (invokeScope?: InvokeAgentScope) => Promise<void>
): Promise<void> {
const agentInfo = extractAgentDetailsFromTurnContext(context);
const tenantInfo = extractTenantDetailsFromTurnContext(context);
Expand All @@ -97,6 +92,7 @@ async function runWithTelemetry(
.callerId((context.activity.from as any)?.aadObjectId)
.callerUpn(context.activity.from?.id)
.correlationId(context.activity.id ?? `corr-${Date.now()}`)
.sourceMetadataName(context.activity.channelId)
.build();

await baggageScope.run(async () => {
Expand Down Expand Up @@ -130,11 +126,23 @@ async function runWithTelemetry(
await invokeScope.withActiveSpanAsync(async () => {
invokeScope.recordInputMessages([requestContent]);

await handler();

invokeScope.recordOutputMessages([
`${options.operationName} handled by PerplexityAgent`,
]);
try {
await handler(invokeScope);

// Default "happy path" marker
invokeScope.recordOutputMessages([
`${options.operationName} handled by PerplexityAgent`,
]);
invokeScope.recordOutputMessages([
`${options.operationName} succeeded`,
]);
} catch (error) {
const err = error as Error;
// Error markers
invokeScope.recordError(err);
// Preserve original behavior by rethrowing
throw error;
}
});
} finally {
invokeScope.dispose();
Expand Down Expand Up @@ -164,11 +172,12 @@ agentApplication.onAgentNotification(
executionType: ExecutionType.EventToAgent,
requestContent: `NotificationType=${activity.notificationType}`,
},
async () => {
async (invokeScope) => {
await perplexityAgent.handleAgentNotificationActivity(
context,
state,
activity
activity,
invokeScope
);
}
);
Expand All @@ -192,11 +201,12 @@ agentApplication.onAgenticWordNotification(
executionType: ExecutionType.EventToAgent,
requestContent: `WordNotificationType=${activity.notificationType}`,
},
async () => {
async (invokeScope) => {
await perplexityAgent.handleAgentNotificationActivity(
context,
state,
activity
activity,
invokeScope
);
}
);
Expand All @@ -220,19 +230,20 @@ agentApplication.onAgenticEmailNotification(
executionType: ExecutionType.EventToAgent,
requestContent: `EmailNotificationType=${activity.notificationType}`,
},
async () => {
async (invokeScope) => {
await perplexityAgent.handleAgentNotificationActivity(
context,
state,
activity
activity,
invokeScope
);
}
);
}
);

/* --------------------------------------------------------------------
* ✅ Playground Events (Simulated) + telemetry
* ✅ Playground Events (Simulated) + telemetry (delegated to PerplexityAgent)
* -------------------------------------------------------------------- */

agentApplication.onActivity(
Expand All @@ -246,17 +257,12 @@ agentApplication.onActivity(
executionType: ExecutionType.HumanToAgent,
requestContent: JSON.stringify(context.activity.value ?? {}),
},
async () => {
const value: MentionInWordValue = context.activity
.value as MentionInWordValue;
const docName: string = value.mention.displayName;
const docUrl: string = value.docUrl;
const userName: string = value.mention.userPrincipalName;
const contextSnippet: string = value.context
? `Context: ${value.context}`
: "";
const message: string = `✅ You were mentioned in **${docName}** by ${userName}\n📄 ${docUrl}\n${contextSnippet}`;
await context.sendActivity(message);
async (invokeScope) => {
await perplexityAgent.handlePlaygroundMentionInWord(
context,
state,
invokeScope
);
}
);
}
Expand All @@ -273,17 +279,12 @@ agentApplication.onActivity(
executionType: ExecutionType.HumanToAgent,
requestContent: JSON.stringify(context.activity.value ?? {}),
},
async () => {
const activity = context.activity as SendEmailActivity;
const email = activity.value;

const message: string = `📧 Email Notification:
From: ${email.from}
To: ${email.to.join(", ")}
Subject: ${email.subject}
Body: ${email.body}`;

await context.sendActivity(message);
async (invokeScope) => {
await perplexityAgent.handlePlaygroundSendEmail(
context,
state,
invokeScope
);
}
);
}
Expand All @@ -300,10 +301,12 @@ agentApplication.onActivity(
executionType: ExecutionType.HumanToAgent,
requestContent: JSON.stringify(context.activity.value ?? {}),
},
async () => {
const activity = context.activity as SendTeamsMessageActivity;
const message = `💬 Teams Message: ${activity.value.text} (Scope: ${activity.value.destination.scope})`;
await context.sendActivity(message);
async (invokeScope) => {
await perplexityAgent.handlePlaygroundSendTeamsMessage(
context,
state,
invokeScope
);
}
);
}
Expand All @@ -320,8 +323,12 @@ agentApplication.onActivity(
executionType: ExecutionType.HumanToAgent,
requestContent: "custom",
},
async () => {
await context.sendActivity("this is a custom activity handler");
async (invokeScope) => {
await perplexityAgent.handlePlaygroundCustom(
context,
state,
invokeScope
);
}
);
}
Expand All @@ -346,8 +353,12 @@ agentApplication.onActivity(
executionType: ExecutionType.HumanToAgent,
requestContent: context.activity.text || "Unknown text",
},
async () => {
await perplexityAgent.handleAgentMessageActivity(context, state);
async (invokeScope) => {
await perplexityAgent.handleAgentMessageActivity(
context,
state,
invokeScope
);
}
);
}
Expand All @@ -370,8 +381,12 @@ agentApplication.onActivity(
executionType: ExecutionType.EventToAgent,
requestContent: `InstallationUpdate action=${action}`,
},
async () => {
await perplexityAgent.handleInstallationUpdateActivity(context, state);
async (invokeScope) => {
await perplexityAgent.handleInstallationUpdateActivity(
context,
state,
invokeScope
);
}
);
}
Expand Down
78 changes: 78 additions & 0 deletions nodejs/perplexity/sample-agent/src/chatFlowService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { TurnContext, TurnState } from "@microsoft/agents-hosting";
import type { InvokeAgentScope } from "@microsoft/agents-a365-observability";
import { PerplexityClient } from "./perplexityClient.js";
import { ToolRunner } from "./toolRunner.js";

/**
* ChatFlowService manages the chat and tool invocation flow.
*/
export class ChatFlowService {
constructor(private readonly getPerplexityClient: () => PerplexityClient) {}

/**
* Runs the main chat and tool flow.
* @param turnContext The context of the current turn.
* @param _state The state of the current turn.
* @param userMessage The user's message.
* @param invokeScope The scope for invoking the agent.
*/
async runChatFlow(
turnContext: TurnContext,
_state: TurnState,
userMessage: string,
invokeScope: InvokeAgentScope | undefined
): Promise<void> {
const streamingResponse = (turnContext as any).streamingResponse;
const perplexityClient = this.getPerplexityClient();

try {
invokeScope?.recordInputMessages([userMessage]);

if (streamingResponse) {
streamingResponse.queueInformativeUpdate(
"I'm working on your request..."
);
}

invokeScope?.recordOutputMessages([
"Message path: PerplexityInvocationStarted",
]);

const response = await perplexityClient.invokeAgentWithScope(userMessage);

invokeScope?.recordOutputMessages([
"Message path: PerplexityInvocationSucceeded",
]);

if (streamingResponse) {
streamingResponse.queueTextChunk(response);
await streamingResponse.endStream();
} else {
await turnContext.sendActivity(response);
}

invokeScope?.recordOutputMessages([
"Message path: ChatOnly_CompletedSuccessfully",
]);
} catch (error) {
const err = error as any;
const errorMessage = `Error: ${err.message || err}`;

invokeScope?.recordError(error as Error);
invokeScope?.recordOutputMessages([
"Message path: ChatOnly_Error",
errorMessage,
]);

if (streamingResponse) {
streamingResponse.queueTextChunk(errorMessage);
await streamingResponse.endStream();
} else {
await turnContext.sendActivity(errorMessage);
}
}
}
}
Loading