SimpleLLMFunc - AI Coding Agent Instructions
📋 Project Overview
SimpleLLMFunc is a lightweight LLM application framework enabling developers to write LLM-powered workflows and agents using Python decorators with DocString-as-Prompt philosophy. Core idea: "Everything is Function, Prompt is Code".
🏗 Architecture & Key Components
1. Core Design Pattern: Prompt as DocString
- Function behavior is defined in the DocString, not the function body (which stays empty with
pass)
- Decorators (
@llm_function, @llm_chat, @tool) intercept function calls and delegate to LLM
- Type hints on parameters and return types ensure type safety and automatic validation
- Example from
/SimpleLLMFunc/llm_decorator/llm_function_decorator.py:@llm_function(llm_interface=my_llm)
async def analyze_product_review(product_name: str, review_text: str) -> ProductReview:
"""You are a product review expert analyzing the following review...
Args:
product_name: Name of the product
review_text: User review content
Returns:
Structured ProductReview object with rating, pros, cons, summary
"""
pass # Prompt as Code - LLM handles the actual execution
2. Async-First Architecture
- All LLM decorators ONLY support
async def functions (non-negotiable requirement)
- Use
await when calling decorated functions or asyncio.run() at top level
- Example from examples/event_stream_chatbot.py: consume
ResponseYield / EventYield in an async loop
- Enables high-concurrency API calls via native asyncio integration
3. Three-Tier Component Stack
Tier 1: Decorators (llm_decorator/)
@llm_function: Stateless single-call transformations (request → response)
@llm_chat: Multi-turn conversations with history management
@tool: Tool definitions for use by agents (async functions only)
- All accept
toolkit parameter for tool composition
- Support
_template_params for dynamic DocString templating (v0.2.14+)
Tier 2: LLM Interface (interface/)
LLM_Interface (abstract): Base contract for all LLM implementations
OpenAICompatible: Universal implementation for any OpenAI-compatible API
- Loads config from JSON files via
load_from_json_file() method
- Auto-routes to correct provider/model from hierarchical config
APIKeyPool: Min-heap based load balancing for multiple API keys
TokenBucket: Rate limiting to prevent API throttling
Tier 3: Base Modules (base/)
messages/: Constructs system/user prompts + handles multimodal content
post_process.py: Deserializes LLM responses to target types
type_resolve/: Analyzes function signatures for type information
ReAct.py: Orchestrates LLM calls with tool execution loops (max 5 by default)
tool_call/: Tool extraction, invocation, and result validation
4. Configuration & Providers
.env file: LOG_LEVEL=DEBUG setting
provider.json: Hierarchical structure with vendor → model → credentials{
"volc_engine": {
"deepseek-v3-250324": {
"api_keys": ["key1", "key2"],
"base_url": "https://api.volc.example.com/v1",
"model": "deepseek-chat",
"rate_limit_capacity": 10
}
}
}
- Load via:
OpenAICompatible.load_from_json_file("provider.json")["volc_engine"]["deepseek-v3-250324"]
5. Logging & Tracing System (logger/)
- Console-only output (no file persistence in v0.2.13+)
- Auto-generated
trace_id in format {func_name}_{uuid} for correlated logging
- Context manager:
async_log_context(trace_id=..., function_name=...)
- Functions:
app_log(), push_error(), push_debug(), push_warning()
- Structured context inheritance across async calls
- See
logger/core.py for logger setup
🔄 Execution Data Flow
Standard llm_function Flow:
- Call Capture → Decorator intercepts
async def my_func(...)
- Argument Binding → Map args/kwargs to function signature
- Prompt Construction:
- System prompt = function's DocString + custom template (if provided)
- User prompt = formatted arguments + type descriptions
- LLM Invocation → Send to
llm_interface.chat(messages=[...])
- Tool Loop (if toolkit provided):
- Check if LLM invoked tools via
tool_calls field
- Execute tools via
base/ReAct.py (max 5 iterations)
- Include tool results in next LLM message
- Response Deserialization → Convert LLM text output to return type
- If return type is Pydantic model: auto-parse JSON
- If return type is primitive: direct conversion
llm_chat Flow (Multi-turn):
- Accepts
history: List[Dict[str, str]] parameter
- Maintains conversation state across calls
- Yields tuples of
(content_chunk, updated_history) in stream mode
- Returns mode (
text vs raw) affects tool call visibility in history
- Final yield contains only
("", updated_history) to signal completion
🛠 Tool System Patterns
Function Decorator Way (Recommended):
from SimpleLLMFunc.tool import tool
from SimpleLLMFunc.type import ImgPath, ImgUrl
@tool(name="get_weather", description="Get weather for location")
async def get_weather(location: str, days: int = 1) -> dict:
"""Get weather forecast
Args:
location: City name or coordinates
days: Forecast days (default 1)
Returns:
Weather data dictionary
"""
# Actual implementation
return {"location": location, "forecast": [...]}
# Multimodal tool returns (v0.2.10+):
@tool(name="generate_chart", description="Generate chart image")
async def generate_chart(data: str) -> ImgPath:
"""Return local image path for tool use"""
return ImgPath("/path/to/chart.png")
@tool(name="search_image", description="Search web images")
async def search_image(query: str) -> ImgUrl:
"""Return web image URL for tool use"""
return ImgUrl("https://example.com/image.jpg")
Usage in Decorators:
@llm_function(
llm_interface=llm,
toolkit=[get_weather, generate_chart] # Pass decorated functions directly
)
async def plan_trip(destination: str) -> str:
"""Plan a trip using tools to get weather and generate itinerary"""
pass
🚀 Development Patterns
1. Type Safety through Pydantic
- Always use Pydantic
BaseModel for complex return types
- Define
Field(..., description="...") for LLM understanding
- Example from README.md
ProductReview:class ProductReview(BaseModel):
rating: int = Field(..., description="1-5 star rating")
pros: List[str] = Field(..., description="List of advantages")
cons: List[str] = Field(..., description="List of disadvantages")
summary: str = Field(..., description="Summary text")
2. Multimodal Input Handling
- Use
Text, ImgUrl, ImgPath from SimpleLLMFunc.type
- Framework auto-converts to proper message format
- Example:
from SimpleLLMFunc.type import Text, ImgUrl, ImgPath
@llm_function(llm_interface=llm)
async def analyze_images(
description: Text,
web_img: ImgUrl,
local_img: ImgPath
) -> str:
"""Analyze and compare images"""
pass
3. Custom Templates for Reusable Functions
- Use
system_prompt_template / user_prompt_template parameters
- Reference variables with
{variable_name} syntax
- Combine with
_template_params at call time (v0.2.14+):@llm_function(
llm_interface=llm,
system_prompt_template="You are a {role} expert..."
)
async def expert_analysis(topic: str) -> str:
"""Analyze the topic"""
pass
# Call with different roles:
result = await expert_analysis(
topic="Python design patterns",
_template_params={"role": "Software Architecture"}
)
4. Error Handling & Validation
5. Logging Integration
from SimpleLLMFunc.logger import app_log, async_log_context
@llm_function(llm_interface=llm)
async def my_function(text: str) -> str:
"""Process text"""
pass
# In calling code:
async with async_log_context(trace_id="custom_id", function_name="my_func"):
result = await my_function("input")
app_log("Processing complete") # Auto-inherits trace_id
📁 Critical Files by Use Case
| Use Case |
Key Files |
| Add new LLM provider |
interface/openai_compatible.py, update provider.json |
| Create LLM function |
llm_decorator/llm_function_decorator.py (see flow 219-260) |
| Create agent with tools |
llm_decorator/llm_chat_decorator.py, tool/tool.py |
| Debug tool invocation |
base/ReAct.py (tool loop orchestration), base/tool_call/execution.py |
| Extend return types |
base/post_process.py, base/type_resolve/ |
| Customize prompts |
base/messages/ (template and message assembly logic) |
⚠️ Common Pitfalls
- Using sync functions with decorators → Will fail at runtime. Always use
async def
- Weak models + Pydantic return types → JSON parsing may fail silently. Verify with strong models (gpt-4, deepseek-v3) first
- Forgetting to pass
_template_params → Template variables won't be substituted
- Not awaiting or using
asyncio.run() → Coroutine object returned instead of result
- Tool parameter descriptions → Extract from DocString (
Args: section) and Pydantic Field(description=...)
🔗 Import Patterns
# Core decorators & interfaces
from SimpleLLMFunc import (
llm_function, llm_chat,
OpenAICompatible,
app_log, async_log_context
)
# Types
from SimpleLLMFunc.type import Text, ImgUrl, ImgPath
# Tool system
from SimpleLLMFunc.tool import tool, Tool
# Logger functions
from SimpleLLMFunc.logger import push_error, push_debug
📊 Testing Strategy
- Integration tests in
/examples folder demonstrate real-world flows
- Use small/cheap models first (e.g., gpt-3.5-turbo) before production
- Validate Pydantic models separately from LLM output processing
- Check trace logs in
trace_indices/ for debugging multi-tool workflows
🎯 For This Repository Branch: refactor/recoding-all
This branch is actively refactoring all components. Key areas under transformation:
- Logger system: migrated to console-only in v0.2.13
- Tool system: multimodal return support stabilized in v0.2.8+
- Type inference: improved in v0.2.12+
Always refer to CHANGELOG.md for the latest breaking changes.
1---2name: 922-copilot-instructions-ba72f1883description: SimpleLLMFunc - AI Coding Agent Instructions4---5# SimpleLLMFunc - AI Coding Agent Instructions67## 📋 Project Overview89SimpleLLMFunc is a lightweight LLM application framework enabling developers to write LLM-powered workflows and agents using Python decorators with DocString-as-Prompt philosophy. Core idea: **"Everything is Function, Prompt is Code"**.1011## 🏗 Architecture & Key Components1213### 1. **Core Design Pattern: Prompt as DocString**14- Function behavior is defined in the DocString, not the function body (which stays empty with `pass`)15- Decorators (`@llm_function`, `@llm_chat`, `@tool`) intercept function calls and delegate to LLM16- Type hints on parameters and return types ensure type safety and automatic validation17- Example from `/SimpleLLMFunc/llm_decorator/llm_function_decorator.py`:18 ```python19 @llm_function(llm_interface=my_llm)20 async def analyze_product_review(product_name: str, review_text: str) -> ProductReview:21 """You are a product review expert analyzing the following review...22 23 Args:24 product_name: Name of the product25 review_text: User review content26 27 Returns:28 Structured ProductReview object with rating, pros, cons, summary29 """30 pass # Prompt as Code - LLM handles the actual execution31 ```3233### 2. **Async-First Architecture**34- **All LLM decorators ONLY support `async def` functions** (non-negotiable requirement)35- Use `await` when calling decorated functions or `asyncio.run()` at top level36- Example from examples/event_stream_chatbot.py: consume `ResponseYield` / `EventYield` in an async loop37- Enables high-concurrency API calls via native asyncio integration3839### 3. **Three-Tier Component Stack**4041#### **Tier 1: Decorators** (`llm_decorator/`)42- **`@llm_function`**: Stateless single-call transformations (request → response)43- **`@llm_chat`**: Multi-turn conversations with history management44- **`@tool`**: Tool definitions for use by agents (async functions only)45- All accept `toolkit` parameter for tool composition46- Support `_template_params` for dynamic DocString templating (v0.2.14+)4748#### **Tier 2: LLM Interface** (`interface/`)49- **`LLM_Interface` (abstract)**: Base contract for all LLM implementations50- **`OpenAICompatible`**: Universal implementation for any OpenAI-compatible API51 - Loads config from JSON files via `load_from_json_file()` method52 - Auto-routes to correct provider/model from hierarchical config53- **`APIKeyPool`**: Min-heap based load balancing for multiple API keys54- **`TokenBucket`**: Rate limiting to prevent API throttling5556#### **Tier 3: Base Modules** (`base/`)57- **`messages/`**: Constructs system/user prompts + handles multimodal content58- **`post_process.py`**: Deserializes LLM responses to target types59- **`type_resolve/`**: Analyzes function signatures for type information60- **`ReAct.py`**: Orchestrates LLM calls with tool execution loops (max 5 by default)61- **`tool_call/`**: Tool extraction, invocation, and result validation6263### 4. **Configuration & Providers** 64- `.env` file: `LOG_LEVEL=DEBUG` setting65- `provider.json`: Hierarchical structure with vendor → model → credentials66 ```json67 {68 "volc_engine": {69 "deepseek-v3-250324": {70 "api_keys": ["key1", "key2"],71 "base_url": "https://api.volc.example.com/v1",72 "model": "deepseek-chat",73 "rate_limit_capacity": 1074 }75 }76 }77 ```78- Load via: `OpenAICompatible.load_from_json_file("provider.json")["volc_engine"]["deepseek-v3-250324"]`7980### 5. **Logging & Tracing System** (`logger/`)81- Console-only output (no file persistence in v0.2.13+)82- Auto-generated `trace_id` in format `{func_name}_{uuid}` for correlated logging83- Context manager: `async_log_context(trace_id=..., function_name=...)`84- Functions: `app_log()`, `push_error()`, `push_debug()`, `push_warning()`85- Structured context inheritance across async calls86- See `logger/core.py` for logger setup8788## 🔄 Execution Data Flow8990### Standard `llm_function` Flow:911. **Call Capture** → Decorator intercepts `async def my_func(...)`922. **Argument Binding** → Map args/kwargs to function signature933. **Prompt Construction**:94 - System prompt = function's DocString + custom template (if provided)95 - User prompt = formatted arguments + type descriptions964. **LLM Invocation** → Send to `llm_interface.chat(messages=[...])`975. **Tool Loop** (if toolkit provided):98 - Check if LLM invoked tools via `tool_calls` field99 - Execute tools via `base/ReAct.py` (max 5 iterations)100 - Include tool results in next LLM message1016. **Response Deserialization** → Convert LLM text output to return type102 - If return type is Pydantic model: auto-parse JSON103 - If return type is primitive: direct conversion104105### `llm_chat` Flow (Multi-turn):1061. Accepts `history: List[Dict[str, str]]` parameter1072. Maintains conversation state across calls1083. Yields tuples of `(content_chunk, updated_history)` in stream mode1094. Returns mode (`text` vs `raw`) affects tool call visibility in history1105. Final yield contains only `("", updated_history)` to signal completion111112## 🛠 Tool System Patterns113114### Function Decorator Way (Recommended):115```python116from SimpleLLMFunc.tool import tool117from SimpleLLMFunc.type import ImgPath, ImgUrl118119@tool(name="get_weather", description="Get weather for location")120async def get_weather(location: str, days: int = 1) -> dict:121 """Get weather forecast122 123 Args:124 location: City name or coordinates125 days: Forecast days (default 1)126 127 Returns:128 Weather data dictionary129 """130 # Actual implementation131 return {"location": location, "forecast": [...]}132133# Multimodal tool returns (v0.2.10+):134@tool(name="generate_chart", description="Generate chart image")135async def generate_chart(data: str) -> ImgPath:136 """Return local image path for tool use"""137 return ImgPath("/path/to/chart.png")138139@tool(name="search_image", description="Search web images")140async def search_image(query: str) -> ImgUrl:141 """Return web image URL for tool use"""142 return ImgUrl("https://example.com/image.jpg")143```144145### Usage in Decorators:146```python147@llm_function(148 llm_interface=llm,149 toolkit=[get_weather, generate_chart] # Pass decorated functions directly150)151async def plan_trip(destination: str) -> str:152 """Plan a trip using tools to get weather and generate itinerary"""153 pass154```155156## 🚀 Development Patterns157158### 1. **Type Safety through Pydantic**159- Always use Pydantic `BaseModel` for complex return types160- Define `Field(..., description="...")` for LLM understanding161- Example from README.md `ProductReview`:162 ```python163 class ProductReview(BaseModel):164 rating: int = Field(..., description="1-5 star rating")165 pros: List[str] = Field(..., description="List of advantages")166 cons: List[str] = Field(..., description="List of disadvantages")167 summary: str = Field(..., description="Summary text")168 ```169170### 2. **Multimodal Input Handling**171- Use `Text`, `ImgUrl`, `ImgPath` from `SimpleLLMFunc.type`172- Framework auto-converts to proper message format173- Example:174 ```python175 from SimpleLLMFunc.type import Text, ImgUrl, ImgPath176 177 @llm_function(llm_interface=llm)178 async def analyze_images(179 description: Text,180 web_img: ImgUrl,181 local_img: ImgPath182 ) -> str:183 """Analyze and compare images"""184 pass185 ```186187### 3. **Custom Templates for Reusable Functions**188- Use `system_prompt_template` / `user_prompt_template` parameters189- Reference variables with `{variable_name}` syntax190- Combine with `_template_params` at call time (v0.2.14+):191 ```python192 @llm_function(193 llm_interface=llm,194 system_prompt_template="You are a {role} expert..."195 )196 async def expert_analysis(topic: str) -> str:197 """Analyze the topic"""198 pass199 200 # Call with different roles:201 result = await expert_analysis(202 topic="Python design patterns",203 _template_params={"role": "Software Architecture"}204 )205 ```206207### 4. **Error Handling & Validation**208- LLM response validation happens automatically in `base/post_process.py`209- Weak models may fail JSON parsing (issue logged, user must handle)210- Wrap calls in try-except for production:211 ```python212 try:213 result = await analyze_product_review(name, review)214 except Exception as e:215 app_log(f"Analysis failed: {e}")216 # Fallback logic217 ```218219### 5. **Logging Integration**220```python221from SimpleLLMFunc.logger import app_log, async_log_context222223@llm_function(llm_interface=llm)224async def my_function(text: str) -> str:225 """Process text"""226 pass227228# In calling code:229async with async_log_context(trace_id="custom_id", function_name="my_func"):230 result = await my_function("input")231 app_log("Processing complete") # Auto-inherits trace_id232```233234## 📁 Critical Files by Use Case235236| Use Case | Key Files |237|----------|-----------|238| Add new LLM provider | `interface/openai_compatible.py`, update `provider.json` |239| Create LLM function | `llm_decorator/llm_function_decorator.py` (see flow 219-260) |240| Create agent with tools | `llm_decorator/llm_chat_decorator.py`, `tool/tool.py` |241| Debug tool invocation | `base/ReAct.py` (tool loop orchestration), `base/tool_call/execution.py` |242| Extend return types | `base/post_process.py`, `base/type_resolve/` |243| Customize prompts | `base/messages/` (template and message assembly logic) |244245## ⚠️ Common Pitfalls2462471. **Using sync functions with decorators** → Will fail at runtime. **Always use `async def`**2482. **Weak models + Pydantic return types** → JSON parsing may fail silently. Verify with strong models (gpt-4, deepseek-v3) first2493. **Forgetting to pass `_template_params`** → Template variables won't be substituted2504. **Not awaiting or using `asyncio.run()`** → Coroutine object returned instead of result2515. **Tool parameter descriptions** → Extract from DocString (`Args:` section) and Pydantic `Field(description=...)`252253## 🔗 Import Patterns254255```python256# Core decorators & interfaces257from SimpleLLMFunc import (258 llm_function, llm_chat,259 OpenAICompatible,260 app_log, async_log_context261)262263# Types264from SimpleLLMFunc.type import Text, ImgUrl, ImgPath265266# Tool system267from SimpleLLMFunc.tool import tool, Tool268269# Logger functions270from SimpleLLMFunc.logger import push_error, push_debug271```272273## 📊 Testing Strategy274275- **Integration tests** in `/examples` folder demonstrate real-world flows276- Use small/cheap models first (e.g., gpt-3.5-turbo) before production277- Validate Pydantic models separately from LLM output processing278- Check trace logs in `trace_indices/` for debugging multi-tool workflows279280## 🎯 For This Repository Branch: `refactor/recoding-all`281282This branch is actively refactoring all components. Key areas under transformation:283- Logger system: migrated to console-only in v0.2.13284- Tool system: multimodal return support stabilized in v0.2.8+285- Type inference: improved in v0.2.12+286287Always refer to CHANGELOG.md for the latest breaking changes.