Ollamaをチームで使うためのOSS、Ollama4Teamを公開しました
TLDR
Ollamaをチームで使えるソフトウェアをOSSで公開しました。
機能として、
- OllamaのAPIを呼ぶ際の簡易的な認証
- ウェブから
- メンバー管理
- 統計情報の表示
- モデルの追加・削除
を提供しています。
こちらからインストールできます。
ぜひスターお願いします(スター乞食)。
背景
Llama等のオープンなモデルは外部にデータを公開せずに利用できる等のメリットがあります。しかし計算資源的に、70B等の大きなパラメータのモデルを手元の環境で動かすことは難しくなります。
そこで、チーム(中小規模の企業、大学の研究室等)が所有する共有の計算資源でローカルモデル用のサーバを立てたくなります。ローカルモデルを手軽に動かすのに便利なソフトウェアとしてOllamaがありますが、Ollamaはチームで利用するために必要な機能が不足しています。
チームで共有してローカルのモデルを利用する場合、
- サーバを建てるので、利用者は認証したい
- 誰がいつどの程度利用しているのかを知りたい
という要望があります。
その要望に応えるのがOllama4Teamです。
Ollama4Team
JupyterHubに着想を得てます。
インストール
詳細はリポジトリにありますが、
- Git clone
- envを編集
- Docker-compose up
でOllamaを含めて起動できます。
Generate or embeddings
Generate(https://OLLAMA4TEAM_URL/api/generate
)とEmbeddings(https://OLLAMA4TEAM_URL/api/embeddings
)があります。Streamにも対応しています。
curl -X POST -H "Content-Type: application/json" -H "Authorization: Basic YOUR_PASSWORD" -d '{"model": "YOUR_MODEL", "prompt": "YOUR_PROMPT"}' "https://OLLAMA4TEAM_URL/api/embeddings"
で呼べます。
OllamaのAPIに準拠していて、Authorization: Basic YOUR_PASSWORD
を付け加えます。
- Generate a completion
- Generate Embeddings
Langchainから使う方法はTipsに記述してます。
メンバー管理
下図のようにメンバーを管理できます。adminは基本的に全ての操作を、memberは自分のパスワード変更やアカウント追加が許可されています(今後、権限はenvで詳細に設定できるようにしていきます)。
モデルの追加・削除
統計情報の表示
統計情報のダッシュボードは充足予定です。
Tips
Langchainでの利用
以下コードをコピペして、
コピペコード
from typing import Any, Dict, Iterator, List, Optional
import requests
from langchain_community.llms import Ollama
from langchain_community.embeddings import OllamaEmbeddings
class Ollama4Team(Ollama):
password: str
def _create_stream(
self,
api_url: str,
payload: Any,
stop: Optional[List[str]] = None,
**kwargs: Any,
) -> Iterator[str]:
if self.stop is not None and stop is not None:
raise ValueError("`stop` found in both the input and default params.")
elif self.stop is not None:
stop = self.stop
params = self._default_params
for key in self._default_params:
if key in kwargs:
params[key] = kwargs[key]
if "options" in kwargs:
params["options"] = kwargs["options"]
else:
params["options"] = {
**params["options"],
"stop": stop,
**{k: v for k, v in kwargs.items() if k not in self._default_params},
}
if payload.get("messages"):
request_payload = {"messages": payload.get("messages", []), **params}
else:
request_payload = {
"prompt": payload.get("prompt"),
"images": payload.get("images", []),
**params,
}
response = requests.post(
url=api_url,
headers={
"Content-Type": "application/json",
"Authorization": f"Basic {self.password}",
**(self.headers if isinstance(self.headers, dict) else {}),
},
json=request_payload,
stream=True,
timeout=self.timeout,
)
response.encoding = "utf-8"
if response.status_code != 200:
if response.status_code == 404:
raise self.OllamaEndpointNotFoundError(
"Ollama call failed with status code 404. "
"Maybe your model is not found "
f"and you should pull the model with `ollama pull {self.model}`."
)
else:
optional_detail = response.text
raise ValueError(
f"Ollama call failed with status code {response.status_code}."
f" Details: {optional_detail}"
)
return response.iter_lines(decode_unicode=True)
@property
def _identifying_params(self) -> Dict[str, Any]:
"""Return a dictionary of identifying parameters."""
return {
# The model name allows users to specify custom token counting
# rules in LLM monitoring applications (e.g., in LangSmith users
# can provide per token pricing for their model and monitor
# costs for the given LLM.)
"model_name": self.model,
}
@property
def _llm_type(self) -> str:
"""Get the type of language model used by this chat model. Used for logging purposes only."""
return "Ollama4Team"
class Ollama4TeamEmbeddings(OllamaEmbeddings):
password: str
def _process_emb_response(self, input: str) -> List[float]:
"""Process a response from the API.
Args:
response: The response from the API.
Returns:
The response as a dictionary.
"""
headers = {
"Content-Type": "application/json",
"Authorization": f"Basic {self.password}",
**(self.headers or {}),
}
try:
res = requests.post(
f"{self.base_url}/api/embeddings",
headers=headers,
json={"model": self.model, "prompt": input, **self._default_params},
)
except requests.exceptions.RequestException as e:
raise ValueError(f"Error raised by inference endpoint: {e}")
if res.status_code != 200:
raise ValueError(
"Error raised by inference API HTTP code: %s, %s"
% (res.status_code, res.text)
)
try:
t = res.json()
return t["embedding"]
except requests.exceptions.JSONDecodeError as e:
raise ValueError(
f"Error raised by inference API: {e}.\nResponse: {res.text}"
)
以下のように呼び出せます。
llm = Ollama4Team(model="llama3", password="password", base_url="https://localhost:3000")
for chunk in llm.stream("Write me a 1 verse song about sparkling water."):
print(chunk, end="|", flush=True)
embeddings = Ollama4TeamEmbeddings(model="llama3", password="password", base_url="https://localhost:3000")
text = "This is a test document."
query_result = embeddings.embed_query(text)
base_urlにはOllama4TeamのURLを指定し、passwordには自分のアカウントのパスワードを指定します。
さいごに
Ollamaをチームで使うためのOSSを作成しました。
共有の計算資源でローカルLLMを立ち上げる必要のある方は、ぜひ触ってみてください!
そしてPRやIssueはウェルカムです!励みになるのでスター☆ポチっとお願いしますー。
Discussion