Skip to content

Add user identity pattern to dotnet/agent-framework sample#233

Open
sellakumaran wants to merge 2 commits intomainfrom
users/sellak/user-identity
Open

Add user identity pattern to dotnet/agent-framework sample#233
sellakumaran wants to merge 2 commits intomainfrom
users/sellak/user-identity

Conversation

@sellakumaran
Copy link

Demonstrates how A365 agents identify the user they are talking to:

  • Log Activity.From (Id, Name, AadObjectId) at the start of each turn
  • CurrentUserTool exposes GetCurrentUser() (activity payload, no API call) and GetCurrentUserExtendedProfileAsync() (Microsoft Graph /me) as LLM-callable tools
  • Inject user's display name into agent instructions for personalized responses
  • README section explains both patterns and token requirements for Graph calls

Demonstrates how A365 agents identify the user they are talking to:
- Log Activity.From (Id, Name, AadObjectId) at the start of each turn
- CurrentUserTool exposes GetCurrentUser() (activity payload, no API call)
  and GetCurrentUserExtendedProfileAsync() (Microsoft Graph /me) as LLM-callable tools
- Inject user's display name into agent instructions for personalized responses
- README section explains both patterns and token requirements for Graph calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 4, 2026 00:31
@sellakumaran sellakumaran requested a review from a team as a code owner March 4, 2026 00:31
@github-actions
Copy link

github-actions bot commented Mar 4, 2026

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “user identity” pattern to the .NET Agent Framework sample, showing how to identify the current user from the activity payload and (optionally) enrich that identity via Microsoft Graph, plus using the user’s name for more personalized agent behavior.

Changes:

  • Introduces CurrentUserTool exposing LLM-callable functions for basic identity (Activity.From) and extended profile (GET /me via Graph).
  • Logs Activity.From identity fields at the start of each turn and injects the user display name into agent instructions/welcome message.
  • Documents both identity patterns and token requirements in the sample README.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
dotnet/agent-framework/sample-agent/Tools/CurrentUserTool.cs Adds new tool for retrieving basic identity and (optionally) Graph /me profile.
dotnet/agent-framework/sample-agent/Agent/MyAgent.cs Logs user identity each turn, registers the new tool, and personalizes instructions/welcome text using user display name.
dotnet/agent-framework/sample-agent/README.md Documents basic identity vs. Graph-based extended profile and token requirements.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +52 to +53
private static string GetAgentInstructions(string? userName) =>
AgentInstructionsTemplate.Replace("{userName}", string.IsNullOrEmpty(userName) ? "unknown" : userName, StringComparison.Ordinal);
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

displayName comes from Activity.From.Name (channel-provided) and is inserted verbatim into the system instructions. Because this is untrusted user-controlled text, it can enable prompt-injection (e.g., a display name containing newlines/instructions) or unexpectedly bloat the prompt. Consider sanitizing/normalizing the name (strip control chars/newlines, trim, enforce a max length) and/or explicitly marking it as untrusted text in the instructions (e.g., quote it and instruct the model not to treat it as instructions).

Copilot uses AI. Check for mistakes.
Comment on lines +158 to +162
_logger?.LogInformation(
"Turn received from user — DisplayName: '{Name}', UserId: '{Id}', AadObjectId: '{AadObjectId}'",
fromAccount?.Name ?? "(unknown)",
fromAccount?.Id ?? "(unknown)",
fromAccount?.AadObjectId ?? "(none)");
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logs DisplayName, channel user ID, and especially AadObjectId at Information level on every turn. Those values are user identifiers/PII and will commonly end up in production logs. Consider reducing this to Debug (or feature-flagging it), and/or redacting/hashing AadObjectId before logging.

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +79
// Note: In production, inject IHttpClientFactory via the constructor to avoid socket exhaustion.
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

var response = await httpClient.GetAsync(graphEndpoint).ConfigureAwait(false);
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetCurrentUserExtendedProfileAsync creates and disposes a new HttpClient per call. Even in samples this can lead to socket exhaustion and makes it harder to apply retry/timeouts consistently. Prefer injecting IHttpClientFactory (e.g., a named/typed Graph client) and reusing it here, with an explicit timeout and any existing resilience policies.

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +90
var parsed = JsonSerializer.Deserialize<JsonElement>(json);
return JsonSerializer.Serialize(parsed, new JsonSerializerOptions { WriteIndented = true });
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsonSerializer.Deserialize<JsonElement>(json) will throw if Graph returns non-JSON (or unexpected JSON) even on a 2xx response, which would surface as a tool failure. Consider wrapping the parse/pretty-print in a try/catch for JsonException and returning a safe fallback (e.g., raw response text or a clearer error) instead of throwing.

Suggested change
var parsed = JsonSerializer.Deserialize<JsonElement>(json);
return JsonSerializer.Serialize(parsed, new JsonSerializerOptions { WriteIndented = true });
try
{
var parsed = JsonSerializer.Deserialize<JsonElement>(json);
return JsonSerializer.Serialize(parsed, new JsonSerializerOptions { WriteIndented = true });
}
catch (JsonException ex)
{
logger?.LogWarning(ex, "Failed to parse Graph /me response as JSON. Returning raw response text.");
return json;
}

Copilot uses AI. Check for mistakes.
Activity.From.Name is not reliably populated in the MembersAdded event
in Teams/A365 flows. The user name is injected into the LLM instructions
on the first message turn instead, where Activity.From.Name is always set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants