Skip to main content

SilkLoom Core

A lightweight pandas accessor for batch LLM extraction.

DataFrame rows → Jinja2 prompt render → OpenAI-compatible API → repaired JSON → result DataFrame

One call — df.llm.extract(template) — concurrently sends every row to an LLM, parses the JSON response, and returns a DataFrame you can join back to the original.

Table of Contents

Install

pip install silkloom-core

# optional: progress bar
pip install silkloom-core[progress]

Importing silkloom_core registers the df.llm accessor on every DataFrame.

Quick Start

import pandas as pd
import silkloom_core

silkloom_core.configure(api_key="...", base_url="https://api.openai.com/v1")

df = pd.DataFrame({
    "title": ["A clear experiment", "A weak evaluation"],
    "abstract": ["Reliable and reproducible.", "Too small to conclude much."],
})

df = df.llm.extract(
    "Title: {{ title }}\nAbstract: {{ abstract }}\nReturn JSON with keys label and summary.",
    model="gpt-4o-mini",
    max_workers=8,
    json_mode=True,
)
# df now has: title, abstract, label, summary

extract() returns the original DataFrame with extracted columns appended. Pass join=False if you only want the extracted columns.

Configuration

SilkLoom supports three layers of client configuration, from broadest to narrowest.

Global Configuration

Configure once at the start of a script — every DataFrame can call extract() directly:

silkloom_core.configure(
    api_key="...",
    base_url="https://api.openai.com/v1",
)

# Or pass a pre-built client:
silkloom_core.configure(client=OpenAI(api_key="...", base_url="..."))

Per-DataFrame Setup

Override the global default for a specific DataFrame:

df.llm.setup(
    api_key="...",
    base_url="...",
    cache_path="special_cache.db",
)

# Chain directly into extract:
df.llm.setup(client=client).extract("...", model="gpt-4o-mini")

Per-Call Client

Override the client for a single extract() call — useful for mixing providers:

from openai import OpenAI

openai_client = OpenAI(api_key="...", base_url="https://api.openai.com/v1")
zhipu_client = OpenAI(api_key="...", base_url="https://open.bigmodel.cn/api/paas/v4")

silkloom_core.configure(client=openai_client)

# Same DataFrame, different providers:
df.llm.extract("...", model="gpt-4o")                          # → OpenAI
df.llm.extract("...", model="glm-4-flash", client=zhipu_client) # → Zhipu

API Key Rotation

When api_key contains |, the keys are split and wrapped in a KeyRotatingClient that distributes API calls round-robin across all keys — useful for staying under rate limits without manual load balancing:

# 3 keys → each request goes to the next key in rotation
silkloom_core.configure(
    api_key="key1|key2|key3",
    base_url="https://api.openai.com/v1",
)

df.llm.extract("{{ text }}", max_workers=8)  # 8 threads, 3 keys → ~2-3 calls per key

Works with setup() too:

df.llm.setup(
    api_key="key1|key2|key3",
    base_url="https://api.openai.com/v1",
).extract("{{ text }}")

You can also build a KeyRotatingClient manually — for example, to mix keys from different providers:

from openai import OpenAI
from silkloom_core import KeyRotatingClient

client = KeyRotatingClient([
    OpenAI(api_key="openai-key", base_url="https://api.openai.com/v1"),
    OpenAI(api_key="deepseek-key", base_url="https://api.deepseek.com/v1"),
])
silkloom_core.configure(client=client)

Tip: Combine key rotation with max_workers to parallelize across keys. With 3 keys and max_workers=9, each key handles ~3 concurrent requests.

Priority Chain

extract(client=...)  >  df.llm.setup(client=...)  >  silkloom_core.configure(client=...)  >  error

If no client is configured, extract() raises RuntimeError with a clear message.

Note: The SQLite cache file is created lazily — only on the first extract() call, never as a side effect of configure() or setup().

Extraction

Prompt Templates

Prompts use Jinja2 with StrictUndefined — typos in column names raise immediately instead of silently producing empty strings. Literal JSON braces ({ }) are safe; only {{ }} is treated as a template expression.

out = df.llm.extract(
    'Classify {{ text }} and return JSON like {"label": "positive", "score": 0.9}',
    model="gpt-4o-mini",
    temperature=0.1,
    max_workers=4,
    max_retries=2,
)

Result Columns

The returned DataFrame has the same index as the input. Column semantics:

Condition Column(s)
Model returns a JSON object Each key becomes a column
Model returns a non-object JSON value _llm_raw
JSON parse fails _llm_error + _llm_raw
API call fails after all retries _llm_error

Malformed JSON is repaired with json_repair before parsing.

Concurrency and Tuning

All tuning parameters are passed directly to extract() — no separate config object needed.

Concurrency (max_workers)

Controls the thread pool size for parallel API calls. Default is 4.

# 16 concurrent threads — faster, but watch out for rate limits
df.llm.extract("{{ text }}", max_workers=16)

# Sequential — useful for debugging or strict rate-limit scenarios
df.llm.extract("{{ text }}", max_workers=1)

Guideline: Set max_workers according to your provider's rate limit (RPM). For OpenAI's default tier, 4–8 is safe; for high-volume providers like DeepSeek, 8–16 works well. If you hit 429 Too Many Requests, lower the value or increase max_retries.

Retries (max_retries)

On API errors (timeouts, rate limits, server errors), the request is retried with exponential backoff (1s → 2s → 4s …). Default is 2 retries.

# More resilient for flaky endpoints
df.llm.extract("{{ text }}", max_retries=5)

API Parameters (**request_options)

Any keyword argument not recognized by extract() is forwarded directly to client.chat.completions.create(). Common ones:

Parameter Type Description
temperature float Sampling temperature (0–2). Lower = more deterministic.
max_tokens int Maximum tokens to generate in the response.
top_p float Nucleus sampling probability.
frequency_penalty float Penalize repeated tokens (-2–2).
presence_penalty float Penalize tokens already present (-2–2).
seed int Random seed for reproducibility (provider-dependent).
stop str | list[str] Stop sequences.
out = df.llm.extract(
    "Classify {{ text }} into positive/negative/neutral.",
    model="gpt-4o-mini",
    max_workers=8,
    temperature=0.1,
    max_tokens=200,
    top_p=0.9,
    seed=42,
)

Combined Example

silkloom_core.configure(api_key="...", base_url="https://api.deepseek.com/v1")

result = df.llm.extract(
    "Analyze: {{ content }}\nReturn JSON with keys sentiment and confidence.",
    model="deepseek-chat",
    max_workers=12,        # 12 concurrent threads
    max_retries=3,         # retry up to 3 times on failure
    temperature=0.2,       # low temperature for consistent output
    max_tokens=500,        # cap response length
    json_mode=True,        # force JSON output
    system_prompt="You are a sentiment analysis engine.",
)

Cache and Audit

Every API call is recorded in a SQLite database. Successful responses (ok = 1) are reused as cache hits on subsequent runs with the same request. Failed requests are also stored for debugging but are retried next time.

df.llm.setup(cache_path="cache/llm.sqlite").extract(...)

The cache table schema:

Column Type Description
cache_key TEXT PK SHA-256 of the full request
ok INTEGER 1 = success (cacheable), 0 = failure
model TEXT Model name used
messages_json TEXT Rendered messages array
params_json TEXT Request params (excluding model/messages)
request_json TEXT Full request payload
response TEXT Raw model response text
parsed_json TEXT Parsed result dict
error TEXT Error message (NULL on success)
attempts INTEGER Number of attempts made
created_at TEXT Row creation timestamp
updated_at TEXT Last update timestamp

To start fresh: delete the SQLite file or use a different cache_path.

Images

Pass image_column for local file paths, HTTP(S) URLs, or data:image/... URLs. Local files are auto-encoded as base64 data URLs with MIME detection. A single cell can hold a list of images for multi-image input.

out = df.llm.extract(
    "Extract fields from this receipt and return JSON.",
    image_column="receipt_path",
    model="gpt-4o-mini",
)

Rows with missing image values (NaN, None) fall back to text-only prompts.

Progress and Cancel

Progress bar — tqdm is used when verbose=True (default). If tqdm isn't installed, it degrades silently.

Callback — for UI integration:

def progress(done, total):
    print(f"{done}/{total}")

out = df.llm.extract("Analyze {{ text }}", progress_callback=progress)

Cancel — from another thread:

df.llm.cancel()

Queued work is cancelled where possible. Running rows stop before their next retry. Already-completed results are preserved in the returned DataFrame.

API Reference

silkloom_core.configure(...)

Parameter Type Default Description
api_key str | None None API key. If contains |, split into multiple keys for round-robin rotation
base_url str | None None API base URL
cache_path str | Path ".llm_cache.db" SQLite cache file path
client Any | None None Pre-built client (overrides api_key/base_url)
**client_options Extra kwargs for OpenAI()

df.llm.setup(...)

Same parameters as configure(). Returns self for chaining.

df.llm.extract(prompt_template, *, ...)

Parameter Type Default Description
prompt_template str Jinja2 template (required)
client Any | None None Per-call client override
image_column str | None None Column with image paths/URLs
system_prompt str | None "Please output valid JSON only." System message; None to omit
model str "gpt-4o-mini" Model name
max_workers int 4 Concurrent API call threads
json_mode bool False Set response_format={"type":"json_object"}
max_retries int 2 Retries on API error (exponential backoff)
join bool True If True, return original DataFrame with extracted columns appended; if False, return only extracted columns
progress_callback Callable[[int, int], None] | None None Called with (completed, total)
verbose bool True Show tqdm progress bar
**request_options Extra kwargs for chat.completions.create()

df.llm.cancel()

No parameters. Signals cancellation to all in-flight work.

silkloom_core.KeyRotatingClient(clients)

Wraps multiple OpenAI-compatible clients and rotates through them round-robin. Thread-safe.

Parameter Type Description
clients list[Any] List of OpenAI-compatible client instances
from silkloom_core import KeyRotatingClient
from openai import OpenAI

client = KeyRotatingClient([
    OpenAI(api_key="key1", base_url="https://api.openai.com/v1"),
    OpenAI(api_key="key2", base_url="https://api.openai.com/v1"),
])
silkloom_core.configure(client=client)

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

silkloom_core-7.2.0.tar.gz (20.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

silkloom_core-7.2.0-py3-none-any.whl (13.7 kB view details)

Uploaded Python 3

File details

Details for the file silkloom_core-7.2.0.tar.gz.

File metadata

  • Download URL: silkloom_core-7.2.0.tar.gz
  • Upload date:
  • Size: 20.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for silkloom_core-7.2.0.tar.gz
Algorithm Hash digest
SHA256 23d68e258c437f3da621975e2ca02942af47d7bca206911e115c4bfc926dd273
MD5 86184e8a7ab2bd5101281978fbfd83e1
BLAKE2b-256 0d79a6acc052447daf7b44a49ffa0903cf2a5155fe25836575538d39017a9fb3

See more details on using hashes here.

Provenance

The following attestation bundles were made for silkloom_core-7.2.0.tar.gz:

Publisher: publish.yml on LeLiu-GeoAI/silkloom-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file silkloom_core-7.2.0-py3-none-any.whl.

File metadata

  • Download URL: silkloom_core-7.2.0-py3-none-any.whl
  • Upload date:
  • Size: 13.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for silkloom_core-7.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d068f1f288a6cda755c43977da08cc25cb9ff8e35af6f690d85f0b2cd5e203fa
MD5 a16b13c32e566befaf097e60344062d9
BLAKE2b-256 4df69e30ccab93dd74daa56c0fe6812e27f12048ff8a3a5d884700fdd1f0efa2

See more details on using hashes here.

Provenance

The following attestation bundles were made for silkloom_core-7.2.0-py3-none-any.whl:

Publisher: publish.yml on LeLiu-GeoAI/silkloom-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page