refactor: add intent_classifier.py, register in init.py, LLM bridge shared

This commit is contained in:
yumoqing 2026-08-02 00:00:20 +08:00
parent 0253615fcc
commit 0423fa3c99
3 changed files with 68 additions and 2 deletions

View File

@ -367,5 +367,11 @@ def load_pipeline_service():
# Load built-in interactive step types
load_builtin_types()
# Register intent classifier + LLM bridge (shared across all pipelines)
from .intent_classifier import intent_classify
from .llm_bridge import llm_call
env.intent_classify = intent_classify
env.pipeline_llm_call = llm_call
debug(f"[{MODULE_NAME}] v{MODULE_VERSION} loaded — pipeline engine with human-in-the-loop support")
return True

View File

@ -0,0 +1,60 @@
"""
pipeline_service/intent_classifier.py Reusable intent classification.
DSPY can call: await intent_classify(model_name, message, intents, context)
Registered on ServerEnv in pipeline_service/init.py
"""
import json
import logging
logger = logging.getLogger("pipeline.intent_classifier")
INTENT_PROMPT = """你是一个意图分类器。分析用户输入,返回 JSON。
可用意图
{intent_list}
当前上下文{context_str}
返回纯 JSON不要 markdown 包裹
{{"intent":"...","confidence":0.0-1.0,"params":{{"key":"value"...}},"missing_info":"..."}}"""
async def intent_classify(intents: list, message: str, context: dict = None) -> dict:
"""Classify user intent. Returns {intent, confidence, params, missing_info}.
Args:
intents: [{"name":"new_project","description":"创建新项目"},...]
message: user input text
context: {"project_name":"...","iteration_name":"..."}
"""
if not intents:
return {"intent": "chat", "confidence": 0.5, "params": {}, "missing_info": ""}
intent_list = "\n".join(
f"- {i['name']}: {i.get('description','')}" for i in intents
)
ctx_parts = []
if context:
for k, v in context.items():
if v:
ctx_parts.append(f"{k}={v}")
context_str = ", ".join(ctx_parts) if ctx_parts else "无上下文"
prompt = INTENT_PROMPT.format(intent_list=intent_list, context_str=context_str)
try:
from pipeline_service.llm_bridge import llm_call
raw = await llm_call(
prompt=f"{prompt}\n\n用户输入:{message}",
model=None,
temperature=0.2,
)
raw = raw.strip()
if raw.startswith("```"):
raw = raw.split("\n", 1)[1].rsplit("```", 1)[0]
return json.loads(raw)
except Exception as e:
logger.warning("intent_classify failed: %s", e)
return {"intent": "chat", "confidence": 0.3, "params": {}, "missing_info": ""}

View File

@ -17,7 +17,7 @@ _model_cache: dict = {}
def _decrypt_key(encrypted: str) -> str:
"""Decrypt api_key stored with password_encode."""
"""Decrypt api_key stored with password_encode. Falls back to plaintext."""
if not encrypted:
return ""
try:
@ -27,7 +27,7 @@ def _decrypt_key(encrypted: str) -> str:
key = config.password_key
return unpassword(key, encrypted)
except Exception:
return encrypted
return encrypted # already plaintext or decrypt failed
async def _get_model_config(model_name: str = None) -> dict: