원클릭으로
pillar-dev
Implement or extend Chatnificent pillars (LLM, Store, Engine, Auth, Tools, etc.)
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Implement or extend Chatnificent pillars (LLM, Store, Engine, Auth, Tools, etc.)
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Deploy the Chatnificent website (examples/showcase.py) to the Linode production server. Use when the user asks to deploy, ship, push to prod, or release the website. Runs the push-already-done → pull → reload → verify loop.
Create an example Chatnificent app to be showcased in the /examples directory.
Implement or modify a Chatnificent server (DevServer, Starlette, FastAPI, etc.)
| name | pillar-dev |
| description | Implement or extend Chatnificent pillars (LLM, Store, Engine, Auth, Tools, etc.) |
Use this skill when implementing a new pillar, extending an existing one, or working on cross-pillar integration (Engine orchestration, data models, customization).
llm.py)class LLM(ABC):
@abstractmethod
def generate_response(self, messages: List[Dict[str, Any]], **kwargs) -> Any:
"""Generate response from LLM. Returns native provider response."""
@abstractmethod
def extract_content(self, llm_response: Any) -> str:
"""Extract text content from provider response."""
# Usage
import chatnificent as chat
app = chat.Chatnificent(llm=chat.llm.Anthropic(api_key="sk-..."))
store.py)class Store(ABC):
@abstractmethod
def save_conversation(self, user_id: str, conversation: Conversation) -> None:
"""Save conversation to storage."""
@abstractmethod
def load_conversation(self, user_id: str, convo_id: str) -> Optional[Conversation]:
"""Load conversation from storage."""
# Usage
app = chat.Chatnificent(store=chat.store.SQLite(db_path="chats.db"))
server.py)class Server(ABC):
@abstractmethod
def create_server(self, **kwargs) -> None:
"""Initialize the HTTP server."""
@abstractmethod
def run(self, **kwargs) -> None:
"""Start serving requests."""
DevServer is the primary server — zero-dependency stdlib HTTP server with SSE streaming.
DashServer wraps Plotly Dash for use with Dash-based layouts (Bootstrap, Mantine, Minimal).
layout.py)class Layout(ABC):
@abstractmethod
def render(self) -> Any:
"""Render the layout. Returns HTML string (DevServer) or Dash component tree (DashServer)."""
Default renders the templates/default/ folder (template.html, styles.css, scripts.js, vendor/) — a zero-dep vanilla HTML/JS chat UI for DevServer.
Dash-based layouts (Bootstrap, Mantine, Minimal) build Dash component trees and require DashServer.
The Engine pillar manages the request lifecycle and enables complex, multi-step agentic workflows. It orchestrates the interaction between pillars and handles tool calling loops.
Retrieval (RAG context) runs once per request, before the loop — not inside it. The loop itself is bounded by Orchestrator.max_agentic_turns (default 5) to prevent runaway tool invocations.
The engine can loop multiple times to allow the LLM to use tools, process results, and form a final answer:
max_agentic_turns is reachedPillar Contracts:
handle_message() — non-streaming path, returns a complete Conversationhandle_message_stream() — streaming path, yields SSE event dicts ({"event": "delta", "data": "..."})The server routes between these based on llm.default_params.get("stream", False).
class CustomEngine(chat.engine.Orchestrator):
# Override HOOKS for monitoring
def _after_llm_call(self, llm_response: Any) -> None:
tokens = getattr(llm_response, 'usage', 'N/A')
print(f"Tokens used: {tokens}")
# Override SEAMS for custom logic
def _prepare_llm_payload(self, conversation, context: Optional[str]):
payload = super()._prepare_llm_payload(conversation, context)
payload.insert(0, {"role": "system", "content": "Be concise."})
return payload
app = chat.Chatnificent(engine=CustomEngine())
models.py)# Role constants
USER_ROLE = "user"
ASSISTANT_ROLE = "assistant"
SYSTEM_ROLE = "system"
TOOL_ROLE = "tool"
MODEL_ROLE = "model" # Gemini uses "model" instead of "assistant"
@dataclass
class Conversation:
id: str
messages: list # List[Dict[str, Any]] — provider-native dicts
def copy(self, deep: bool = False) -> "Conversation": ...
Messages are plain dicts in each provider's native format. There is no
universal message schema — an OpenAI message looks different from an Anthropic
one, and that's intentional. The LLM pillar owns the shape of its own messages
via create_assistant_message() and create_tool_result_messages().
Chatnificent stores messages in each provider's native dict format. There is no universal intermediary format — an OpenAI conversation and an Anthropic conversation look different on disk, and that's by design.
The engine never inspects message internals. It only touches the minimal universal contract:
{"role": "user", "content": text}Each LLM concrete class is responsible for:
create_assistant_message() — converting its native response into a persistable dictcreate_tool_result_messages() — formatting tool results for its own APIextract_content() — pulling display text from its native responseis_tool_message() — identifying its own tool-related messagesThe Tools pillar outputs a standard JSON Schema tool definition. Each LLM's
_translate_tool_schema() converts that into the provider's native tool format
before calling the API.
This approach:
raw_api_requests.jsonl and raw_api_responses.jsonl for auditingResearch Setup:
import chatnificent as chat
app = chat.Chatnificent(
llm=chat.llm.Anthropic(),
store=chat.store.File(directory="./research_chats"),
tools=chat.tools.PythonTool(),
)
Enterprise Setup:
app = chat.Chatnificent(
llm=chat.llm.OpenAI(),
store=chat.store.SQLite(db_path="enterprise.db"),
auth=chat.auth.SingleUser(user_id="corp_user"),
)
Local Development:
app = chat.Chatnificent(
llm=chat.llm.Ollama(model="llama3.2"),
store=chat.store.InMemory(),
tools=chat.tools.PythonTool(),
)
Dash UI with Bootstrap:
from chatnificent.server import DashServer
app = chat.Chatnificent(
server=DashServer(),
layout=chat.layout.Bootstrap(),
llm=chat.llm.Gemini(),
store=chat.store.SQLite(db_path="global.db"),
)
import json
class RedisStore(chat.store.Store):
def __init__(self, redis_url: str):
self.client = redis.from_url(redis_url)
def save_conversation(self, user_id: str, conversation: Conversation):
key = f"chat:{user_id}:{conversation.id}"
data = {"id": conversation.id, "messages": conversation.messages}
self.client.set(key, json.dumps(data))
def load_conversation(self, user_id: str, convo_id: str):
key = f"chat:{user_id}:{convo_id}"
raw = self.client.get(key)
if not raw:
return None
data = json.loads(raw)
return Conversation(id=data["id"], messages=data["messages"])
# Implement other required methods...
app = chat.Chatnificent(store=RedisStore("redis://localhost:6379"))
| Task | How |
|---|---|
| New LLM provider | Subclass llm.LLM, implement generate_response() and extract_content() |
| Custom storage | Subclass store.Store, implement save/load methods |
| UI changes (DevServer) | Edit templates/default/{template.html, styles.css, scripts.js} |
| UI changes (Dash) | Subclass layout.DashLayout |
| Request lifecycle | Subclass engine.Orchestrator, override hooks/seams |
| Tool integration | Subclass tools.Tool, handle tool call dicts -> tool result dicts |
| Different HTTP server | Subclass server.Server, implement create_server() and run() |