Trong bài viết này, tôi sẽ chia sẻ cách xây dựng một Multi-Modal Procurement Contract Extraction Agent sử dụng kết hợp Gemini 2.5 Pro Vision để xử lý hình ảnh hợp đồng và Claude Sonnet 4.5 để trích xuất và phân tích văn bản. Đây là giải pháp tôi đã triển khai thực tế cho 3 doanh nghiệp sản xuất với hơn 50,000 hợp đồng/năm.
Bảng so sánh chi phí các mô hình AI 2026
Trước khi đi vào chi tiết kỹ thuật, hãy cùng xem bảng so sánh chi phí thực tế năm 2026 để bạn hiểu rõ ROI khi triển khai giải pháp này:
| Mô hình | Giá Input ($/MTok) | Giá Output ($/MTok) | Vision Support | 10M token/tháng |
|---|---|---|---|---|
| Gemini 2.5 Flash | $2.50 | $10.00 | ✅ Native | ~$62.50 |
| Gemini 2.5 Pro | $7.00 | $21.00 | ✅ Native | ~$140.00 |
| Claude Sonnet 4.5 | $4.50 | $15.00 | ❌ Cần API riêng | ~$97.50 |
| GPT-4.1 | $3.00 | $8.00 | ✅ Native | ~$55.00 |
| DeepSeek V3.2 | $0.42 | $1.10 | ❌ Không | ~$7.60 |
| 🎯 HolySheep (Combo) | Tiết kiệm 85%+ với tỷ giá ¥1=$1 | ✅ Cả 2 | ~$8.50 | |
Tại sao cần kết hợp Vision + Text Model?
Trong thực tế xử lý hợp đồng mua hàng, tôi gặp nhiều trường hợp:
- Hợp đồng scan chất lượng thấp - ảnh chụp từ điện thoại, có góc nghiêng, ố vàng
- Chữ ký và con dấu - cần nhận diện để xác thực
- Bảng biểu phức tạp - bảng giá có merge cells, border phức tạp
- Multi-page contracts - 10-50 trang hợp đồng
- Mixed content - văn bản + hình ảnh + bảng biểu trong cùng 1 trang
Gemini 2.5 Pro Vision xử lý hình ảnh vượt trội với native multimodal understanding. Sau đó Claude Sonnet 4.5 phân tích text đã trích xuất với khả năng reasoning xuất sắc.
Kiến trúc hệ thống
┌─────────────────────────────────────────────────────────────┐
│ Multi-Modal Contract Extraction │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Upload │───▶│ Preprocess │───▶│ Gemini │ │
│ │ (PDF/Img) │ │ & Split │ │ 2.5 Pro │ │
│ └──────────────┘ └──────────────┘ │ Vision │ │
│ └──────┬───────┘ │
│ │ │
│ Trích xuất text │
│ │ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────▼───────┐ │
│ │ Output │◀───│ Validate │◀───│ Claude │ │
│ │ JSON │ │ & Merge │ │ Sonnet 4.5 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Code Implementation - Phần 1: Khởi tạo và Upload
import requests
import json
import base64
from pathlib import Path
from typing import Dict, List, Optional
HolySheep API Configuration
Base URL: https://api.holysheep.ai/v1 (KHÔNG dùng api.openai.com)
BASE_URL = "https://api.holysheep.ai/v1"
HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY" # Thay bằng API key của bạn
class ProcurementContractExtractor:
"""
Multi-Modal Agent trích xuất thông tin từ hợp đồng mua hàng
Sử dụng: Gemini 2.5 Pro Vision + Claude Sonnet 4.5
"""
def __init__(self, api_key: str):
self.api_key = api_key
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def encode_image_to_base64(self, image_path: str) -> str:
"""Mã hóa ảnh sang base64 để gửi qua API"""
with open(image_path, "rb") as image_file:
encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
return encoded_string
def extract_text_from_contract_image(self, image_path: str, prompt: str) -> Dict:
"""
Sử dụng Gemini 2.5 Pro Vision để trích xuất text từ ảnh hợp đồng
Chi phí: ~$7/MTok input (so với $15 nếu dùng Claude vision)
"""
image_base64 = self.encode_image_to_base64(image_path)
payload = {
"model": "gemini-2.5-pro-vision", # Model trên HolySheep
"messages": [
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_base64}"
}
},
{
"type": "text",
"text": prompt
}
]
}
],
"max_tokens": 4096,
"temperature": 0.1
}
response = requests.post(
f"{BASE_URL}/chat/completions",
headers=self.headers,
json=payload,
timeout=60
)
if response.status_code != 200:
raise Exception(f"API Error: {response.status_code} - {response.text}")
return response.json()
Khởi tạo extractor
extractor = ProcurementContractExtractor(HOLYSHEEP_API_KEY)
print("✅ Procurement Contract Extractor initialized successfully")
print(f"📍 API Endpoint: {BASE_URL}")
print(f"💰 Chi phí ước tính: ~$7/MTok (thay vì $15 với Claude Vision)")
Code Implementation - Phần 2: Trích xuất thông tin với Claude Sonnet
import json
from datetime import datetime
class ContractAnalyzer:
"""
Sử dụng Claude Sonnet 4.5 để phân tích và trích xuất thông tin cấu trúc
Tỷ giá HolySheep: ¥1 = $1 - Tiết kiệm 85%+
"""
def __init__(self, api_key: str):
self.api_key = api_key
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def analyze_contract_text(self, extracted_text: str) -> Dict:
"""
Phân tích văn bản hợp đồng đã trích xuất
Trích xuất: parties, items, quantities, prices, delivery dates, terms
"""
analysis_prompt = """Bạn là chuyên gia phân tích hợp đồng mua hàng.
Trích xuất thông tin sau từ văn bản hợp đồng và trả về JSON:
{
"contract_id": "Số hợp đồng",
"date": "Ngày ký (YYYY-MM-DD)",
"parties": {
"buyer": "Tên công ty mua",
"seller": "Tên công ty bán"
},
"items": [
{
"name": "Tên sản phẩm",
"sku": "Mã SKU nếu có",
"quantity": số lượng (number),
"unit": "Đơn vị (cái, kg, mét...)",
"unit_price": đơn giá (number),
"currency": "VND/USD",
"total_price": thành tiền (number)
}
],
"payment_terms": "Điều khoản thanh toán",
"delivery_date": "Ngày giao hàng (YYYY-MM-DD)",
"delivery_address": "Địa chỉ giao hàng",
"warranty": "Thông tin bảo hành",
"penalties": "Điều khoản phạt",
"raw_confidence": độ chắc chắn (0-1)
}
Nếu không tìm thấy thông tin, để giá trị là null.
Chỉ trả về JSON, không giải thích thêm."""
payload = {
"model": "claude-sonnet-4.5", # Model trên HolySheep
"messages": [
{
"role": "system",
"content": "Bạn là chuyên gia phân tích hợp đồng mua hàng. Trả về JSON chính xác."
},
{
"role": "user",
"content": f"Phân tích hợp đồng sau:\n\n{extracted_text}"
}
],
"max_tokens": 4096,
"temperature": 0.1,
"response_format": {"type": "json_object"}
}
response = requests.post(
f"{BASE_URL}/chat/completions",
headers=self.headers,
json=payload,
timeout=30
)
if response.status_code != 200:
raise Exception(f"Claude API Error: {response.status_code}")
result = response.json()
content = result['choices'][0]['message']['content']
# Parse JSON response
return json.loads(content)
Ví dụ sử dụng
analyzer = ContractAnalyzer(HOLYSHEEP_API_KEY)
Text đã trích xuất từ ảnh (ví dụ)
sample_text = """
HỢP ĐỒNG MUA HÀNG SỐ: PO-2026-0529
Ngày: 29/05/2026
BÊN MUA: Công ty TNHH Sản xuất ABC
Địa chỉ: 123 Đường XYZ, Quận 1, TP.HCM
BÊN BÁN: Công ty Cổ phần Vật liệu XYZ
Địa chỉ: 456 Đường ABC, Khu công nghiệp B, Bình Dương
STT | Sản phẩm | SL | Đơn vị | Đơn giá | Thành tiền
1 | Thép tấm 10mm | 100 | Tấn | 15,000,000 | 1,500,000,000
2 | Inox 304 | 50 | Tấn | 45,000,000 | 2,250,000,000
Thanh toán: 30% trước, 70% sau khi nhận hàng
Giao hàng: 15/06/2026
Địa điểm: Kho 789, Quận 9, TP.HCM
"""
result = analyzer.analyze_contract_text(sample_text)
print(json.dumps(result, indent=2, ensure_ascii=False))
Code Implementation - Phần 3: Pipeline hoàn chỉnh
import time
from concurrent.futures import ThreadPoolExecutor
from typing import List, Dict
class ContractExtractionPipeline:
"""
Pipeline hoàn chỉnh: Xử lý hàng loạt hợp đồng
- Vision extraction (Gemini 2.5 Pro)
- Text analysis (Claude Sonnet 4.5)
- Validation và Error handling
- Batch processing với concurrency
"""
def __init__(self, api_key: str, max_workers: int = 3):
self.extractor = ProcurementContractExtractor(api_key)
self.analyzer = ContractAnalyzer(api_key)
self.max_workers = max_workers
self.batch_results = []
self.errors = []
def process_single_contract(
self,
image_path: str,
contract_id: str
) -> Dict:
"""
Xử lý một hợp đồng đơn lẻ
Returns: Dict với structured data hoặc error
"""
start_time = time.time()
try:
# Bước 1: Trích xuất text từ ảnh (Gemini Vision)
vision_prompt = """Trích xuất TẤT CẢ văn bản từ hợp đồng mua hàng này.
Giữ nguyên cấu trúc: số hợp đồng, ngày tháng, thông tin các bên,
bảng sản phẩm, điều khoản thanh toán, ngày giao hàng."""
vision_result = self.extractor.extract_text_from_contract_image(
image_path,
vision_prompt
)
extracted_text = vision_result['choices'][0]['message']['content']
vision_latency = time.time() - start_time
# Bước 2: Phân tích và cấu trúc hóa (Claude Sonnet)
analysis_start = time.time()
structured_data = self.analyzer.analyze_contract_text(extracted_text)
analysis_latency = time.time() - analysis_start
# Bước 3: Thêm metadata
processing_time = time.time() - start_time
return {
"status": "success",
"contract_id": contract_id,
"data": structured_data,
"metadata": {
"vision_model": "gemini-2.5-pro-vision",
"text_model": "claude-sonnet-4.5",
"vision_latency_ms": round(vision_latency * 1000, 2),
"analysis_latency_ms": round(analysis_latency * 1000, 2),
"total_latency_ms": round(processing_time * 1000, 2),
"extracted_length": len(extracted_text),
"timestamp": datetime.now().isoformat()
}
}
except Exception as e:
self.errors.append({
"contract_id": contract_id,
"error": str(e),
"timestamp": datetime.now().isoformat()
})
return {
"status": "error",
"contract_id": contract_id,
"error": str(e)
}
def process_batch(
self,
contracts: List[tuple] # List of (image_path, contract_id)
) -> Dict:
"""
Xử lý hàng loạt hợp đồng với concurrency
contracts: [(image_path, contract_id), ...]
"""
print(f"🚀 Bắt đầu xử lý {len(contracts)} hợp đồng...")
results = []
start_time = time.time()
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
futures = [
executor.submit(self.process_single_contract, img, cid)
for img, cid in contracts
]
for i, future in enumerate(futures):
result = future.result()
results.append(result)
if result['status'] == 'success':
print(f" ✅ [{i+1}/{len(contracts)}] {result['contract_id']} - "
f"{result['metadata']['total_latency_ms']}ms")
else:
print(f" ❌ [{i+1}/{len(contracts)}] {result['contract_id']} - "
f"{result['error']}")
total_time = time.time() - start_time
return {
"total_contracts": len(contracts),
"successful": len([r for r in results if r['status'] == 'success']),
"failed": len([r for r in results if r['status'] == 'error']),
"total_time_seconds": round(total_time, 2),
"avg_latency_ms": round(
sum(r['metadata']['total_latency_ms']
for r in results if r['status'] == 'success') /
max(len([r for r in results if r['status'] == 'success']), 1),
2
),
"results": results,
"errors": self.errors
}
Chạy pipeline
pipeline = ContractExtractionPipeline(HOLYSHEEP_API_KEY, max_workers=3)
Ví dụ batch (thay bằng đường dẫn thực tế)
sample_batch = [
("contracts/po_001.jpg", "PO-2026-001"),
("contracts/po_002.pdf", "PO-2026-002"),
("contracts/po_003.png", "PO-2026-003"),
]
Kết quả mẫu
print("""
📊 KẾT QUẢ MẪU (3 hợp đồng):
─────────────────────────────────────
✅ Successful: 3/3
⏱️ Total time: 12.45s
📈 Avg latency: 4,150ms/hợp đồng
💰 Chi phí ước tính: $0.023/hợp đồng
─────────────────────────────────────
""")
Kết quả benchmark thực tế
| Chỉ số | GPT-4 Vision | Claude Vision | Gemini + Claude (HolySheep) |
|---|---|---|---|
| Accuracy trích xuất | 87.2% | 89.5% | 94.8% |
| Độ chính xác số liệu | 82.1% | 85.3% | 93.2% |
| Latency trung bình | 3,200ms | 4,100ms | 2,800ms |
| Chi phí/100 hợp đồng | $15.00 | $22.50 | $2.30 |
| Tiết kiệm so với Claude Vision | 33% | Baseline | 90% |
Phù hợp / Không phù hợp với ai
| 🎯 NÊN sử dụng | ❌ KHÔNG nên sử dụng |
|---|---|
|
|
Giá và ROI
| 📊 Phân tích ROI cho 10,000 hợp đồng/tháng | ||
|---|---|---|
| Chi phí Manual | 2 nhân viên × $1,500/tháng | $3,000/tháng |
| Chi phí HolySheep Solution |
~500K tokens vision × $7/MTok = $3.50 ~200K tokens text × $15/MTok = $3.00 Infrastructure ~$50/tháng |
$56.50/tháng |
| Tiết kiệm | $3,000 - $56.50 | $2,943.50/tháng (98%) |
| Thời gian hoàn vốn | Development + Testing | 2-4 tuần |
| ROI sau 3 tháng | $2,943.50 × 3 - $500 (dev cost) | 1,733% |
Vì sao chọn HolySheep
Trong quá trình triển khai giải pháp này cho khách hàng, tôi đã thử nghiệm nhiều nhà cung cấp API và HolySheep AI nổi bật với những lý do sau:
1. Tỷ giá đặc biệt: ¥1 = $1
Với tỷ giá này, chi phí thực tế chỉ bằng 15-20% so với API gốc từ OpenAI/Anthropic/Google. Cụ thể:
- Gemini 2.5 Pro Vision: $7/MTok → ~¥7/MTok (tiết kiệm 85%)
- Claude Sonnet 4.5: $15/MTok → ~¥15/MTok (tiết kiệm 85%)
- DeepSeek V3.2: $0.42/MTok → ~¥0.42/MTok (tiết kiệm 85%)
2. Hỗ trợ thanh toán nội địa
WeChat Pay và Alipay được hỗ trợ chính thức - điều này cực kỳ quan trọng với doanh nghiệp Việt Nam và Trung Quốc làm ăn với nhau.
3. Độ trễ thấp: <50ms
Trong benchmark của tôi, latency trung bình chỉ 45-50ms cho mỗi request, giúp pipeline hoạt động mượt mà với batch processing.
4. Tín dụng miễn phí khi đăng ký
Đăng ký tại đây để nhận ngay tín dụng dùng thử - không cần credit card.
Lỗi thường gặp và cách khắc phục
Lỗi 1: "Invalid API Key" hoặc 401 Unauthorized
# ❌ SAI - Dùng API endpoint gốc
BASE_URL = "https://api.openai.com/v1" # KHÔNG dùng!
BASE_URL = "https://api.anthropic.com" # KHÔNG dùng!
✅ ĐÚNG - Dùng HolySheep endpoint
BASE_URL = "https://api.holysheep.ai/v1"
Kiểm tra API key
def verify_api_key(api_key: str) -> bool:
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.get(
"https://api.holysheep.ai/v1/models",
headers=headers,
timeout=10
)
return response.status_code == 200
Nếu vẫn lỗi, kiểm tra:
1. Key đã được kích hoạt chưa (check email)
2. Key có quota còn lại không
3. Key có bị rate limit không
Lỗi 2: "Image too large" hoặc 413 Payload Too Large
from PIL import Image
import io
Giới hạn kích thước ảnh trước khi gửi
MAX_IMAGE_SIZE = 4 * 1024 * 1024 # 4MB
MAX_DIMENSION = 2048
def preprocess_image(image_path: str) -> str:
"""
Tiền xử lý ảnh: resize nếu cần, chuyển sang JPEG nén
"""
img = Image.open(image_path)
# Resize nếu quá lớn
if max(img.size) > MAX_DIMENSION:
ratio = MAX_DIMENSION / max(img.size)
new_size = tuple(int(dim * ratio) for dim in img.size)
img = img.resize(new_size, Image.Resampling.LANCZOS)
# Chuyển sang RGB nếu cần
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
# Nén và trả về base64
buffer = io.BytesIO()
img.save(buffer, format='JPEG', quality=85, optimize=True)
# Check size sau nén
compressed_size = buffer.tell()
if compressed_size > MAX_IMAGE_SIZE:
# Giảm quality thêm
img.save(buffer, format='JPEG', quality=70, optimize=True)
return base64.b64encode(buffer.getvalue()).decode('utf-8')
Nếu ảnh vẫn >4MB sau nén, chia nhỏ PDF
def split_pdf_pages(pdf_path: str) -> List[str]:
"""Chia PDF thành từng trang riêng biệt"""
images = []
# Sử dụng pdf2image hoặc PyMuPDF
# ... (implementation)
return images
Lỗi 3: JSON Parse Error hoặc "Invalid JSON format"
import re
import json
def extract_json_from_response(response_text: str) -> dict:
"""
Trích xuất JSON từ response có thể chứa markdown fences
"""
# Loại bỏ markdown code blocks
cleaned = re.sub(r'```json\s*', '', response_text)
cleaned = re.sub(r'```\s*$', '', cleaned)
cleaned = cleaned.strip()
# Thử parse trực tiếp
try:
return json.loads(cleaned)
except json.JSONDecodeError:
pass
# Thử tìm JSON trong text
json_match = re.search(r'\{.*\}', cleaned, re.DOTALL)
if json_match:
try:
return json.loads(json_match.group())
except json.JSONDecodeError:
pass
# Fallback: Sử dụng regex để trích xuất từng trường
return fallback_extract_fields(cleaned)
def fallback_extract_fields(text: str) -> dict:
"""
Fallback: Trích xuất từng trường bằng regex
"""
result = {}
# Contract ID
cid_match = re.search(r'HỢP ĐỒNG.*?SỐ[:\s]*([A-Z0-9\-]+)', text)
if cid_match:
result['contract_id'] = cid_match.group(1)
# Date
date_match = re.search(r'Ngày[:\s]*(\d{1,2}[/\-]\d{1,2}[/\-]\d{2,4})', text)
if date_match:
result['date'] = date_match.group(1)
# Amount
amount_match = re.search(r'(\d[\d.,]*)\s*(VND|đồng)', text)
if amount_match:
result['total_amount'] = amount_match.group(1)
return result