ask method allows you to ask questions about your ingested documents and receive answers grounded in your content. The SDK supports conversational memory, enabling follow-up questions that maintain context.
Method Overview
- Python
- TypeScript
Sync Method
client.sources.ask()Async Method
await client.sources.ask() (using AsyncGraphor)Async Method
await client.sources.ask()All TypeScript methods are async and return a Promise.Method Signature
- Python
- TypeScript
client.sources.ask(
question: str, # Required
conversation_id: str | None = None,
reset: bool | None = None,
file_ids: list[str] | None = None,
file_names: list[str] | None = None, # Deprecated
output_schema: dict | None = None,
thinking_level: str | None = None,
include_citation_images: bool | None = None,
include_citation_markup: bool | None = None,
timeout: float | None = None
) -> SourceAskResponse
await client.sources.ask({
question: string, // Required
conversation_id?: string | null,
reset?: boolean | null,
file_ids?: string[] | null,
file_names?: string[] | null, // Deprecated
output_schema?: Record<string, unknown> | null,
thinking_level?: 'fast' | 'balanced' | 'accurate' | null,
include_citation_images?: boolean | null,
include_citation_markup?: boolean | null,
}): Promise<SourceAskResponse>
Parameters
- Python
- TypeScript
| Parameter | Type | Description | Required |
|---|---|---|---|
question | str | The question to ask about your documents | Yes |
conversation_id | str | Conversation identifier to maintain memory context across questions | No |
reset | bool | When True, starts a new conversation and ignores previous history | No |
file_ids | list[str] | Restrict search to specific documents by file ID (preferred) | No |
file_names | list[str] | Restrict search to specific documents by file name (deprecated, use file_ids) | No |
output_schema | dict | JSON Schema to request structured output (see below) | No |
thinking_level | str | Controls model and thinking configuration: "fast", "balanced", "accurate" (default) | No |
include_citation_images | bool | When True, populates image_base64 (base64 PNG of the cited page) inside each citations entry. Default False. See When to use include_citation_images. | No |
include_citation_markup | bool | When True, the answer field keeps the raw structured citation markup [N](file_id|pX|sY|eZ|fNAME) instead of stripping it down to plain [N] markers. Default False. The markup is an implementation detail — prefer parsing citations. Has no effect when output_schema is set. | No |
timeout | float | Request timeout in seconds | No |
| Parameter | Type | Description | Required |
|---|---|---|---|
question | string | The question to ask about your documents | Yes |
conversation_id | string | null | Conversation identifier to maintain memory context across questions | No |
reset | boolean | null | When true, starts a new conversation and ignores previous history | No |
file_ids | string[] | null | Restrict search to specific documents by file ID (preferred) | No |
file_names | string[] | null | Restrict search to specific documents by file name (deprecated, use file_ids) | No |
output_schema | Record<string, unknown> | null | JSON Schema to request structured output (see below) | No |
thinking_level | 'fast' | 'balanced' | 'accurate' | null | Controls model and thinking configuration (default: "accurate") | No |
include_citation_images | boolean | null | When true, populates image_base64 inside each citations entry. Default false. See When to use include_citation_images. | No |
include_citation_markup | boolean | null | When true, the answer field keeps the raw structured citation markup [N](file_id|pX|sY|eZ|fNAME). Default false. Has no effect when output_schema is set. | No |
Thinking Level
Thethinking_level parameter controls the model and thinking configuration used for answering questions:
| Value | Description |
|---|---|
"fast" | Uses a faster model without extended thinking. Best for simple questions where speed is prioritized. |
"balanced" | Uses a more capable model with low thinking. Good balance between quality and speed. |
"accurate" | Default. Uses a more capable model with high thinking. Best for complex questions requiring deep reasoning. |
Response Object
The method returns aSourceAskResponse object:
| Property | Type | Description |
|---|---|---|
answer | str | The answer to your question, with inline [N] markers pointing to entries in citations. When output_schema is provided, this will be a short status message. |
conversation_id | str | None | Conversation identifier for follow-up questions |
structured_output | dict | None | Structured output validated against the requested output_schema. Present only when output_schema is provided. |
raw_json | str | None | Raw JSON-text produced by the model before validation. Present only when output_schema is provided. |
citations | list[Citation] | None | Structured references resolving each [N] marker in answer. May be empty/None when the agent did not ground its answer (e.g. small-talk follow-ups). See Citations. |
usage | Usage | None | Token usage breakdown for the request |
elapsed_s | float | None | Wall-clock time in seconds |
Citations
Each entry incitations corresponds to one [N] marker that appears in the answer text. Use index to map a marker to its citation.
| Field | Type | Description |
|---|---|---|
index | int | The 1-based citation number that appears as [N] in answer |
file_id | str | Unique identifier of the source file |
file_name | str | Display name of the source file |
page_number | int | 1-based page number where the cited content appears |
section_number | int | None | Optional section number within the page |
element_id | str | None | Optional element identifier (e.g. specific paragraph or table) |
text_preview | str | None | Short text excerpt around the cited content |
image_base64 | str | None | Base64-encoded PNG of the cited page. Populated only when the call used include_citation_images=True. May be None if the source is not visualizable (e.g. plain text). |
When to use include_citation_images
The include_citation_images flag is convenient for quick prototyping or one-off requests where you want both the answer and the visual previews in a single round-trip.
For real applications — especially when answers commonly cite many pages — prefer leaving the flag as False (the default) and lazy-load the screenshots on demand via the dedicated get_page_screenshot method. Reasons:
- Payload size: each base64 PNG is typically 100-400 KB. Five citations can push the JSON response above a megabyte.
- Latency: rendering screenshots adds seconds to the response. Without the flag, the answer comes back as soon as the model finishes.
- Cache locality: the screenshot endpoint is keyed by
(file_id, page_number)and emits aCache-Controlhint. Lazy-loading lets browsers and CDNs cache the bytes; inlining base64 prevents that.
include_citation_images=True only when you control both ends and know the answer will cite at most 1-2 pages. Otherwise, ship the answer with structured citations and fetch images on hover/click.
Code Examples
Basic Question
- Python
- TypeScript
from graphor import Graphor
client = Graphor()
# Ask a simple question
response = client.sources.ask(
question="What are the main findings in this report?"
)
print(f"Answer: {response.answer}")
print(f"Conversation ID: {response.conversation_id}")
import Graphor from 'graphor';
const client = new Graphor();
// Ask a simple question
const response = await client.sources.ask({
question: 'What are the main findings in this report?',
});
console.log(`Answer: ${response.answer}`);
console.log(`Conversation ID: ${response.conversation_id}`);
Conversation with Memory
Useconversation_id to maintain context across multiple questions:
- Python
- TypeScript
from graphor import Graphor
client = Graphor()
# First question
response = client.sources.ask(
question="What products are mentioned in the catalog?"
)
print(f"Answer: {response.answer}")
# Follow-up question using conversation memory
follow_up = client.sources.ask(
question="Which one is the most expensive?",
conversation_id=response.conversation_id
)
print(f"Follow-up: {follow_up.answer}")
# Another follow-up
another = client.sources.ask(
question="What are its specifications?",
conversation_id=response.conversation_id
)
print(f"Answer: {another.answer}")
import Graphor from 'graphor';
const client = new Graphor();
// First question
const response = await client.sources.ask({
question: 'What products are mentioned in the catalog?',
});
console.log(`Answer: ${response.answer}`);
// Follow-up question using conversation memory
const followUp = await client.sources.ask({
question: 'Which one is the most expensive?',
conversation_id: response.conversation_id,
});
console.log(`Follow-up: ${followUp.answer}`);
// Another follow-up
const another = await client.sources.ask({
question: 'What are its specifications?',
conversation_id: response.conversation_id,
});
console.log(`Answer: ${another.answer}`);
Reset Conversation
Start fresh by using thereset parameter:
- Python
- TypeScript
from graphor import Graphor
client = Graphor()
# Start a conversation
response = client.sources.ask(
question="What is the company's revenue?"
)
# Switch to a new topic - reset the conversation
new_response = client.sources.ask(
question="What are the safety guidelines?",
conversation_id=response.conversation_id,
reset=True # Ignores previous conversation history
)
print(f"Answer: {new_response.answer}")
import Graphor from 'graphor';
const client = new Graphor();
// Start a conversation
const response = await client.sources.ask({
question: "What is the company's revenue?",
});
// Switch to a new topic - reset the conversation
const newResponse = await client.sources.ask({
question: 'What are the safety guidelines?',
conversation_id: response.conversation_id,
reset: true, // Ignores previous conversation history
});
console.log(`Answer: ${newResponse.answer}`);
Filter by Specific Documents
Restrict the search to specific files usingfile_ids (preferred):
- Python
- TypeScript
from graphor import Graphor
client = Graphor()
# Ask about specific documents using file_ids (preferred)
response = client.sources.ask(
question="What is the total amount due?",
file_ids=["file_abc123", "file_def456"]
)
print(f"Answer: {response.answer}")
# Or using file_names (deprecated)
response = client.sources.ask(
question="What is the total amount due?",
file_names=["invoice-2024.pdf", "invoice-2023.pdf"]
)
print(f"Answer: {response.answer}")
import Graphor from 'graphor';
const client = new Graphor();
// Ask about specific documents using file_ids (preferred)
const response = await client.sources.ask({
question: 'What is the total amount due?',
file_ids: ['file_abc123', 'file_def456'],
});
console.log(`Answer: ${response.answer}`);
// Or using file_names (deprecated)
const response2 = await client.sources.ask({
question: 'What is the total amount due?',
file_names: ['invoice-2024.pdf', 'invoice-2023.pdf'],
});
console.log(`Answer: ${response2.answer}`);
Working with Citations
Every grounded answer comes back with a structuredcitations array. The default flow is fetch screenshots on demand — only render images for the citations the user actually inspects.
- Python
- TypeScript
from graphor import Graphor
client = Graphor()
# Default: lazy-load screenshots when needed
response = client.sources.ask(
question="What was the revenue in 2025?",
file_ids=["file_abc123"]
)
print(response.answer)
# -> "Revenue grew 23% year-over-year, reaching $4.2B [1]…"
for c in (response.citations or []):
print(f"[{c.index}] {c.file_name} p{c.page_number}")
# When the user hovers/clicks on [N], fetch the page screenshot
screenshot = client.sources.get_page_screenshot(
page_number=c.page_number,
file_id=c.file_id,
max_width=900,
)
# screenshot.image_base64 is a ready-to-render PNG
import Graphor from 'graphor';
const client = new Graphor();
// Default: lazy-load screenshots when needed
const response = await client.sources.ask({
question: 'What was the revenue in 2025?',
file_ids: ['file_abc123'],
});
console.log(response.answer);
// -> "Revenue grew 23% year-over-year, reaching $4.2B [1]…"
for (const c of response.citations ?? []) {
console.log(`[${c.index}] ${c.file_name} p${c.page_number}`);
// When the user hovers/clicks on [N], fetch the page screenshot
const screenshot = await client.sources.getPageScreenshot(
c.page_number!,
{ file_id: c.file_id!, max_width: 900 },
);
// screenshot.image_base64 is a ready-to-render PNG
}
Inlining citation images in the response
When you really do want the screenshots in the same round-trip — for example, a one-off batch job that won’t be re-rendered — setinclude_citation_images=true. Avoid this in interactive UIs and any flow where the answer typically cites many pages.
- Python
- TypeScript
response = client.sources.ask(
question="Confirm the invoice number and due date.",
file_ids=["invoice_abc123"],
include_citation_images=True, # base64 PNG embedded per citation
)
for c in (response.citations or []):
if c.image_base64:
with open(f"page_{c.page_number}.png", "wb") as f:
import base64
f.write(base64.b64decode(c.image_base64))
const response = await client.sources.ask({
question: 'Confirm the invoice number and due date.',
file_ids: ['invoice_abc123'],
include_citation_images: true, // base64 PNG embedded per citation
});
for (const c of response.citations ?? []) {
if (c.image_base64) {
const bytes = Buffer.from(c.image_base64, 'base64');
await fs.promises.writeFile(`page_${c.page_number}.png`, bytes);
}
}
Using Thinking Level
Control the model’s reasoning depth withthinking_level:
- Python
- TypeScript
from graphor import Graphor
client = Graphor()
# Fast mode for simple questions
response = client.sources.ask(
question="What is the document title?",
thinking_level="fast"
)
print(f"Answer: {response.answer}")
# Accurate mode for complex analysis
response = client.sources.ask(
question="Analyze the legal implications of the termination clause and identify potential risks.",
file_names=["contract.pdf"],
thinking_level="accurate"
)
print(f"Analysis: {response.answer}")
import Graphor from 'graphor';
const client = new Graphor();
// Fast mode for simple questions
const response = await client.sources.ask({
question: 'What is the document title?',
thinking_level: 'fast',
});
console.log(`Answer: ${response.answer}`);
// Accurate mode for complex analysis
const analysis = await client.sources.ask({
question: 'Analyze the legal implications of the termination clause and identify potential risks.',
file_names: ['contract.pdf'],
thinking_level: 'accurate',
});
console.log(`Analysis: ${analysis.answer}`);
Structured Output with JSON Schema
Request structured data by providing anoutput_schema:
- Python
- TypeScript
from graphor import Graphor
client = Graphor()
# Define the output schema
invoice_schema = {
"type": "object",
"properties": {
"invoice_number": {"type": ["string", "null"]},
"total_amount_due": {"type": ["number", "null"]},
"currency": {"type": ["string", "null"]},
"due_date": {"type": ["string", "null"]}
}
}
# Ask with structured output
response = client.sources.ask(
question="Extract the invoice number, total amount, currency, and due date.",
file_names=["invoice-2024.pdf"],
output_schema=invoice_schema
)
# Access the structured data
if response.structured_output:
data = response.structured_output
print(f"Invoice: {data.get('invoice_number')}")
print(f"Amount: {data.get('total_amount_due')} {data.get('currency')}")
print(f"Due: {data.get('due_date')}")
# Raw JSON is also available
print(f"Raw JSON: {response.raw_json}")
import Graphor from 'graphor';
const client = new Graphor();
// Define the output schema
const invoiceSchema = {
type: 'object',
properties: {
invoice_number: { type: ['string', 'null'] },
total_amount_due: { type: ['number', 'null'] },
currency: { type: ['string', 'null'] },
due_date: { type: ['string', 'null'] },
},
};
// Ask with structured output
const response = await client.sources.ask({
question: 'Extract the invoice number, total amount, currency, and due date.',
file_names: ['invoice-2024.pdf'],
output_schema: invoiceSchema,
});
// Access the structured data
if (response.structured_output) {
const data = response.structured_output as Record<string, unknown>;
console.log(`Invoice: ${data.invoice_number}`);
console.log(`Amount: ${data.total_amount_due} ${data.currency}`);
console.log(`Due: ${data.due_date}`);
}
// Raw JSON is also available
console.log(`Raw JSON: ${response.raw_json}`);
Extract Array of Items
Extract multiple items with a schema:- Python
- TypeScript
from graphor import Graphor
client = Graphor()
# Schema for extracting a list of products
products_schema = {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": ["number", "null"]},
"quantity": {"type": ["integer", "null"]}
}
}
}
response = client.sources.ask(
question="Extract all products with their prices and quantities from the order.",
file_names=["order.pdf"],
output_schema=products_schema
)
if response.structured_output:
products = response.structured_output
for product in products:
print(f"- {product['name']}: ${product.get('price', 'N/A')} x {product.get('quantity', 'N/A')}")
import Graphor from 'graphor';
const client = new Graphor();
// Schema for extracting a list of products
const productsSchema = {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
price: { type: ['number', 'null'] },
quantity: { type: ['integer', 'null'] },
},
},
};
const response = await client.sources.ask({
question: 'Extract all products with their prices and quantities from the order.',
file_names: ['order.pdf'],
output_schema: productsSchema,
});
if (response.structured_output) {
const products = response.structured_output as Array<Record<string, unknown>>;
for (const product of products) {
console.log(`- ${product.name}: $${product.price ?? 'N/A'} x ${product.quantity ?? 'N/A'}`);
}
}
Async Usage
- Python
- TypeScript
import asyncio
from graphor import AsyncGraphor
async def ask_questions():
client = AsyncGraphor()
# Ask a question
response = await client.sources.ask(
question="What are the key terms in this contract?"
)
print(f"Answer: {response.answer}")
# Follow-up
follow_up = await client.sources.ask(
question="When does it expire?",
conversation_id=response.conversation_id
)
print(f"Follow-up: {follow_up.answer}")
asyncio.run(ask_questions())
import Graphor from 'graphor';
const client = new Graphor();
async function askQuestions() {
// Ask a question
const response = await client.sources.ask({
question: 'What are the key terms in this contract?',
});
console.log(`Answer: ${response.answer}`);
// Follow-up
const followUp = await client.sources.ask({
question: 'When does it expire?',
conversation_id: response.conversation_id,
});
console.log(`Follow-up: ${followUp.answer}`);
}
await askQuestions();
Error Handling
- Python
- TypeScript
import graphor
from graphor import Graphor
client = Graphor()
try:
response = client.sources.ask(
question="What is the summary of this document?"
)
print(f"Answer: {response.answer}")
except graphor.BadRequestError as e:
print(f"Invalid request: {e}")
except graphor.AuthenticationError as e:
print(f"Invalid API key: {e}")
except graphor.NotFoundError as e:
print(f"Document not found: {e}")
except graphor.UnprocessableEntityError as e:
print(f"Invalid output_schema or structured output validation failed: {e}")
except graphor.RateLimitError as e:
print(f"Rate limit exceeded: {e}")
except graphor.APIConnectionError as e:
print(f"Connection error: {e}")
except graphor.APIStatusError as e:
print(f"API error (status {e.status_code}): {e}")
import Graphor from 'graphor';
const client = new Graphor();
try {
const response = await client.sources.ask({
question: 'What is the summary of this document?',
});
console.log(`Answer: ${response.answer}`);
} catch (err) {
if (err instanceof Graphor.BadRequestError) {
console.log(`Invalid request: ${err.message}`);
} else if (err instanceof Graphor.AuthenticationError) {
console.log(`Invalid API key: ${err.message}`);
} else if (err instanceof Graphor.NotFoundError) {
console.log(`Document not found: ${err.message}`);
} else if (err instanceof Graphor.UnprocessableEntityError) {
console.log(`Invalid output_schema or validation failed: ${err.message}`);
} else if (err instanceof Graphor.RateLimitError) {
console.log(`Rate limit exceeded: ${err.message}`);
} else if (err instanceof Graphor.APIConnectionError) {
console.log(`Connection error: ${err.message}`);
} else if (err instanceof Graphor.APIError) {
console.log(`API error (status ${err.status}): ${err.message}`);
} else {
throw err;
}
}
Advanced Examples
Chatbot Class
Build a conversational chatbot:- Python
- TypeScript
from graphor import Graphor
import graphor
class DocumentChatbot:
def __init__(self, api_key: str | None = None):
self.client = Graphor(api_key=api_key) if api_key else Graphor()
self.conversation_id = None
self.history = []
def ask(self, question: str, file_names: list[str] | None = None) -> str:
"""Ask a question and maintain conversation history."""
try:
response = self.client.sources.ask(
question=question,
conversation_id=self.conversation_id,
file_names=file_names
)
# Update conversation ID
self.conversation_id = response.conversation_id
# Store in history
self.history.append({
"question": question,
"answer": response.answer
})
return response.answer
except graphor.APIStatusError as e:
return f"Error: {e}"
def reset(self):
"""Start a new conversation."""
self.conversation_id = None
self.history = []
def get_history(self) -> list[dict]:
"""Get conversation history."""
return self.history.copy()
# Usage
chatbot = DocumentChatbot()
# Have a conversation
print(chatbot.ask("What products are available?"))
print(chatbot.ask("Tell me more about the first one"))
print(chatbot.ask("What's its price?"))
# View history
for entry in chatbot.get_history():
print(f"Q: {entry['question']}")
print(f"A: {entry['answer'][:100]}...")
print()
# Reset for a new topic
chatbot.reset()
print(chatbot.ask("What are the shipping options?"))
import Graphor from 'graphor';
interface ChatEntry {
question: string;
answer: string;
}
class DocumentChatbot {
private client: Graphor;
private conversationId: string | null = null;
private history: ChatEntry[] = [];
constructor(apiKey?: string) {
this.client = apiKey ? new Graphor({ apiKey }) : new Graphor();
}
async ask(question: string, fileNames?: string[]): Promise<string> {
try {
const response = await this.client.sources.ask({
question,
conversation_id: this.conversationId ?? undefined,
file_names: fileNames,
});
// Update conversation ID
this.conversationId = response.conversation_id ?? null;
// Store in history
this.history.push({ question, answer: response.answer });
return response.answer;
} catch (err) {
if (err instanceof Graphor.APIError) {
return `Error: ${err.message}`;
}
throw err;
}
}
reset(): void {
this.conversationId = null;
this.history = [];
}
getHistory(): ChatEntry[] {
return [...this.history];
}
}
// Usage
const chatbot = new DocumentChatbot();
// Have a conversation
console.log(await chatbot.ask('What products are available?'));
console.log(await chatbot.ask('Tell me more about the first one'));
console.log(await chatbot.ask("What's its price?"));
// View history
for (const entry of chatbot.getHistory()) {
console.log(`Q: ${entry.question}`);
console.log(`A: ${entry.answer.slice(0, 100)}...`);
console.log();
}
// Reset for a new topic
chatbot.reset();
console.log(await chatbot.ask('What are the shipping options?'));
Multi-Document Q&A
Ask questions across multiple documents:- Python
- TypeScript
from graphor import Graphor
client = Graphor()
def compare_documents(file_names: list[str], question: str) -> str:
"""Ask a comparative question across multiple documents."""
response = client.sources.ask(
question=question,
file_names=file_names
)
return response.answer
# Compare financial reports
answer = compare_documents(
file_names=["report-2023.pdf", "report-2024.pdf"],
question="How did revenue change between 2023 and 2024?"
)
print(answer)
import Graphor from 'graphor';
const client = new Graphor();
async function compareDocuments(fileNames: string[], question: string): Promise<string> {
const response = await client.sources.ask({
question,
file_names: fileNames,
});
return response.answer;
}
// Compare financial reports
const answer = await compareDocuments(
['report-2023.pdf', 'report-2024.pdf'],
'How did revenue change between 2023 and 2024?',
);
console.log(answer);
Structured Data Extraction Pipeline
Extract structured data from multiple documents:- Python
- TypeScript
from graphor import Graphor
import graphor
from typing import Any
client = Graphor()
def extract_structured_data(
file_names: list[str],
question: str,
schema: dict
) -> list[dict[str, Any]]:
"""Extract structured data from multiple documents."""
results = []
for file_name in file_names:
try:
response = client.sources.ask(
question=question,
file_names=[file_name],
output_schema=schema
)
if response.structured_output:
results.append({
"file": file_name,
"data": response.structured_output,
"success": True
})
else:
results.append({
"file": file_name,
"data": None,
"success": False,
"error": "No structured output returned"
})
except graphor.APIStatusError as e:
results.append({
"file": file_name,
"data": None,
"success": False,
"error": str(e)
})
return results
# Extract invoice data from multiple invoices
invoice_schema = {
"type": "object",
"properties": {
"invoice_number": {"type": ["string", "null"]},
"vendor": {"type": ["string", "null"]},
"total": {"type": ["number", "null"]},
"date": {"type": ["string", "null"]}
}
}
invoices = ["invoice1.pdf", "invoice2.pdf", "invoice3.pdf"]
results = extract_structured_data(
file_names=invoices,
question="Extract the invoice number, vendor name, total amount, and date.",
schema=invoice_schema
)
for result in results:
if result["success"]:
data = result["data"]
print(f"OK - {result['file']}: #{data.get('invoice_number')} - ${data.get('total')}")
else:
print(f"FAIL - {result['file']}: {result['error']}")
import Graphor from 'graphor';
const client = new Graphor();
interface ExtractionResult {
file: string;
data: Record<string, unknown> | null;
success: boolean;
error?: string;
}
async function extractStructuredData(
fileNames: string[],
question: string,
schema: Record<string, unknown>,
): Promise<ExtractionResult[]> {
const results: ExtractionResult[] = [];
for (const fileName of fileNames) {
try {
const response = await client.sources.ask({
question,
file_names: [fileName],
output_schema: schema,
});
if (response.structured_output) {
results.push({
file: fileName,
data: response.structured_output as Record<string, unknown>,
success: true,
});
} else {
results.push({
file: fileName,
data: null,
success: false,
error: 'No structured output returned',
});
}
} catch (err) {
results.push({
file: fileName,
data: null,
success: false,
error: err instanceof Graphor.APIError ? err.message : String(err),
});
}
}
return results;
}
// Extract invoice data from multiple invoices
const invoiceSchema = {
type: 'object',
properties: {
invoice_number: { type: ['string', 'null'] },
vendor: { type: ['string', 'null'] },
total: { type: ['number', 'null'] },
date: { type: ['string', 'null'] },
},
};
const invoices = ['invoice1.pdf', 'invoice2.pdf', 'invoice3.pdf'];
const results = await extractStructuredData(
invoices,
'Extract the invoice number, vendor name, total amount, and date.',
invoiceSchema,
);
for (const result of results) {
if (result.success && result.data) {
console.log(`OK - ${result.file}: #${result.data.invoice_number} - $${result.data.total}`);
} else {
console.log(`FAIL - ${result.file}: ${result.error}`);
}
}
Parallel Questions
Ask multiple questions in parallel:- Python
- TypeScript
import asyncio
from graphor import AsyncGraphor
async def ask_parallel_questions(questions: list[str]):
"""Ask multiple questions in parallel."""
client = AsyncGraphor()
tasks = [
client.sources.ask(question=q)
for q in questions
]
responses = await asyncio.gather(*tasks, return_exceptions=True)
results = []
for question, response in zip(questions, responses):
if isinstance(response, Exception):
results.append({"question": question, "error": str(response)})
else:
results.append({"question": question, "answer": response.answer})
return results
# Usage
questions = [
"What is the total revenue?",
"Who are the main competitors?",
"What are the key risks?"
]
results = asyncio.run(ask_parallel_questions(questions))
for result in results:
print(f"Q: {result['question']}")
if "answer" in result:
print(f"A: {result['answer'][:200]}...")
else:
print(f"Error: {result['error']}")
print()
import Graphor from 'graphor';
const client = new Graphor();
async function askParallelQuestions(questions: string[]) {
const promises = questions.map((question) =>
client.sources
.ask({ question })
.then((response) => ({ question, answer: response.answer }))
.catch((err) => ({
question,
error: err instanceof Graphor.APIError ? err.message : String(err),
})),
);
return Promise.all(promises);
}
// Usage
const questions = [
'What is the total revenue?',
'Who are the main competitors?',
'What are the key risks?',
];
const results = await askParallelQuestions(questions);
for (const result of results) {
console.log(`Q: ${result.question}`);
if ('answer' in result) {
console.log(`A: ${result.answer.slice(0, 200)}...`);
} else {
console.log(`Error: ${result.error}`);
}
console.log();
}
Interactive Q&A Session
Build an interactive command-line Q&A:- Python
- TypeScript
from graphor import Graphor
import graphor
def interactive_qa():
"""Interactive Q&A session with documents."""
client = Graphor()
conversation_id = None
print("Document Q&A Session")
print("Type 'quit' to exit, 'reset' to start a new conversation")
print("-" * 50)
while True:
question = input("\nYou: ").strip()
if question.lower() == "quit":
print("Goodbye!")
break
if question.lower() == "reset":
conversation_id = None
print("Conversation reset.")
continue
if not question:
continue
try:
response = client.sources.ask(
question=question,
conversation_id=conversation_id
)
conversation_id = response.conversation_id
print(f"\nAssistant: {response.answer}")
except graphor.APIStatusError as e:
print(f"\nError: {e}")
# Run interactive session
# interactive_qa()
import Graphor from 'graphor';
import readline from 'readline';
async function interactiveQA() {
const client = new Graphor();
let conversationId: string | null = null;
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const prompt = (query: string): Promise<string> =>
new Promise((resolve) => rl.question(query, resolve));
console.log('Document Q&A Session');
console.log("Type 'quit' to exit, 'reset' to start a new conversation");
console.log('-'.repeat(50));
while (true) {
const question = (await prompt('\nYou: ')).trim();
if (question.toLowerCase() === 'quit') {
console.log('Goodbye!');
rl.close();
break;
}
if (question.toLowerCase() === 'reset') {
conversationId = null;
console.log('Conversation reset.');
continue;
}
if (!question) continue;
try {
const response = await client.sources.ask({
question,
conversation_id: conversationId ?? undefined,
});
conversationId = response.conversation_id ?? null;
console.log(`\nAssistant: ${response.answer}`);
} catch (err) {
if (err instanceof Graphor.APIError) {
console.log(`\nError: ${err.message}`);
} else {
throw err;
}
}
}
}
// Run interactive session
// await interactiveQA();
Output Schema Guidelines
When usingoutput_schema, follow these guidelines:
Supported Schema Features
- Basic types:
string,number,integer,boolean,null - Objects with
properties - Arrays with
items - Union with
nullonly:["string", "null"]
Unsupported Features
oneOf,anyOf,allOf$refreferences- Complex unions beyond
null
Schema Examples
- Python
- TypeScript
# Simple object
person_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": ["integer", "null"]},
"email": {"type": ["string", "null"]}
}
}
# Array of objects
items_schema = {
"type": "array",
"items": {
"type": "object",
"properties": {
"item": {"type": "string"},
"quantity": {"type": "integer"},
"price": {"type": "number"}
}
}
}
# Nested objects
order_schema = {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"customer": {
"type": "object",
"properties": {
"name": {"type": "string"},
"address": {"type": ["string", "null"]}
}
},
"total": {"type": "number"}
}
}
// Simple object
const personSchema = {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: ['integer', 'null'] },
email: { type: ['string', 'null'] },
},
};
// Array of objects
const itemsSchema = {
type: 'array',
items: {
type: 'object',
properties: {
item: { type: 'string' },
quantity: { type: 'integer' },
price: { type: 'number' },
},
},
};
// Nested objects
const orderSchema = {
type: 'object',
properties: {
order_id: { type: 'string' },
customer: {
type: 'object',
properties: {
name: { type: 'string' },
address: { type: ['string', 'null'] },
},
},
total: { type: 'number' },
},
};
Error Reference
| Error Type | Status Code | Description |
|---|---|---|
BadRequestError | 400 | Invalid parameters or request format |
AuthenticationError | 401 | Invalid or missing API key |
NotFoundError | 404 | Specified file not found |
UnprocessableEntityError | 422 | Invalid output_schema or structured output validation failed |
RateLimitError | 429 | Too many requests, please retry after waiting |
InternalServerError | ≥500 | Server-side error |
APIConnectionError | N/A | Network connectivity issues |
APITimeoutError | N/A | Request timed out |
Best Practices
-
Use conversation memory — Pass
conversation_idfor follow-up questions to maintain context - Be specific — Clear, specific questions get better answers
-
Scope when needed — Use
file_idsorfile_namesto focus on specific documents for faster, more accurate responses -
Use structured output for integration — Provide
output_schemato get JSON you can reliably parse in code -
Reset when changing topics — Set
reset=Truewhen switching to unrelated questions -
Lazy-load citation images — Keep
include_citation_images=False(the default) and callget_page_screenshoton demand. Inlining base64 only makes sense for low-citation, low-frequency requests — for typical chat UIs it bloats the payload by hundreds of KB per cited page. -
Parse
citations, not the markup — Use the structuredcitationsarray. The inline[N](file_id|pX|...)markup is hidden by default and is an implementation detail that may change. - Handle errors gracefully — Implement proper error handling for production applications
- Python
- TypeScript
# Good: Specific question with context
response = client.sources.ask(
question="What was the total revenue for Q4 2024 compared to Q4 2023?",
file_names=["annual-report-2024.pdf"]
)
# Good: Follow-up with conversation memory
follow_up = client.sources.ask(
question="What were the main drivers of this change?",
conversation_id=response.conversation_id
)
// Good: Specific question with context
const response = await client.sources.ask({
question: 'What was the total revenue for Q4 2024 compared to Q4 2023?',
file_names: ['annual-report-2024.pdf'],
});
// Good: Follow-up with conversation memory
const followUp = await client.sources.ask({
question: 'What were the main drivers of this change?',
conversation_id: response.conversation_id,
});
Next Steps
Get Page Screenshot
Lazy-load citation page previews on demand
Document Chat Guide
Learn best practices for chatting with your documents
Extract API
Extract structured data from documents
Upload Sources
Upload documents to chat with
Prebuilt RAG
Retrieve relevant chunks from your documents

