Skip to content

feat: Add SharePoint Knowledge Agent - AI Agent for Document-Based Knowledge Management#30

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1777564158-sharepoint-knowledge-agent
Open

feat: Add SharePoint Knowledge Agent - AI Agent for Document-Based Knowledge Management#30
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1777564158-sharepoint-knowledge-agent

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Apr 30, 2026

Summary

Adds a new SharePoint Knowledge Agent microservice built with C# .NET 8 that:

  1. Reads documents from SharePoint via Microsoft Graph API (supports PDF, DOCX, TXT, HTML, CSV, etc.)
  2. Creates Knowledge Articles (KA) by extracting text and chunking documents with configurable overlap
  3. Stores embeddings in Qdrant vector database using Azure OpenAI text-embedding-ada-002 for semantic search
  4. Integrates with Azure Application Insights to monitor for exceptions and alerts
  5. Retrieves relevant Knowledge Articles when issues are detected via semantic similarity search
  6. Sends email notifications with alert details and correlated knowledge articles in rich HTML format

Architecture

  • Clean Architecture: Core (domain models + interfaces) → Infrastructure (service implementations) → API (controllers)
  • Background Jobs: Periodic SharePoint sync and Application Insights alert monitoring
  • Webhook Support: Real-time alert processing via /api/agent/alerts/webhook
  • Containerized: Dockerfile + docker-compose with Qdrant vector database

Key Components

Service Responsibility
SharePointService Microsoft Graph API document access
DocumentProcessor Text extraction + sentence-based chunking
EmbeddingService Azure OpenAI embedding generation
QdrantVectorStoreService Vector storage and similarity search
KnowledgeArticleService Article lifecycle + search orchestration
AppInsightsService Exception monitoring + telemetry
EmailNotificationService Rich HTML alert emails with KA references
KnowledgeAgentOrchestrator End-to-end workflow coordination

API Endpoints

  • POST /api/knowledge/search — Semantic search across knowledge articles
  • GET /api/knowledge/articles — List all knowledge articles
  • POST /api/agent/sync — Trigger manual SharePoint sync
  • POST /api/agent/monitor — Trigger manual monitoring cycle
  • POST /api/agent/alerts/webhook — Application Insights alert webhook
  • GET /health — Health check

Review & Testing Checklist for Human

  • Verify Azure AD App Registration has Sites.Read.All and Files.Read.All Graph API permissions configured
  • Confirm appsettings.json is updated with actual SharePoint site/drive IDs, Azure OpenAI keys, App Insights connection string, and SMTP credentials before deployment
  • Test end-to-end flow: upload a document to SharePoint → verify it gets indexed → trigger an alert → confirm email with relevant KA is sent
  • Validate Qdrant vector database is accessible and the collection is created on startup
  • Review email notification formatting with different alert types/severities

Notes

  • The solution targets .NET 8 as requested; a global.json pins the SDK version
  • Document text extraction for PDF uses a simplified approach; for production, consider integrating PdfPig or iTextSharp
  • The in-memory article store is suitable for development; swap with a database (e.g., PostgreSQL/Cosmos DB) for production
  • Docker Compose includes Qdrant for local development

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/7c2aa9ed4a76462ab9440fee8dce82bd


Open in Devin Review

- C# .NET 8 AI Agent that reads documents from SharePoint via Microsoft Graph API
- Creates Knowledge Articles from document content with text extraction and chunking
- Stores document embeddings in Qdrant vector database for semantic search
- Integrates with Azure Application Insights to detect issues/exceptions
- Retrieves relevant Knowledge Articles when alerts are detected
- Sends email notifications with alert details and related knowledge articles
- Background jobs for periodic SharePoint sync and alert monitoring
- Webhook endpoint for real-time Application Insights alert processing
- Docker and docker-compose support for containerized deployment
- Clean Architecture: Core (domain), Infrastructure (services), API (endpoints)
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown
Contributor Author

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +30 to +40
_ = Task.Run(async () =>
{
try
{
await _orchestrator.SyncSharePointDocumentsAsync(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during manual sync");
}
}, cancellationToken);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🔴 Request-scoped CancellationToken passed to fire-and-forget Task.Run cancels background work

In TriggerSync and TriggerMonitoring, the cancellationToken parameter is bound to the HTTP request's HttpContext.RequestAborted. Since the controller returns Accepted() immediately, the HTTP connection closes shortly after, and ASP.NET Core signals the cancellation token. The background Task.Run receives this token both as its scheduler token (line 40/61) and passes it into the orchestrator methods (line 34/55), causing the sync/monitoring work to be cancelled almost immediately via OperationCanceledException.

Prompt for agents
In AgentController.TriggerSync (and similarly TriggerMonitoring), the CancellationToken from the HTTP request is passed into Task.Run and to the orchestrator methods. This token gets cancelled when the HTTP response is sent and the connection closes, aborting the background work.

The fix should use CancellationToken.None (or an application-lifetime token) for the background work instead of the request-scoped cancellationToken. You could inject IHostApplicationLifetime and use its ApplicationStopping token, or simply use CancellationToken.None.

Alternatively, this fire-and-forget pattern should be replaced with a proper background task queue (e.g., IBackgroundTaskQueue or Channel<T>) to avoid additional issues with using scoped services after the request scope is disposed.

Affected methods: TriggerSync (lines 25-43), TriggerMonitoring (lines 47-64) in AgentController.cs.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

private readonly EmailOptions _emailOptions;
private readonly ILogger<KnowledgeAgentOrchestrator> _logger;

private DateTime _lastSyncTime = DateTime.MinValue;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🔴 Incremental sync never works: _lastSyncTime resets on every scope because orchestrator is scoped

_lastSyncTime is an instance field on KnowledgeAgentOrchestrator (line 19), but the orchestrator is registered as scoped in DependencyInjection.cs:30. Every time SharePointSyncJob runs its loop iteration, it creates a new DI scope (SharePointSyncJob.cs:38), which creates a new KnowledgeAgentOrchestrator instance with _lastSyncTime = DateTime.MinValue. The conditional at line 47 therefore always takes the full sync branch — the incremental sync path (fetching only modified documents) is dead code.

Prompt for agents
The _lastSyncTime instance field in KnowledgeAgentOrchestrator is meant to track the last successful sync time to enable incremental syncing (only fetching modified documents). However, KnowledgeAgentOrchestrator is registered as scoped (DependencyInjection.cs:30), so each scope creates a fresh instance where _lastSyncTime is DateTime.MinValue.

The SharePointSyncJob creates a new scope on every loop iteration (SharePointSyncJob.cs:38), so each sync always does a full document fetch instead of an incremental one.

Possible fixes:
1. Make _lastSyncTime a static field so it persists across instances.
2. Extract sync state into a dedicated singleton service that tracks the last sync time.
3. Change the orchestrator registration to singleton (but this would conflict with its scoped dependencies).

Option 2 is the cleanest approach — create an ISyncStateService singleton that stores the last sync timestamp.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +45 to +49
var response = await QueryAppInsightsAsync(query, cancellationToken);

if (response != null)
{
alerts.AddRange(ParseExceptionResults(response));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 JsonDocument returned from QueryAppInsightsAsync is never disposed, causing resource leak

QueryAppInsightsAsync returns a JsonDocument at AppInsightsService.cs:98, which implements IDisposable and holds pooled memory. In GetRecentAlertsAsync (line 45-49), the returned JsonDocument is used but never disposed. Over repeated monitoring cycles, this leaks memory from the ArrayPool<byte> used internally by JsonDocument.

Suggested change
var response = await QueryAppInsightsAsync(query, cancellationToken);
if (response != null)
{
alerts.AddRange(ParseExceptionResults(response));
var response = await QueryAppInsightsAsync(query, cancellationToken);
if (response != null)
{
using (response)
{
alerts.AddRange(ParseExceptionResults(response));
}
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

0 participants