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