Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

supporting local model for jupyter-ai with ollama #868

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions docs/source/users/index.md
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for adding this PR, which also complements PR #646.

[1] It would be nice to add more documentation on Ollama, directing the user to install it first and how, pointing to the necessary Ollama resources. Specific examples of Ollama CLI commands needed would help the user. See the note by @pedrogutobjj above for example. Hopefully, people who use Ollama with its CLI will know what to do, but the docs should reflect the requirement that the Ollama local server should be kicked off with ollama serve.

[2] Since the number of models in Ollama is very large, the drop down list can become unwieldy. Would it be possible to implement Ollama in the same way as Huggingface is implemented with a input box for the model in chat settings?
image
It would also need a similar approach for choice of Embedding Model.

Original file line number Diff line number Diff line change
Expand Up @@ -153,23 +153,24 @@ Jupyter AI supports a wide range of model providers and models. To use Jupyter A

Jupyter AI supports the following model providers:

| Provider | Provider ID | Environment variable(s) | Python package(s) |
|---------------------|----------------------|----------------------------|---------------------------------|
| AI21 | `ai21` | `AI21_API_KEY` | `ai21` |
| Anthropic | `anthropic` | `ANTHROPIC_API_KEY` | `langchain-anthropic` |
| Anthropic (chat) | `anthropic-chat` | `ANTHROPIC_API_KEY` | `langchain-anthropic` |
| Bedrock | `bedrock` | N/A | `boto3` |
| Bedrock (chat) | `bedrock-chat` | N/A | `boto3` |
| Cohere | `cohere` | `COHERE_API_KEY` | `langchain_cohere` |
| ERNIE-Bot | `qianfan` | `QIANFAN_AK`, `QIANFAN_SK` | `qianfan` |
| Gemini | `gemini` | `GOOGLE_API_KEY` | `langchain-google-genai` |
| GPT4All | `gpt4all` | N/A | `gpt4all` |
| Hugging Face Hub | `huggingface_hub` | `HUGGINGFACEHUB_API_TOKEN` | `huggingface_hub`, `ipywidgets`, `pillow` |
| MistralAI | `mistralai` | `MISTRAL_API_KEY` | `langchain-mistralai` |
| NVIDIA | `nvidia-chat` | `NVIDIA_API_KEY` | `langchain_nvidia_ai_endpoints` |
| OpenAI | `openai` | `OPENAI_API_KEY` | `langchain-openai` |
| OpenAI (chat) | `openai-chat` | `OPENAI_API_KEY` | `langchain-openai` |
| SageMaker | `sagemaker-endpoint` | N/A | `boto3` |
| Provider | Provider ID | Environment variable(s) | Python package(s) |
|------------------|----------------------|----------------------------|-------------------------------------------|
| AI21 | `ai21` | `AI21_API_KEY` | `ai21` |
| Anthropic | `anthropic` | `ANTHROPIC_API_KEY` | `langchain-anthropic` |
| Anthropic (chat) | `anthropic-chat` | `ANTHROPIC_API_KEY` | `langchain-anthropic` |
| Bedrock | `bedrock` | N/A | `boto3` |
| Bedrock (chat) | `bedrock-chat` | N/A | `boto3` |
| Cohere | `cohere` | `COHERE_API_KEY` | `cohere` |
| ERNIE-Bot | `qianfan` | `QIANFAN_AK`, `QIANFAN_SK` | `qianfan` |
| Gemini | `gemini` | `GOOGLE_API_KEY` | `langchain-google-genai` |
| GPT4All | `gpt4all` | N/A | `gpt4all` |
| Hugging Face Hub | `huggingface_hub` | `HUGGINGFACEHUB_API_TOKEN` | `huggingface_hub`, `ipywidgets`, `pillow` |
| MistralAI | `mistralai` | `MISTRAL_API_KEY` | `langchain-mistralai` |
| NVIDIA | `nvidia-chat` | `NVIDIA_API_KEY` | `langchain_nvidia_ai_endpoints` |
| OpenAI | `openai` | `OPENAI_API_KEY` | `langchain-openai` |
| OpenAI (chat) | `openai-chat` | `OPENAI_API_KEY` | `langchain-openai` |
| SageMaker | `sagemaker-endpoint` | N/A | `boto3` |
| Ollama | `ollama` | `OLLAMA_API_URL` | N/A |

The environment variable names shown above are also the names of the settings keys used when setting up the chat interface.
If multiple variables are listed for a provider, **all** must be specified.
Expand Down
2 changes: 2 additions & 0 deletions packages/jupyter-ai-magics/jupyter_ai_magics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
BedrockEmbeddingsProvider,
GPT4AllEmbeddingsProvider,
HfHubEmbeddingsProvider,
OllamaEmbeddingsProvider,
QianfanEmbeddingsEndpointProvider,
)
from .exception import store_exception
Expand All @@ -23,6 +24,7 @@
BedrockProvider,
GPT4AllProvider,
HfHubProvider,
OllamaProvider,
QianfanProvider,
SmEndpointProvider,
TogetherAIProvider,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
from langchain.pydantic_v1 import BaseModel, Extra
from langchain_community.embeddings import (
BedrockEmbeddings,
CohereEmbeddings,
GPT4AllEmbeddings,
HuggingFaceHubEmbeddings,
OllamaEmbeddings,
OpenAIEmbeddings,
QianfanEmbeddingsEndpoint,
)

Expand Down Expand Up @@ -66,6 +69,42 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs, **model_kwargs)


class OllamaEmbeddingsProvider(BaseEmbeddingsProvider, OllamaEmbeddings):
id = "ollama"
name = "Ollama"
models = [
"gemma",
"gemma2",
"llama2",
"llama3",
"llama3:70b",
"phi3",
"mistral",
"tinyllama",
"qwen2",
"qwen2:7b",
"qwen2:72b",
]
model_id_key = "model"


class CohereEmbeddingsProvider(BaseEmbeddingsProvider, CohereEmbeddings):
id = "cohere"
name = "Cohere"
models = [
"embed-english-v2.0",
"embed-english-light-v2.0",
"embed-multilingual-v2.0",
"embed-english-v3.0",
"embed-english-light-v3.0",
"embed-multilingual-v3.0",
"embed-multilingual-light-v3.0",
]
model_id_key = "model"
pypi_package_deps = ["cohere"]
auth_strategy = EnvAuthStrategy(name="COHERE_API_KEY")


class HfHubEmbeddingsProvider(BaseEmbeddingsProvider, HuggingFaceHubEmbeddings):
id = "huggingface_hub"
name = "Hugging Face Hub"
Expand Down
80 changes: 80 additions & 0 deletions packages/jupyter-ai-magics/jupyter_ai_magics/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Union,
)

import requests
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the functions in your new class can be removed, then you will not need this line.

from jsonpath_ng import parse
from langchain.prompts import (
ChatPromptTemplate,
Expand All @@ -37,6 +38,7 @@
Bedrock,
GPT4All,
HuggingFaceEndpoint,
Ollama,
SagemakerEndpoint,
Together,
)
Expand Down Expand Up @@ -728,6 +730,84 @@ async def _acall(self, *args, **kwargs) -> Coroutine[Any, Any, str]:
return await self._call_in_executor(*args, **kwargs)


class OllamaProvider(BaseProvider, Ollama):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see a similar implementation in PR #646 with a much simpler class and see if all the functions defined in your file from line 760 onwards are necessary, or being called to produce the response.

id = "ollama"
name = "Ollama"
model_id_key = "model"
# List of available models
models = [
"gemma",
"gemma2",
"llama2",
"llama3",
"llama3:70b",
"phi3",
"mistral",
"tinyllama",
"qwen2",
"qwen2:7b",
"qwen2:72b",
]
# Default base URL for the API
base_url = "http://localhost:11434"
# Fields for configuring the provider
fields = [
TextField(
key="ollama_api_base", label="Base API URL (optional)", format="text"
),
]

def __init__(self, **kwargs):
# Extract 'ollama_api_base' from kwargs, if not present, it returns None
ollama_api_base = kwargs.pop("ollama_api_base", None)
# Initialize the parent class with the remaining kwargs
super().__init__(**kwargs)
# Set the base_url to ollama_api_base if provided, otherwise use the default base_url
self.base_url = ollama_api_base or self.base_url
# Create a session for making requests
self.session = requests.Session()

def _send_request(self, endpoint: str, data: dict) -> dict:
"""Send a POST request to the specified Ollama API endpoint."""
url = f"{self.base_url}/{endpoint}"
print("url is : ", url)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need this print statement? If so, use self.log.info() as is the approach in much of the code base.

try:
response = self.session.post(url, json=data)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
raise ValueError(f"Error during Ollama API call: {e}") from e

def _generate(self, prompt: str, model: str = "mistral", stop: list = None) -> str:
"""Generate text using the /generate endpoint."""
data = {
"model": model,
"prompt": prompt,
**({"stop": stop} if stop else {}),
}
response = self._send_request("api/generate", data)
return response.get("response", "")

def _chat(self, messages: list, model: str = "mistral") -> str:
"""Chat using the /chat endpoint."""
data = {
"model": model,
"messages": messages,
}
response = self._send_request("api/chat", data)
return response.get("response", "")

def _call(self, prompt: str = None, messages: list = None, **kwargs) -> str:
"""Determine which API endpoint to use based on input and call it."""
print(self.base_url)
if prompt is not None:
return self._generate(prompt=prompt, **kwargs)
elif messages is not None:
return self._chat(messages=messages, **kwargs)
else:
raise ValueError("Either 'prompt' or 'messages' must be provided.")
Comment on lines +781 to +808
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a need for implementing the Ollama class methods? Ollama chat model provides these methods, forking the code might deviate from the official LangChain LLMs.
https://python.langchain.com/v0.1/docs/integrations/chat/ollama/

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I noted the same above, and I have also tested removing these functions that the functionality is not impaired.



class JsonContentHandler(LLMContentHandler):
content_type = "application/json"
accepts = "application/json"
Expand Down
2 changes: 2 additions & 0 deletions packages/jupyter-ai-magics/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ anthropic-chat = "jupyter_ai_magics.partner_providers.anthropic:ChatAnthropicPro
cohere = "jupyter_ai_magics.partner_providers.cohere:CohereProvider"
gpt4all = "jupyter_ai_magics:GPT4AllProvider"
huggingface_hub = "jupyter_ai_magics:HfHubProvider"
ollama = "jupyter_ai_magics:OllamaProvider"
openai = "jupyter_ai_magics.partner_providers.openai:OpenAIProvider"
openai-chat = "jupyter_ai_magics.partner_providers.openai:ChatOpenAIProvider"
azure-chat-openai = "jupyter_ai_magics.partner_providers.openai:AzureChatOpenAIProvider"
Expand All @@ -76,6 +77,7 @@ cohere = "jupyter_ai_magics.partner_providers.cohere:CohereEmbeddingsProvider"
mistralai = "jupyter_ai_magics.partner_providers.mistralai:MistralAIEmbeddingsProvider"
gpt4all = "jupyter_ai_magics:GPT4AllEmbeddingsProvider"
huggingface_hub = "jupyter_ai_magics:HfHubEmbeddingsProvider"
ollama = "jupyter_ai_magics:OllamaEmbeddingsProvider"
openai = "jupyter_ai_magics.partner_providers.openai:OpenAIEmbeddingsProvider"
qianfan = "jupyter_ai_magics:QianfanEmbeddingsEndpointProvider"

Expand Down
Loading