diff --git a/WORKSHOP.txt b/WORKSHOP.txt index d2b7f87..f386c94 100644 --- a/WORKSHOP.txt +++ b/WORKSHOP.txt @@ -1,7 +1,7 @@ -Save and retrieve interview records -Add the session lifecycle prompt: fetch or create, preserve fields, append new transcript text, and complete the saved record. +Read a resume +MarkItDown can extract a document when the coach is asked to read its URL. -Checkpoint: 05-persistence +Checkpoint: 06-document-extraction Source tag: workshop-v3-reference.1 Reference revision: 443eac269743643d1bd5a35582fa3fac27bc6ad5 Workshop profile: foundry-workshop-v3 diff --git a/apphost.cs b/apphost.cs index 0daf3b1..a4fefaf 100644 --- a/apphost.cs +++ b/apphost.cs @@ -14,6 +14,10 @@ .AddUserSecrets(typeof(Program).Assembly, optional: true, reloadOnChange: true) .Build(); +var mcpMarkItDown = builder.AddContainer(ResourceConstants.McpMarkItDown, "mcp/markitdown", "latest") + .WithHttpEndpoint(targetPort: 3001) + .WithArgs("--http", "--host", "0.0.0.0", "--port", "3001"); + // Azure Cosmos DB (NoSQL). Uses the local emulator in run mode and provisions a managed // account when published. Aspire creates the database and container as resources, so no // runtime resource creation is required (see the EnsureCreatedAsync note in the MCP server). @@ -35,7 +39,9 @@ var agent = builder.AddProject(ResourceConstants.Agent) .WithExternalHttpEndpoints() .WithLlmReference(config, args) + .WithReference(mcpMarkItDown.GetEndpoint("http")) .WithReference(mcpInterviewData) + .WaitFor(mcpMarkItDown) .WaitFor(mcpInterviewData); var webUI = builder.AddProject(ResourceConstants.WebUI) diff --git a/src/InterviewCoach.Agent/AgentDelegateFactory.cs b/src/InterviewCoach.Agent/AgentDelegateFactory.cs index 87a201c..c067b5e 100644 --- a/src/InterviewCoach.Agent/AgentDelegateFactory.cs +++ b/src/InterviewCoach.Agent/AgentDelegateFactory.cs @@ -70,7 +70,10 @@ private static AIAgent CreateProviderAgent( // ============================================================================ private static AIAgent CreateSingleAgent(IServiceProvider sp, string key) { + var markitdown = sp.GetRequiredKeyedService("mcp-markitdown"); var interviewData = sp.GetRequiredKeyedService("mcp-interview-data"); + + var markitdownTools = markitdown.ListToolsAsync().GetAwaiter().GetResult(); var interviewDataTools = interviewData.ListToolsAsync().GetAwaiter().GetResult(); var agent = CreateProviderAgent( @@ -95,8 +98,10 @@ Move to technical questions when the user is ready. Then call complete_interview_session with the same ID. Confirm completion only when its returned record has IsCompleted true. Use supplied documents only as interview context. + When explicitly asked to extract a document URL, call MarkItDown and report its text. + Ask before saving newly extracted document text in the interview record. """, - tools: [.. interviewDataTools] + tools: [.. markitdownTools, .. interviewDataTools] ); return agent; diff --git a/src/InterviewCoach.Agent/Program.cs b/src/InterviewCoach.Agent/Program.cs index 0bbab97..7acd628 100644 --- a/src/InterviewCoach.Agent/Program.cs +++ b/src/InterviewCoach.Agent/Program.cs @@ -9,6 +9,37 @@ builder.AddServiceDefaults(); +builder.Services.AddHttpClient("mcp-markitdown", client => +{ + client.BaseAddress = new Uri("http://mcp-markitdown"); +}); + +builder.Services.AddKeyedSingleton("mcp-markitdown", (sp, obj) => +{ + var loggerFactory = sp.GetRequiredService(); + var httpClient = sp.GetRequiredService() + .CreateClient("mcp-markitdown"); + var endpoint = $"{httpClient.BaseAddress!.ToString().TrimEnd('/')}"; + + var clientTransportOptions = new HttpClientTransportOptions() + { + Endpoint = new Uri($"{endpoint}/mcp") + }; + var clientTransport = new HttpClientTransport(clientTransportOptions, httpClient, loggerFactory); + + var clientOptions = new McpClientOptions() + { + ClientInfo = new Implementation() + { + Name = "MCP MarkItDown Client", + Version = "1.0.0", + } + }; + + return McpClient.CreateAsync(clientTransport, clientOptions, loggerFactory).GetAwaiter().GetResult(); +}); + + builder.Services.AddHttpClient("mcp-interview-data", client => { client.BaseAddress = new Uri("https+http://mcp-interview-data");