Skip to content

Commit 2953098

Browse files
committed
Unbound Integration
+ remove my defaults & dotenv version squash with model list minor fixes
1 parent ec6963b commit 2953098

File tree

4 files changed

+91
-4
lines changed

4 files changed

+91
-4
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ ALIBABA_API_KEY=
2424
MOONSHOT_ENDPOINT=https://api.moonshot.cn/v1
2525
MOONSHOT_API_KEY=
2626

27+
UNBOUND_ENDPOINT=https://api.getunbound.ai
28+
UNBOUND_API_KEY=
29+
2730
# Set to false to disable anonymized telemetry
2831
ANONYMIZED_TELEMETRY=false
2932

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ gradio==5.10.0
44
json-repair
55
langchain-mistralai==0.2.4
66
langchain-google-genai==2.0.8
7-
MainContentExtractor==0.0.4
7+
MainContentExtractor==0.0.4

src/utils/llm.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from openai import OpenAI
22
import pdb
3+
import os
4+
from dotenv import load_dotenv
35
from langchain_openai import ChatOpenAI
46
from langchain_core.globals import get_llm_cache
57
from langchain_core.language_models.base import (
@@ -29,6 +31,9 @@
2931
from langchain_core.output_parsers.base import OutputParserLike
3032
from langchain_core.runnables import Runnable, RunnableConfig
3133
from langchain_core.tools import BaseTool
34+
from pydantic import Field, PrivateAttr
35+
import requests
36+
import urllib3
3237

3338
from typing import (
3439
TYPE_CHECKING,
@@ -103,6 +108,72 @@ def invoke(
103108
return AIMessage(content=content, reasoning_content=reasoning_content)
104109

105110

111+
# Load environment variables
112+
load_dotenv()
113+
114+
class UnboundChatOpenAI(ChatOpenAI):
115+
"""Chat model that uses Unbound's API."""
116+
117+
_session: requests.Session = PrivateAttr()
118+
119+
def __init__(self, *args: Any, **kwargs: Any) -> None:
120+
kwargs["base_url"] = kwargs.get("base_url", os.getenv("UNBOUND_ENDPOINT", "https://api.getunbound.ai"))
121+
kwargs["api_key"] = kwargs.get("api_key", os.getenv("UNBOUND_API_KEY"))
122+
if not kwargs["api_key"]:
123+
raise ValueError("UNBOUND_API_KEY environment variable is not set")
124+
super().__init__(*args, **kwargs)
125+
126+
self.client = OpenAI(
127+
base_url=kwargs["base_url"],
128+
api_key=kwargs["api_key"]
129+
)
130+
131+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
132+
133+
self._session = requests.Session()
134+
self._session.verify = False
135+
136+
def invoke(
137+
self,
138+
input: LanguageModelInput,
139+
config: Optional[RunnableConfig] = None,
140+
*,
141+
stop: Optional[list[str]] = None,
142+
**kwargs: Any,
143+
) -> AIMessage:
144+
message_history = []
145+
for input_ in input:
146+
if isinstance(input_, SystemMessage):
147+
message_history.append({"role": "system", "content": input_.content})
148+
elif isinstance(input_, AIMessage):
149+
message_history.append({"role": "assistant", "content": input_.content})
150+
else:
151+
message_history.append({"role": "user", "content": input_.content})
152+
153+
response = self._session.post(
154+
f"{self.client.base_url}/v1/chat/completions",
155+
headers={"Authorization": f"Bearer {self.client.api_key}", "Content-Type": "application/json"},
156+
json={
157+
"model": self.model_name or "gpt-4o-mini",
158+
"messages": message_history
159+
}
160+
)
161+
response.raise_for_status()
162+
data = response.json()
163+
content = data["choices"][0]["message"]["content"]
164+
return AIMessage(content=content)
165+
166+
async def ainvoke(
167+
self,
168+
input: LanguageModelInput,
169+
config: Optional[RunnableConfig] = None,
170+
*,
171+
stop: Optional[list[str]] = None,
172+
**kwargs: Any,
173+
) -> AIMessage:
174+
return self.invoke(input, config, stop=stop, **kwargs)
175+
176+
106177
class DeepSeekR1ChatOllama(ChatOllama):
107178

108179
async def ainvoke(

src/utils/utils.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from langchain_openai import AzureChatOpenAI, ChatOpenAI
1313
import gradio as gr
1414

15-
from .llm import DeepSeekR1ChatOpenAI, DeepSeekR1ChatOllama
15+
from .llm import DeepSeekR1ChatOpenAI, DeepSeekR1ChatOllama, UnboundChatOpenAI
1616

1717
PROVIDER_DISPLAY_NAMES = {
1818
"openai": "OpenAI",
@@ -21,7 +21,8 @@
2121
"deepseek": "DeepSeek",
2222
"google": "Google",
2323
"alibaba": "Alibaba",
24-
"moonshot": "MoonShot"
24+
"moonshot": "MoonShot",
25+
"unbound": "Unbound AI"
2526
}
2627

2728

@@ -151,14 +152,25 @@ def get_llm_model(provider: str, **kwargs):
151152
base_url=base_url,
152153
api_key=api_key,
153154
)
154-
155155
elif provider == "moonshot":
156156
return ChatOpenAI(
157157
model=kwargs.get("model_name", "moonshot-v1-32k-vision-preview"),
158158
temperature=kwargs.get("temperature", 0.0),
159159
base_url=os.getenv("MOONSHOT_ENDPOINT"),
160160
api_key=os.getenv("MOONSHOT_API_KEY"),
161161
)
162+
elif provider == "unbound":
163+
if not kwargs.get("base_url", ""):
164+
base_url = os.getenv("UNBOUND_ENDPOINT", "https://api.getunbound.ai")
165+
else:
166+
base_url = kwargs.get("base_url")
167+
168+
return UnboundChatOpenAI(
169+
model=kwargs.get("model_name", "gpt-4o-mini"),
170+
temperature=kwargs.get("temperature", 0.0),
171+
base_url=base_url,
172+
api_key=api_key,
173+
)
162174
else:
163175
raise ValueError(f"Unsupported provider: {provider}")
164176

@@ -176,6 +188,7 @@ def get_llm_model(provider: str, **kwargs):
176188
"mistral": ["mixtral-large-latest", "mistral-large-latest", "mistral-small-latest", "ministral-8b-latest"],
177189
"alibaba": ["qwen-plus", "qwen-max", "qwen-turbo", "qwen-long"],
178190
"moonshot": ["moonshot-v1-32k-vision-preview", "moonshot-v1-8k-vision-preview"],
191+
"unbound": ["gemini-2.0-flash","gpt-4o-mini", "gpt-4o", "gpt-4.5-preview"]
179192
}
180193

181194

0 commit comments

Comments
 (0)