Overview
This page provides an in-depth look at each microservice in the Pricing Intelligence platform, including implementation details, APIs, and internal architecture.
Harvey API
Purpose
Harvey (Holistic Analysis and Regulation Virtual Expert for You) is the AI agent that orchestrates the entire pricing analysis workflow. It implements the ReAct pattern (Reasoning + Acting) to answer natural language queries about SaaS pricing.
Technology Stack
Language : Python 3.11+
Framework : FastAPI
AI Model : OpenAI GPT (configurable via OPENAI_MODEL env var)
Protocol : MCP (Model Context Protocol)
Key Components
1. Agent Core (harvey_api/src/harvey_api/agent.py)
The HarveyAgent class implements the ReAct loop:
class HarveyAgent :
async def handle_question (
self ,
question : str ,
pricing_urls : Optional[List[ str ]] = None ,
yaml_contents : Optional[List[ str ]] = None ,
) -> Dict[ str , Any]:
# 1. Generate plan using LLM
plan = await self ._generate_plan(
question, pricing_urls, yaml_alias_map
)
# 2. Validate requirements
self ._validate_yaml_requirement(plan, yaml_contents)
# 3. Execute actions via MCP
results, last_payload = await self ._execute_actions(
actions, default_reference, available_urls, objective, yaml_alias_map
)
# 4. Generate natural language answer
answer = await self ._generate_answer(
question, plan, payload, yaml_alias_map
)
return { "plan" : plan, "result" : result_payload, "answer" : answer}
2. Planning System
Harvey uses a two-stage LLM approach :
Planning Phase
The LLM analyzes the user’s question and available context (URLs, uploaded YAMLs) to generate a structured execution plan: {
"actions" : [
"iPricing" ,
{ "name" : "optimal" , "objective" : "minimize" , "filters" : { "features" : [ "SSO" ]}}
],
"requires_uploaded_yaml" : false ,
"use_pricing2yaml_spec" : false
}
Execution Phase
Harvey executes each action sequentially via MCP tools, collecting results
Answer Phase
The LLM synthesizes tool results into a natural language answer, grounded in the actual data
3. Grounding Mechanism
Harvey implements context-aware grounding to prevent hallucinations:
# From harvey_api/src/harvey_api/agent.py:73-102
DEFAULT_PLAN_PROMPT = """You are H.A.R.V.E.Y., an expert AI agent designed to reason about pricing models using the ReAct pattern.
## Context Awareness & Grounding
You have full access to the uploaded Pricing2Yaml (iPricing) files.
**CRITICAL:** You MUST analyze the YAML content to identify the exact keys used for features (`feature.name`) and usage limits (`usageLimit.name`) **before** constructing any filters.
### Per-Context Grounding
When handling multiple pricings (e.g., comparing SaaS A vs. SaaS B), you MUST generate **separate actions** for each pricing source/URL.
* Filters for SaaS A must use **SaaS A's** exact feature/limit names.
* Filters for SaaS B must use **SaaS B's** exact feature/limit names.
"""
This approach ensures that feature names like “SSO” vs “Single Sign-On” are correctly mapped to the actual YAML schema before constructing filters.
4. MCP Client Integration
Harvey communicates with the MCP Server via the official MCP Python SDK:
# harvey_api/src/harvey_api/clients/mcp.py
class MCPWorkflowClient :
async def run_optimal (
self ,
url : str ,
filters : Optional[Dict[ str , Any]],
solver : str ,
objective : str ,
refresh : bool ,
yaml_content : Optional[ str ],
) -> Dict[ str , Any]:
result = await self ._call_tool(
"optimal" ,
{
"pricing_url" : url,
"pricing_yaml" : yaml_content,
"filters" : filters,
"solver" : solver,
"objective" : objective,
"refresh" : refresh,
},
)
return result
API Endpoints
POST /chat
Main conversational endpoint for pricing queries.
curl -X POST http://localhost:8086/chat \
-H "Content-Type: application/json" \
-d '{
"message": "What is the cheapest plan for Buffer with 10 channels?",
"pricing_urls": ["https://buffer.com/pricing"]
}'
Response:
{
"plan" : {
"actions" : [ "iPricing" , { "name" : "optimal" , "objective" : "minimize" }],
"requires_uploaded_yaml" : false ,
"use_pricing2yaml_spec" : false
},
"result" : {
"optimal" : {
"subscription" : { "plan" : "Team" , "addOns" : []},
"cost" : 10.0
}
},
"answer" : "The cheapest plan for Buffer that includes 10 channels is the **Team plan** at $10/month."
}
POST /upload
Upload a Pricing2Yaml file for analysis.
Response:
{
"filename" : "hubspot-pricing.yaml" ,
"relative_path" : "/static/hubspot-pricing.yaml"
}
DELETE /pricing/{filename}
Delete an uploaded YAML file.
curl -X DELETE http://localhost:8086/pricing/hubspot-pricing.yaml
Configuration
Environment Variable Default Description OPENAI_API_KEYrequired OpenAI API key for LLM access OPENAI_MODELgpt-5-nanoOpenAI model to use MCP_SERVER_URLhttp://mcp-server:8085/sseMCP Server endpoint LOG_LEVELINFOLogging level
MCP Server
Purpose
The MCP Server implements the Model Context Protocol specification, exposing standardized tools and resources to AI agents. It acts as a bridge between Harvey and the backend services (A-MINT and Analysis API).
Technology Stack
Language : Python 3.11+
Framework : FastMCP (official MCP library)
Transport : Server-Sent Events (SSE) or stdio
Cache : In-memory or Redis
The server exposes five core tools:
# mcp_server/src/pricing_mcp/mcp_server.py:193-223
@mcp.tool ( name = "iPricing" )
async def ipricing (
pricing_url : Optional[ str ] = None ,
pricing_yaml : Optional[ str ] = None ,
refresh : bool = False ,
) -> Dict[ str , Any]:
"""Return the canonical Pricing2Yaml (iPricing) document."""
result = await container.workflow.get_ipricing(
url = pricing_url,
yaml_content = pricing_yaml,
refresh = refresh,
)
return result
Example Usage:
{
"tool" : "iPricing" ,
"arguments" : {
"pricing_url" : "https://buffer.com/pricing"
}
}
Returns:
{
"request" : { "url" : "https://buffer.com/pricing" , "refresh" : false },
"pricing_yaml" : "saasName: Buffer \n version: 1.0 \n plans: \n - name: Free \n ..." ,
"source" : "amint"
}
2. summary - Pricing Statistics
@mcp.tool ()
async def summary (
pricing_url : Optional[ str ] = None ,
pricing_yaml : Optional[ str ] = None ,
refresh : bool = False ,
) -> Dict[ str , Any]:
"""Return contextual pricing summary data."""
result = await container.workflow.run_summary(
url = pricing_url,
yaml_content = pricing_yaml,
refresh = refresh,
)
return result
Returns:
{
"summary" : {
"numberOfPlans" : 3 ,
"numberOfFeatures" : 15 ,
"numberOfAddOns" : 5 ,
"minPlanPrice" : 10.0 ,
"maxPlanPrice" : 100.0
}
}
3. subscriptions - Configuration Enumeration
@mcp.tool ()
async def subscriptions (
pricing_url : Optional[ str ] = None ,
pricing_yaml : Optional[ str ] = None ,
filters : Optional[Dict[ str , Any]] = None ,
solver : str = "minizinc" ,
refresh : bool = False ,
) -> Dict[ str , Any]:
"""Enumerate subscriptions within the pricing configuration space."""
result = await container.workflow.run_subscriptions(
url = pricing_url or "" ,
filters = filters,
solver = solver,
refresh = refresh,
yaml_content = pricing_yaml,
)
return result
Filter Schema:
interface FilterCriteria {
minPrice ?: number ;
maxPrice ?: number ;
maxSubscriptionSize ?: number ;
features ?: string [];
usageLimits ?: Record < string , number >;
}
4. optimal - Best Configuration
@mcp.tool ()
async def optimal (
pricing_url : Optional[ str ] = None ,
pricing_yaml : Optional[ str ] = None ,
filters : Optional[Dict[ str , Any]] = None ,
solver : str = "minizinc" ,
objective : str = "minimize" ,
refresh : bool = False ,
) -> Dict[ str , Any]:
"""Compute the optimal subscription under the provided constraints."""
result = await container.workflow.run_optimal(
url = pricing_url or "" ,
filters = filters,
solver = solver,
objective = objective,
refresh = refresh,
yaml_content = pricing_yaml,
)
return result
Returns:
{
"result" : {
"optimal" : {
"subscription" : {
"plan" : "Pro" ,
"addOns" : [ "Advanced Analytics" ]
},
"cost" : 49.0 ,
"cardinality" : 1
}
}
}
5. validate - Pricing Model Validation
@mcp.tool ()
async def validate (
pricing_url : Optional[ str ] = None ,
pricing_yaml : Optional[ str ] = None ,
solver : str = "minizinc" ,
refresh : bool = False ,
) -> Dict[ str , Any]:
"""Validate the pricing configuration against the selected solver."""
result = await container.workflow.run_validation(
url = pricing_url,
solver = solver,
refresh = refresh,
yaml_content = pricing_yaml,
)
return result
MCP Resources
The server exposes one static resource:
@mcp.resource ( "resource://pricing/specification" )
async def pricing2yaml_specification () -> str :
"""Expose the Pricing2Yaml specification excerpt as a reusable resource."""
return _PRICING2YAML_SPEC
This allows Harvey to query the Pricing2Yaml schema when validating or explaining syntax.
Workflow Orchestration
The PricingWorkflow class orchestrates calls to A-MINT and Analysis APIs:
# mcp_server/src/pricing_mcp/workflows/pricing.py:15-44
class PricingWorkflow :
async def ensure_pricing_yaml ( self , url : str , refresh : bool = False ) -> str :
# Check cache first
cache_key = f "pricing: { url } "
if not refresh:
cached = await self ._cache.get(cache_key)
if cached:
return cached
# Call A-MINT to extract pricing
yaml_content = await self ._amint.transform(TransformOptions( url = url))
# Cache result
await self ._cache.set(cache_key, yaml_content, ttl_seconds = self ._cache_ttl)
return yaml_content
Analysis API
Purpose
The Analysis API is the core business logic layer that handles pricing validation, optimization, and statistics using constraint programming.
Technology Stack
Language : TypeScript/Node.js 18+
Framework : Express.js
Solver Interface : MiniZinc (via shell execution)
Secondary Solver : Choco (via CSP Service REST API)
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Analysis API │
├─────────────────────────────────────────────────────────────┤
│ Express.js + TypeScript │
│ ├─ REST Endpoints │
│ ├─ Validation middleware │
│ ├─ Multer for file upload │
│ └─ CORS and error handling │
├─────────────────────────────────────────────────────────────┤
│ Business Services │
│ ├─ MinizincService │
│ ├─ JobManager │
│ ├─ PricingValidator │
│ └─ AnalyticsProcessor │
├─────────────────────────────────────────────────────────────┤
│ MiniZinc Engine │
│ ├─ CSP models in .mzn │
│ ├─ YAML → DZN conversion │
│ ├─ Solver execution │
│ └─ Result post-processing │
└─────────────────────────────────────────────────────────────┘
API Endpoints
POST /api/v1/pricing/summary
Returns statistical summary of a pricing model.
Response:
{
"numberOfPlans" : 3 ,
"numberOfFeatures" : 15 ,
"numberOfAddOns" : 5 ,
"minPlanPrice" : 10.0 ,
"maxPlanPrice" : 100.0 ,
"hasFreePlan" : true ,
"currency" : "USD"
}
POST /api/v1/pricing/analysis
Starts an asynchronous analysis job.
curl -X POST http://localhost:8002/api/v1/pricing/analysis \
-F "[email protected] " \
-F "operation=optimal" \
-F "solver=minizinc" \
-F "objective=minimize" \
-F 'filters={"features": ["SSO"], "maxPrice": 50}'
Response:
{
"jobId" : "job_12345" ,
"status" : "PENDING"
}
GET /api/v1/pricing/analysis/{jobId}
Retrieves job status or results.
curl http://localhost:8002/api/v1/pricing/analysis/job_12345
Response (completed):
{
"jobId" : "job_12345" ,
"status" : "COMPLETED" ,
"results" : {
"optimal" : {
"subscription" : { "plan" : "Pro" , "addOns" : []},
"cost" : 29.0
}
}
}
Operations
validate - Model Consistency Check
Verifies mathematical and logical coherence:
Price consistency between plans
Feature coherence between plans
Add-on dependency validation
Detection of impossible configurations
optimal - Best Configuration
Finds optimal configuration based on criteria:
minimize : Cheapest plan satisfying filters
maximize : Most expensive configuration
Returns single best result with cost breakdown
subscriptions - Configuration Enumeration
Lists all valid subscription combinations:
Complete list of plan + add-on configurations
Cost of each configuration
Total cardinality (count)
filter - Filtered Subscriptions
Same as subscriptions but with filter constraints applied
Solver Integration
The Analysis API supports two CSP solvers:
MiniZinc (Default)
Direct integration via shell execution:
// analysis_api/src/services/minizinc.service.ts
const result = await execAsync (
`minizinc --solver chuffed -a ${ modelFile } ${ dataFile } `
);
Choco (via CSP Service)
RESTful integration with Java-based Choco solver:
const response = await axios . post (
` ${ CHOCO_API } /solve` ,
{ yaml: pricingYaml , operation , filters }
);
A-MINT API
Purpose
A-MINT (AI-powered Model INTelligence) extracts structured pricing data from unstructured sources (web pages) and converts them to the Pricing2Yaml format.
Technology Stack
Language : Python 3.11+
Framework : FastAPI
AI Model : OpenAI GPT-4 (vision-capable for screenshots)
Scraping : BeautifulSoup, Playwright
How It Works
Fetch Pricing Page
Downloads HTML content from the provided URL, optionally taking screenshots
Extract Structure
Uses LLM to identify plans, features, prices, and limits from the raw HTML/screenshots
Generate Pricing2Yaml
Converts extracted data into standardized YAML format with schema validation
Validate & Return
Validates YAML against Pricing2Yaml schema before returning
API Endpoint
POST /transform
curl -X POST http://localhost:8001/transform \
-H "Content-Type: application/json" \
-d '{"url": "https://buffer.com/pricing"}'
Response:
saasName : Buffer
version : 1.0
day : 30
year : 365
currency : USD
plans :
- name : Free
price : 0
features :
- channels : { min : 3 , max : 3 }
- analytics : basic
- name : Team
price : 10
features :
- channels : { min : 10 , max : 10 }
- analytics : advanced
- collaboration : true
Configuration
Environment Variable Default Description OPENAI_API_KEYrequired OpenAI API key for extraction ANALYSIS_APIhttp://analysis-api:3000/api/v1Analysis API endpoint LOG_LEVELINFOLogging level
CSP Service (Choco)
Purpose
The CSP Service wraps the Choco constraint solver, providing RESTful access to Java-based constraint satisfaction solving.
Technology Stack
Language : Java 17
Framework : Spring Boot
Solver : Choco Solver 4.x
Key Components
1. YAML to CSP Converter
// csp/src/main/java/org/isa/pricing/csp/parser/Yaml2CSP.java
public class Yaml2CSP {
public Model convertToModel ( String yamlContent ) {
// Parse YAML
PricingModel pricing = parseYaml (yamlContent);
// Create Choco model
Model model = new Model ( "Pricing CSP" );
// Define variables (plan selection, add-ons)
IntVar planVar = model . intVar ( "plan" , 0 , pricing . plans . size () - 1 );
BoolVar [] addonVars = model . boolVarArray ( "addons" , pricing . addOns . size ());
// Add constraints (features, limits, prices)
addFeatureConstraints (model, planVar, addonVars, pricing);
addPriceConstraints (model, planVar, addonVars, pricing);
return model;
}
}
2. CSP Controller
@ RestController
@ RequestMapping ( "/solve" )
public class CSPController {
@ PostMapping
public CSPOutput solve (@ RequestBody SolveRequest request ) {
Model model = yaml2CSP . convertToModel ( request . getYaml ());
// Apply filters
applyFilters (model, request . getFilters ());
// Set objective
if ( request . getObjective (). equals ( "minimize" )) {
model . setObjective ( Model . MINIMIZE , costVar);
}
// Solve
Solver solver = model . getSolver ();
Solution solution = solver . findOptimalSolution (costVar);
return buildOutput (solution);
}
}
API Endpoint
POST /solve
curl -X POST http://localhost:8000/solve \
-H "Content-Type: application/json" \
-d '{
"yaml": "<pricing yaml content>",
"operation": "optimal",
"objective": "minimize",
"filters": {"features": ["SSO"]}
}'
Frontend
Purpose
React-based chat interface for interacting with Harvey.
Technology Stack
Framework : React 18 + Vite
Language : TypeScript
UI Library : Custom components
State Management : React Context
Key Features
Real-time Streaming : SSE connection to Harvey for streaming responses
File Upload : Drag-and-drop YAML file upload
Pricing Visualization : Renders pricing tables and comparisons
Chat History : Maintains conversation context
Next Steps
Architecture Overview Return to architecture overview
Data Flow Explore request/response patterns