feat(llm-proxy): OpenAI 兼容代理端点 + token 签发/吊销/列表 dspy
This commit is contained in:
parent
20f3e6ebb2
commit
7c57550277
38
wwwroot/api/llm_token_create.dspy
Normal file
38
wwwroot/api/llm_token_create.dspy
Normal 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)
|
||||||
21
wwwroot/api/llm_token_revoke.dspy
Normal file
21
wwwroot/api/llm_token_revoke.dspy
Normal 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)
|
||||||
16
wwwroot/api/llm_tokens.dspy
Normal file
16
wwwroot/api/llm_tokens.dspy
Normal 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)
|
||||||
54
wwwroot/api/llm_v1/chat/completions.dspy
Normal file
54
wwwroot/api/llm_v1/chat/completions.dspy
Normal 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)
|
||||||
Loading…
x
Reference in New Issue
Block a user