feat(llm-proxy): OpenAI 兼容代理端点 + token 签发/吊销/列表 dspy

This commit is contained in:
yumoqing 2026-08-25 14:11:42 +08:00
parent 20f3e6ebb2
commit 7c57550277
4 changed files with 129 additions and 0 deletions

View File

@ -0,0 +1,38 @@
# llm_token_create.dspy — 签发短期 LLM 代理 token登录用户机构隔离
#
# 用途给运行环境browser-use 子进程 / 沙箱脚本 / 被开发的 AI 应用)发一把
# 短期钥匙,代替下发真实模型 api_key。真 key 永不出服务进程。
user_id = await get_user()
if not user_id:
return json.dumps({"success": False, "error": "未登录"}, ensure_ascii=False)
p = params_kw or {}
project_id = (p.get('project_id', '') or '').strip()
task_id = (p.get('task_id', '') or '').strip()
model_name = (p.get('model_name', '') or '').strip()
purpose = (p.get('purpose', '') or '').strip()
ttl_hours = p.get('ttl_hours', 8)
max_calls = p.get('max_calls', 500)
# 机构隔离token 的 org_id 一律取当前登录用户所属机构,不接受前端传入
org_id = get_userorgid()
if not org_id:
org_id = '0'
# 指定了 project_id 时校验 owner防越权给别人的项目签发 token
if project_id:
ok_owner, msg_owner = await check_project_owner(project_id, user_id)
if not ok_owner:
return json.dumps({"success": False, "error": msg_owner or "仅项目 owner 可签发"},
ensure_ascii=False)
ok, token = await create_llm_token(org_id, project_id=project_id, task_id=task_id,
model_name=model_name, purpose=purpose,
ttl_hours=ttl_hours, max_calls=max_calls,
created_by=user_id)
if ok:
return json.dumps({"success": True, "token": token, "org_id": org_id,
"base_url": entire_url('/pipeline_core/api/llm_v1')},
ensure_ascii=False)
return json.dumps({"success": False, "error": token}, ensure_ascii=False)

View File

@ -0,0 +1,21 @@
# llm_token_revoke.dspy — 吊销短期 LLM 代理 token
user_id = await get_user()
if not user_id:
return json.dumps({"success": False, "error": "未登录"}, ensure_ascii=False)
p = params_kw or {}
token_id = (p.get('token_id', '') or '').strip()
task_id = (p.get('task_id', '') or '').strip()
if task_id:
ok, msg = await revoke_task_tokens(task_id)
elif token_id:
ok, msg = await revoke_llm_token(token_id=token_id)
else:
return json.dumps({"success": False, "error": "须指定 token_id 或 task_id"},
ensure_ascii=False)
if ok:
return json.dumps({"success": True, "message": msg}, ensure_ascii=False)
return json.dumps({"success": False, "error": msg}, ensure_ascii=False)

View File

@ -0,0 +1,16 @@
# llm_tokens.dspy — 列出已签发 token元信息 + 用量,不返回 token 明文,供审计)
user_id = await get_user()
if not user_id:
return json.dumps({"success": False, "error": "未登录"}, ensure_ascii=False)
p = params_kw or {}
project_id = (p.get('project_id', '') or '').strip()
status = (p.get('status', '') or '').strip()
# 机构隔离:只看本机构签发的 token
org_id = get_userorgid() or '0'
rows = await list_llm_tokens(org_id=org_id, project_id=project_id or None,
status=status or None)
return json.dumps({"success": True, "tokens": rows}, ensure_ascii=False, default=str)

View File

@ -0,0 +1,54 @@
# completions.dspy — OpenAI 兼容 LLM 代理端点(内部运行环境专用)
#
# URL: /pipeline_core/api/llm_v1/chat/completions
# OpenAI 兼容客户端browser-use / LangChain / openai SDK配置
# base_url = http://<host>/pipeline_core/api/llm_v1
# api_key = <短期 token>pipeline_llm_tokens 签发,非真实模型 key
#
# 鉴权Authorization: Bearer <短期 token>,不依赖登录会话(运行环境无 session
# 机构隔离:按 token 绑定的 org_id 解析真实模型 key运行环境拿不到真 key。
auth = ''
try:
auth = request.headers.get('Authorization', '') or ''
except Exception:
auth = ''
if not auth:
auth = (params_kw or {}).get('api_key', '') or ''
if not auth:
return json.dumps({"error": {"message": "缺少 Authorization Bearer token",
"type": "invalid_request_error", "code": "missing_token"}},
ensure_ascii=False)
payload = None
try:
payload = await request.json()
except Exception:
payload = None
if not isinstance(payload, dict):
try:
_raw = await request.text()
payload = json.loads(_raw) if _raw else None
except Exception:
payload = None
if not isinstance(payload, dict):
_pk = dict(params_kw or {})
_msgs = _pk.get('messages')
if isinstance(_msgs, str):
try:
_msgs = json.loads(_msgs)
except Exception:
_msgs = None
payload = {"model": _pk.get('model', ''), "messages": _msgs} if _msgs else None
if not isinstance(payload, dict) or not payload.get('messages'):
return json.dumps({"error": {"message": "请求体缺 messages",
"type": "invalid_request_error", "code": "missing_messages"}},
ensure_ascii=False)
ok, result = await proxy_chat_completion(auth, payload)
if ok:
return json.dumps(result, ensure_ascii=False, default=str)
return json.dumps({"error": {"message": result, "type": "invalid_request_error",
"code": "proxy_error"}}, ensure_ascii=False)