-
Notifications
You must be signed in to change notification settings - Fork 50
Add user identity pattern to dotnet/agent-framework sample #233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
71f649b
Add user identity pattern to dotnet/agent-framework sample
sellakumaran 40878f6
Simplify welcome message — name not available in MembersAdded event
sellakumaran a6450eb
Simplify user identity pattern in dotnet/agent-framework
sellakumaran 3cb73ae
feat: add user identity logging + name injection to dotnet/semantic-k…
sellakumaran 36699c3
feat: add user identity logging + name injection to all Node.js samples
sellakumaran 92a19e6
docs: add user identity pattern to all design docs
sellakumaran 172a01a
Renumber Observability Integration section in design.md
sellakumaran 2ef7847
fix: address PR #233 review comments — PII logging, prompt injection,…
sellakumaran 0b6f6d0
fix: address PR #233 review comments from gwharris7
sellakumaran 22306cf
fix: address additional PR #233 review comment from gwharris7
sellakumaran File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,244 @@ | ||
| # User Identity Rollout — Implementation Plan | ||
|
|
||
| ## Context | ||
|
|
||
| Agents need to identify who they are talking to in order to personalize responses and log user activity. The universal pattern across all 15 samples is: | ||
|
|
||
| 1. Log `Activity.From` fields (`Name`, `Id`, `AadObjectId`) at message handler entry | ||
| 2. Inject the user's display name into the LLM system instructions / prompt | ||
| 3. Document the pattern in the sample README | ||
|
|
||
| No new tool files, no Graph calls, no per-orchestrator tool registration. | ||
|
|
||
| The `dotnet/agent-framework` sample was originally built with a `CurrentUserTool` (LLM-callable tools + Graph `/me`). That is being simplified to match the uniform pattern used by all other samples. | ||
|
|
||
| --- | ||
|
|
||
| ## Workflow | ||
|
|
||
| For `dotnet/agent-framework` (Step 0): | ||
| 1. Make changes locally | ||
| 2. User deploys and tests manually | ||
| 3. Commit and push | ||
| 4. Proceed with remaining samples | ||
|
|
||
| --- | ||
|
|
||
| ## Step 0 — Simplify `dotnet/agent-framework` (branch `users/sellak/user-identity`) | ||
|
|
||
| **Delete:** `dotnet/agent-framework/sample-agent/Tools/CurrentUserTool.cs` | ||
|
|
||
| **Modify `Agent/MyAgent.cs`:** | ||
| - Remove the `CurrentUserTool` instantiation and the two `AIFunctionFactory.Create(currentUserTool.*)` registrations | ||
| - Remove the Graph-related instruction from `AgentInstructionsTemplate`: | ||
| ``` | ||
| For richer user profile information (email, job title, department), use {{CurrentUserTool.GetCurrentUserExtendedProfileAsync}}. | ||
| ``` | ||
| - Keep: `accessToken` acquisition, `agentId` warning log, logging of `Activity.From`, `{userName}` injection into instructions | ||
|
|
||
| **Modify `README.md`:** | ||
| - Simplify "Working with User Identity" to the activity-payload pattern only (field table + log snippet) | ||
| - Remove "Extended profile from Microsoft Graph" subsection | ||
| - Remove `Tools/CurrentUserTool.cs` file reference | ||
|
|
||
| **Verify:** `dotnet build` passes, no `CurrentUserTool` references remain. | ||
|
|
||
| --- | ||
|
|
||
| ## Changes Per Sample (Steps 1–9, after Step 0 is tested and committed) | ||
|
|
||
| Every sample gets the same three changes — no new files: | ||
|
|
||
| | # | Change | Detail | | ||
| |---|--------|--------| | ||
| | 1 | **Log** | Add structured log of `Activity.From` (Name, Id, AadObjectId) at the start of the message handler | | ||
| | 2 | **Inject** | Read `Activity.From.Name` and inject it into the LLM system instructions / prompt template as the user's display name | | ||
| | 3 | **README** | Add "Working with User Identity" section (see template below) | | ||
|
|
||
| --- | ||
|
|
||
| ### C# — `dotnet/semantic-kernel/sample-agent/` | ||
|
|
||
| **Modify `Agents/MyAgent.cs`:** | ||
|
|
||
| ```csharp | ||
| var fromAccount = turnContext.Activity.From; | ||
| _logger?.LogInformation( | ||
| "Turn received from user — DisplayName: '{Name}', UserId: '{Id}', AadObjectId: '{AadObjectId}'", | ||
| fromAccount?.Name ?? "(unknown)", | ||
| fromAccount?.Id ?? "(unknown)", | ||
| fromAccount?.AadObjectId ?? "(none)"); | ||
| ``` | ||
|
|
||
| Inject `Activity.From.Name` into the SK kernel's system instructions using the same `{userName}` template replacement pattern as the reference. | ||
|
|
||
| **Modify `README.md`** — add section. | ||
|
|
||
| --- | ||
|
|
||
| ### Python — 5 samples | ||
|
|
||
| Python uses `activity.from_property`. Samples with `turn_context_utils.py` (`claude`, `crewai`) already extract `caller_name`, `caller_id`, `caller_aad_object_id` — reuse those. | ||
|
|
||
| **Modify `agent.py` in each sample:** | ||
|
|
||
| ```python | ||
| from_prop = turn_context.activity.from_property | ||
| logger.info( | ||
| "Turn received from user — DisplayName: '%s', UserId: '%s', AadObjectId: '%s'", | ||
| getattr(from_prop, "name", None) or "(unknown)", | ||
| getattr(from_prop, "id", None) or "(unknown)", | ||
| getattr(from_prop, "aad_object_id", None) or "(none)", | ||
| ) | ||
| display_name = getattr(from_prop, "name", None) or "unknown" | ||
| ``` | ||
|
|
||
| Inject `display_name` into the system prompt string. | ||
|
|
||
| | Sample | Notes | | ||
| |--------|-------| | ||
| | `python/agent-framework/sample-agent/` | No existing identity code. Add logging + injection. | | ||
| | `python/claude/sample-agent/` | Has `turn_context_utils.py` — reuse `caller_details`. | | ||
| | `python/openai/sample-agent/` | No existing identity code. Add logging + injection. | | ||
| | `python/crewai/sample_agent/` | Has `turn_context_utils.py` — reuse `caller_details`. | | ||
| | `python/google-adk/sample-agent/` | No existing identity code. Add logging + injection. | | ||
|
|
||
| **Modify `README.md`** in each — add section. | ||
|
|
||
| --- | ||
|
|
||
| ### Node.js / TypeScript — 8 samples | ||
|
|
||
| **Modify `src/agent.ts` in each sample (except n8n):** | ||
|
|
||
| ```typescript | ||
| const from = turnContext.activity?.from; | ||
| logger.info( | ||
| `Turn received from user — DisplayName: '${from?.name ?? "(unknown)"}', UserId: '${from?.id ?? "(unknown)"}', AadObjectId: '${from?.aadObjectId ?? "(none)"}'` | ||
| ); | ||
| const displayName = from?.name ?? "unknown"; | ||
| ``` | ||
|
|
||
| Inject `displayName` into the system prompt string. | ||
|
|
||
| | Sample | Notes | | ||
| |--------|-------| | ||
| | `nodejs/openai/sample-agent/` | Add logging + injection. | | ||
| | `nodejs/claude/sample-agent/` | Add logging + injection. | | ||
| | `nodejs/langchain/sample-agent/` | Add logging + injection. | | ||
| | `nodejs/devin/sample-agent/` | Explore structure first, then apply. | | ||
| | `nodejs/n8n/` | **README only** — no agent code to modify. | | ||
| | `nodejs/perplexity/sample-agent/` | Already extracts userId/userName/aadObjectId — add log line + inject into prompt. | | ||
| | `nodejs/vercel-sdk/sample-agent/` | Add logging + injection. | | ||
| | `nodejs/copilot-studio/sample-agent/` | Explore structure first, then apply. | | ||
|
|
||
| **Modify `README.md`** in each — add section. | ||
|
|
||
| --- | ||
|
|
||
| ## Design Doc Updates (4 files) | ||
|
|
||
| Prose additions only — no code changes. | ||
|
|
||
| | File | Change | | ||
| |------|--------| | ||
| | `docs/design.md` | Add "User Identity" note under Message Processing Flow | | ||
| | `dotnet/docs/design.md` | Add C# logging snippet + name-injection pattern | | ||
| | `python/docs/design.md` | Add Python snippet using `activity.from_property` | | ||
| | `nodejs/docs/design.md` | Add TypeScript snippet using `activity?.from` | | ||
|
|
||
| Content for all four: | ||
| > Agents identify the user from `Activity.From` (populated by the A365 platform on every message). Log `Id`, `Name`, and `AadObjectId` at `Information`/`info` level at message handler entry. Inject `Name` into LLM system instructions for personalization. For extended profile data (email, job title), a delegated Graph call to `/me` is required (app-only tokens use `/users/{AadObjectId}`). | ||
|
|
||
| --- | ||
|
|
||
| ## README "Working with User Identity" Template | ||
|
|
||
| Use this in every sample README: | ||
|
|
||
| ```markdown | ||
| ## Working with User Identity | ||
|
|
||
| On every incoming message, the A365 platform populates `Activity.From` with basic user | ||
| information — always available with no API calls or token acquisition: | ||
|
|
||
| | Field | Description | | ||
| |---|---| | ||
| | `Activity.From.Id` | Channel-specific user ID (e.g., `29:1AbcXyz...` in Teams) | | ||
| | `Activity.From.Name` | Display name as known to the channel | | ||
| | `Activity.From.AadObjectId` | Azure AD Object ID — use this to call Microsoft Graph | | ||
|
|
||
| The sample logs these fields at the start of every message turn and injects the display name | ||
| into the LLM system instructions for personalized responses. | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Execution Order | ||
|
|
||
| 0. **`dotnet/agent-framework`** — simplify locally → user tests → commit and push | ||
| 1. **`dotnet/semantic-kernel`** — closest to reference; validates C# pattern in SK context | ||
| 2. **`python/claude`**, **`python/crewai`** — already have `turn_context_utils`; quick wins | ||
| 3. **`python/agent-framework`**, **`python/openai`**, **`python/google-adk`** | ||
| 4. **`nodejs/perplexity`** — already extracts identity; log + inject + README | ||
| 5. **`nodejs/openai`**, **`nodejs/claude`**, **`nodejs/langchain`**, **`nodejs/vercel-sdk`** | ||
| 6. **`nodejs/devin`**, **`nodejs/copilot-studio`** — explore structure before modifying | ||
| 7. **`nodejs/n8n`** — README only | ||
| 8. **Design docs** (all 4) | ||
|
|
||
| --- | ||
|
|
||
| ## Testing Strategy | ||
|
|
||
| ### Tier 1 — Build/compile gate (all samples, required) | ||
|
|
||
| | Language | Command | Run from | | ||
| |----------|---------|---------| | ||
| | C# | `dotnet build` | solution directory | | ||
| | Python | `python -m py_compile agent.py` | `sample-agent/` | | ||
| | TypeScript | `npm run build` | `sample-agent/` | | ||
|
|
||
| n8n is README-only — no build step. | ||
|
|
||
| ### Tier 2 — `/review-staged` gate (all samples, required) | ||
|
|
||
| Run `/review-staged` after implementing each sample batch, before committing. All critical/high findings must be resolved. | ||
|
|
||
| ### Tier 3 — E2E functional test (one per language, required) | ||
|
|
||
| `dotnet/agent-framework` is the E2E baseline (user-deployed and tested). Additionally validate one per language: | ||
|
|
||
| | Language | Sample | What to verify | | ||
| |----------|--------|----------------| | ||
| | C# | `dotnet/semantic-kernel` | Send a message — LLM response uses the user's name | | ||
| | Python | `python/agent-framework` | Send a message — LLM response uses the display name | | ||
| | Node.js | `nodejs/openai` | Send a message — LLM response uses the display name | | ||
|
|
||
| The remaining 11 samples are covered by identical pattern + build pass + `/review-staged`. | ||
|
|
||
| ### Tier 4 — Design docs (review only) | ||
|
|
||
| Prose-only additions — verify accuracy against reference code, no deploy needed. | ||
|
|
||
| --- | ||
|
|
||
| ## Public Documentation Suggestions | ||
|
|
||
| **Target page:** [a365-dev-lifecycle — Step 1: Build and run agent](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/a365-dev-lifecycle#1-build-and-run-agent) | ||
|
|
||
| Step 1 currently lists: Observability, Notifications, Tooling, Agent Identity — no mention of identifying the human user on each message. Suggested addition (file as a docs PR against `MicrosoftDocs/agent365-docs-pr`): | ||
|
|
||
| **New bullet after "Agent Identity":** | ||
|
|
||
| ```markdown | ||
| - **User Identity** – On every incoming message the A365 platform populates `Activity.From` | ||
| with the user's display name, channel user ID, and Azure AD Object ID. No additional API call | ||
| is required. For extended profile data (email, job title, department), call Microsoft Graph | ||
| `/me` using the access token already acquired for the turn (delegated token with `User.Read` | ||
| scope; use `/users/{AadObjectId}` for app-only tokens). | ||
| ``` | ||
|
|
||
| **New linked page `user-identity.md`** covering: | ||
| - The three-field table (`Activity.From.Id`, `.Name`, `.AadObjectId`) | ||
| - When to use `Activity.From` vs Graph `/me` | ||
| - Code snippet links to the Agent365-Samples repository | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.