I recently shipped an internal research assistant that takes a raw technical topic and returns a structured brief with reasoning, risks, and action items. Kimi K2.5 on Oxlo.ai handles the chain-of-thought work without the cost surprises that come from token-based billing on long prompts, so I will walk you through the exact setup I used.
What you'll need
Python 3.10 or newer, the OpenAI SDK, and an Oxlo.ai API key from https://portal.oxlo.ai. Oxlo.ai uses request-based pricing, which means a 10,000 token prompt costs the same as a 100 token prompt, so you can feed Kimi K2.5 large context windows without watching the meter.
pip install openai
Step 1: Scaffold the client
First, initialize the OpenAI-compatible client pointing at Oxlo.ai. I read the key from an environment variable, but you can paste it directly for local testing.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.getenv("OXLO_API_KEY", "YOUR_OXLO_API_KEY")
)
Step 2: Design the system prompt
Kimi K2.5 excels at extended reasoning, so I give it a strict persona and output schema upfront. This reduces hallucination and makes downstream parsing trivial.
SYSTEM_PROMPT = """You are a senior staff engineer who researches technical topics deeply.
When given a topic, follow this exact workflow:
1. Reason silently about core concepts, trade-offs, and failure modes.
2. Produce a structured report with these sections:
- Summary: one paragraph, plain language.
- Architecture Notes: key components and how they interact.
- Risk Matrix: list 3 specific risks, each with severity (High/Medium/Low) and mitigation.
- Action Items: 2 concrete next steps for a team implementing this.
Keep the tone technical and concise. Do not use markdown headers inside the JSON values."""
Step 3: Add the research function
Next, wrap the API call in a helper that streams the response. Streaming lets you monitor Kimi K2.5's reasoning in real time, which is useful when the model pauses to work through complex chain-of-thought steps.
def research_topic(topic: str, model: str = "kimi-k2.5") -> str:
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Research this topic thoroughly: {topic}"},
],
stream=True,
)
full_text = ""
for chunk in response:
delta = chunk.choices[0].delta.content or ""
full_text += delta
print(delta, end="", flush=True)
print()
return full_text
Step 4: Enforce structured output
Raw text is fine for logs, but I usually need machine-readable output. Oxlo.ai supports JSON mode, so I switch the prompt to request valid JSON and set response_format accordingly.
import json
SYSTEM_PROMPT_JSON = """You are a senior staff engineer who researches technical topics deeply.
Reason step by step, then produce a single JSON object with this exact schema:
{
"summary": "string",
"architecture_notes": "string",
"risk_matrix": [
{"risk": "string", "severity": "High|Medium|Low", "mitigation": "string"}
],
"action_items": ["string", "string"]
}
Do not include markdown code fences. Output raw JSON only."""
def research_topic_json(topic: str, model: str = "kimi-k2.5") -> dict:
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": SYSTEM_PROMPT_JSON},
{"role": "user", "content": f"Research this topic thoroughly: {topic}"},
],
response_format={"type": "json_object"},
stream=False,
)
raw = response.choices[0].message.content
return json.loads(raw)
Step 5: Optimize for long context
Kimi K2.5 shines on large inputs. I often feed it full documentation pages or prior conversation threads. Because Oxlo.ai charges per request rather than per token, I do not have to truncate context to save money. Here is a helper that prepends reference material before the user question.
def research_with_context(topic: str, reference_docs: list[str], model: str = "kimi-k2.5") -> dict:
context_block = "\n\n".join(
f"[Document {i+1}]\n{doc}" for i, doc in enumerate(reference_docs)
)
user_message = f"""Reference material:
{context_block}
Based on the above, research this topic thoroughly: {topic}"""
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": SYSTEM_PROMPT_JSON},
{"role": "user", "content": user_message},
],
response_format={"type": "json_object"},
stream=False,
)
raw = response.choices[0].message.content
return json.loads(raw)
Step 6: Add logging and error handling
In production I wrap the call in a retry loop, but the core logic is what matters here. I log the raw response length so I can verify the model is not returning truncated output when I push context close to the limit.
def research_topic_safe(topic: str, reference_docs: list[str] | None = None) -> dict:
docs = reference_docs or []
context_block = "\n\n".join(
f"[Document {i+1}]\n{doc}" for i, doc in enumerate(docs)
)
user_content = f"Research this topic thoroughly: {topic}"
if context_block:
user_content = f"Reference material:\n{context_block}\n\n{user_content}"
response = client.chat.completions.create(
model="kimi-k2.5",
messages=[
{"role": "system", "content": SYSTEM_PROMPT_JSON},
{"role": "user", "content": user_content},
],
response_format={"type": "json_object"},
stream=False,
)
raw = response.choices[0].message.content
print(f"Raw response length: {len(raw)} chars")
return json.loads(raw)
Run it
Here is how I call the finished agent from the CLI. I pass a topic and an optional document snippet.
if __name__ == "__main__":
topic = "Event-driven architectures with Kafka and exactly-once semantics"
reference = """Apache Kafka provides idempotent producers and transactional APIs
to achieve exactly-once delivery between producer and consumer. However,
network partitions and consumer rebalance events can still produce duplicates
if offsets are committed before processing completes."""
result = research_topic_safe(topic, reference_docs=[reference])
print(json.dumps(result, indent=2))
Example output:
Raw response length: 1847 chars
{
"summary": "Exactly-once semantics in Kafka rely on idempotent producers and transactions, but application-level duplicates remain possible during rebalances.",
"architecture_notes": "Idempotent producers assign sequence numbers per partition. Transactional consumers read and process messages inside transactions, then commit offsets atomically. Rebalance listeners must synchronize state to avoid committing unprocessed offsets.",
"risk_matrix": [
{
"risk": "Consumer rebalances during partition revocation can cause duplicate processing if the consumer has already read messages but not yet committed.",
"severity": "High",
"mitigation": "Use a rebalance listener to pause processing and commit current offsets before partitions are revoked."
},
{
"risk": "Long transaction timeouts can block consumers and stall partition progress.",
"severity": "Medium",
"mitigation": "Keep transaction scope small and tune transaction.timeout.ms to match processing latency."
},
{
"risk": "Producer retries with expired sequence numbers can violate idempotency after broker failover.",
"severity": "Medium",
"mitigation": "Monitor producer retry rates and ensure broker min.insync.replicas is set appropriately."
}
],
"action_items": [
"Implement a consumer rebalance listener that pauses polling and commits offsets before partition revocation.",
"Add end-to-end integration tests that simulate broker restarts and network partitions to verify exactly-once behavior."
]
}
Wrap up
You now have a working research agent that leverages Kimi K2.5's reasoning on Oxlo.ai. Because Oxlo.ai bills per request, you can expand the reference material to thousands of tokens without reworking your budget. A natural next step is to wire this into a Slack bot or CI pipeline so architecture reviews happen automatically on every design doc pull request.
Top comments (0)