Skip to main content
The 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

Sync Method

client.sources.ask()

Async Method

await client.sources.ask()

Method Signature

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,
    timeout: float | None = None
) -> SourceAskResponse

Parameters

ParameterTypeDescriptionRequired
questionstrThe question to ask about your documents✅ Yes
conversation_idstrConversation identifier to maintain memory context across questionsNo
resetboolWhen True, starts a new conversation and ignores previous historyNo
file_idslist[str]Restrict search to specific documents by file ID (preferred)No
file_nameslist[str]Restrict search to specific documents by file name (deprecated, use file_ids)No
output_schemadictJSON Schema to request structured output (see below)No
thinking_levelstrControls model and thinking configuration: "fast", "balanced", "accurate" (default)No
timeoutfloatRequest timeout in secondsNo

Thinking Level

The thinking_level parameter controls the model and thinking configuration used for answering questions:
ValueDescription
"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 a SourceAskResponse object:
PropertyTypeDescription
answerstrThe answer to your question. When output_schema is provided, this will be a short status message.
conversation_idstr | NoneConversation identifier for follow-up questions
structured_outputdict | NoneStructured output validated against the requested output_schema. Present only when output_schema is provided.
raw_jsonstr | NoneRaw JSON-text produced by the model before validation. Present only when output_schema is provided.

Code Examples

Basic Question

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}")

Conversation with Memory

Use conversation_id to maintain context across multiple questions:
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}")

Reset Conversation

Start fresh by using the reset parameter:
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}")

Filter by Specific Documents

Restrict the search to specific files using file_ids (preferred):
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}")

Using Thinking Level

Control the model’s reasoning depth with thinking_level:
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}")

Structured Output with JSON Schema

Request structured data by providing an output_schema:
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}")

Extract Array of Items

Extract multiple items with a schema:
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')}")

Async Usage

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())

Error Handling

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}")

Advanced Examples

Chatbot Class

Build a conversational chatbot:
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?"))

Multi-Document Q&A

Ask questions across multiple documents:
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)

Structured Data Extraction Pipeline

Extract structured data from multiple documents:
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"✅ {result['file']}: #{data.get('invoice_number')} - ${data.get('total')}")
    else:
        print(f"❌ {result['file']}: {result['error']}")

Async Parallel Questions

Ask multiple questions in parallel:
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()

Interactive Q&A Session

Build an interactive command-line Q&A:
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()

Output Schema Guidelines

When using output_schema, follow these guidelines:

Supported Schema Features

  • Basic types: string, number, integer, boolean, null
  • Objects with properties
  • Arrays with items
  • Union with null only: ["string", "null"]

Unsupported Features

  • oneOf, anyOf, allOf
  • $ref references
  • Complex unions beyond null

Schema Examples

# 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"}
    }
}

Error Reference

Error TypeStatus CodeDescription
BadRequestError400Invalid parameters or request format
AuthenticationError401Invalid or missing API key
NotFoundError404Specified file not found
UnprocessableEntityError422Invalid output_schema or structured output validation failed
RateLimitError429Too many requests, please retry after waiting
InternalServerError≥500Server-side error
APIConnectionErrorN/ANetwork connectivity issues
APITimeoutErrorN/ARequest timed out

Best Practices

  1. Use conversation memory — Pass conversation_id for follow-up questions to maintain context
  2. Be specific — Clear, specific questions get better answers
  3. Scope when needed — Use file_names to focus on specific documents for faster, more accurate responses
  4. Use structured output for integration — Provide output_schema to get JSON you can reliably parse in code
  5. Reset when changing topics — Set reset=True when switching to unrelated questions
  6. Handle errors gracefully — Implement proper error handling for production applications
# 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
)

Next Steps