Author: Ranjith Kumar Neela
Last Updated: October 2025
- Azure subscription with Owner or Contributor role
- Access to create Azure OpenAI resources (requires application approval)
- Power Platform environment (Premium license)
- Go to Azure Portal
- Click Create a resource
- Search for Azure OpenAI
- Click Create
Configuration:
- Subscription: Select your subscription
- Resource Group: Create new or use existing (e.g.,
rg-document-summarizer) - Region:
East US(recommended for GPT-4o availability) - Name:
openai-doc-summarizer(must be globally unique) - Pricing Tier:
Standard S0
- Click Review + Create → Create
- Wait for deployment (2-3 minutes)
- Navigate to your Azure OpenAI resource
- Click Go to Azure OpenAI Studio (or visit oai.azure.com)
- Click Deployments in left menu
- Click + Create new deployment
Deployment Settings:
- Model:
gpt-4o - Model version:
2024-08-06(or latest available) - Deployment name:
gpt-4o(use this exact name for consistency) - Deployment type:
Standard - Tokens per Minute Rate Limit:
30K(adjust based on needs) - Content filter:
Default
- Click Create
- Wait for deployment (30 seconds)
- In Azure OpenAI Studio, click Deployments
- Click on your
gpt-4odeployment - Note the Endpoint URL (format:
https://{your-resource}.openai.azure.com/) - Go back to Azure Portal → Your OpenAI resource
- Click Keys and Endpoint in left menu
- Copy KEY 1 (save securely)
- Copy Endpoint (same as above)
Save these values:
Endpoint: https://openai-doc-summarizer.openai.azure.com/
Key: [your-api-key-here]
Deployment Name: gpt-4o
API Version: 2024-10-01-preview
- Go to Azure Active Directory in Azure Portal
- Click App registrations → New registration
Registration:
- Name:
PowerAutomate-OpenAI-Connector - Supported account types:
Accounts in this organizational directory only - Redirect URI: Leave blank
- Click Register
- Copy Application (client) ID
- Copy Directory (tenant) ID
- In your app registration, click Certificates & secrets
- Click New client secret
- Description:
Power Automate Access - Expires:
24 months
- Click Add
- Copy the secret VALUE immediately (you can't see it again)
Save these values:
Client ID: [your-client-id]
Tenant ID: [your-tenant-id]
Client Secret: [your-client-secret]
- Go back to your Azure OpenAI resource
- Click Access control (IAM)
- Click + Add → Add role assignment
- Select Cognitive Services OpenAI User role
- Click Next
- Click + Select members
- Search for
PowerAutomate-OpenAI-Connector - Select it → Click Select
- Click Review + assign
- Go to Power Apps
- Select your environment
- Click Tables → New table → Set advanced properties
Table Properties:
- Display name:
Documents - Plural name:
Documents - Primary column:
Document Name - Enable attachments: Yes
Columns to Add:
| Display Name | Name | Data Type | Required | Notes |
|---|---|---|---|---|
| Document Name | cr_documentname | Text | Yes | Primary |
| File Content | cr_filecontent | File | Yes | Max 32 MB |
| File Type | cr_filetype | Choice | Yes | Options: PDF, DOCX |
| Upload Date | cr_uploaddate | Date and Time | Yes | Auto-set |
| Processing Status | cr_processingstatus | Choice | Yes | Options: Pending, Processing, Completed, Error |
| Error Message | cr_errormessage | Text | No | Max 2000 chars |
| Token Count | cr_tokencount | Whole Number | No | - |
| Uploaded By | cr_uploadedby | Lookup | Yes | To System User |
Create Choices:
For File Type:
- Label:
PDF, Value:1 - Label:
DOCX, Value:2
For Processing Status:
- Label:
Pending, Value:1 - Label:
Processing, Value:2 - Label:
Completed, Value:3 - Label:
Error, Value:4
Table Properties:
- Display name:
Summaries - Plural name:
Summaries - Primary column:
Summary ID
Columns to Add:
| Display Name | Name | Data Type | Required | Notes |
|---|---|---|---|---|
| Summary ID | cr_summaryid | Text | Yes | Primary (auto-generated) |
| Document | cr_documentid | Lookup | Yes | To Documents table |
| Summary Text | cr_summarytext | Multiline Text | Yes | Max 100K chars |
| Processing Time | cr_processingtime | Decimal | No | In seconds |
| Tokens Used | cr_tokensused | Whole Number | No | - |
| Estimated Cost | cr_estimatedcost | Currency | No | In USD |
| Created Date | cr_createddate | Date and Time | Yes | Auto-set |
| Quality Rating | cr_qualityrating | Whole Number | No | 1-5 scale |
| Model Version | cr_modelversion | Text | No | e.g., "gpt-4o" |
- Go to Tables → Documents
- Click Properties → Advanced options
- Enable Audit changes
- Set Ownership:
User or team owned - Save
Repeat for Summaries table.
- Go to Power Automate
- Click + Create → Automated cloud flow
- Name:
Process Document Summarization - Trigger: When a row is added, modified or deleted (Dataverse)
- Click Create
Trigger Settings:
- Change type:
Added - Table name:
Documents - Scope:
Organization - Run as:
Flow owner
- Click + New step
- Search for Condition
- Choose:
Processing StatusequalsPending
- Click Add an action
- Search for Update a row (Dataverse)
- Table name:
Documents - Row ID:
Documents(from trigger) - Processing Status:
Processing
- Click Add an action
- Search for Get a row by ID (Dataverse)
- Table name:
Documents - Row ID:
Documents(from trigger)
For DOCX files:
- Add Condition:
File TypeequalsDOCX - In "If yes":
- Add Create file (OneDrive or SharePoint)
- File name:
temp_@{triggerOutputs()?['body/cr_documentname']} - File content:
File Content(from Get row) - Add Convert Word Document to Text (Word Online)
- File: Output from Create file
- Add Delete file (clean up temp file)
For PDF files:
- In "If no":
- Add Analyze document (AI Builder)
- Document:
File Content - Model: Use pre-built document processing
- Add Compose action
- Name:
Calculate Tokens - Formula:
div(add(div(length(outputs('Convert_to_Text')?['body']), 4), div(length(split(outputs('Convert_to_Text')?['body'], ' ')), 0.75)), 1)
- Add Condition
- Choose:
Calculate Tokensis less than or equal to1000
- Add HTTP action
- Method:
POST - URI:
https://openai-doc-summarizer.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-01-preview
- Headers:
{
"Content-Type": "application/json",
"api-key": "[YOUR-API-KEY]"
}- Body:
{
"messages": [
{
"role": "system",
"content": "You are an expert document summarization assistant specializing in business and technical content. Your summaries are concise, accurate, and preserve critical information. You maintain an objective, professional tone and never add information not present in the source document."
},
{
"role": "user",
"content": "Summarize the following document. Focus on: Main objectives and key findings, Important decisions and recommendations, Critical data points and metrics, Action items and next steps. Target length: 200 words.\n\nDocument:\n@{outputs('Convert_to_Text')?['body']}\n\nProvide a well-structured summary:"
}
],
"max_tokens": 800,
"temperature": 0.5,
"top_p": 0.95,
"frequency_penalty": 0.3,
"presence_penalty": 0.1
}In the "If no" branch of Step 16:
-
Add Initialize variable action
- Name:
ChunkSize - Type:
Integer - Value:
1000
- Name:
-
Add Initialize variable action
- Name:
ChunkOverlap - Type:
Integer - Value:
100
- Name:
-
Add Initialize variable action
- Name:
ChunkSummaries - Type:
Array - Value:
[]
- Name:
-
Add Initialize variable action
- Name:
CurrentPosition - Type:
Integer - Value:
0
- Name:
-
Add Initialize variable action
- Name:
DocumentText - Type:
String - Value:
outputs('Convert_to_Text')?['body']
- Name:
- Add Compose action
- Name:
Calculate Chunk Count - Formula:
div(sub(length(variables('DocumentText')), variables('ChunkOverlap')), sub(variables('ChunkSize'), variables('ChunkOverlap')))
- Add Compose action
- Name:
Create Chunks Array - Formula:
range(0, int(outputs('Calculate_Chunk_Count')))
- Add Apply to each action
- Select an output from previous steps:
outputs('Create_Chunks_Array') - Click ... → Settings
- Concurrency Control:
On - Degree of Parallelism:
10
Inside the loop:
A. Calculate Chunk Position
Add Compose action
- Name:
Chunk Start Position - Formula:
mul(item(), sub(variables('ChunkSize'), variables('ChunkOverlap')))
Add Compose action
- Name:
Chunk End Position - Formula:
min(add(outputs('Chunk_Start_Position'), variables('ChunkSize')), length(variables('DocumentText')))
B. Extract Chunk Text
Add Compose action
- Name:
Chunk Text - Formula:
substring(variables('DocumentText'), outputs('Chunk_Start_Position'), sub(outputs('Chunk_End_Position'), outputs('Chunk_Start_Position')))
C. Summarize Chunk
Add HTTP action
- Method:
POST - URI:
https://openai-doc-summarizer.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-01-preview
- Headers:
{
"Content-Type": "application/json",
"api-key": "[YOUR-API-KEY]"
}- Body:
{
"messages": [
{
"role": "system",
"content": "You are an expert at summarizing document sections. Create concise summaries that capture key points while maintaining context."
},
{
"role": "user",
"content": "Summarize this section of a larger document. Focus on main points and key information. Target length: 100 words.\n\nSection @{add(item(), 1)} of @{length(outputs('Create_Chunks_Array'))}:\n@{outputs('Chunk_Text')}\n\nSummary:"
}
],
"max_tokens": 500,
"temperature": 0.5,
"top_p": 0.95,
"frequency_penalty": 0.3,
"presence_penalty": 0.1
}D. Parse Chunk Response
Add Parse JSON action
- Content:
Body(from HTTP) - Schema: (same as Step 18 below)
E. Append to Chunk Summaries
Add Append to array variable action
- Name:
ChunkSummaries - Value:
{
"chunk_number": @{add(item(), 1)},
"summary": "@{body('Parse_JSON_Chunk')?['choices'][0]?['message']?['content']}",
"tokens": @{body('Parse_JSON_Chunk')?['usage']?['total_tokens']}
}After the Apply to each loop:
- Add Compose action
- Name:
Combined Chunk Summaries - Formula:
- Name:
join(
select(variables('ChunkSummaries'), concat('Section ', string(item()?['chunk_number']), ': ', item()?['summary'])),
'\n\n'
)
- Add Condition
- Check if:
length(variables('ChunkSummaries'))is greater than5
If yes (many chunks - need final synthesis):
Add HTTP action
- Method:
POST - URI:
https://openai-doc-summarizer.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-01-preview
- Headers:
{
"Content-Type": "application/json",
"api-key": "[YOUR-API-KEY]"
}- Body:
{
"messages": [
{
"role": "system",
"content": "You are an expert at synthesizing multiple summaries into a cohesive final summary. Create a unified summary that captures the overall document's key points."
},
{
"role": "user",
"content": "The following are summaries of different sections of a document. Create a final, cohesive summary that captures the main points of the entire document. Target length: 300 words.\n\nSection Summaries:\n@{outputs('Combined_Chunk_Summaries')}\n\nFinal Summary:"
}
],
"max_tokens": 800,
"temperature": 0.5,
"top_p": 0.95,
"frequency_penalty": 0.3,
"presence_penalty": 0.1
}If no (few chunks - use combined summaries directly):
Add Compose action
- Name:
Final Summary - Value:
outputs('Combined_Chunk_Summaries')
- Add Compose action
- Name:
Total Tokens Used - Formula:
- Name:
add(
sum(select(variables('ChunkSummaries'), item()?['tokens'])),
if(greater(length(variables('ChunkSummaries')), 5), body('Parse_JSON_Final')?['usage']?['total_tokens'], 0)
)
- Add Compose action
- Name:
Total Cost - Formula:
- Name:
mul(div(outputs('Total_Tokens_Used'), 1000), 0.005)
Note: For chunked processing, parsing happens inside the loop (Step 17B.3.D). This step is only for single-pass processing.
- Add Parse JSON action (in the "If yes" branch from Step 16)
- Content:
Body(from HTTP action in Step 17) - Schema:
{
"type": "object",
"properties": {
"choices": {
"type": "array",
"items": {
"type": "object",
"properties": {
"message": {
"type": "object",
"properties": {
"content": {
"type": "string"
}
}
}
}
}
},
"usage": {
"type": "object",
"properties": {
"prompt_tokens": {
"type": "integer"
},
"completion_tokens": {
"type": "integer"
},
"total_tokens": {
"type": "integer"
}
}
}
}
}In the "If yes" branch (single-pass):
- Add Compose action
- Name:
Calculate Cost - Formula:
add(mul(div(body('Parse_JSON')?['usage']?['prompt_tokens'], 1000), 0.0025), mul(div(body('Parse_JSON')?['usage']?['completion_tokens'], 1000), 0.01))
Note: For chunking, cost is calculated in Step 17B.6.
After both "If yes" and "If no" branches complete, add actions outside the condition:
This ensures both single-pass and chunked processing converge to the same final steps.
-
Add Initialize variable action (before the condition in Step 16)
- Name:
FinalSummary - Type:
String - Value:
""
- Name:
-
Add Initialize variable action (before the condition in Step 16)
- Name:
TotalTokens - Type:
Integer - Value:
0
- Name:
-
Add Initialize variable action (before the condition in Step 16)
- Name:
TotalCost - Type:
Float - Value:
0.0
- Name:
In the "If yes" branch (single-pass), add at the end:
-
Set variable
FinalSummary- Value:
body('Parse_JSON')?['choices'][0]?['message']?['content']
- Value:
-
Set variable
TotalTokens- Value:
body('Parse_JSON')?['usage']?['total_tokens']
- Value:
-
Set variable
TotalCost- Value:
outputs('Calculate_Cost')
- Value:
In the "If no" branch (chunking), add at the end:
-
Set variable
FinalSummary- Value:
if(greater(length(variables('ChunkSummaries')), 5), body('Parse_JSON_Final')?['choices'][0]?['message']?['content'], outputs('Combined_Chunk_Summaries'))
- Value:
-
Set variable
TotalTokens- Value:
outputs('Total_Tokens_Used')
- Value:
-
Set variable
TotalCost- Value:
outputs('Total_Cost')
- Value:
After the condition (outside both branches):
- Add Add a new row (Dataverse)
- Table name:
Summaries - Document:
Documents(from trigger) - Summary Text:
variables('FinalSummary') - Processing Time:
div(sub(ticks(utcNow()), ticks(triggerOutputs()?['body/createdon'])), 10000000) - Tokens Used:
variables('TotalTokens') - Estimated Cost:
variables('TotalCost') - Model Version:
gpt-4o
- Add Update a row (Dataverse)
- Table name:
Documents - Row ID:
Documents(from trigger) - Processing Status:
Completed - Token Count:
variables('TotalTokens')
- Click ... on the main scope → Configure run after
- Add parallel branch for has failed
- Add Update a row (Dataverse)
- Processing Status:
Error - Error Message:
@{body('HTTP')?['error']?['message']}
- Click Save
- Click Test → Manually
- Upload a test document via Power App
- Monitor flow execution
- Go to Power Apps
- Click + Create → Canvas app from blank
- App name:
Document Summarizer - Format:
Tablet - Click Create
- Click Data (left panel)
- Click + Add data
- Search for Dataverse
- Select Documents table
- Select Summaries table
- Click Connect
-
Add Label (Title):
- Text:
"AI Document Summarizer" - Font size:
24 - Position: Top center
- Text:
-
Add File Upload control:
- Insert → Input → Add picture
- Rename to:
FileUpload - Position: Center
-
Add Button (Upload):
- Text:
"Upload Document" - OnSelect:
- Text:
Patch(
Documents,
Defaults(Documents),
{
'Document Name': FileUpload.FileName,
'File Content': FileUpload.Image,
'File Type': If(
EndsWith(FileUpload.FileName, ".pdf"),
'File Type (Documents)'.PDF,
'File Type (Documents)'.DOCX
),
'Upload Date': Now(),
'Processing Status': 'Processing Status (Documents)'.Pending,
'Uploaded By': User()
}
);
Notify("Document uploaded successfully!", NotificationType.Success);
Reset(FileUpload)
-
Add Gallery (Vertical):
- Insert → Gallery → Vertical
- Data source:
Filter(Documents, 'Uploaded By'.'Primary Email' = User().Email) - Fields: Document Name, Upload Date, Processing Status
-
Add Search box:
- Insert → Input → Text input
- Update Gallery Items:
Filter(
Documents,
'Uploaded By'.'Primary Email' = User().Email &&
(IsBlank(SearchBox.Text) || StartsWith('Document Name', SearchBox.Text))
)
-
Add Form (Display):
- Insert → Forms → Display form
- Data source:
Summaries - Item:
LookUp(Summaries, Document = Gallery.Selected)
-
Add Labels:
- Document Name
- Processing Status
- Summary Text (multiline)
- Processing Time
- Estimated Cost
-
Add Rating control:
- Insert → Input → Rating
- Default:
ThisItem.'Quality Rating' - OnChange: Update Summaries table
- Click File → Save
- Click Publish
- Click Publish this version
- Share with users
- Upload a test PDF document
- Verify flow triggers
- Check document status updates
- Verify summary is created
- Check cost calculation
- Go to Power Automate → My flows
- Click on your flow → Analytics
- Monitor:
- Success rate
- Average duration
- Error rate
- Go to Azure Portal → Cost Management
- Filter by Azure OpenAI resource
- Set up budget alerts:
- Budget: $100/month
- Alert at: 80%, 100%
Azure OpenAI:
Resource Name: openai-doc-summarizer
Region: East US
Deployment: gpt-4o
API Version: 2024-10-01-preview
Endpoint: https://openai-doc-summarizer.openai.azure.com/
Dataverse Tables:
- Documents (cr_documents)
- Summaries (cr_summaries)
Power Automate Flow:
- Trigger: Dataverse row added
- Actions: Convert → Summarize → Store
Power Apps:
- Upload Screen
- Gallery Screen
- Detail Screen
Issue: Flow fails with 401 Unauthorized
- Check API key is correct
- Verify service principal has Cognitive Services OpenAI User role
Issue: Flow times out
- Reduce chunk size
- Increase flow timeout settings
- Check document size limits
Issue: High costs
- Implement caching for duplicate documents
- Reduce max_tokens parameter
- Monitor token usage in flow
Issue: Poor summary quality
- Adjust temperature (lower = more focused)
- Refine system prompt
- Increase max_tokens for longer summaries
- ✅ Deploy to production environment
- ✅ Train users on the application
- ✅ Set up monitoring dashboards
- ✅ Configure backup and disaster recovery
- ✅ Document operational procedures
Support: contact@rkneela.com
Repository: https://github.com/rkneela0912/AI-Powered-Document-Summarizer