Skip to content

Commit b5e517f

Browse files
authored
Merge pull request #1 from docusign/init-upload
init-upload
2 parents 65fcc4c + 325714f commit b5e517f

27 files changed

Lines changed: 5033 additions & 458 deletions

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ env/
1717

1818
# Tokens and credentials
1919
.mcp_tokens.json
20-
docusign_private_key.pem
2120
*.pem
2221

2322
# IDE

README.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Price Adjustment Agent
2+
3+
An intelligent multi-agent system that automates contract price adjustments using AI orchestration, Docusign MCP integration, and real-time inflation data from the Bureau of Labor Statistics.
4+
5+
## 🎯 Overview
6+
7+
The Price Adjustment Agent automatically:
8+
- **Identifies** contracts expiring within 30 days via Docusign MCP server
9+
- **Calculates** inflation-based price adjustments using real-time CPI data
10+
- **Triggers** Docusign Maestro workflows for document generation
11+
- **Tracks** all activities in a comprehensive SQLite database
12+
- **Visualizes** progress through a real-time web dashboard
13+
14+
## ✨ Key Features
15+
16+
### 🎨 Real-time Web Dashboard
17+
- **Live Workflow Pipeline**: Visual 4-step progress tracking
18+
- **One-Click Triggers**: Start workflows instantly with user ID
19+
- **Real-time Updates**: WebSocket-powered live status updates
20+
- **Smart Status Display**: Differentiates between processed, skipped, and failed items
21+
- **Toast Notifications**: Instant feedback on all actions
22+
- **Responsive Design**: Works on desktop and mobile
23+
24+
### 🔗 Streamlit MCP Explorer
25+
- **Direct MCP Tool Access**: Browse and call all Docusign MCP tools interactively
26+
- **Agreement Management**: View agreements, envelopes, templates
27+
- **Maestro Workflows**: Trigger and monitor workflows
28+
- **Account Info**: View account details, users, branding, billing
29+
30+
### 🤖 AI-Powered Orchestration
31+
- **Master Orchestrator**: GPT-4o coordinates the entire workflow via Microsoft Foundry
32+
- **Docusign Navigator**: Finds and filters expiring agreements via MCP
33+
- **BLS Agent**: Fetches CPI data and calculates adjustments
34+
- **Maestro Agent**: Triggers Docusign workflows for amendments via MCP
35+
36+
## 🏗️ Architecture
37+
38+
![Architecture Diagram](architecture.jpg)
39+
40+
41+
## 🛠️ Tech Stack
42+
43+
- **Backend**: Flask (Python 3.8+) with Flask-SocketIO for real-time updates
44+
- **AI**: Microsoft Foundry with GPT-4o
45+
- **Docusign Integration**: MCP server via OAuth + JSON-RPC 2.0
46+
- **Database**: SQLite with singleton pattern
47+
- **APIs**: Docusign MCP, BLS.gov, Docusign Maestro
48+
- **Frontend**: HTML/CSS/JavaScript with WebSocket support, Streamlit explorer
49+
- **Deployment**: Azure App Service with Gunicorn + Eventlet
50+
51+
## 📦 Installation
52+
53+
### Prerequisites
54+
55+
- Python 3.8 or higher
56+
- Microsoft Foundry project (required for AI orchestration)
57+
- Docusign Developer Account with MCP access
58+
- BLS API Key (optional — works without, limited to 25 requests/day)
59+
60+
## 🎨 Using the Dashboard
61+
62+
### Main Interface
63+
64+
1. **Workflow Pipeline** — Visual 4-step process:
65+
- Step 1: Contract Extraction (Docusign Navigator)
66+
- Step 2: Contract Filtering (Identifies unprocessed agreements)
67+
- Step 3: Price Calculation (BLS Agent with CPI data)
68+
- Step 4: Amendment & Workflow (Maestro Agent)
69+
70+
2. **Start Workflow Panel**:
71+
- Enter User ID (e.g., "ashutosh" or any identifier)
72+
- Click "Start Manual Workflow"
73+
- Watch real-time progress via WebSocket
74+
75+
3. **Status Summary**:
76+
- Current workflow status
77+
- Contracts found and processed
78+
- Total adjustment amounts
79+
80+
### Workflow States
81+
82+
- **✅ Green**: Step completed successfully
83+
- **🟡 Orange**: Step currently processing
84+
- **🔘 Gray**: Step skipped (no action needed)
85+
- **❌ Red**: Step failed
86+
87+
## 🔄 Workflow Process
88+
89+
1. **Discovery**: Finds agreements expiring in next 30 days via Docusign MCP `getAllAgreements`
90+
2. **Filtering**: Identifies which agreements haven't been processed yet
91+
3. **Calculation**: Fetches CPI data from BLS.gov and calculates inflation-based adjustments
92+
4. **Execution**: Triggers Docusign Maestro workflows via MCP `triggerWorkflow`
93+
94+
## ☁️ Azure Deployment
95+
96+
The app deploys to Azure App Service. Configuration is in `.azure/config`:
97+
98+
- **App name**: `docusign-price-adjustment`
99+
- **Resource group**: `rg-price-adjustment`
100+
- **Region**: Sweden Central
101+
- **SKU**: F1 (Free tier)
102+
103+
### Startup
104+
105+
Azure uses `startup.sh` which runs:
106+
```bash
107+
gunicorn --worker-class eventlet -w 1 \
108+
--bind=0.0.0.0:${PORT:-8000} \
109+
--timeout 600 \
110+
app:app
111+
```
112+
113+
Set your environment variables in Azure App Service > Configuration > Application Settings.
114+
`WEBSITE_HOSTNAME` is set automatically by Azure and used to build the OAuth callback URL.
115+
116+
## 🔍 Troubleshooting
117+
118+
### Common Issues
119+
120+
1. **"No agreements found"**: Normal if no contracts expire in next 30 days
121+
2. **"All already processed"**: System correctly skips processed agreements
122+
3. **Connection issues**: Check `MICROSOFT_FOUNDRY_ENDPOINT` is set and Azure identity is configured
123+
4. **Docusign errors**: Visit `/docusign/oauth/start` to re-authenticate
124+
5. **Token expired**: The MCP client auto-refreshes tokens; if it fails, re-authenticate via OAuth
125+
126+
127+

__init__.py

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
"""
2+
Microsoft Foundry Multi-Agent Setup - Azure OpenAI Assistants API
3+
4+
Four assistants created via the openai SDK (AsyncAzureOpenAI).
5+
Each assistant has its own instructions and tools.
6+
The App Service coordinates the four assistants sequentially.
7+
"""
8+
9+
import asyncio
10+
import os
11+
import json
12+
from dotenv import load_dotenv
13+
import logging
14+
15+
logger = logging.getLogger(__name__)
16+
17+
# Load environment variables
18+
load_dotenv()
19+
20+
# ── Azure OpenAI setup (lazy) ──────────────────────────────────────────
21+
foundry_endpoint = os.getenv("MICROSOFT_FOUNDRY_ENDPOINT", "")
22+
foundry_model = os.getenv("MICROSOFT_FOUNDRY_MODEL", "gpt-4o")
23+
foundry_api_version = os.getenv("MICROSOFT_FOUNDRY_API_VERSION", "2025-04-01-preview")
24+
25+
if foundry_endpoint:
26+
foundry_endpoint = foundry_endpoint.rstrip('/')
27+
28+
# Lazy-init: don't create the credential/client at import time so the
29+
# Flask app can start even when Azure credentials aren't available yet.
30+
_credential = None
31+
_token_provider = None
32+
_foundry_client = None
33+
34+
def _get_credential():
35+
global _credential
36+
if _credential is None:
37+
from azure.identity import DefaultAzureCredential
38+
_credential = DefaultAzureCredential()
39+
return _credential
40+
41+
def _get_token_provider():
42+
global _token_provider
43+
if _token_provider is None:
44+
from azure.identity import get_bearer_token_provider
45+
_token_provider = get_bearer_token_provider(
46+
_get_credential(), "https://cognitiveservices.azure.com/.default"
47+
)
48+
return _token_provider
49+
50+
def _get_foundry_client():
51+
"""Return the AsyncAzureOpenAI client for the Assistants API."""
52+
global _foundry_client
53+
if _foundry_client is None:
54+
from openai import AsyncAzureOpenAI
55+
_foundry_client = AsyncAzureOpenAI(
56+
azure_endpoint=foundry_endpoint,
57+
azure_ad_token_provider=_get_token_provider(),
58+
api_version=foundry_api_version,
59+
)
60+
logger.info(f"🔧 Azure OpenAI Assistants API configured: endpoint={foundry_endpoint}, model={foundry_model}")
61+
return _foundry_client
62+
63+
64+
# ── Tool schemas per agent ──────────────────────────────────────────────
65+
66+
NAVIGATOR_TOOLS = [
67+
{
68+
"type": "function",
69+
"function": {
70+
"name": "navigate_docusign_documents",
71+
"description": "Find DocuSign agreements expiring in the next 30 days and store them in database",
72+
"parameters": {
73+
"type": "object",
74+
"properties": {
75+
"request_id": {"type": "string", "description": "The request ID for tracking"}
76+
},
77+
"required": ["request_id"]
78+
}
79+
}
80+
},
81+
{
82+
"type": "function",
83+
"function": {
84+
"name": "get_unprocessed_agreements",
85+
"description": "Get agreements from database that haven't been processed yet for price adjustment",
86+
"parameters": {
87+
"type": "object",
88+
"properties": {
89+
"request_id": {"type": "string", "description": "The request ID to filter agreements for"}
90+
},
91+
"required": ["request_id"]
92+
}
93+
}
94+
}
95+
]
96+
97+
BLS_TOOLS = [
98+
{
99+
"type": "function",
100+
"function": {
101+
"name": "fetch_cpi_data",
102+
"description": "Fetch Consumer Price Index data for specific agreement price adjustment calculation",
103+
"parameters": {
104+
"type": "object",
105+
"properties": {
106+
"input_data": {
107+
"type": "string",
108+
"description": "JSON string with agreement details: request_id, user_id, agreement_id, effective_date, expiration_date, contract_value, currency"
109+
}
110+
},
111+
"required": ["input_data"]
112+
}
113+
}
114+
}
115+
]
116+
117+
MAESTRO_TOOLS = [
118+
{
119+
"type": "function",
120+
"function": {
121+
"name": "trigger_maestro_workflow",
122+
"description": "Trigger DocuSign Maestro workflow for agreement processing with updated contract values",
123+
"parameters": {
124+
"type": "object",
125+
"properties": {
126+
"input_data": {
127+
"type": "string",
128+
"description": "JSON string with BLS results: request_id, user_id, agreement_id, original_value, adjusted_value, inflation_rate, etc."
129+
}
130+
},
131+
"required": ["input_data"]
132+
}
133+
}
134+
}
135+
]
136+
137+
138+
# ── Agent instructions ──────────────────────────────────────────────────
139+
140+
NAVIGATOR_INSTRUCTIONS = """You are the DocuSign Navigator Agent. Your job is to find contracts expiring soon.
141+
142+
When given a request_id:
143+
1. Call navigate_docusign_documents with the request_id to find agreements expiring in the next 30 days
144+
2. Call get_unprocessed_agreements with the same request_id to get agreements needing price adjustment
145+
146+
Return the exact JSON response from each tool. Log your status as 'DOCUSIGN_COMPLETE' when finished."""
147+
148+
BLS_INSTRUCTIONS = """You are the BLS (Bureau of Labor Statistics) Agent. Your job is to calculate inflation-based price adjustments.
149+
150+
You will receive a JSON string with agreement details. Call fetch_cpi_data with that data to retrieve CPI data and calculate the adjusted contract value.
151+
152+
Return the exact JSON response. Log your status as 'BLS_COMPLETE' when finished."""
153+
154+
MAESTRO_INSTRUCTIONS = """You are the Maestro Agent responsible for triggering DocuSign Maestro workflows.
155+
156+
You will receive a JSON string with price adjustment results. Call trigger_maestro_workflow with that data to initiate the approval workflow.
157+
158+
Return the exact JSON response with instance_id. Log your status as 'MAESTRO_COMPLETE' when finished."""
159+
160+
ORCHESTRATOR_INSTRUCTIONS = """You are the Master Orchestrator Agent for the Price Adjustment workflow.
161+
162+
You coordinate three specialized agents. The App Service will present you with the output from each agent in sequence. Your job is to:
163+
164+
1. Review the Navigator Agent's output and determine which agreements need processing
165+
2. For each unprocessed agreement, prepare the input JSON for the BLS Agent
166+
3. Review the BLS Agent's output and prepare the input for the Maestro Agent
167+
4. Review Maestro's output and compile the final status
168+
169+
RULES:
170+
- If Navigator finds no expiring agreements, report SUCCESS with "nothing to process"
171+
- If no unprocessed agreements remain, report SUCCESS with "all already processed"
172+
- If BLS fails for one agreement, continue with others
173+
- If Maestro fails for one agreement, continue with others
174+
175+
Return final JSON:
176+
{
177+
"status": "SUCCESS/PARTIAL/FAILED",
178+
"summary": "brief explanation",
179+
"agreements_found": number,
180+
"agreements_processed_successfully": number,
181+
"agreements_failed": number,
182+
"maestro_workflows_triggered": number,
183+
"total_value_adjustments": "total dollar amount"
184+
}"""
185+
186+
187+
# ── Create assistants in Azure OpenAI ──────────────────────────────────
188+
189+
_agent_ids = {}
190+
191+
async def _create_agent(name, instructions, tools):
192+
"""Create a single assistant in Azure OpenAI via the Assistants API."""
193+
try:
194+
client = _get_foundry_client()
195+
assistant = await client.beta.assistants.create(
196+
model=foundry_model,
197+
name=name,
198+
instructions=instructions,
199+
tools=tools,
200+
)
201+
logger.info(f"🤖 Created assistant '{name}': {assistant.id}")
202+
return assistant.id
203+
except Exception as e:
204+
logger.error(f"❌ Failed to create assistant '{name}': {e}")
205+
raise
206+
207+
208+
async def get_agent_ids():
209+
"""Create all four assistants if they don't exist yet. Returns dict of name→id."""
210+
global _agent_ids
211+
if _agent_ids:
212+
return _agent_ids
213+
214+
_agent_ids["navigator"] = await _create_agent(
215+
"docusign_navigator_agent", NAVIGATOR_INSTRUCTIONS, NAVIGATOR_TOOLS
216+
)
217+
_agent_ids["bls"] = await _create_agent(
218+
"bls_agent", BLS_INSTRUCTIONS, BLS_TOOLS
219+
)
220+
_agent_ids["maestro"] = await _create_agent(
221+
"maestro_agent", MAESTRO_INSTRUCTIONS, MAESTRO_TOOLS
222+
)
223+
_agent_ids["orchestrator"] = await _create_agent(
224+
"master_orchestrator_agent", ORCHESTRATOR_INSTRUCTIONS, tools=[]
225+
)
226+
227+
logger.info(f"🤖 All 4 assistants created: {list(_agent_ids.keys())}")
228+
return _agent_ids
229+
230+
231+
def get_foundry_client():
232+
"""Return the configured AsyncAzureOpenAI client."""
233+
return _get_foundry_client()

0 commit comments

Comments
 (0)