Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions workshop/lab02-multi-agent/PersonalCareerCopilot/.azdignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
agent.manifest.yaml
agent.yaml
.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,4 @@ __pycache__
*.pyo
*.pyd
.Python
.foundry
.vscode
.env
README.md
9 changes: 0 additions & 9 deletions workshop/lab02-multi-agent/PersonalCareerCopilot/.env.example

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"hostedAgentDeployOptions": {
"deploymentMethod": "container"
},
"projectId": "<your-foundry-project-id>",
"containerRegistry":"<your-container-registry-name>.azurecr.io"
"projectId": "<your_project_id>",
"containerRegistry": "<your_container_registry>"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "Debug Local Agent Server",
"name": "Debug Local Agent HTTP Server",
"type": "debugpy",
"request": "attach",
"connect": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
"type": "aitk",
"command": "debug-check-prerequisites",
"args": {
"portOccupancy": [5679, 8088],
"pyPackageInstalled": ["agent-framework", "debugpy"]
"portOccupancy": [
5679,
8088
],
"pyPackageInstalled": ["debugpy"]
}
Comment thread
ShivamGoyal03 marked this conversation as resolved.
},
{
"label": "Run Agent/Workflow HTTP Server",
"label": "Run Agent HTTP Server",
"type": "shell",
"command": {
"value": "${command:python.interpreterPath}",
Expand All @@ -22,7 +25,9 @@
"options": {
"cwd": "${workspaceFolder}"
},
"dependsOn": ["Validate prerequisites"],
"dependsOn": [
"Validate prerequisites"
],
"problemMatcher": {
"pattern": [
{
Expand All @@ -35,7 +40,7 @@
"background": {
"activeOnStart": true,
"beginsPattern": ".*",
"endsPattern": "AgentServerHost started|Running on|Started server process"
"endsPattern": "Application startup complete|running on|Running on|Started server process|AgentServerHost started"
}
}
},
Expand All @@ -46,7 +51,9 @@
"presentation": {
"reveal": "never"
},
"dependsOn": ["Run Agent/Workflow HTTP Server"]
"dependsOn": [
"Run Agent HTTP Server"
]
},
{
"label": "Terminate All Tasks",
Expand Down
50 changes: 24 additions & 26 deletions workshop/lab02-multi-agent/PersonalCareerCopilot/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PersonalCareerCopilot - Resume → Job Fit Evaluator

A multi-agent workflow that evaluates how well a resume matches a job description, then generates a personalized learning roadmap to close the gaps.
A workflow-first multi-agent app that evaluates how well a resume matches a job description, then generates a personalized learning roadmap to close the gaps.

---

Expand All @@ -16,15 +16,12 @@ A multi-agent workflow that evaluates how well a resume matches a job descriptio
## Workflow

```mermaid
flowchart TD
flowchart LR
UserInput["User Input: Resume + Job Description"] --> ResumeParser
UserInput --> JobDescriptionAgent
ResumeParser --> MatchingAgent
JobDescriptionAgent --> MatchingAgent
MatchingAgent --> GapAnalyzerMCP["Gap Analyzer &
Microsoft Learn Docs MCP"]
GapAnalyzerMCP --> FinalOutput["Final Output:
Fit Score + Roadmap"]
ResumeParser -- "parsed resume + JD relay" --> JobDescriptionAgent
JobDescriptionAgent -- "JD requirements + resume relay" --> MatchingAgent
MatchingAgent -- "fit report + gaps" --> GapAnalyzerMCP["Gap Analyzer +\nMicrosoft Learn MCP"]
GapAnalyzerMCP --> FinalOutput["Final Output:\nFit Score + Roadmap"]
```

---
Expand All @@ -33,6 +30,8 @@ flowchart TD

### 1. Set up environment

This folder is the reference implementation for the workflow-based Lab 02 scaffold. Its `main.py` uses the existing prompt blocks plus `WorkflowBuilder` to wire the four agents together.

```powershell
cd workshop\lab02-multi-agent\PersonalCareerCopilot
python -m venv .venv
Expand All @@ -43,23 +42,23 @@ pip install -r requirements.txt

### 2. Configure credentials

Copy the example env file and fill in your Foundry project details:
Create a `.env` file in this folder:

```powershell
cp .env.example .env
copy .env .env.bak 2>$null; echo $null > .env
```

Edit `.env`:

```env
AZURE_AI_PROJECT_ENDPOINT=https://<your-account>.services.ai.azure.com/api/projects/<your-project>
MODEL_DEPLOYMENT_NAME=gpt-4.1-mini
FOUNDRY_PROJECT_ENDPOINT=https://<your-account>.services.ai.azure.com/api/projects/<your-project>
AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4.1-mini
```

| Value | Where to find it |
|-------|------------------|
| `AZURE_AI_PROJECT_ENDPOINT` | Microsoft Foundry sidebar in VS Code → right-click your project → **Copy Project Endpoint** |
| `MODEL_DEPLOYMENT_NAME` | Foundry sidebar → expand project → **Models + endpoints** → deployment name |
| `FOUNDRY_PROJECT_ENDPOINT` | Foundry Toolkit sidebar → right-click your project → **Copy Project Endpoint** |
| `AZURE_AI_MODEL_DEPLOYMENT_NAME` | Foundry sidebar → expand project → **Models + endpoints** → deployment name |

### 3. Run locally

Expand Down Expand Up @@ -97,15 +96,14 @@ Certifications: Azure Solutions Architect Expert preferred.

### 5. Deploy to Foundry

`Ctrl+Shift+P` → **Microsoft Foundry: Deploy Hosted Agent** → select your project → confirm.
`Ctrl+Shift+P` → **Foundry Toolkit: Deploy Hosted Agent** → select your project → confirm.

---

## Project structure

```
PersonalCareerCopilot/
├── .env.example ← Template for environment variables
├── .env ← Your credentials (git-ignored)
├── agent.yaml ← Hosted agent definition (name, resources, env vars)
├── Dockerfile ← Container image for Foundry deployment
Expand All @@ -120,33 +118,33 @@ PersonalCareerCopilot/
Defines the hosted agent for Foundry Agent Service:
- `kind: hosted` - runs as a managed container
- `protocols` - `responses` protocol with `version: 1.0.0`, exposing the `/responses` HTTP endpoint
- `environment_variables` - `AZURE_AI_PROJECT_ENDPOINT` and `MODEL_DEPLOYMENT_NAME` are injected at deploy time
- `environment_variables` - `AZURE_AI_MODEL_DEPLOYMENT_NAME` is declared here; `FOUNDRY_PROJECT_ENDPOINT` is injected automatically at deploy time

### `main.py`

Contains:
- **Agent instructions** - four `*_INSTRUCTIONS` constants, one per agent
- **MCP tool** - `search_microsoft_learn_for_plan()` calls `https://learn.microsoft.com/api/mcp` via Streamable HTTP
- **Agent creation** - four `Agent()` + `AgentExecutor()` instances sharing one `FoundryChatClient`
- **Workflow graph** - `WorkflowBuilder` wires agents with fan-out/fan-in and sequential patterns
- **Workflow graph** - `WorkflowBuilder` wires agents as a sequential pipeline: ResumeParser → JD Agent → MatchingAgent → GapAnalyzer
- **Server startup** - `ResponsesHostServer` runs on port 8088

### `requirements.txt`

| Package | Version | Purpose |
|---------|---------|---------|
| `agent-framework` | `>=1.1.0` | Core runtime: `Agent`, `AgentExecutor`, `WorkflowBuilder`, `@tool` |
| `agent-framework-foundry-hosting` | latest | `ResponsesHostServer` + Foundry hosting integration |
| `debugpy` | latest | Python debugging (F5 in VS Code) |
| `mcp` | latest | MCP client for GapAnalyzer (`mcp.client.streamable_http`) |
| Package | Purpose |
|---------|----------|
| `agent-framework-foundry` | Core runtime: `Agent`, `AgentExecutor`, `WorkflowBuilder`, `@tool`, `FoundryChatClient` |
| `agent-framework-foundry-hosting` | `ResponsesHostServer` + Foundry hosting integration |
| `mcp<2,>=1.24.0` | MCP client for GapAnalyzer (`streamable_http_client`) |
| `debugpy` | Python debugging (F5 in VS Code) |

---

## Troubleshooting

| Issue | Fix |
|-------|-----|
| `KeyError: 'AZURE_AI_PROJECT_ENDPOINT'` | Create `.env` with `AZURE_AI_PROJECT_ENDPOINT` and `MODEL_DEPLOYMENT_NAME` |
| `KeyError: 'FOUNDRY_PROJECT_ENDPOINT'` | Create `.env` with `FOUNDRY_PROJECT_ENDPOINT` and `AZURE_AI_MODEL_DEPLOYMENT_NAME` |
Comment thread
ShivamGoyal03 marked this conversation as resolved.
Outdated
| `ModuleNotFoundError: No module named 'agent_framework'` | Activate venv and run `pip install -r requirements.txt` |
| No Microsoft Learn URLs in output | Check internet connectivity to `https://learn.microsoft.com/api/mcp` |
| Only 1 gap card (truncated) | Verify `GAP_ANALYZER_INSTRUCTIONS` includes the `CRITICAL:` block |
Expand Down
36 changes: 14 additions & 22 deletions workshop/lab02-multi-agent/PersonalCareerCopilot/agent.yaml
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml

name: PersonalCareerCopilot
kind: hosted
name: resume-job-fit-evaluator
protocols:
- protocol: responses
version: 1.0.0
environment_variables:
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
description: >
A multi-agent workflow that evaluates resume-to-job fit. Four agents collaborate:
Resume Parser, Job Description Agent, Matching Agent, and Gap Analyzer.
Upload a resume and job description to get a fit score, missing skills, and a
personalized learning roadmap.
An Agent Framework workflow hosted by Foundry.
metadata:
authors:
- ShivamGoyal03
tags:
- Agent Framework
- AI Agent Hosting
- Azure AI AgentServer
- Microsoft Agent Framework
- Multi-Agent Workflow
- Resume Evaluator
- Job Fit
- Learning Roadmap
protocols:
- protocol: responses
version: 1.0.0
- Responses Protocol
- Streaming
resources:
cpu: '0.25'
memory: 0.5Gi
environment_variables:
- name: AZURE_AI_PROJECT_ENDPOINT
value: ${AZURE_AI_PROJECT_ENDPOINT}
- name: MODEL_DEPLOYMENT_NAME
value: ${MODEL_DEPLOYMENT_NAME}
cpu: '0.5'
memory: 1.0Gi
dockerfile_path: Dockerfile
60 changes: 41 additions & 19 deletions workshop/lab02-multi-agent/PersonalCareerCopilot/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,51 @@
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamable_http_client

load_dotenv(override=True)
# Load environment variables from .env file
load_dotenv()
Comment thread
ShivamGoyal03 marked this conversation as resolved.
Outdated

PROJECT_ENDPOINT = os.environ["AZURE_AI_PROJECT_ENDPOINT"]
MODEL_DEPLOYMENT_NAME = os.getenv("MODEL_DEPLOYMENT_NAME", "gpt-4.1-mini")
MICROSOFT_LEARN_MCP_ENDPOINT = os.getenv(
"MICROSOFT_LEARN_MCP_ENDPOINT", "https://learn.microsoft.com/api/mcp"
)

RESUME_PARSER_INSTRUCTIONS = """\
You are the Resume Parser.
Extract resume text into a compact, structured profile for downstream matching.
You are the Resume Parser and Content Router.
Your input contains a resume and usually a job description - BOTH must be preserved.

Output exactly these sections:
TASK 1 - Parse the resume into a structured candidate profile.
TASK 2 - Copy the job description verbatim into the pass-through section below.

Output EXACTLY these two labeled sections:

[PARSED RESUME]
1) Candidate Profile
2) Technical Skills (grouped categories)
3) Soft Skills
4) Certifications & Awards
5) Domain Experience
6) Notable Achievements

[JOB DESCRIPTION PASS-THROUGH]
<Copy the complete job description here exactly as given. Do NOT summarize or paraphrase.
If no job description is present, write only: No job description provided.>

Rules:
- Use only explicit or strongly implied evidence.
- Use only explicit or strongly implied evidence for the resume sections.
- Do not invent skills, titles, or experience.
- Keep concise bullets; no long paragraphs.
- If input is not a resume, return a short warning and request resume text.
- Keep resume bullets concise; no long paragraphs.
- The [JOB DESCRIPTION PASS-THROUGH] section MUST contain the FULL, UNMODIFIED JD text.
Omitting or truncating it breaks the downstream Job Description Agent.
"""

JOB_DESCRIPTION_INSTRUCTIONS = """\
You are the Job Description Analyst.
Extract a structured requirement profile from a JD.
You are the Job Description Analyst and Resume Relay.
Your input is the Resume Parser output. It contains two clearly labeled sections:
- [PARSED RESUME] - copy this verbatim to [PARSED RESUME PASS-THROUGH] in your output.
- [JOB DESCRIPTION PASS-THROUGH] - extract the structured JD requirements from here only.

Output exactly these sections:
Output EXACTLY these two labeled sections:

[JD REQUIREMENTS]
1) Role Overview
2) Required Skills
3) Preferred Skills
Expand All @@ -52,16 +65,26 @@
7) Domain / Industry
8) Key Responsibilities

[PARSED RESUME PASS-THROUGH]
<Copy the complete [PARSED RESUME] section here exactly as given. Do NOT summarize or paraphrase.>

Rules:
- Extract requirements ONLY from [JOB DESCRIPTION PASS-THROUGH] - do not use [PARSED RESUME] content.
- Copy [PARSED RESUME] verbatim - the Matching Agent depends on it downstream.
- Keep required vs preferred clearly separated.
- Only use what the JD states; do not invent hidden requirements.
- Flag vague requirements briefly.
- If input is not a JD, return a short warning and request JD text.
- If the JD pass-through says \"No job description provided\", write in [JD REQUIREMENTS]:
\"No JD available - cannot extract requirements. Ask the user to re-submit with a job description.\"
"""

MATCHING_AGENT_INSTRUCTIONS = """\
You are the Matching Agent.
Compare parsed resume output vs JD output and produce an evidence-based fit report.
Your input is the Job Description Analyst output. It contains two clearly labeled sections:
- [JD REQUIREMENTS] - the structured job requirements; use this as the target profile.
- [PARSED RESUME PASS-THROUGH] - the candidate's parsed profile; use this as the candidate profile.

Compare [PARSED RESUME PASS-THROUGH] vs [JD REQUIREMENTS] and produce an evidence-based fit report.

Scoring (100 total):
- Required Skills 40
Expand Down Expand Up @@ -95,8 +118,8 @@
- Prefer Microsoft Learn for free resources.

CRITICAL: You MUST produce a SEPARATE detailed gap card for EVERY skill listed in
the Missing Skills and Certification Gaps sections of the matching report. Do NOT
skip or combine gaps. Do NOT summarize multiple gaps into one card.
the Missing Skills and Certification Gaps sections of the matching report. Do NOT
skip or combine gaps. Do NOT summarize multiple gaps into one card.

Output format:
1) Personalized Learning Roadmap for [Role Title]
Expand Down Expand Up @@ -166,8 +189,8 @@ async def search_microsoft_learn_for_plan(

def main():
client = FoundryChatClient(
project_endpoint=PROJECT_ENDPOINT,
model=MODEL_DEPLOYMENT_NAME,
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)

Expand All @@ -192,7 +215,6 @@ def main():
output_executors=[gap_executor],
)
.add_edge(resume_executor, jd_executor)
.add_edge(resume_executor, matching_executor)
.add_edge(jd_executor, matching_executor)
.add_edge(matching_executor, gap_executor)
.build()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
agent-framework>=1.1.0
# Use the narrow Foundry subpackages to keep dependencies light.
agent-framework-foundry
agent-framework-foundry-hosting
mcp<2,>=1.24.0

# debugpy enables local debugging of this agent with the Foundry Toolkit VS Code extension.
debugpy
mcp