We're building a document analysis agent that ingests long-form text, produces structured summaries, extracts actionable insights, and answers follow-up questions. I use this exact pipeline to process earnings call transcripts and research reports without worrying about token costs scaling with input length.
What you'll need
You need Python 3.10 or higher, the OpenAI SDK, and an Oxlo.ai API key from https://portal.oxlo.ai.
pip install openai
1. Set up the Oxlo.ai client
I initialize the OpenAI-compatible client pointing at Oxlo.ai and verify connectivity with a lightweight test call.
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[{"role": "user", "content": "Confirm the client is live"}],
)
print(response.choices[0].message.content)
2. Define the analysis system prompt
The system prompt is the contract. It forces structured output and prevents the model from adding unnecessary commentary.
SYSTEM_PROMPT = """You are a document analysis engine. Process the full text provided by the user and produce a structured analysis in exactly this format:
SUMMARY:
A 3-sentence summary of the core content.
KEY THEMES:
- Theme 1
- Theme 2
- Theme 3
SENTIMENT:
One word: Positive, Negative, or Neutral.
ACTION ITEMS:
- Any explicit tasks, deadlines, or commitments mentioned.
Analyze the complete text. Do not truncate your thinking. Maintain the exact formatting above."""
3. Build the core analysis function
Now I wrap the API call into a reusable function. I default to Llama 3.3 70B for general-purpose reasoning, but you can swap in Qwen 3 32B or Kimi K2.6 depending on the document language and complexity.
def analyze_document(text: str, model: str = "llama-3.3-70b") -> str:
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Analyze this document:\n\n{text}"},
],
temperature=0.2,
)
return response.choices[0].message.content
4. Add multi-document synthesis
For comparing several sources, I concatenate them with clear separators and ask for a unified synthesis. Because Oxlo.ai uses per-request pricing, passing long combined texts does not inflate the cost the way token-based providers do.
def synthesize_documents(documents: list[str], model: str = "kimi-k2.6") -> str:
combined = "\n\n---DOCUMENT SEPARATOR---\n\n".join(
[f"[Document {i+1}]\n{doc}" for i, doc in enumerate(documents)]
)
synthesis_prompt = (
"You are reading multiple related documents. "
"Produce a unified synthesis that identifies agreements, contradictions, and gaps. "
"Format:\n\nUNIFIED SUMMARY:\n...\n\nCONTRADICTIONS:\n- ...\n\nGAPS:\n- ..."
)
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": synthesis_prompt},
{"role": "user", "content": combined},
],
temperature=0.3,
)
return response.choices[0].message.content
5. Implement contextual follow-up Q&A
I want to ask questions about the document after the initial analysis. I store the original text and pass it back with the conversation history so the model has full context for each turn.
class DocumentAgent:
def __init__(self, document: str, model: str = "deepseek-v3.2"):
self.document = document
self.model = model
self.history = []
def ask(self, question: str) -> str:
context = (
f"The user previously provided this document:\n\n{self.document}\n\n"
f"Conversation so far:\n"
)
for turn in self.history:
context += f"User: {turn['question']}\nAssistant: {turn['answer']}\n"
context += f"\nNow answer this question based only on the document: {question}"
response = client.chat.completions.create(
model=self.model,
messages=[
{
"role": "system",
"content": (
"You are a precise research assistant. "
"Answer questions using only the provided document. "
"If the answer is not in the text, say 'Not mentioned in the document.'"
),
},
{"role": "user", "content": context},
],
temperature=0.1,
)
answer = response.choices[0].message.content
self.history.append({"question": question, "answer": answer})
return answer
Run it
Here is a complete test script that feeds a sample product requirements document through the pipeline, then runs a synthesis with a second document, and finally asks a follow-up question.
if __name__ == "__main__":
# Sample PRD text
prd = """
Product Requirements Document: CloudSync v2
Overview:
CloudSync v2 will introduce real-time collaboration features, endpoint encryption,
and a new pricing tier for enterprise teams. The target launch date is Q3 2025.
Key Features:
1. Real-time document editing with operational transformation.
2. AES-256 encryption at rest and in transit.
3. Role-based access control with SAML 2.0 SSO integration.
Open Questions:
- Whether to support on-premise deployments in phase 1.
- Final API rate limits for the enterprise tier.
Action Items:
- Engineering to deliver encryption module by June 15.
- Product to finalize pricing by May 30.
- Legal to review SLA terms by April 10.
"""
# Step 1: Analyze single document
print("=== SINGLE DOCUMENT ANALYSIS ===")
analysis = analyze_document(prd, model="llama-3.3-70b")
print(analysis)
# Step 2: Synthesize with a second document
competitive_intel = """
Competitor Analysis: SyncBox Enterprise
SyncBox recently launched real-time collaboration and end-to-end encryption.
They do not offer SAML SSO in their base enterprise tier, but they do support
on-premise deployments. Their API rate limits are 10,000 requests per minute.
Customer feedback indicates dissatisfaction with their complex billing structure.
"""
print("\n=== MULTI-DOCUMENT SYNTHESIS ===")
synthesis = synthesize_documents([prd, competitive_intel], model="kimi-k2.6")
print(synthesis)
# Step 3: Follow-up Q&A
print("\n=== FOLLOW-UP Q&A ===")
agent = DocumentAgent(prd, model="deepseek-v3.2")
answer = agent.ask("What is the deadline for the encryption module?")
print(answer)
Example output:
=== SINGLE DOCUMENT ANALYSIS ===
SUMMARY:
CloudSync v2 is a product update targeting Q3 2025 that adds real-time collaboration, endpoint encryption, and enterprise pricing tiers. It includes operational transformation for editing, AES-256 encryption, and SAML 2.0 SSO. Several action items have deadlines in Q2.
KEY THEMES:
- Security and compliance enhancements
- Enterprise market expansion
- Real-time collaboration infrastructure
SENTIMENT:
Positive
ACTION ITEMS:
- Engineering: deliver encryption module by June 15
- Product: finalize pricing by May 30
- Legal: review SLA terms by April 10
=== MULTI-DOCUMENT SYNTHESIS ===
UNIFIED SUMMARY:
Both CloudSync v2 and SyncBox Enterprise target enterprise customers with real-time collaboration and encryption. CloudSync plans SAML 2.0 SSO by default while SyncBox omits it from the base tier. SyncBox currently leads on on-premise support and has defined API rate limits.
CONTRADICTIONS:
- CloudSync is considering on-premise support as an open question, while SyncBox already offers it.
- CloudSync plans flat enterprise pricing, whereas SyncBox uses usage-based pricing.
GAPS:
- CloudSync has not finalized API rate limits for the enterprise tier.
- SyncBox pricing model may be a competitive weakness to exploit.
=== FOLLOW-UP Q&A ===
The deadline for the encryption module is June 15.
Wrap-up
Swap in qwen-3-32b for multilingual documents, or deepseek-r1-671b when you need deep reasoning over legal or technical texts. If you are processing hundreds of pages daily, Oxlo.ai request-based pricing removes the penalty for long inputs. Check the details at https://oxlo.ai/pricing and scale the pipeline from there.
Top comments (0)