666 lines
20 KiB
Python
666 lines
20 KiB
Python
# moop.py — Model Option (moop).
|
|
#
|
|
# Penyimpanan model/API key/provider dipindah dari config.yaml ke database
|
|
# sqlite. Modul ini bertanggung jawab atas:
|
|
# * inisialisasi schema (referensi: plan/moop.sql)
|
|
# * CRUD model set, provider (moop_api), key, dan model
|
|
# * default model set per tipe (llm/embedding/imagegen/imagevision)
|
|
# * resolve "chain" kandidat (base_url, model, api_key) untuk auto-switch
|
|
#
|
|
# Prioritas auto-switch di dalam satu model set:
|
|
# 1. Key (urut berdasarkan priority, NULL = disabled)
|
|
# 2. Setelah semua key gagal baru pindah Model/Provider berikutnya.
|
|
# Setiap kandidat dalam chain membawa (api_id, model_id) agar pemanggil
|
|
# bisa membedakan rotasi key vs pindah provider.
|
|
|
|
import os
|
|
import sqlite3
|
|
import uuid
|
|
|
|
import config
|
|
|
|
MODEL_TYPES = ("llm", "embedding", "imagegen", "imagevision")
|
|
|
|
_SCHEMA = """
|
|
PRAGMA foreign_keys = ON;
|
|
|
|
CREATE TABLE IF NOT EXISTS moop_model_set (
|
|
id BLOB PRIMARY KEY NOT NULL,
|
|
name TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS moop_model_type (
|
|
id BLOB PRIMARY KEY NOT NULL,
|
|
type TEXT NOT NULL UNIQUE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS moop_model_set_type (
|
|
id BLOB PRIMARY KEY NOT NULL,
|
|
"set" BLOB NOT NULL,
|
|
type BLOB NOT NULL,
|
|
FOREIGN KEY ("set") REFERENCES moop_model_set(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
|
FOREIGN KEY (type) REFERENCES moop_model_type(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
|
UNIQUE ("set", type)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS moop_model_type_default (
|
|
id BLOB PRIMARY KEY NOT NULL,
|
|
type BLOB NOT NULL UNIQUE,
|
|
set_type BLOB NOT NULL UNIQUE,
|
|
FOREIGN KEY (type) REFERENCES moop_model_type(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
|
FOREIGN KEY (set_type) REFERENCES moop_model_set_type(id) ON UPDATE CASCADE ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS moop_api (
|
|
id BLOB PRIMARY KEY NOT NULL,
|
|
name TEXT NOT NULL,
|
|
baseurl TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS moop_key (
|
|
id BLOB PRIMARY KEY NOT NULL,
|
|
key TEXT NOT NULL,
|
|
priority INTEGER NULL,
|
|
api BLOB NOT NULL,
|
|
FOREIGN KEY (api) REFERENCES moop_api(id) ON UPDATE CASCADE ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS moop_model (
|
|
id BLOB PRIMARY KEY NOT NULL,
|
|
api BLOB NOT NULL,
|
|
"set" BLOB NOT NULL,
|
|
model TEXT NOT NULL,
|
|
priority INTEGER NULL,
|
|
FOREIGN KEY (api) REFERENCES moop_api(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
|
FOREIGN KEY ("set") REFERENCES moop_model_set(id) ON UPDATE CASCADE ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_moop_key_api ON moop_key(api);
|
|
CREATE INDEX IF NOT EXISTS idx_moop_key_api_priority ON moop_key(api, priority);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_moop_model_api ON moop_model(api);
|
|
CREATE INDEX IF NOT EXISTS idx_moop_model_set ON moop_model("set");
|
|
CREATE INDEX IF NOT EXISTS idx_moop_model_api_priority ON moop_model(api, priority);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_moop_model_set_type_set ON moop_model_set_type("set");
|
|
CREATE INDEX IF NOT EXISTS idx_moop_model_set_type_type ON moop_model_set_type(type);
|
|
"""
|
|
|
|
|
|
def moop_db_path():
|
|
return os.path.expanduser(config.moop_db_path)
|
|
|
|
|
|
def _id():
|
|
return uuid.uuid4().bytes
|
|
|
|
|
|
def _connect(path=None):
|
|
p = os.path.expanduser(path or moop_db_path())
|
|
parent = os.path.dirname(p)
|
|
if parent:
|
|
os.makedirs(parent, exist_ok=True)
|
|
conn = sqlite3.connect(p, timeout=30)
|
|
conn.row_factory = sqlite3.Row
|
|
conn.execute("PRAGMA foreign_keys = ON")
|
|
try:
|
|
# Self-healing: buat schema + seed tipe kalau DB belum pernah dibuat.
|
|
if not conn.execute(
|
|
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='moop_model_type'"
|
|
).fetchone():
|
|
conn.executescript(_SCHEMA)
|
|
for t in MODEL_TYPES:
|
|
conn.execute(
|
|
"INSERT INTO moop_model_type (id, type) VALUES (?, ?)",
|
|
(_id(), t),
|
|
)
|
|
conn.commit()
|
|
except sqlite3.OperationalError:
|
|
pass
|
|
return conn
|
|
|
|
|
|
# ---------------------------------------------------------------- init ---
|
|
|
|
def init_db(path=None):
|
|
"""Buat schema kalau belum ada, lalu seed tipe model yang didukung."""
|
|
conn = _connect(path)
|
|
try:
|
|
conn.executescript(_SCHEMA)
|
|
for t in MODEL_TYPES:
|
|
conn.execute(
|
|
"INSERT OR IGNORE INTO moop_model_type (id, type) VALUES (?, ?)",
|
|
(_id(), t),
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def ensure_defaults(path=None):
|
|
"""Untuk tipe yang belum punya default set, ambil set+type pertama."""
|
|
conn = _connect(path)
|
|
try:
|
|
_ensure_defaults_conn(conn)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def _ensure_defaults_conn(conn):
|
|
types = conn.execute("SELECT id FROM moop_model_type").fetchall()
|
|
for t in types:
|
|
tid = t["id"]
|
|
has = conn.execute(
|
|
"SELECT id FROM moop_model_type_default WHERE type = ?", (tid,)
|
|
).fetchone()
|
|
if has:
|
|
continue
|
|
st = conn.execute(
|
|
'SELECT st.id FROM moop_model_set_type st '
|
|
'WHERE st.type = ? ORDER BY st.rowid LIMIT 1',
|
|
(tid,),
|
|
).fetchone()
|
|
if st:
|
|
conn.execute(
|
|
"INSERT OR IGNORE INTO moop_model_type_default (id, type, set_type) "
|
|
"VALUES (?, ?, ?)",
|
|
(_id(), tid, st["id"]),
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------- sets ---
|
|
|
|
def _set_types_conn(conn, set_id):
|
|
return [
|
|
r["type"]
|
|
for r in conn.execute(
|
|
'SELECT mt.type AS type FROM moop_model_set_type st '
|
|
'JOIN moop_model_type mt ON mt.id = st.type '
|
|
'WHERE st."set" = ? ORDER BY mt.type',
|
|
(set_id,),
|
|
).fetchall()
|
|
]
|
|
|
|
|
|
def _set_defaults_conn(conn, set_id):
|
|
return [
|
|
r["type_name"]
|
|
for r in conn.execute(
|
|
'SELECT mt.type AS type_name FROM moop_model_type_default d '
|
|
'JOIN moop_model_type mt ON mt.id = d.type '
|
|
'JOIN moop_model_set_type st ON st.id = d.set_type '
|
|
'WHERE st."set" = ?',
|
|
(set_id,),
|
|
).fetchall()
|
|
]
|
|
|
|
|
|
def list_sets(path=None):
|
|
conn = _connect(path)
|
|
try:
|
|
result = []
|
|
for r in conn.execute(
|
|
"SELECT id, name FROM moop_model_set ORDER BY name COLLATE NOCASE"
|
|
).fetchall():
|
|
set_id = r["id"]
|
|
result.append({
|
|
"id": set_id,
|
|
"name": r["name"],
|
|
"types": _set_types_conn(conn, set_id),
|
|
"default_for": _set_defaults_conn(conn, set_id),
|
|
})
|
|
return result
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def get_set(path=None, set_id=None):
|
|
if set_id is None:
|
|
return None
|
|
conn = _connect(path)
|
|
try:
|
|
r = conn.execute(
|
|
"SELECT id, name FROM moop_model_set WHERE id = ?", (set_id,)
|
|
).fetchone()
|
|
if not r:
|
|
return None
|
|
return {
|
|
"id": r["id"],
|
|
"name": r["name"],
|
|
"types": _set_types_conn(conn, set_id),
|
|
"default_for": _set_defaults_conn(conn, set_id),
|
|
}
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def create_set(path=None, name=""):
|
|
conn = _connect(path)
|
|
try:
|
|
set_id = _id()
|
|
conn.execute(
|
|
"INSERT INTO moop_model_set (id, name) VALUES (?, ?)",
|
|
(set_id, (name or "").strip()),
|
|
)
|
|
conn.commit()
|
|
return set_id
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def rename_set(path=None, set_id=None, name=""):
|
|
if set_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
conn.execute(
|
|
"UPDATE moop_model_set SET name = ? WHERE id = ?",
|
|
((name or "").strip(), set_id),
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def delete_set(path=None, set_id=None):
|
|
if set_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
conn.execute("DELETE FROM moop_model_set WHERE id = ?", (set_id,))
|
|
conn.commit()
|
|
_ensure_defaults_conn(conn)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
# --------------------------------------------------------------- types ---
|
|
|
|
def _type_id(conn, type_name):
|
|
r = conn.execute(
|
|
"SELECT id FROM moop_model_type WHERE type = ?", (type_name,)
|
|
).fetchone()
|
|
if r:
|
|
return r["id"]
|
|
tid = _id()
|
|
conn.execute(
|
|
"INSERT INTO moop_model_type (id, type) VALUES (?, ?)", (tid, type_name)
|
|
)
|
|
return tid
|
|
|
|
|
|
def add_type(path=None, set_id=None, type_name=""):
|
|
if set_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
tid = _type_id(conn, type_name)
|
|
exists = conn.execute(
|
|
'SELECT id FROM moop_model_set_type WHERE "set" = ? AND type = ?',
|
|
(set_id, tid),
|
|
).fetchone()
|
|
if not exists:
|
|
conn.execute(
|
|
'INSERT INTO moop_model_set_type (id, "set", type) VALUES (?, ?, ?)',
|
|
(_id(), set_id, tid),
|
|
)
|
|
conn.commit()
|
|
_ensure_defaults_conn(conn)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def remove_type(path=None, set_id=None, type_name=""):
|
|
if set_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
tid = conn.execute(
|
|
"SELECT id FROM moop_model_type WHERE type = ?", (type_name,)
|
|
).fetchone()
|
|
if not tid:
|
|
return
|
|
conn.execute(
|
|
'DELETE FROM moop_model_set_type WHERE "set" = ? AND type = ?',
|
|
(set_id, tid["id"]),
|
|
)
|
|
conn.commit()
|
|
_ensure_defaults_conn(conn)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
# -------------------------------------------------------------- default ---
|
|
|
|
def set_default(path=None, type_name="", set_id=None):
|
|
"""Set `set_id` sebagai default untuk `type_name`. Jika set belum punya
|
|
type tersebut, type otomatis ditambahkan."""
|
|
if set_id is None or not type_name:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
tid = _type_id(conn, type_name)
|
|
st = conn.execute(
|
|
'SELECT id FROM moop_model_set_type WHERE "set" = ? AND type = ?',
|
|
(set_id, tid),
|
|
).fetchone()
|
|
if not st:
|
|
st_id = _id()
|
|
conn.execute(
|
|
'INSERT INTO moop_model_set_type (id, "set", type) VALUES (?, ?, ?)',
|
|
(st_id, set_id, tid),
|
|
)
|
|
else:
|
|
st_id = st["id"]
|
|
conn.execute("DELETE FROM moop_model_type_default WHERE type = ?", (tid,))
|
|
conn.execute(
|
|
"INSERT INTO moop_model_type_default (id, type, set_type) VALUES (?, ?, ?)",
|
|
(_id(), tid, st_id),
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def get_default_set_id(path=None, type_name=""):
|
|
conn = _connect(path)
|
|
try:
|
|
r = conn.execute(
|
|
'SELECT s.id AS id FROM moop_model_type_default d '
|
|
'JOIN moop_model_type mt ON mt.id = d.type '
|
|
'JOIN moop_model_set_type st ON st.id = d.set_type '
|
|
'JOIN moop_model_set s ON s.id = st."set" '
|
|
'WHERE mt.type = ?',
|
|
(type_name,),
|
|
).fetchone()
|
|
return r["id"] if r else None
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
# -------------------------------------------------------------- provider ---
|
|
|
|
def list_apis(path=None):
|
|
conn = _connect(path)
|
|
try:
|
|
rows = conn.execute(
|
|
"SELECT a.id, a.name, a.baseurl, "
|
|
"(SELECT COUNT(*) FROM moop_key k WHERE k.api = a.id) AS key_count, "
|
|
"(SELECT COUNT(*) FROM moop_model m WHERE m.api = a.id) AS model_count "
|
|
"FROM moop_api a ORDER BY a.name COLLATE NOCASE"
|
|
).fetchall()
|
|
return [dict(r) for r in rows]
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def get_api(path=None, api_id=None):
|
|
if api_id is None:
|
|
return None
|
|
conn = _connect(path)
|
|
try:
|
|
r = conn.execute(
|
|
"SELECT id, name, baseurl FROM moop_api WHERE id = ?", (api_id,)
|
|
).fetchone()
|
|
return dict(r) if r else None
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def create_api(path=None, name="", baseurl=""):
|
|
conn = _connect(path)
|
|
try:
|
|
api_id = _id()
|
|
conn.execute(
|
|
"INSERT INTO moop_api (id, name, baseurl) VALUES (?, ?, ?)",
|
|
(api_id, (name or "").strip(), (baseurl or "").strip()),
|
|
)
|
|
conn.commit()
|
|
return api_id
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def update_api(path=None, api_id=None, name="", baseurl=""):
|
|
if api_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
conn.execute(
|
|
"UPDATE moop_api SET name = ?, baseurl = ? WHERE id = ?",
|
|
((name or "").strip(), (baseurl or "").strip(), api_id),
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def delete_api(path=None, api_id=None):
|
|
if api_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
conn.execute("DELETE FROM moop_api WHERE id = ?", (api_id,))
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
# ------------------------------------------------------------------ keys ---
|
|
|
|
def list_keys(path=None, api_id=None):
|
|
conn = _connect(path)
|
|
try:
|
|
rows = conn.execute(
|
|
"SELECT id, key, priority FROM moop_key WHERE api = ? "
|
|
"ORDER BY priority IS NULL ASC, priority ASC, rowid ASC",
|
|
(api_id,),
|
|
).fetchall()
|
|
return [dict(r) for r in rows]
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def add_key(path=None, api_id=None, key="", priority=None):
|
|
if api_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
conn.execute(
|
|
"INSERT INTO moop_key (id, key, priority, api) VALUES (?, ?, ?, ?)",
|
|
(_id(), key or "", priority, api_id),
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def update_key(path=None, key_id=None, key="", priority=None):
|
|
if key_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
conn.execute(
|
|
"UPDATE moop_key SET key = ?, priority = ? WHERE id = ?",
|
|
(key or "", priority, key_id),
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def delete_key(path=None, key_id=None):
|
|
if key_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
conn.execute("DELETE FROM moop_key WHERE id = ?", (key_id,))
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
# ----------------------------------------------------------------- models ---
|
|
|
|
def list_models(path=None, set_id=None):
|
|
conn = _connect(path)
|
|
try:
|
|
rows = conn.execute(
|
|
'SELECT m.id AS model_id, m.model, m.priority, '
|
|
'a.id AS api_id, a.name AS provider, a.baseurl AS baseurl '
|
|
'FROM moop_model m JOIN moop_api a ON a.id = m.api '
|
|
'WHERE m."set" = ? '
|
|
'ORDER BY m.priority IS NULL ASC, m.priority ASC, m.rowid ASC',
|
|
(set_id,),
|
|
).fetchall()
|
|
return [dict(r) for r in rows]
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def add_model(path=None, set_id=None, api_id=None, model="", priority=None):
|
|
if set_id is None or api_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
conn.execute(
|
|
'INSERT INTO moop_model (id, api, "set", model, priority) '
|
|
"VALUES (?, ?, ?, ?, ?)",
|
|
(_id(), api_id, set_id, model or "", priority),
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def update_model(path=None, model_id=None, model="", priority=None):
|
|
if model_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
conn.execute(
|
|
"UPDATE moop_model SET model = ?, priority = ? WHERE id = ?",
|
|
(model or "", priority, model_id),
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def delete_model(path=None, model_id=None):
|
|
if model_id is None:
|
|
return
|
|
conn = _connect(path)
|
|
try:
|
|
conn.execute("DELETE FROM moop_model WHERE id = ?", (model_id,))
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
# ----------------------------------------------------------------- chain ---
|
|
|
|
def resolve_chain(path=None, set_id=None):
|
|
"""Bangun chain kandidat untuk satu model set.
|
|
|
|
Urutan: model diurutkan berdasarkan priority (NULL = disabled), lalu untuk
|
|
tiap model, semua key provider-nya (priority, NULL = disabled). Provider
|
|
tanpa key tetap jadi 1 kandidat dengan api_key kosong (= no bearer).
|
|
Setiap kandidat membawa (api_id, model_id) untuk membedakan rotasi key."""
|
|
if set_id is None:
|
|
return []
|
|
conn = _connect(path)
|
|
try:
|
|
models = conn.execute(
|
|
'SELECT m.id AS model_id, m.model, '
|
|
'a.id AS api_id, a.name AS provider, a.baseurl AS base_url '
|
|
'FROM moop_model m JOIN moop_api a ON a.id = m.api '
|
|
'WHERE m."set" = ? AND m.priority IS NOT NULL '
|
|
'ORDER BY m.priority ASC, m.rowid ASC',
|
|
(set_id,),
|
|
).fetchall()
|
|
candidates = []
|
|
for m in models:
|
|
keys = conn.execute(
|
|
"SELECT key FROM moop_key WHERE api = ? AND priority IS NOT NULL "
|
|
"ORDER BY priority ASC, rowid ASC",
|
|
(m["api_id"],),
|
|
).fetchall()
|
|
if keys:
|
|
for k in keys:
|
|
candidates.append({
|
|
"base_url": m["base_url"],
|
|
"model": m["model"],
|
|
"api_key": k["key"],
|
|
"api_id": m["api_id"],
|
|
"model_id": m["model_id"],
|
|
"provider": m["provider"],
|
|
})
|
|
else:
|
|
candidates.append({
|
|
"base_url": m["base_url"],
|
|
"model": m["model"],
|
|
"api_key": "",
|
|
"api_id": m["api_id"],
|
|
"model_id": m["model_id"],
|
|
"provider": m["provider"],
|
|
})
|
|
return candidates
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def default_chain(path=None, type_name=""):
|
|
"""Chain untuk default model set dari tipe tertentu."""
|
|
set_id = get_default_set_id(path, type_name)
|
|
if not set_id:
|
|
return []
|
|
return resolve_chain(path, set_id)
|
|
|
|
|
|
def resolve_provider(path=None, base_url="", model=""):
|
|
"""Cari nama provider untuk (base_url, model) di chain default semua tipe."""
|
|
base_url = (base_url or "").rstrip("/")
|
|
for t in MODEL_TYPES:
|
|
for cand in default_chain(path, t):
|
|
if (cand["base_url"] or "").rstrip("/") == base_url and cand["model"] == model:
|
|
return cand["provider"]
|
|
return None
|
|
|
|
|
|
def configure_client(llm_client, path=None):
|
|
"""Isi chain llm + imagevision ke LLMClient berdasarkan default set.
|
|
Mengembalikan True jika chain default 'llm' tersedia."""
|
|
p = os.path.expanduser(path or moop_db_path())
|
|
init_db(p)
|
|
chains = {
|
|
"llm": default_chain(p, "llm"),
|
|
"imagevision": default_chain(p, "imagevision"),
|
|
}
|
|
llm_client.set_chains(chains)
|
|
return bool(chains["llm"])
|
|
|
|
|
|
def first_endpoint(path=None, type_name=""):
|
|
"""Kandidat pertama (base_url, model, api_key) dari chain default tipe.
|
|
Mengembalikan None jika tidak ada."""
|
|
chain = default_chain(path, type_name)
|
|
if not chain:
|
|
return None
|
|
c = chain[0]
|
|
return c["base_url"], c["model"], c.get("api_key")
|
|
|
|
|
|
def embedding_endpoint(path=None):
|
|
"""Endpoint embedding (url, model, api_key) dari default chain 'embedding'."""
|
|
return first_endpoint(path, "embedding")
|
|
|
|
|
|
def imagegen_endpoint(path=None):
|
|
"""Endpoint image generation (url, model, api_key) dari default chain 'imagegen'."""
|
|
return first_endpoint(path, "imagegen")
|