Totoro fe70cfc5ae
feat(plugins): add standalone Jev text classification example (#5735)
* feat(examples): add a standalone text classification plugin

A deployment-installed package contributes one model tool, classify_texts,
and a status action through the full-stack plugin contract. The tool labels a
list of texts against caller-supplied categories with a deployment-configured
backend: Jev (one choice question per item over a shared batch state, checked
per item) or an OpenAI-compatible chat endpoint (a JSON label list validated
in code). Items are packed into requests under a size limit and sent with
bounded concurrency; every failure is explicit per call or per item;
credentials come only from deployment-named environment variables; item text
never enters instructions, question keys, results or errors, and the served
model name is kept to a short identifier. Each batch runs under an asyncio
timeout bounded by the call deadline, so the whole call stays inside the
host's 30-second limit, and ids and category names are bounded in bytes so
300 results fit the host's 64 KiB output limit.

* docs(examples): add an opt-in live smoke test for the text classification plugin

The script loads the package through the real extension path and classifies 20
synthetic English and Chinese sentences plus a blank and an over-long item with
the configured backend, printing labels and statuses only. Against real Jev it
agreed on all 20 labels in two requests and 1.6 seconds, with the served model
reported as jev-1.13.0.

* fix(examples): measure request size as sent and cover transport failures

httpx serializes json= bodies with the json.dumps default, escaping every
non-ASCII character as six bytes, while the size estimators measured raw
UTF-8, so CJK batches could reach about twice the 256 KiB limit. The
estimators now measure the same encoding; a test with CJK categories and
texts checks the bytes the transport actually received. The network status
gets a test through connect and protocol errors, and the deadline test's
wall-clock slack is widened for loaded runners.

* fix(examples): match classifier request wire encoding

* fix(examples): bound classification output keys by JSON size

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-09-24 16:36:30 +08:00

39 lines
2.0 KiB
Python

"""Standalone text classification plugin. No imports from DeerFlow host internals."""
import os
from deerflow_extension_api import BackendAction, ModelTool, PluginContribution, SettingsField, extension
from .classify import INPUT_SCHEMA, TOOL_DESCRIPTION, Options, classify_texts
@extension(api="0.2.2", name="jev-classify")
def install(registry, config):
options = Options.model_validate(dict(config))
key_env = options.jev_api_key_env if options.backend == "jev" else options.llm_api_key_env
async def classify(payload, context):
return await classify_texts(payload, options)
async def status(payload, context):
if payload:
raise ValueError("Status takes no arguments")
# Configuration only: never keys, environment variable names or item text.
return {"enabled": options.enabled, "backend": options.backend, "configured": bool(os.environ.get(key_env)), "batch_size": options.batch_size, "max_items": options.max_items}
contribution = PluginContribution(
namespace="community.jev-classify",
title="文本分类 / Text classification",
description="按给定类别给文本列表打标签;后端由部署方配置为 Jev 或聊天模型。/ Label a list of texts with the deployment-configured Jev or chat-model backend.",
enabled=options.enabled,
fields=(
SettingsField("backend", "后端 / Backend", "string", options.backend, max_length=8),
SettingsField("batch_size", "每请求条数 / Items per request", "integer", options.batch_size, minimum=1, maximum=20),
SettingsField("max_items", "每次调用上限 / Items per call", "integer", options.max_items, minimum=1, maximum=300),
),
backend=(BackendAction("status", status),),
tools=(ModelTool("classify_texts", TOOL_DESCRIPTION, INPUT_SCHEMA, classify),),
)
if registry.plugin(contribution) is not True:
raise RuntimeError("Text classification requires the full-stack plugin host contract")