ورودی PDF — PDF Inputs
آشا پردازش PDF را از طریق endpoint /v1/chat/completions پشتیبانی میکند. PDF را میتوان بهصورت URL مستقیم یا Data URL با کدگذاری base64 در آرایهٔ پیامها و از طریق نوع محتوای file فرستاد؛ این قابلیت روی همهٔ مدلهای آشا کار میکند.
- پشتیبانی از URL: PDFهای عمومی در دسترس را بدون دانلود یا کدکردن، مستقیم بفرستید.
- پشتیبانی از base64: برای فایلهای محلی یا اسناد خصوصی که عمومی نیستند لازم است.
پیکربندی پلاگین
برای پیکربندی پردازش PDF از پارامتر plugins در
درخواست خود استفاده کنید. آشا چند موتور پردازش PDF با قابلیتها و قیمتهای متفاوت ارائه میدهد:
{
"plugins": [
{
"id": "file-parser",
"pdf": {
"engine": "cloudflare-ai"
}
}
]
}
قیمتگذاری
آشا چند موتور پردازش PDF ارائه میدهد:
mistral-ocr— بهترین گزینه برای اسناد اسکنشده یا PDFهای دارای تصویر (۲ دلار به ازای هر ۱٬۰۰۰ صفحه).cloudflare-ai— تبدیل PDF به markdown با Cloudflare Workers AI (رایگان).native— فقط برای مدلهایی که بهصورت بومی ورودی فایل را پشتیبانی میکنند (بهصورت توکن ورودی صورتحساب میشود).
pdf-text منسوخ شده و بهصورت خودکار به
cloudflare-ai هدایت میشود. درخواستهای موجودی که
از pdf-text استفاده میکنند همچنان کار میکنند.
اگر موتور را صریحاً مشخص نکنید، آشا ابتدا از قابلیت بومیِ پردازش فایلِ خودِ مدل استفاده
میکند و اگر در دسترس نبود، از موتور
mistral-ocr استفاده میشود.
محدودیت تصاویر OCR
وقتی موتور mistral-ocr تصاویر را از PDF استخراج
میکند، آشا حداکثر ۸ تصویر به ازای هر PDF از Mistral از طریق پارامتر
image_limit درخواست میکند و بیش از ۸ تصویر به ازای
هر درخواست به مدل پاییندست ارسال نمیکند. تصاویر اضافه حذف میشوند اما همهٔ متن استخراجشده
بهطور کامل حفظ میشود.
این سقف وجود دارد چون محدودیت تعداد تصویر در هر prompt میان ارائهدهندهها تفاوت زیادی دارد؛ برخی درخواستهای بیش از ۸ تصویر را یکجا رد میکنند و حتی ارائهدهندههایی با سقف بالاتر وقتی یک PDF بلند به ازای هر صفحه یک تصویر تولید میکند، اغلب با خطای طول context شکست میخورند. سقف ۸ تصویر، درخواستها را در محدودهٔ همهٔ ارائهدهندههای پشتیبانیشده نگه میدارد.
اگر مدل پاییندست اصلاً ورودی تصویر نمیپذیرد، تصاویر استخراجشدهٔ OCR کاملاً حذف و فقط متن پارسشده ارسال میشود.
استفاده از URLهای PDF
برای PDFهای عمومی، میتوانید URL را مستقیم بفرستید بدون اینکه لازم باشد فایل را دانلود و کد کنید:
import os
import requests
url = "https://app.asha-ai.ir/v1/chat/completions"
headers = {
"Authorization": f"Bearer {os.environ['ASHA_API_KEY']}",
"Content-Type": "application/json",
}
messages = [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What are the main points in this document?",
},
{
"type": "file",
"file": {
"filename": "document.pdf",
"file_data": "https://bitcoin.org/bitcoin.pdf",
},
},
],
}
]
# Optional: Configure the PDF processing engine
plugins = [
{
"id": "file-parser",
"pdf": {
"engine": "mistral-ocr",
},
}
]
payload = {
"model": "~anthropic/claude-sonnet-4",
"messages": messages,
"plugins": plugins,
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
const response = await fetch(
"https://app.asha-ai.ir/v1/chat/completions",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ASHA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "~anthropic/claude-sonnet-4",
messages: [
{
role: "user",
content: [
{
type: "text",
text: "What are the main points in this document?",
},
{
type: "file",
file: {
filename: "document.pdf",
file_data: "https://bitcoin.org/bitcoin.pdf",
},
},
],
},
],
// Optional: Configure the PDF processing engine
plugins: [
{
id: "file-parser",
pdf: {
engine: "mistral-ocr",
},
},
],
}),
}
);
const data = await response.json();
console.log(data);
curl https://app.asha-ai.ir/v1/chat/completions
-H "Content-Type: application/json"
-H "Authorization: Bearer $ASHA_API_KEY"
-d '{
"model": "~anthropic/claude-sonnet-4",
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "What are the main points in this document?" },
{ "type": "file", "file": { "filename": "document.pdf", "file_data": "https://bitcoin.org/bitcoin.pdf" } }
]
}
],
"plugins": [
{ "id": "file-parser", "pdf": { "engine": "mistral-ocr" } }
]
}'
استفاده از PDF با کدگذاری Base64
برای فایلهای PDF محلی یا وقتی باید محتوای PDF را مستقیم بفرستید، میتوانید فایل را base64 کد کنید:
import base64
import os
import requests
def encode_pdf_to_base64(pdf_path):
with open(pdf_path, "rb") as pdf_file:
return base64.b64encode(pdf_file.read()).decode("utf-8")
url = "https://app.asha-ai.ir/v1/chat/completions"
headers = {
"Authorization": f"Bearer {os.environ['ASHA_API_KEY']}",
"Content-Type": "application/json",
}
# Read and encode the PDF
pdf_path = "path/to/your/document.pdf"
base64_pdf = encode_pdf_to_base64(pdf_path)
data_url = f"data:application/pdf;base64,{base64_pdf}"
messages = [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What are the main points in this document?",
},
{
"type": "file",
"file": {
"filename": "document.pdf",
"file_data": data_url,
},
},
],
}
]
# Optional: Configure the PDF processing engine.
# PDF parsing works even if the plugin is not explicitly set.
plugins = [
{
"id": "file-parser",
"pdf": {
"engine": "cloudflare-ai",
},
}
]
payload = {
"model": "~google/gemma-3-27b-it",
"messages": messages,
"plugins": plugins,
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
import fs from "fs/promises";
async function encodePDFToBase64(pdfPath: string): Promise<string> {
const pdfBuffer = await fs.readFile(pdfPath);
const base64PDF = pdfBuffer.toString("base64");
return `data:application/pdf;base64,${base64PDF}`;
}
// Read and encode the PDF
const pdfPath = "path/to/your/document.pdf";
const base64PDF = await encodePDFToBase64(pdfPath);
const response = await fetch(
"https://app.asha-ai.ir/v1/chat/completions",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ASHA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "~google/gemma-3-27b-it",
messages: [
{
role: "user",
content: [
{
type: "text",
text: "What are the main points in this document?",
},
{
type: "file",
file: {
filename: "document.pdf",
file_data: base64PDF,
},
},
],
},
],
// Optional: Configure the PDF processing engine.
// PDF parsing works even if the plugin is not explicitly set.
plugins: [
{
id: "file-parser",
pdf: {
engine: "cloudflare-ai",
},
},
],
}),
}
);
const data = await response.json();
console.log(data);
# Base64-encode your PDF file
PDF_BASE64=$(base64 < document.pdf | tr -d 'n')
DATA_URL="data:application/pdf;base64,$PDF_BASE64"
curl https://app.asha-ai.ir/v1/chat/completions
-H "Content-Type: application/json"
-H "Authorization: Bearer $ASHA_API_KEY"
-d '{
"model": "~google/gemma-3-27b-it",
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "What are the main points in this document?" },
{ "type": "file", "file": { "filename": "document.pdf", "file_data": "'"$DATA_URL"'" } }
]
}
],
"plugins": [
{ "id": "file-parser", "pdf": { "engine": "cloudflare-ai" } }
]
}'
حذف هزینهٔ پارس کردن
وقتی PDF را به API میفرستید، پاسخ ممکن است در پیامِ دستیار شامل file annotation باشد. این annotationها حاوی اطلاعات ساختیافته دربارهٔ سند PDF پارسشده هستند. با فرستادن این annotationها در درخواستهای بعدی میتوانید از پارسِ دوبارهٔ همان سند اجتناب کنید؛ این کار هم زمان پردازش و هم هزینه را کم میکند.
در اینجا نحوهٔ استفادهٔ مجدد از file annotationها آمده است:
import base64
import os
import requests
def encode_pdf_to_base64(pdf_path):
with open(pdf_path, "rb") as pdf_file:
return base64.b64encode(pdf_file.read()).decode("utf-8")
url = "https://app.asha-ai.ir/v1/chat/completions"
headers = {
"Authorization": f"Bearer {os.environ['ASHA_API_KEY']}",
"Content-Type": "application/json",
}
# Read and encode the PDF
pdf_path = "path/to/your/document.pdf"
base64_pdf = encode_pdf_to_base64(pdf_path)
data_url = f"data:application/pdf;base64,{base64_pdf}"
# Initial request with the PDF
messages = [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What are the main points in this document?",
},
{
"type": "file",
"file": {
"filename": "document.pdf",
"file_data": data_url,
},
},
],
}
]
payload = {
"model": "~google/gemma-3-27b-it",
"messages": messages,
}
response = requests.post(url, headers=headers, json=payload)
response_data = response.json()
# Store the annotations from the response
file_annotations = None
if response_data.get("choices") and len(response_data["choices"]) > 0:
if "annotations" in response_data["choices"][0]["message"]:
file_annotations = response_data["choices"][0]["message"]["annotations"]
# Follow-up request using the annotations (without sending the PDF again)
if file_annotations:
follow_up_messages = [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What are the main points in this document?",
},
{
"type": "file",
"file": {
"filename": "document.pdf",
"file_data": data_url,
},
},
],
},
{
"role": "assistant",
"content": "The document contains information about...",
"annotations": file_annotations,
},
{
"role": "user",
"content": "Can you elaborate on the second point?",
},
]
follow_up_payload = {
"model": "~google/gemma-3-27b-it",
"messages": follow_up_messages,
}
follow_up_response = requests.post(url, headers=headers, json=follow_up_payload)
print(follow_up_response.json())
import fs from "fs/promises";
async function encodePDFToBase64(pdfPath: string): Promise<string> {
const pdfBuffer = await fs.readFile(pdfPath);
const base64PDF = pdfBuffer.toString("base64");
return `data:application/pdf;base64,${base64PDF}`;
}
async function processDocument() {
// Read and encode the PDF
const pdfPath = "path/to/your/document.pdf";
const base64PDF = await encodePDFToBase64(pdfPath);
const initialResponse = await fetch(
"https://app.asha-ai.ir/v1/chat/completions",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ASHA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "~google/gemma-3-27b-it",
messages: [
{
role: "user",
content: [
{
type: "text",
text: "What are the main points in this document?",
},
{
type: "file",
file: {
filename: "document.pdf",
file_data: base64PDF,
},
},
],
},
],
}),
}
);
const initialData = await initialResponse.json();
// Store the annotations from the response
let fileAnnotations = null;
if (initialData.choices && initialData.choices.length > 0) {
if (initialData.choices[0].message.annotations) {
fileAnnotations = initialData.choices[0].message.annotations;
}
}
// Follow-up request using the annotations (without sending the PDF again)
if (fileAnnotations) {
const followUpResponse = await fetch(
"https://app.asha-ai.ir/v1/chat/completions",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ASHA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "~google/gemma-3-27b-it",
messages: [
{
role: "user",
content: [
{
type: "text",
text: "What are the main points in this document?",
},
{
type: "file",
file: {
filename: "document.pdf",
file_data: base64PDF,
},
},
],
},
{
role: "assistant",
content: "The document contains information about...",
annotations: fileAnnotations,
},
{
role: "user",
content: "Can you elaborate on the second point?",
},
],
}),
}
);
const followUpData = await followUpResponse.json();
console.log(followUpData);
}
}
processDocument();
mistral-ocr که هزینهٔ اضافی دارد مفید است.
طرح دادهٔ File Annotations
وقتی آشا یک PDF را پارس میکند، پاسخ شامل file annotation در پیامِ دستیار است. در اینجا نوع TypeScript برای طرح annotation آمده است:
type FileAnnotation = {
type: 'file';
file: {
hash: string; // Unique hash identifying the parsed file
name?: string; // Original filename (optional)
content: ContentPart[]; // Parsed content from the file
};
};
type ContentPart =
| { type: 'text'; text: string }
| { type: 'image_url'; image_url: { url: string } };
آرایهٔ content شامل محتوای پارسشدهٔ PDF است که
ممکن است بلوکهای متن و تصاویر (بهصورت Data URL با base64) را داشته باشد. فیلد
hash بهصورت یکتا محتوای پارسشدهٔ فایل را شناسایی
میکند و وقتی annotation را در درخواستهای بعدی قرار میدهید، برای رد شدن از پارسِ دوباره بهکار
میرود.
قالب پاسخ
API پاسخ را در قالب زیر برمیگرداند:
{
"id": "gen-1234567890",
"provider": "DeepInfra",
"model": "google/gemma-3-27b-it",
"object": "chat.completion",
"created": 1234567890,
"choices": [
{
"message": {
"role": "assistant",
"content": "The document discusses...",
"annotations": [
{
"type": "file",
"file": {
"hash": "abc123...",
"name": "document.pdf",
"content": [
{ "type": "text", "text": "Parsed text content..." },
{ "type": "image_url", "image_url": { "url": "data:image/png;base64,..." } }
]
}
}
]
}
}
],
"usage": {
"prompt_tokens": 1000,
"completion_tokens": 100,
"total_tokens": 1100
}
}
پاسخهای خطا با Annotationهای پارسشده
اگر آشا PDF شما را با موفقیت پارس کند اما همهٔ ارائهدهندههای استنتاج در تولید پاسخ شکست
بخورند، پاسخ خطا همچنان شامل annotationهای پارسشده در
error.metadata.file_annotations است. شکل آن با
FileAnnotation مسیر موفقیت که در بالا مستند شد یکی
است؛ بنابراین میتوانید همان آرایه را در یک retry مستقیم به آشا بدهید تا از پارسِ دوباره رد شوید.
این برای موتورهای mistral-ocr و
cloudflare-ai صادق است که PDF را قبل از فرستادن به
مدل پارس میکنند. موتور native annotation تولید
نمیکند چون فایل مستقیم به مدل ارسال میشود.
{
"error": {
"code": 502,
"message": "Provider returned an error",
"metadata": {
"file_annotations": [
{
"type": "file",
"file": {
"hash": "abc123...",
"name": "document.pdf",
"content": [
{ "type": "text", "text": "Parsed text content..." }
]
}
}
]
}
}
}
وقتی annotationها را از هر دو مسیر موفقیت و خطا میخوانید، طبق
file.hash dedupe کنید. hash برای همان فایل پارسشده
در هر دو شکل پایدار است:
function isFileAnnotation(value: unknown): value is FileAnnotation {
if (typeof value !== 'object' || value === null) return false;
const candidate = value as { type?: unknown; file?: { hash?: unknown } };
return (
candidate.type === 'file' &&
typeof candidate.file?.hash === 'string'
);
}
function extractFileAnnotations(response: unknown): FileAnnotation[] {
if (typeof response !== 'object' || response === null) return [];
const root = response as {
choices?: Array<{ message?: { annotations?: unknown[] } }>;
error?: { metadata?: { file_annotations?: unknown[] } };
};
const fromMessage = root.choices?.[0]?.message?.annotations ?? [];
const fromError = root.error?.metadata?.file_annotations ?? [];
const seen = new Set<string>();
const out: FileAnnotation[] = [];
for (const a of [...fromMessage, ...fromError]) {
if (isFileAnnotation(a) && !seen.has(a.file.hash)) {
seen.add(a.file.hash);
out.push(a);
}
}
return out;
}
سؤالات متداول
آیا URL مستقیم برای PDF کافی است؟
بله؛ برای اسناد عمومی میتوانید URL را مستقیم بفرستید. برای فایلهای محلی یا خصوصی باید فایل را base64 کد کنید.
موتور پیشفرض پارس چیست؟
ابتدا قابلیت بومیِ خودِ مدل بررسی میشود و در نبود آن از mistral-ocr استفاده میشود.
هزینهٔ OCR چگونه در صورتحساب اعمال میشود؟
هزینهٔ OCR شامل همهٔ درخواستها میشود و کارمزد هر صفحه به حساب آشا تسویه میشود.
چرا حداکثر ۸ تصویر از PDF استخراج میشود؟
برای حفظ سازگاری با محدودیت تعداد تصویر در هر prompt میان ارائهدهندهها؛ متن کامل همیشه حفظ میشود.
چطور از پارسِ دوبارهٔ همان PDF جلوگیری کنم؟
file annotationهای پاسخ قبلی را در درخواست بعدی بازگردانید؛ آشا بهجای پارسِ دوباره از همان اطلاعات استفاده میکند.