본문으로 건너뛰기
cccskills
무료GitHub에 공개

langbot-plugin-dev

Build and debug LangBot plugins that handle messages, commands, and AI tool calls. The skill covers SDK usage, common errors, test environment setup, WebSocket message testing, and preparation for publishing to LangBot Space.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

원문원문 설명 보기

Develop, debug, and test LangBot plugins. Use when creating new LangBot plugins, fixing plugin bugs, setting up a LangBot test environment, or testing plugins via WebSocket. Covers plugin component architecture (EventListener, Command, Tool), the plugin SDK API (invoke_llm, get_llm_models, send_message, plugin storage), common pitfalls, and automated WebSocket-based testing. Triggers on "langbot plugin", "lbp", "GroupChatSummary", "plugin debug", "langbot test".

설치 방법 보기

이럴 때 유용합니다

  • Create a plugin that reacts to incoming messages
  • Fix plugin SDK errors and missing responses
  • Test group and private chats through WebSocket
  • Prepare a plugin for publication on LangBot Space

포함된 파일(2)

  • SKILL.md15.5 KB
  • references/test-env-setup.md2.9 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

LangBot Plugin Development & Debugging

Controlling a running instance via MCP

Beyond writing code, you can drive a live LangBot instance over MCP — no raw HTTP needed. Two MCP servers exist (both reuse existing API keys; see AGENTS.md):

  • LangBot instance — http://<host>:5300/mcp (auth: web-UI lbk_ key or the api.global_api_key from config.yaml). Manage bots, pipelines, models, knowledge bases, and skills. See the langbot-mcp-ops skill.
  • LangBot Space marketplace — https://space.langbot.app/mcp (auth: Personal Access Token). Search plugins / MCP servers / skills. See the langbot-space-ops skill.

Any change to an agent-accessible HTTP API endpoint must keep the matching MCP tool and these skills in sync.

Plugin Architecture

A LangBot plugin consists of:

MyPlugin/
├── manifest.yaml          # Plugin metadata, config schema
├── main.py                # BasePlugin subclass (entry point, shared state)
├── components/
│   ├── event_listener/    # Hook pipeline events
│   │   ├── collector.yaml
│   │   └── collector.py
│   ├── commands/          # !command handlers
│   │   ├── mycommand.yaml
│   │   └── mycommand.py
│   └── tools/             # LLM function-call tools
│       ├── mytool.yaml
│       └── mytool.py

Each component has a .yaml (metadata) and .py (implementation).

README & i18n convention (enforced on the marketplace)

A plugin published to LangBot Space serves a localized README on its detail page. The resolver (langbot-space PluginService.GetPluginREADME) works like this:

  • Root README.md MUST be in English. It is the default and the fallback — when no per-language README matches the viewer's locale, the page serves the root README.md. A non-English root README makes the English/default view show the wrong language.
  • All other languages live under readme/README_{lang}.md — e.g. readme/README_zh_Hans.md, readme/README_ja_JP.md. The 8 supported locales: en_US, zh_Hans, zh_Hant, ja_JP, th_TH, vi_VN, es_ES, ru_RU.
  • manifest.yaml metadata.label / metadata.description should carry the same 8-locale i18n set (repository must be a real, alive URL).
MyPlugin/
├── manifest.yaml
├── README.md                   # English (default + fallback) — REQUIRED, must be English
└── readme/
    ├── README_zh_Hans.md
    ├── README_zh_Hant.md
    ├── README_ja_JP.md
    ├── README_th_TH.md
    ├── README_vi_VN.md
    ├── README_es_ES.md
    └── README_ru_RU.md

manifest.yaml (incl. repository) is the source of truth — the marketplace syncs from it, so edit the package and re-publish rather than patching live data.

Critical SDK Pitfalls

1. MessageChain is a RootModel — iterate directly

# ❌ WRONG — MessageChain has no .components attribute
for component in event.message_chain.components:

# ✅ CORRECT — MessageChain is a Pydantic RootModel, iterate directly
for component in event.message_chain:

2. Message.content must be list[ContentElement] or str, not a single ContentElement

from langbot_plugin.api.entities.builtin.provider import message as provider_message

# ❌ WRONG — single ContentElement
Message(role="user", content=ContentElement.from_text("hello"))

# ✅ CORRECT — list of ContentElement
Message(role="user", content=[ContentElement.from_text("hello")])

# ✅ ALSO CORRECT — plain string
Message(role="user", content="hello")

3. invoke_llm does NOT accept timeout

# ❌ WRONG
await self.invoke_llm(llm_model_uuid=uuid, messages=msgs, timeout=60)

# ✅ CORRECT
await self.invoke_llm(llm_model_uuid=uuid, messages=msgs)

4. invoke_llm response.content can be str OR list

response = await self.invoke_llm(...)
if response.content:
    if isinstance(response.content, str):
        return response.content
    elif isinstance(response.content, list):
        parts = [e.text for e in response.content if hasattr(e, "text") and e.text]
        return "\n".join(parts)

5. get_llm_models() returns UUIDs

# Returns list[str] of model UUIDs
models = await self.get_llm_models()
model_uuid = models[0]  # First available model UUID

Known bug (v4.9.3): The host handler may return list[dict] instead of list[str]. If you hit TypeError: unhashable type: 'dict' in invoke_llm, the fix is in LangBot/src/langbot/pkg/plugin/handler.py — change 'llm_models': llm_models to 'llm_models': [m['uuid'] for m in llm_models].

6. invoke_llm parameter is llm_model_uuid, NOT model_uuid

# ❌ WRONG — will throw "got an unexpected keyword argument"
await self.invoke_llm(messages=msgs, model_uuid=uuid)

# ✅ CORRECT
await self.invoke_llm(messages=msgs, llm_model_uuid=uuid)

7. prevent_default() alone does NOT block LLM response

To fully prevent the default LLM pipeline from responding when your EventListener handles the message, you must call both:

event_context.prevent_default()    # Block default behavior
event_context.prevent_postorder()  # Block subsequent plugins/pipeline

Using only prevent_default() still allows the LLM to generate a response.

8. get_plugin_storage / set_plugin_storage may throw KeyError: 'owner'

This is a version mismatch between the SDK and host. Wrap storage calls in try/except:

try:
    data = await self.get_plugin_storage("my_key")
except Exception:
    data = None  # Fallback gracefully

9. Component YAML must have full structure, not just name/description

# ❌ WRONG — will silently fail to register the component
name: translator
description:
  en_US: 'Does stuff'

# ✅ CORRECT — full component YAML
apiVersion: v1
kind: EventListener
metadata:
  name: translator
  label:
    en_US: Translator
spec:
execution:
  python:
    path: translator.py
    attr: Translator

10. BasePlugin import path

# ❌ WRONG
from langbot_plugin.api.definition.base_plugin import BasePlugin

# ✅ CORRECT
from langbot_plugin.api.definition.plugin import BasePlugin

Pipeline Events

Events the EventListener can hook (from most general to most specific):

EventWhen
GroupMessageReceivedAny group message arrives (before trigger rules)
PersonMessageReceivedAny private message arrives
GroupNormalMessageReceivedGroup message passes trigger rules, going to LLM
PersonNormalMessageReceivedPrivate message going to LLM
GroupCommandSentGroup message matched as command
PersonCommandSentPrivate message matched as command
NormalMessageRespondedLLM generated a response
PromptPreProcessingAbout to build LLM context

Key insight: *MessageReceived fires for ALL messages regardless of trigger rules. *NormalMessageReceived only fires for messages that match the pipeline's trigger rules (e.g., @bot, prefix, random%). Use *MessageReceived for message collection/logging.

EventContext API

@self.handler(events.GroupMessageReceived)
async def on_msg(event_context: context.EventContext):
    event = event_context.event
    event.launcher_id    # Group ID
    event.sender_id      # Sender ID
    event.message_chain  # MessageChain (iterate directly)

    # Reply to the current conversation
    await event_context.reply(MessageChain([Plain(text="hello")]))

    # Block default pipeline behavior
    event_context.prevent_default()

    # Block subsequent plugins
    event_context.prevent_postorder()

Setting Up a Test Environment

Deploy via Docker (GitOps + Portainer)

See references/test-env-setup.md for full deployment steps.

Quick summary:

  1. Create docker-compose.yaml in server-deploy repo
  2. Deploy via Portainer git repository method
  3. Set up admin account via /api/v1/user/init POST
  4. Configure LLM provider and model via API
  5. Copy plugin to data/plugins/ directory

WebSocket Testing

LangBot's WebUI chat uses WebSocket. Connect to test message flow:

ws://<host>:<port>/api/v1/pipelines/<pipeline_uuid>/ws/connect?session_type=group
  • session_type=group for group chat simulation
  • session_type=person for private chat (always triggers pipeline)

Requires Origin header to pass CORS:

const ws = new WebSocket(url, {
  headers: { Origin: 'https://your-langbot-domain' }
});

Send messages:

{"type": "message", "message": [{"type": "Plain", "text": "hello"}]}

Receive:

  • {"type": "connected", ...} — connection established
  • {"type": "user_message", "data": {...}} — echo of sent message
  • {"type": "response", "data": {"content": "...", "is_final": true/false}} — bot reply (streamed)

Group Trigger Rules

Group messages only enter the pipeline if trigger rules are met:

{
  "group-respond-rules": {
    "at": true,          // Respond when @bot
    "prefix": ["ai"],    // Respond to messages starting with "ai"
    "random": 0.0,       // Probability of responding to any message (0.0-1.0)
    "regexp": []         // Regex patterns
  }
}

For testing, set random: 1.0 via PUT /api/v1/pipelines/<uuid> to respond to all messages.

Important: EventListener hooks like GroupMessageReceived fire regardless of trigger rules. Only the LLM processing (GroupNormalMessageReceived and beyond) requires trigger rules.

Plugin Hot-Reload

There is no hot-reload. After changing plugin files:

docker restart <runtime-container>
# Wait ~5 seconds for plugin to re-mount

The main LangBot container does NOT need restart for plugin changes — only the runtime container.

API Quick Reference

Admin Setup

# Initialize admin account (first time only)
curl -X POST $BASE/api/v1/user/init \
  -H "Content-Type: application/json" \
  -d '{"user":"admin@test.com","password":"test123"}'

# Login
curl -X POST $BASE/api/v1/user/auth \
  -H "Content-Type: application/json" \
  -d '{"user":"admin@test.com","password":"test123"}'
# Returns: {"data":{"token":"eyJ..."}}

Provider & Model Setup

# Create provider
curl -X POST $BASE/api/v1/provider/providers \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"MyProvider","requester":"new-api-chat-completions","base_url":"https://api.example.com/v1","api_keys":["sk-xxx"]}'

# Create LLM model
curl -X POST $BASE/api/v1/provider/models/llm \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"gpt-4o-mini","provider_uuid":"<uuid>","abilities":["chat","tool-use"]}'

# List models
curl $BASE/api/v1/provider/models/llm -H "Authorization: Bearer $TOKEN"

Pipeline Config

# Get pipeline
curl $BASE/api/v1/pipelines -H "Authorization: Bearer $TOKEN"

# Update pipeline (e.g., set model, modify trigger rules)
curl -X PUT $BASE/api/v1/pipelines/<uuid> \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '<full pipeline JSON>'

Plugin Config Types

Supported type values in manifest.yaml spec.config:

TypeDescriptionValue
stringText inputstring
int / integerNumber inputint
floatDecimal inputfloat
bool / booleanTogglebool
selectDropdown (needs options)string
prompt-editorMulti-line prompt editorstring
llm-model-selectorLLM model picker UIUUID string
bot-selectorBot picker UIUUID string

Example — let users choose which model the plugin uses:

spec:
  config:
    - name: model
      type: llm-model-selector
      label:
        en_US: 'LLM Model'
        zh_Hans: 'LLM 模型'
      description:
        en_US: 'Select the LLM model. Falls back to first available if not set.'
        zh_Hans: '选择 LLM 模型。未设置时使用第一个可用模型。'
      required: false

Read config in plugin code:

model_uuid = self.get_config().get("model")

Container Restart Timing

After plugin file changes, only the runtime container needs restart:

docker restart langbot-test-runtime
# Wait ~15 seconds before testing

When to restart both (runtime first, then host):

  • Added/removed Command or Tool components (host caches component lists)
  • Changed manifest.yaml structure
docker restart langbot-test-runtime
sleep 8
docker restart langbot-test
sleep 8

⚠️ Do NOT restart both simultaneously — the host may connect before plugins are mounted, causing 502 errors or missing plugin registrations.

Debugging Checklist

When a plugin doesn't work:

  1. Check runtime logs: docker logs <runtime-container> — look for mount/init errors
  2. Check host logs: docker logs <langbot-container> — look for pipeline processing errors
  3. Verify plugin loaded: GET /api/v1/plugins — should list your plugin
  4. Test person mode first: session_type=person always triggers pipeline, isolating trigger rule issues
  5. Check trigger rules: Group mode requires @bot, prefix match, or random% to enter pipeline
  6. Verify model configured: Pipeline's config.ai.runner_config[config.ai.runner.id].model.primary must point to a valid model UUID with working API keys

Publishing Plugins

After testing, publish via lbp publish:

cd /path/to/MyPlugin
lbp publish

This builds .lbpkg and uploads to Space marketplace as a draft. Then go to https://space.langbot.app/market to upload screenshots and submit for review.

Prerequisite: Must be logged in via lbp login --token lbpat_xxx (PAT from Space profile page).

Reference: EventListener-Only Plugin Pattern

For plugins that react to messages without commands or tools (e.g., auto-summarize URLs, collect messages, translate):

MyPlugin/
├── manifest.yaml       # Only EventListener in spec.components
├── main.py             # BasePlugin with shared logic (fetch, LLM calls)
├── components/
│   └── event_listener/
│       ├── detector.yaml
│       └── detector.py
└── requirements.txt

manifest.yaml — only declare EventListener:

spec:
  components:
    EventListener:
      fromDirs:
      - path: components/event_listener/

detector.py — hook *MessageReceived, extract text, process, reply:

@self.handler(events.PersonMessageReceived)
async def on_msg(event_context: context.EventContext):
    event = event_context.event
    text_parts = []
    for component in event.message_chain:
        if isinstance(component, platform_message.Plain):
            text_parts.append(component.text)
    text = "".join(text_parts).strip()
    
    if should_handle(text):
        event_context.prevent_default()
        event_context.prevent_postorder()
        result = await self.plugin.process(text)
        await event_context.reply(platform_message.MessageChain([
            platform_message.Plain(text=result)
        ]))

Key: Access shared plugin logic via self.plugin (the BasePlugin instance).

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Deploy and configure LangBot with Docker Compose or Kubernetes. Set up application settings, plugin execution, the Box sandbox for isolated commands, and API authentication, with guidance on hosting choices and common deployment problems.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Deploy LangBot on a self-hosted server
  • Configure Box and the plugin runtime
  • Enable API authentication for agent access
langbot-app/LangBot1.8만2026년 10월 18일 업데이트

Develop and debug LangBot's backend and web frontend. The skill guides environment setup, API additions, authentication, database migrations, and testing, including updates to matching MCP tools so agents can use changed API features.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Set up the LangBot backend and frontend for development
  • Add an API endpoint and its matching MCP tool
  • Update the database schema with Alembic
langbot-app/LangBot1.8만2026년 10월 18일 업데이트

Develop and migrate LangBot messaging adapters for Event-Based Agents, its event-driven architecture. Check conversion into shared event and message formats, implement platform APIs, and verify behavior through unit tests and real platform interactions.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Adding a messaging platform adapter to LangBot
  • Migrating an existing adapter to the EBA layout
  • Testing event and message conversion in both directions
langbot-app/LangBot1.8만2026년 10월 18일 업데이트

Prepare a local LangBot environment for development and browser testing. The skill covers service startup, shared connection settings, browser access, proxy troubleshooting, and reusable login profiles, with guidance for Linux and WSL environments.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Prepare a local Linux or WSL development environment
  • Start services and verify WebUI access
  • Choose Computer Use or Playwright MCP for browser control
langbot-app/LangBot1.8만2026년 10월 18일 업데이트

Manage LangBot bots and processing workflows through MCP, a standard for connecting agents to tools. Configure authenticated access, inspect resources, update supported settings, and investigate execution histories, logs, and model request diagnostics.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Connect an MCP client to LangBot
  • Create or update bots and processing workflows
  • Investigate execution logs and errors
langbot-app/LangBot1.8만2026년 10월 18일 업데이트

Maintain LangBot skill assets by organizing operating rules, reference guides, test cases, fixtures, and troubleshooting entries. Check existing material before adding files, keep detailed instructions in one place, and verify that indexes stay current.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Add a skill without duplicating existing guidance
  • Organize reference guides and test cases
  • Consolidate troubleshooting for shared causes
langbot-app/LangBot1.8만2026년 10월 18일 업데이트

langbot-app의 스킬 모두 보기

이 스킬의 문제 신고