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
- Quick Start
- Configuration
- Extraction
- Concurrency and Tuning
- Cache and Audit
- Images
- Progress and Cancel
- API Reference
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_workersto parallelize across keys. With 3 keys andmax_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 ofconfigure()orsetup().
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23d68e258c437f3da621975e2ca02942af47d7bca206911e115c4bfc926dd273
|
|
| MD5 |
86184e8a7ab2bd5101281978fbfd83e1
|
|
| BLAKE2b-256 |
0d79a6acc052447daf7b44a49ffa0903cf2a5155fe25836575538d39017a9fb3
|
Provenance
The following attestation bundles were made for silkloom_core-7.2.0.tar.gz:
Publisher:
publish.yml on LeLiu-GeoAI/silkloom-core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
silkloom_core-7.2.0.tar.gz -
Subject digest:
23d68e258c437f3da621975e2ca02942af47d7bca206911e115c4bfc926dd273 - Sigstore transparency entry: 2388641269
- Sigstore integration time:
-
Permalink:
LeLiu-GeoAI/silkloom-core@cee85ac0d62fa63e7cf8029575c0a569a204dbd8 -
Branch / Tag:
refs/tags/v7.2.0 - Owner: https://github.com/LeLiu-GeoAI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cee85ac0d62fa63e7cf8029575c0a569a204dbd8 -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d068f1f288a6cda755c43977da08cc25cb9ff8e35af6f690d85f0b2cd5e203fa
|
|
| MD5 |
a16b13c32e566befaf097e60344062d9
|
|
| BLAKE2b-256 |
4df69e30ccab93dd74daa56c0fe6812e27f12048ff8a3a5d884700fdd1f0efa2
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
silkloom_core-7.2.0-py3-none-any.whl -
Subject digest:
d068f1f288a6cda755c43977da08cc25cb9ff8e35af6f690d85f0b2cd5e203fa - Sigstore transparency entry: 2388641290
- Sigstore integration time:
-
Permalink:
LeLiu-GeoAI/silkloom-core@cee85ac0d62fa63e7cf8029575c0a569a204dbd8 -
Branch / Tag:
refs/tags/v7.2.0 - Owner: https://github.com/LeLiu-GeoAI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cee85ac0d62fa63e7cf8029575c0a569a204dbd8 -
Trigger Event:
push
-
Statement type: