feat: LLM classification for project name matching — _call_llm_raw with temp=0

This commit is contained in:
ymq 2026-08-09 23:18:15 +08:00
parent 98d4f5e8ad
commit 9b4f7dd4ae

View File

@ -115,6 +115,20 @@ async def _sel_model(sor, pref):
recs = await sor.sqlExe("SELECT id,name,provider,model_id,api_base,api_key FROM llm WHERE status='active' LIMIT 1",{})
return recs[0] if recs else None
async def _call_llm_raw(prompt, temp=0.0):
"""Lightweight LLM call for classification tasks. Uses first available model."""
to = aiohttp.ClientTimeout(total=30)
async with DBPools().sqlorContext(dbname) as sor:
model = await _sel_model(sor, None)
if not model:
return "不存在"
hdrs = {"Authorization": f"Bearer {model.api_key or ''}", "Content-Type": "application/json"}
payload = {"model": model.model_id, "messages": [{"role":"user","content":prompt}], "temperature": temp}
async with aiohttp.ClientSession(timeout=to) as s:
async with s.post(f"{model.api_base.rstrip('/')}/chat/completions", headers=hdrs, json=payload) as r:
if r.status != 200: raise ValueError(f"LLM {r.status}")
return (await r.json())["choices"][0]["message"]["content"]
async def _call_llm(model, msgs, temp):
hdrs = {"Authorization": f"Bearer {model.api_key or ''}", "Content-Type": "application/json"}
payload = {"model": model.model_id, "messages": msgs, "temperature": temp}
@ -206,8 +220,21 @@ async def _exec_tool(sor, tool, params, ctx, uid, org_id):
return '可用项目:\n'+'\n'.join(lines)
proj = await _find_project(sor, name)
if not proj:
allp = await sor.sqlExe("SELECT name FROM sd_projects ORDER BY created_at DESC LIMIT 15",{})
return 'FAIL: 未找到。可用: '+', '.join([getattr(r,'name','') for r in (allp or [])])
# LLM classification: ask LLM to match user input against project list
allp = await sor.sqlExe("SELECT id,name FROM sd_projects ORDER BY created_at DESC LIMIT 20",{})
pnames = [getattr(r,'name','') for r in (allp or [])]
classify_prompt = f"用户输入: {name}\n项目列表: {', '.join(pnames)}\n\n判断用户想要哪个项目。只回复项目名或\"不存在\"。"
try:
raw = await _call_llm_raw(classify_prompt, 0.0)
matched = raw.strip().strip('"').strip("'")
for r in (allp or []):
if getattr(r,'name','') == matched:
proj = r; break
except:
pass
if not proj:
allp2 = await sor.sqlExe("SELECT name FROM sd_projects ORDER BY created_at DESC LIMIT 15",{})
return 'FAIL: 未找到。可用: '+', '.join([getattr(r,'name','') for r in (allp2 or [])])
if proj.id == ctx['pid']:
return f'已在「{proj.name}」项目中,无需切换'
await _save_ctx(sor, uid, proj.id)
@ -445,10 +472,7 @@ if action == 'send_message':
return
repos_str = ', '.join([r['n'] for r in ctx['repos']]) or '无'
# Load all project names so LLM can match user's input
all_projects = await sor.sqlExe("SELECT name FROM sd_projects ORDER BY created_at DESC LIMIT 20",{})
proj_list = ', '.join([getattr(p,'name','') for p in (all_projects or [])])
env_text = f"【当前项目: {ctx['pname'] or '未选择'}】\n所有项目: {proj_list}\n工作空间: {ctx['ws'] or '未设置'} | 仓库: {repos_str}"
env_text = f"【当前项目: {ctx['pname'] or '未选择'}】— 所有操作在当前项目内完成\n工作空间: {ctx['ws'] or '未设置'} | 仓库: {repos_str}"
system = AGENT_PROMPT.replace('__TOOLS__',TOOLS_TEXT).replace('__ENV__',env_text)
msgs = [{"role":"system","content":system}]
for h in (await sor.sqlExe("SELECT role,content FROM pipeline_conversations WHERE created_by=${u}$ ORDER BY created_at ASC LIMIT 20",{"u":uid}) or []):