diff --git a/wwwroot/api/cockpit_chat.dspy b/wwwroot/api/cockpit_chat.dspy index 9a0cc69..8b66fce 100644 --- a/wwwroot/api/cockpit_chat.dspy +++ b/wwwroot/api/cockpit_chat.dspy @@ -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 []):