SimpleLLMFunc Usage
When to use this skill
- Use this skill for application-level work built on top of SimpleLLMFunc.
- Use it when the task mentions
llm_function, llm_chat, tool, OpenAICompatible, OpenAIResponsesCompatible, provider.json, reasoning, PyRepl, SelfReference, FileToolset, or the built-in TUI.
- Do not use this skill for framework-internal refactors; use
simplellmfunc-developer for that.
Core philosophy
- LLM is Function: treat the LLM call like a normal Python function call — signature, type hints, return value.
- Prompt as Code: put the prompt in the function docstring. Code and prompt are never separated.
- Context-Centric: each LLM request is compiled from invocation configuration, a base transcript/history, and internal runtime patches.
ContextMutation is the internal patch protocol that prevents tools and SelfRef from directly modifying the live ReAct transcript.
- Keep orchestration in Python instead of hiding it in giant prompt strings.
- Prefer small, typed, composable building blocks.
How system prompts are really built
This is critical for writing good prompts in SimpleLLMFunc: your docstring is important, but it is usually not the whole final system prompt.
llm_function
- Your docstring is first treated as
function_description.
- If you pass
_template_params, the docstring is formatted before prompt construction.
- The framework then wraps that docstring inside a system template that also adds:
- parameter type descriptions
- return-type instructions
- plain-text or XML output constraints depending on the return type
- If tools are mounted, the framework prepends a deduplicated
<tool_best_practices> block before the main system prompt.
- Runtime argument values are not put in the system prompt; they go into the user prompt.
- For image input, keep the function-like style: declare explicit parameters as
ImgUrl, ImgPath, or lists/unions of those types. Use ImgUrl for web/data URLs and ImgPath for local files.
Write llm_function docstrings as task policy, quality bar, constraints, and style guidance. Do not waste docstring space restating parameter schemas or low-level output formatting that the framework already injects.
llm_chat
- Base system prompt is assembled from two sources:
DataFromAgentConfig (docstring + template params + tool specs)
DataFromSelfRef (if self_reference_key is set: experiences, summary, working messages)
- Then the framework prepends
<tool_best_practices> when tools exist.
- Then it appends a
<must_principles> block that tells the model to use native structured tool calls instead of writing fake tool calls in assistant text.
- Current turn data is added as the user message, not merged into the system prompt.
- For multimodal user turns, prefer
message: UserChatMessage and construct content with UserChatMessage.multimodal("text", ImgUrl(...), ImgPath(...)). This keeps llm_chat as an Agent abstraction over one explicit user message instead of many loosely named image parameters.
Write llm_chat docstrings as stable assistant policy and long-lived behavior. Put current task content in the function call arguments, not in the docstring.
Prompt-writing implications
- For
llm_function, think: function contract + execution strategy.
- For
llm_chat, think: assistant identity + durable rules.
- Put tool-usage advice in tool
best_practices when possible, not only in the main docstring.
- If you need to durably change a chat agent's context mid-run, use self-reference context helpers such as
runtime.selfref.context.remember(...) / runtime.selfref.context.compact(...) instead of trying to mutate old docstrings.
- When
llm_chat is bound to SelfReference, the framework syncs turn state automatically through the SelfRefSession ReAct lifecycle hooks. Treat runtime.selfref.context.* as the supported way to change durable context instead of editing old history messages in place.
Context-Centric architecture
The framework compiles each LLM request from three inputs:
- invocation configuration: docstring prompt, template params, tool guidance, output contract, and SelfRef snapshot;
- base transcript/history: previous messages plus the current user input;
- internal runtime patches:
ContextMutation objects produced by LLM calls, tools, SelfRef primitives, compaction, and abort/cancel handling.
The main internal rule is: runtime side effects must not directly edit the live transcript. They produce typed patches, and the compile boundary applies those patches in order before rendering the provider-facing message list.
This means same-turn selfref changes (remember, forget, compact) take effect at the next compile boundary, not immediately. It does not mean that docstrings, template params, tool schemas, or initial history are produced by mutations.
SelfRef: Meta Context Editing
SelfRef enables an agent to read and edit durable context at runtime, while respecting the internal transcript patch boundary:
| Operation |
What it does |
Internal patch produced |
| Remember |
Add durable experience that survives across turns |
ExperienceRememberMutation |
| Forget |
Remove experience by ID |
ExperienceForgetMutation |
| Compact |
Replace working transcript with a structured summary |
ContextSummaryMutation |
| Fork |
Spawn a child agent with inherited context snapshot |
(sub-agent runs independently) |
These operations take effect at the next compile boundary — SelfRef cannot bypass compile to modify the live transcript directly.
Fast start modes
- Project mode: load models from
provider.json with OpenAICompatible.load_from_json_file(...) or OpenAIResponsesCompatible.load_from_json_file(...) when you have shared config or multiple models.
- Instant mode: write a tiny
python - <<'PY' snippet, construct APIKeyPool plus the right adapter (OpenAICompatible or OpenAIResponsesCompatible) directly, decorate one function, and call it immediately.
- Prefer instant mode for quick shell usage, generated scripts, demos, and one-off local agents.
Interface choice
- Use
OpenAICompatible for normal OpenAI-style chat/completions endpoints.
- Use
OpenAIResponsesCompatible for OpenAI Responses API endpoints when you want the Responses transport while keeping the same decorator surface.
OpenAIResponsesCompatible uses the same provider.json shape and direct-construction shape as OpenAICompatible.
- When you use
OpenAIResponsesCompatible, the framework still builds normal chat/system messages first; the adapter maps the chosen system prompt to Responses instructions and forwards reasoning={...} kwargs.
- Keep prompt authoring the same across both adapters. Do not rewrite docstrings around raw Responses wire format.
Export the packaged skill
After installing SimpleLLMFunc, export the bundled Agent Skills with:
simplellmfunc-skill usage ~/.config/opencode/skills
simplellmfunc-skill developer ~/.config/opencode/skills
usage exports the simplellmfunc folder.
developer exports the simplellmfunc-developer folder.
- The second argument is the parent directory that receives the exported skill folder.
- Add
--force if you need to overwrite an existing exported copy.
Configuration essentials
Minimal provider.json shape
SimpleLLMFunc expects provider.json to be:
{
"openrouter": [
{
"model_name": "z-ai/glm-5",
"api_keys": ["sk-key-1", "sk-key-2"],
"base_url": "https://openrouter.ai/api/v1",
"max_retries": 5,
"retry_delay": 1.0,
"rate_limit_capacity": 20,
"rate_limit_refill_rate": 3.0
}
]
}
- Top level = provider id -> model config list.
- Lookup shape after loading =
providers[provider_id][model_name].
- Start from
examples/provider_template.json when possible.
How to organize provider.json
Treat provider.json as the canonical project-level model routing table, not as a random dump of keys.
Recommended organization rules:
- Group by provider first, then keep a short list of model configs under that provider.
- Keep
model_name values stable and unique within one provider.
- Put multiple keys under the same hot model in
api_keys instead of duplicating the model entry.
- Tune
max_retries, retry_delay, rate_limit_capacity, and rate_limit_refill_rate per model, not once globally.
- Keep the file focused on runtime model access concerns only: provider, model, keys, endpoint, retry, and rate limits.
- Use
OpenAICompatible.load_from_json_file(...) once near application setup, then pass resolved model handles into decorators.
Recommended mental model:
provider.json decides what model surface is available.
- typed decorators and tools decide how tasks are expressed.
- your harness decides what context reaches the model for one task.
.env and environment variables
The framework mainly reads .env / environment variables for logging and optional Langfuse observability.
LOG_LEVEL=WARNING
LOG_DIR=logs
LANGFUSE_PUBLIC_KEY=your_public_key
LANGFUSE_SECRET_KEY=your_secret_key
LANGFUSE_BASE_URL=https://cloud.langfuse.com
LANGFUSE_EXPORT_ALL_SPANS=true
LANGFUSE_ENABLED=true
- Precedence is: runtime environment variables ->
.env -> framework defaults.
- Recommended default:
LOG_LEVEL=WARNING to reduce noisy framework logs during normal agent usage.
provider.json is the main model/provider config file; .env is not the primary place for provider definitions in project mode.
- For shell-first one-offs, direct constructor snippets are still fine even without
provider.json.
Default workflow
- Choose the right surface:
@llm_function for one typed call.
@llm_chat for multi-turn agent behavior.
@tool for external capabilities the model may call.
PyRepl when the model needs persistent code execution or runtime primitives.
- Define everything as
async def.
- Write a precise docstring prompt and leave the body as
pass.
- Use explicit parameter types and a typed return value; prefer Pydantic for structured outputs.
- Build the model either from
provider.json or directly with APIKeyPool + OpenAICompatible / OpenAIResponsesCompatible.
- Add
toolkit=[...] only when the task truly needs tools.
- Validate with a focused example or event consumer.
Strong typing and Pydantic (Recommended default)
Prefer explicit, typed function contracts over loose string-in/string-out prompting.
Recommended rules:
- Use narrow, explicit parameter types.
- Use typed return values whenever the output shape matters.
- Prefer Pydantic models for structured outputs that must be stable, inspectable, or reusable.
- Let the framework derive structured parsing from the Python return type instead of asking the model to hand-roll JSON.
- Keep the docstring focused on task intent, quality bar, constraints, and edge cases, not schema duplication.
Good pattern:
from pydantic import BaseModel, Field
from SimpleLLMFunc import llm_function
class SearchSummary(BaseModel):
answer: str = Field(description="Direct answer to the user's question")
evidence: list[str] = Field(description="Supporting evidence bullets")
confidence: float = Field(description="0.0 to 1.0 confidence score")
@llm_function(llm_interface=llm)
async def summarize_search_results(query: str, snippets: list[str]) -> SearchSummary:
"""
Answer the query using the provided snippets.
Prefer concise claims backed by evidence.
If the snippets are insufficient, lower confidence and say so explicitly.
"""
pass
Prefer this over:
- returning a free-form string and parsing it manually later
- asking the model to invent ad hoc JSON output shapes in the docstring
- mixing many unrelated fields into one loose
dict[str, Any] unless the shape is genuinely variable
Use plain str returns only when free-form text is truly the product.
Harness Engineering: context planning comes first
When building on SimpleLLMFunc, think in terms of harness engineering.
Core philosophy:
- the main job is context planning
- an agent is not a person; it is a method for constructing the right context for each reasoning step
- at every step, the model should see the shortest clean context that is still complete for the current task
- the system should be designed so this context can be maintained in a durable closed loop
What this means:
- when an agent fails, first ask what was missing from the environment or context
- encode the fix into the environment itself: tools, checks, constraints, docs, files, or workflow structure
- do not rely on the operator remembering the lesson in their head
Core beliefs:
- model capability is usually not the bottleneck; context quality is
- broad context stuffing is usually worse than precise context planning
- agent failures are predictable and should map to concrete harness changes
- cross-session memory must be rebuilt from external state, not assumed
Practical implications:
- choose
@llm_function when one typed transformation gives the cleanest context
- choose
@llm_chat only when multi-turn state genuinely improves the context for the task
- decide between keeping history, forking, or using a sub-agent based only on which option yields the most accurate and compact context for the current reasoning step
- persist state, progress, and important decisions outside the model so a fresh session can reconstruct context deterministically
- prefer tools and checks that let the system verify itself instead of asking the model to self-certify completion
- treat noisy, stale, or weakly relevant context as a design bug
If you remember only one rule, remember this:
- always construct the cleanest, most task-relevant, shortest complete context the system can maintain in a closed loop
Best-practice patterns
Build a general agent (Recommended starting point)
from SimpleLLMFunc import llm_chat, OpenAICompatible, tui
from SimpleLLMFunc.builtin import PyRepl, FileToolset
llm = OpenAICompatible.load_from_json_file("provider.json")["openrouter"]["gpt-5.4"]
repl = PyRepl()
file_tools = FileToolset("./sandbox").toolset
@tui
@llm_chat(
llm_interface=llm,
toolkit=[*repl.toolset, *file_tools],
stream=True,
self_reference_key="agent_main",
)
async def agent(message: str, history=None):
"""You are a practical local coding agent.
## Rules
- Read files before editing. Prefer small, local edits.
- Use execute_code for Python. Use file tools for read/grep/sed.
- When a milestone is done, compact your context via:
runtime.selfref.context.compact(...)
- For parallel subtasks, spawn forks via:
runtime.selfref.fork.spawn(...)
then gather with runtime.selfref.fork.gather_all(...)
"""
if __name__ == "__main__":
agent() # launches an interactive TUI
Instant shell-first usage (Recommended for one-offs)
Use the direct constructor path when you want to turn SimpleLLMFunc into a shell ability with almost no setup besides pasting your literal model settings.
python - <<'PY'
import asyncio
from SimpleLLMFunc import APIKeyPool, OpenAICompatible, llm_function
llm = OpenAICompatible(
api_key_pool=APIKeyPool(
api_keys=["sk-your-key"],
provider_id="openrouter-z-ai-glm-5",
),
model_name="z-ai/glm-5",
base_url="https://openrouter.ai/api/v1",
)
@llm_function(llm_interface=llm)
async def answer(question: str) -> str:
"""Answer the question in a compact, practical way."""
pass
print(asyncio.run(answer("Give me three uses of SimpleLLMFunc.")))
PY
For a shell agent with file tools and REPL, see reference/instant-use.md and examples/instant_chat_agent.py.
Typed llm_function
import asyncio
from pydantic import BaseModel, Field
from SimpleLLMFunc import OpenAICompatible, llm_function
class SentimentReport(BaseModel):
sentiment: str = Field(description="positive, negative, or neutral")
confidence: float = Field(description="0.0 to 1.0 confidence score")
summary: str = Field(description="one-sentence explanation")
models = OpenAICompatible.load_from_json_file("provider.json")
llm = models["openrouter"]["z-ai/glm-5"]
@llm_function(llm_interface=llm)
async def classify_sentiment(text: str) -> SentimentReport:
"""
Classify the sentiment of the input text.
Args:
text: The user text to analyze.
Returns:
A structured sentiment report.
"""
pass
async def main() -> None:
result = await classify_sentiment("The setup was rough, but the product is excellent.")
print(result.model_dump())
asyncio.run(main())
Chat agent with a tool
import asyncio
from SimpleLLMFunc import OpenAICompatible, llm_chat, tool
from SimpleLLMFunc.hooks.stream import is_response_yield
@tool
async def multiply(a: float, b: float) -> float:
"""
Multiply two numbers.
Args:
a: First factor.
b: Second factor.
Returns:
Product of the two inputs.
"""
return a * b
models = OpenAICompatible.load_from_json_file("provider.json")
llm = models["openrouter"]["z-ai/glm-5"]
@llm_chat(llm_interface=llm, toolkit=[multiply], stream=True)
async def tutor(message: str, history: list[dict[str, str]] | None = None):
"""
You are a concise math tutor.
Use the multiply tool when arithmetic is requested.
"""
pass
async def main() -> None:
history: list[dict[str, str]] = []
async for output in tutor("What is 12.5 times 8?", history):
if is_response_yield(output):
print(output.response, end="")
history = output.messages
asyncio.run(main())
Persistent runtime with PyRepl
import asyncio
from SimpleLLMFunc.builtin import PyRepl, SelfReference
MEMORY_KEY = "agent_main"
async def main() -> None:
repl = PyRepl()
selfref = repl.get_runtime_backend("selfref")
assert isinstance(selfref, SelfReference)
selfref.bind_history(
MEMORY_KEY,
[{"role": "system", "content": "Answer in bullet points."}],
)
result = await repl.execute(
"runtime.selfref.context.remember('remember this')\n"
"snapshot = runtime.selfref.context.inspect()\n"
"print(len(snapshot['experiences']))"
)
print(result["stdout"])
asyncio.run(main())
Hard rules and gotchas
- Default to
async def for all decorators. @tool enforces async directly.
- The function body does not implement behavior; the docstring does.
- The docstring is usually only the base material for prompt construction, not the entire final system prompt.
- For
llm_chat, name the history parameter history or chat_history.
- The current direct-construction path for
OpenAICompatible requires APIKeyPool; do not assume a simplified api_key= constructor exists.
- Instant snippets can call the decorated function directly at top level with
asyncio.run(...); no __main__ guard is required.
max_tool_calls=None means no framework-imposed tool-call cap. Set an explicit integer if you need a guardrail.
- Complex structured outputs are parsed from XML-oriented contracts internally. Do not manually force JSON unless you intentionally want plain-text behavior.
@llm_function returns an LLMFunction callable instance; use await fn(...) for the parsed result and fn.stream(...) for ReactOutput.
@llm_chat returns an LLMChat callable instance; calling it always produces ReactOutput events/responses. Consume with async for output in agent(...) and use is_event_yield(output) / is_response_yield(output) to route.
- There is no
enable_event or return_mode decorator option. Do not write old (chunk, history) consumers.
too_long_to_file=True keeps roughly the first 20000 tokens in chat and writes the full tool result to a temp file.
PyRepl.reset() clears REPL variables but keeps runtime backends and self-reference memory.
execute_code returns image-producing code as multimodal tool output when code uses display(Image(...)), returns an image-rich last expression, or returns ImgPath / ImgUrl.
- In 0.8.1, PyRepl and SelfRef were internally split into facade/component modules, but application code should continue using the same public surfaces:
PyRepl, SelfReference, runtime.selfref.context.*, and runtime.selfref.fork.*.
runtime.selfref.context.compact(...) is queued first. When called from a tool run, the compacted context is applied before the next same-turn LLM step when possible, and finalize still commits any leftover queued compaction before the turn ends.
OpenAIResponsesCompatible is a first-class adapter. It maps the selected system prompt to Responses instructions, supports reasoning={...}, and keeps Responses-specific request/stream behavior out of your decorator code.
runtime.selfref.fork.spawn(...) children inherit the pre-fork context snapshot, not the parent's in-flight fork tool-call scene.
runtime.selfref.fork.gather_all(...) returns dict[fork_id -> ForkResult]. Check status first, then read response or result; compact results omit child history unless you request include_history=True.
FileToolset is workspace-scoped and read-before-write guarded.
- Runtime side effects must go through the internal patch boundary. Do not try to directly modify the message list of a running agent — use
runtime.selfref.context.* primitives instead.
Load more context only when needed
- Philosophy and core concepts:
reference/philosophy-and-concepts.md
- Harness engineering guidance:
reference/harness-engineering.md
- System prompt construction and prompt-writing rules:
reference/system-prompt-construction.md
- Instant shell-first setup and constructor usage:
reference/instant-use.md
- Provider and environment setup:
reference/configuration.md
- Decorators, tools, file tools, and event streams:
reference/decorators-and-tools.md
- PyRepl, runtime primitives, and selfref:
reference/pyrepl-runtime.md
- Non-obvious behavior:
reference/gotchas.md
- Mirrored repo docs:
reference/docs-source/quickstart.md, reference/docs-source/guide.md, reference/docs-source/detailed_guide/
- Instant heredoc examples:
examples/instant_llm_function.py, examples/instant_chat_agent.py
- Real repo examples:
examples/agent_as_tool_example.py, examples/llm_function_pydantic_example.py, examples/runtime_primitives_basic_example.py, examples/tui_general_agent_example.py
1---2name: simplellmfunc3description: Use SimpleLLMFunc to build typed async LLM functions, chat agents, tools, event-stream consumers, and PyRepl/selfref workflows. Use when writing or editing app code that imports SimpleLLMFunc, configures provider.json, adds @llm_function/@llm_chat/@tool, chooses OpenAICompatible or OpenAIResponsesCompatible, or mounts PyRepl/FileToolset.4license: MIT5---67# SimpleLLMFunc Usage89## When to use this skill10- Use this skill for application-level work built on top of SimpleLLMFunc.11- Use it when the task mentions `llm_function`, `llm_chat`, `tool`, `OpenAICompatible`, `OpenAIResponsesCompatible`, `provider.json`, `reasoning`, `PyRepl`, `SelfReference`, `FileToolset`, or the built-in TUI.12- Do not use this skill for framework-internal refactors; use `simplellmfunc-developer` for that.1314## Core philosophy15- **LLM is Function**: treat the LLM call like a normal Python function call — signature, type hints, return value.16- **Prompt as Code**: put the prompt in the function docstring. Code and prompt are never separated.17- **Context-Centric**: each LLM request is compiled from invocation configuration, a base transcript/history, and internal runtime patches. `ContextMutation` is the internal patch protocol that prevents tools and SelfRef from directly modifying the live ReAct transcript.18- Keep orchestration in Python instead of hiding it in giant prompt strings.19- Prefer small, typed, composable building blocks.2021## How system prompts are really built2223This is critical for writing good prompts in SimpleLLMFunc: your docstring is important, but it is usually not the whole final system prompt.2425### `llm_function`26- Your docstring is first treated as `function_description`.27- If you pass `_template_params`, the docstring is formatted before prompt construction.28- The framework then wraps that docstring inside a system template that also adds:29 - parameter type descriptions30 - return-type instructions31 - plain-text or XML output constraints depending on the return type32- If tools are mounted, the framework prepends a deduplicated `<tool_best_practices>` block before the main system prompt.33- Runtime argument values are not put in the system prompt; they go into the user prompt.34- For image input, keep the function-like style: declare explicit parameters as `ImgUrl`, `ImgPath`, or lists/unions of those types. Use `ImgUrl` for web/data URLs and `ImgPath` for local files.3536Write `llm_function` docstrings as task policy, quality bar, constraints, and style guidance. Do not waste docstring space restating parameter schemas or low-level output formatting that the framework already injects.3738### `llm_chat`39- Base system prompt is assembled from two sources:40 - `DataFromAgentConfig` (docstring + template params + tool specs)41 - `DataFromSelfRef` (if `self_reference_key` is set: experiences, summary, working messages)42- Then the framework prepends `<tool_best_practices>` when tools exist.43- Then it appends a `<must_principles>` block that tells the model to use native structured tool calls instead of writing fake tool calls in assistant text.44- Current turn data is added as the user message, not merged into the system prompt.45- For multimodal user turns, prefer `message: UserChatMessage` and construct content with `UserChatMessage.multimodal("text", ImgUrl(...), ImgPath(...))`. This keeps `llm_chat` as an Agent abstraction over one explicit user message instead of many loosely named image parameters.4647Write `llm_chat` docstrings as stable assistant policy and long-lived behavior. Put current task content in the function call arguments, not in the docstring.4849### Prompt-writing implications50- For `llm_function`, think: function contract + execution strategy.51- For `llm_chat`, think: assistant identity + durable rules.52- Put tool-usage advice in tool `best_practices` when possible, not only in the main docstring.53- If you need to durably change a chat agent's context mid-run, use self-reference context helpers such as `runtime.selfref.context.remember(...)` / `runtime.selfref.context.compact(...)` instead of trying to mutate old docstrings.54- When `llm_chat` is bound to `SelfReference`, the framework syncs turn state automatically through the `SelfRefSession` ReAct lifecycle hooks. Treat `runtime.selfref.context.*` as the supported way to change durable context instead of editing old history messages in place.5556## Context-Centric architecture5758The framework compiles each LLM request from three inputs:59601. invocation configuration: docstring prompt, template params, tool guidance, output contract, and SelfRef snapshot;612. base transcript/history: previous messages plus the current user input;623. internal runtime patches: `ContextMutation` objects produced by LLM calls, tools, SelfRef primitives, compaction, and abort/cancel handling.6364The main internal rule is: **runtime side effects must not directly edit the live transcript**. They produce typed patches, and the compile boundary applies those patches in order before rendering the provider-facing message list.6566This means same-turn selfref changes (remember, forget, compact) take effect at the next compile boundary, not immediately. It does not mean that docstrings, template params, tool schemas, or initial history are produced by mutations.6768### SelfRef: Meta Context Editing6970SelfRef enables an agent to read and edit durable context at runtime, while respecting the internal transcript patch boundary:7172| Operation | What it does | Internal patch produced |73|-----------|-------------|-------------------------|74| **Remember** | Add durable experience that survives across turns | `ExperienceRememberMutation` |75| **Forget** | Remove experience by ID | `ExperienceForgetMutation` |76| **Compact** | Replace working transcript with a structured summary | `ContextSummaryMutation` |77| **Fork** | Spawn a child agent with inherited context snapshot | (sub-agent runs independently) |7879These operations take effect at the next compile boundary — SelfRef cannot bypass compile to modify the live transcript directly.8081## Fast start modes82- Project mode: load models from `provider.json` with `OpenAICompatible.load_from_json_file(...)` or `OpenAIResponsesCompatible.load_from_json_file(...)` when you have shared config or multiple models.83- Instant mode: write a tiny `python - <<'PY'` snippet, construct `APIKeyPool` plus the right adapter (`OpenAICompatible` or `OpenAIResponsesCompatible`) directly, decorate one function, and call it immediately.84- Prefer instant mode for quick shell usage, generated scripts, demos, and one-off local agents.8586## Interface choice87- Use `OpenAICompatible` for normal OpenAI-style chat/completions endpoints.88- Use `OpenAIResponsesCompatible` for OpenAI Responses API endpoints when you want the Responses transport while keeping the same decorator surface.89- `OpenAIResponsesCompatible` uses the same `provider.json` shape and direct-construction shape as `OpenAICompatible`.90- When you use `OpenAIResponsesCompatible`, the framework still builds normal chat/system messages first; the adapter maps the chosen system prompt to Responses `instructions` and forwards `reasoning={...}` kwargs.91- Keep prompt authoring the same across both adapters. Do not rewrite docstrings around raw Responses wire format.9293## Export the packaged skill9495After installing `SimpleLLMFunc`, export the bundled Agent Skills with:9697```bash98simplellmfunc-skill usage ~/.config/opencode/skills99simplellmfunc-skill developer ~/.config/opencode/skills100```101102- `usage` exports the `simplellmfunc` folder.103- `developer` exports the `simplellmfunc-developer` folder.104- The second argument is the parent directory that receives the exported skill folder.105- Add `--force` if you need to overwrite an existing exported copy.106107## Configuration essentials108109### Minimal `provider.json` shape110111SimpleLLMFunc expects `provider.json` to be:112113```json114{115 "openrouter": [116 {117 "model_name": "z-ai/glm-5",118 "api_keys": ["sk-key-1", "sk-key-2"],119 "base_url": "https://openrouter.ai/api/v1",120 "max_retries": 5,121 "retry_delay": 1.0,122 "rate_limit_capacity": 20,123 "rate_limit_refill_rate": 3.0124 }125 ]126}127```128129- Top level = provider id -> model config list.130- Lookup shape after loading = `providers[provider_id][model_name]`.131- Start from `examples/provider_template.json` when possible.132133### How to organize `provider.json`134135Treat `provider.json` as the canonical project-level model routing table, not as a random dump of keys.136137Recommended organization rules:138139- Group by provider first, then keep a short list of model configs under that provider.140- Keep `model_name` values stable and unique within one provider.141- Put multiple keys under the same hot model in `api_keys` instead of duplicating the model entry.142- Tune `max_retries`, `retry_delay`, `rate_limit_capacity`, and `rate_limit_refill_rate` per model, not once globally.143- Keep the file focused on runtime model access concerns only: provider, model, keys, endpoint, retry, and rate limits.144- Use `OpenAICompatible.load_from_json_file(...)` once near application setup, then pass resolved model handles into decorators.145146Recommended mental model:147148- `provider.json` decides **what model surface is available**.149- typed decorators and tools decide **how tasks are expressed**.150- your harness decides **what context reaches the model for one task**.151152### `.env` and environment variables153154The framework mainly reads `.env` / environment variables for logging and optional Langfuse observability.155156```bash157LOG_LEVEL=WARNING158LOG_DIR=logs159160LANGFUSE_PUBLIC_KEY=your_public_key161LANGFUSE_SECRET_KEY=your_secret_key162LANGFUSE_BASE_URL=https://cloud.langfuse.com163LANGFUSE_EXPORT_ALL_SPANS=true164LANGFUSE_ENABLED=true165```166167- Precedence is: runtime environment variables -> `.env` -> framework defaults.168- Recommended default: `LOG_LEVEL=WARNING` to reduce noisy framework logs during normal agent usage.169- `provider.json` is the main model/provider config file; `.env` is not the primary place for provider definitions in project mode.170- For shell-first one-offs, direct constructor snippets are still fine even without `provider.json`.171172## Default workflow1731. Choose the right surface:174 - `@llm_function` for one typed call.175 - `@llm_chat` for multi-turn agent behavior.176 - `@tool` for external capabilities the model may call.177 - `PyRepl` when the model needs persistent code execution or runtime primitives.1782. Define everything as `async def`.1793. Write a precise docstring prompt and leave the body as `pass`.1804. Use explicit parameter types and a typed return value; prefer Pydantic for structured outputs.1815. Build the model either from `provider.json` or directly with `APIKeyPool` + `OpenAICompatible` / `OpenAIResponsesCompatible`.1826. Add `toolkit=[...]` only when the task truly needs tools.1837. Validate with a focused example or event consumer.184185## Strong typing and Pydantic (Recommended default)186187Prefer explicit, typed function contracts over loose string-in/string-out prompting.188189Recommended rules:190191- Use narrow, explicit parameter types.192- Use typed return values whenever the output shape matters.193- Prefer Pydantic models for structured outputs that must be stable, inspectable, or reusable.194- Let the framework derive structured parsing from the Python return type instead of asking the model to hand-roll JSON.195- Keep the docstring focused on task intent, quality bar, constraints, and edge cases, not schema duplication.196197Good pattern:198199```python200from pydantic import BaseModel, Field201202from SimpleLLMFunc import llm_function203204205class SearchSummary(BaseModel):206 answer: str = Field(description="Direct answer to the user's question")207 evidence: list[str] = Field(description="Supporting evidence bullets")208 confidence: float = Field(description="0.0 to 1.0 confidence score")209210211@llm_function(llm_interface=llm)212async def summarize_search_results(query: str, snippets: list[str]) -> SearchSummary:213 """214 Answer the query using the provided snippets.215216 Prefer concise claims backed by evidence.217 If the snippets are insufficient, lower confidence and say so explicitly.218 """219 pass220```221222Prefer this over:223224- returning a free-form string and parsing it manually later225- asking the model to invent ad hoc JSON output shapes in the docstring226- mixing many unrelated fields into one loose `dict[str, Any]` unless the shape is genuinely variable227228Use plain `str` returns only when free-form text is truly the product.229230## Harness Engineering: context planning comes first231232When building on SimpleLLMFunc, think in terms of harness engineering.233234Core philosophy:235236- the main job is context planning237- an agent is not a person; it is a method for constructing the right context for each reasoning step238- at every step, the model should see the shortest clean context that is still complete for the current task239- the system should be designed so this context can be maintained in a durable closed loop240241What this means:242243- when an agent fails, first ask what was missing from the environment or context244- encode the fix into the environment itself: tools, checks, constraints, docs, files, or workflow structure245- do not rely on the operator remembering the lesson in their head246247Core beliefs:248249- model capability is usually not the bottleneck; context quality is250- broad context stuffing is usually worse than precise context planning251- agent failures are predictable and should map to concrete harness changes252- cross-session memory must be rebuilt from external state, not assumed253254Practical implications:255256- choose `@llm_function` when one typed transformation gives the cleanest context257- choose `@llm_chat` only when multi-turn state genuinely improves the context for the task258- decide between keeping history, forking, or using a sub-agent based only on which option yields the most accurate and compact context for the current reasoning step259- persist state, progress, and important decisions outside the model so a fresh session can reconstruct context deterministically260- prefer tools and checks that let the system verify itself instead of asking the model to self-certify completion261- treat noisy, stale, or weakly relevant context as a design bug262263If you remember only one rule, remember this:264265- always construct the cleanest, most task-relevant, shortest complete context the system can maintain in a closed loop266267## Best-practice patterns268269### Build a general agent (Recommended starting point)270271```python272from SimpleLLMFunc import llm_chat, OpenAICompatible, tui273from SimpleLLMFunc.builtin import PyRepl, FileToolset274275llm = OpenAICompatible.load_from_json_file("provider.json")["openrouter"]["gpt-5.4"]276repl = PyRepl()277file_tools = FileToolset("./sandbox").toolset278279@tui280@llm_chat(281 llm_interface=llm,282 toolkit=[*repl.toolset, *file_tools],283 stream=True,284 self_reference_key="agent_main",285)286async def agent(message: str, history=None):287 """You are a practical local coding agent.288289 ## Rules290 - Read files before editing. Prefer small, local edits.291 - Use execute_code for Python. Use file tools for read/grep/sed.292 - When a milestone is done, compact your context via:293 runtime.selfref.context.compact(...)294 - For parallel subtasks, spawn forks via:295 runtime.selfref.fork.spawn(...)296 then gather with runtime.selfref.fork.gather_all(...)297 """298299if __name__ == "__main__":300 agent() # launches an interactive TUI301```302303### Instant shell-first usage (Recommended for one-offs)304305Use the direct constructor path when you want to turn SimpleLLMFunc into a shell ability with almost no setup besides pasting your literal model settings.306307```bash308python - <<'PY'309import asyncio310311from SimpleLLMFunc import APIKeyPool, OpenAICompatible, llm_function312313314llm = OpenAICompatible(315 api_key_pool=APIKeyPool(316 api_keys=["sk-your-key"],317 provider_id="openrouter-z-ai-glm-5",318 ),319 model_name="z-ai/glm-5",320 base_url="https://openrouter.ai/api/v1",321)322323324@llm_function(llm_interface=llm)325async def answer(question: str) -> str:326 """Answer the question in a compact, practical way."""327 pass328329330print(asyncio.run(answer("Give me three uses of SimpleLLMFunc.")))331PY332```333334For a shell agent with file tools and REPL, see `reference/instant-use.md` and `examples/instant_chat_agent.py`.335336### Typed `llm_function`337338```python339import asyncio340341from pydantic import BaseModel, Field342343from SimpleLLMFunc import OpenAICompatible, llm_function344345346class SentimentReport(BaseModel):347 sentiment: str = Field(description="positive, negative, or neutral")348 confidence: float = Field(description="0.0 to 1.0 confidence score")349 summary: str = Field(description="one-sentence explanation")350351352models = OpenAICompatible.load_from_json_file("provider.json")353llm = models["openrouter"]["z-ai/glm-5"]354355356@llm_function(llm_interface=llm)357async def classify_sentiment(text: str) -> SentimentReport:358 """359 Classify the sentiment of the input text.360361 Args:362 text: The user text to analyze.363364 Returns:365 A structured sentiment report.366 """367 pass368369370async def main() -> None:371 result = await classify_sentiment("The setup was rough, but the product is excellent.")372 print(result.model_dump())373374375asyncio.run(main())376```377378### Chat agent with a tool379380```python381import asyncio382383from SimpleLLMFunc import OpenAICompatible, llm_chat, tool384from SimpleLLMFunc.hooks.stream import is_response_yield385386387@tool388async def multiply(a: float, b: float) -> float:389 """390 Multiply two numbers.391392 Args:393 a: First factor.394 b: Second factor.395396 Returns:397 Product of the two inputs.398 """399 return a * b400401402models = OpenAICompatible.load_from_json_file("provider.json")403llm = models["openrouter"]["z-ai/glm-5"]404405406@llm_chat(llm_interface=llm, toolkit=[multiply], stream=True)407async def tutor(message: str, history: list[dict[str, str]] | None = None):408 """409 You are a concise math tutor.410 Use the multiply tool when arithmetic is requested.411 """412 pass413414415async def main() -> None:416 history: list[dict[str, str]] = []417 async for output in tutor("What is 12.5 times 8?", history):418 if is_response_yield(output):419 print(output.response, end="")420 history = output.messages421422423asyncio.run(main())424```425426### Persistent runtime with `PyRepl`427428```python429import asyncio430431from SimpleLLMFunc.builtin import PyRepl, SelfReference432433434MEMORY_KEY = "agent_main"435436437async def main() -> None:438 repl = PyRepl()439 selfref = repl.get_runtime_backend("selfref")440 assert isinstance(selfref, SelfReference)441442 selfref.bind_history(443 MEMORY_KEY,444 [{"role": "system", "content": "Answer in bullet points."}],445 )446447 result = await repl.execute(448 "runtime.selfref.context.remember('remember this')\n"449 "snapshot = runtime.selfref.context.inspect()\n"450 "print(len(snapshot['experiences']))"451 )452 print(result["stdout"])453454455asyncio.run(main())456```457458## Hard rules and gotchas459- Default to `async def` for all decorators. `@tool` enforces async directly.460- The function body does not implement behavior; the docstring does.461- The docstring is usually only the base material for prompt construction, not the entire final system prompt.462- For `llm_chat`, name the history parameter `history` or `chat_history`.463- The current direct-construction path for `OpenAICompatible` requires `APIKeyPool`; do not assume a simplified `api_key=` constructor exists.464- Instant snippets can call the decorated function directly at top level with `asyncio.run(...)`; no `__main__` guard is required.465- `max_tool_calls=None` means no framework-imposed tool-call cap. Set an explicit integer if you need a guardrail.466- Complex structured outputs are parsed from XML-oriented contracts internally. Do not manually force JSON unless you intentionally want plain-text behavior.467- `@llm_function` returns an `LLMFunction` callable instance; use `await fn(...)` for the parsed result and `fn.stream(...)` for `ReactOutput`.468- `@llm_chat` returns an `LLMChat` callable instance; calling it always produces `ReactOutput` events/responses. Consume with `async for output in agent(...)` and use `is_event_yield(output)` / `is_response_yield(output)` to route.469- There is no `enable_event` or `return_mode` decorator option. Do not write old `(chunk, history)` consumers.470- `too_long_to_file=True` keeps roughly the first 20000 tokens in chat and writes the full tool result to a temp file.471- `PyRepl.reset()` clears REPL variables but keeps runtime backends and self-reference memory.472- `execute_code` returns image-producing code as multimodal tool output when code uses `display(Image(...))`, returns an image-rich last expression, or returns `ImgPath` / `ImgUrl`.473- In 0.8.1, PyRepl and SelfRef were internally split into facade/component modules, but application code should continue using the same public surfaces: `PyRepl`, `SelfReference`, `runtime.selfref.context.*`, and `runtime.selfref.fork.*`.474- `runtime.selfref.context.compact(...)` is queued first. When called from a tool run, the compacted context is applied before the next same-turn LLM step when possible, and finalize still commits any leftover queued compaction before the turn ends.475- `OpenAIResponsesCompatible` is a first-class adapter. It maps the selected system prompt to Responses `instructions`, supports `reasoning={...}`, and keeps Responses-specific request/stream behavior out of your decorator code.476- `runtime.selfref.fork.spawn(...)` children inherit the pre-fork context snapshot, not the parent's in-flight fork tool-call scene.477- `runtime.selfref.fork.gather_all(...)` returns `dict[fork_id -> ForkResult]`. Check `status` first, then read `response` or `result`; compact results omit child history unless you request `include_history=True`.478- `FileToolset` is workspace-scoped and read-before-write guarded.479- Runtime side effects must go through the internal patch boundary. Do not try to directly modify the message list of a running agent — use `runtime.selfref.context.*` primitives instead.480481## Load more context only when needed482- Philosophy and core concepts: `reference/philosophy-and-concepts.md`483- Harness engineering guidance: `reference/harness-engineering.md`484- System prompt construction and prompt-writing rules: `reference/system-prompt-construction.md`485- Instant shell-first setup and constructor usage: `reference/instant-use.md`486- Provider and environment setup: `reference/configuration.md`487- Decorators, tools, file tools, and event streams: `reference/decorators-and-tools.md`488- PyRepl, runtime primitives, and selfref: `reference/pyrepl-runtime.md`489- Non-obvious behavior: `reference/gotchas.md`490- Mirrored repo docs: `reference/docs-source/quickstart.md`, `reference/docs-source/guide.md`, `reference/docs-source/detailed_guide/`491- Instant heredoc examples: `examples/instant_llm_function.py`, `examples/instant_chat_agent.py`492- Real repo examples: `examples/agent_as_tool_example.py`, `examples/llm_function_pydantic_example.py`, `examples/runtime_primitives_basic_example.py`, `examples/tui_general_agent_example.py`