Skip to content

Commit d711c85

Browse files
committed
Added siliconflow API support
1 parent 2df50b3 commit d711c85

File tree

3 files changed

+145
-4
lines changed

3 files changed

+145
-4
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ MOONSHOT_API_KEY=
2727
UNBOUND_ENDPOINT=https://api.getunbound.ai
2828
UNBOUND_API_KEY=
2929

30+
SiliconFLOW_ENDPOINT=https://api.siliconflow.cn/v1/
31+
SiliconFLOW_API_KEY=
32+
3033
# Set to false to disable anonymized telemetry
3134
ANONYMIZED_TELEMETRY=false
3235

src/utils/llm.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
Literal,
3838
Optional,
3939
Union,
40-
cast,
40+
cast, List,
4141
)
4242

4343

@@ -136,3 +136,91 @@ def invoke(
136136
if "**JSON Response:**" in content:
137137
content = content.split("**JSON Response:**")[-1]
138138
return AIMessage(content=content, reasoning_content=reasoning_content)
139+
140+
141+
class SiliconFlowChat(ChatOpenAI):
142+
"""Wrapper for SiliconFlow Chat API, fully compatible with OpenAI-spec format."""
143+
144+
def __init__(self, *args: Any, **kwargs: Any) -> None:
145+
super().__init__(*args, **kwargs)
146+
147+
# Ensure the API client is initialized with SiliconFlow's endpoint and key
148+
self.client = OpenAI(
149+
api_key=kwargs.get("api_key"),
150+
base_url=kwargs.get("base_url")
151+
)
152+
153+
async def ainvoke(
154+
self,
155+
input: LanguageModelInput,
156+
config: Optional[RunnableConfig] = None,
157+
*,
158+
stop: Optional[List[str]] = None,
159+
**kwargs: Any,
160+
) -> AIMessage:
161+
"""Async call SiliconFlow API."""
162+
163+
# Convert input messages into OpenAI-compatible format
164+
message_history = []
165+
for input_msg in input:
166+
if isinstance(input_msg, SystemMessage):
167+
message_history.append({"role": "system", "content": input_msg.content})
168+
elif isinstance(input_msg, AIMessage):
169+
message_history.append({"role": "assistant", "content": input_msg.content})
170+
else: # HumanMessage or similar
171+
message_history.append({"role": "user", "content": input_msg.content})
172+
173+
# Send request to SiliconFlow API (OpenAI-spec endpoint)
174+
response = await self.client.chat.completions.create(
175+
model=self.model_name,
176+
messages=message_history,
177+
stop=stop,
178+
**kwargs,
179+
)
180+
181+
# Extract the AI response (SiliconFlow's response must match OpenAI format)
182+
if hasattr(response.choices[0].message, "reasoning_content"):
183+
reasoning_content = response.choices[0].message.reasoning_content
184+
else:
185+
reasoning_content = None
186+
187+
content = response.choices[0].message.content
188+
return AIMessage(content=content, reasoning_content=reasoning_content) # Return reasoning_content if needed
189+
190+
def invoke(
191+
self,
192+
input: LanguageModelInput,
193+
config: Optional[RunnableConfig] = None,
194+
*,
195+
stop: Optional[List[str]] = None,
196+
**kwargs: Any,
197+
) -> AIMessage:
198+
"""Sync call SiliconFlow API."""
199+
200+
# Same conversion as async version
201+
message_history = []
202+
for input_msg in input:
203+
if isinstance(input_msg, SystemMessage):
204+
message_history.append({"role": "system", "content": input_msg.content})
205+
elif isinstance(input_msg, AIMessage):
206+
message_history.append({"role": "assistant", "content": input_msg.content})
207+
else:
208+
message_history.append({"role": "user", "content": input_msg.content})
209+
210+
# Sync call
211+
response = self.client.chat.completions.create(
212+
model=self.model_name,
213+
messages=message_history,
214+
stop=stop,
215+
**kwargs,
216+
)
217+
218+
# Handle reasoning_content (if supported)
219+
reasoning_content = None
220+
if hasattr(response.choices[0].message, "reasoning_content"):
221+
reasoning_content = response.choices[0].message.reasoning_content
222+
223+
return AIMessage(
224+
content=response.choices[0].message.content,
225+
reasoning_content=reasoning_content, # Only if SiliconFlow supports it
226+
)

src/utils/utils.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from langchain_ollama import ChatOllama
1515
from langchain_openai import AzureChatOpenAI, ChatOpenAI
1616

17-
from .llm import DeepSeekR1ChatOpenAI, DeepSeekR1ChatOllama
17+
from .llm import DeepSeekR1ChatOpenAI, DeepSeekR1ChatOllama,SiliconFlowChat
1818

1919
PROVIDER_DISPLAY_NAMES = {
2020
"openai": "OpenAI",
@@ -165,9 +165,26 @@ def get_llm_model(provider: str, **kwargs):
165165
return ChatOpenAI(
166166
model=kwargs.get("model_name", "gpt-4o-mini"),
167167
temperature=kwargs.get("temperature", 0.0),
168-
base_url = os.getenv("UNBOUND_ENDPOINT", "https://api.getunbound.ai"),
168+
base_url=os.getenv("UNBOUND_ENDPOINT", "https://api.getunbound.ai"),
169169
api_key=api_key,
170170
)
171+
elif provider == "siliconflow":
172+
if not kwargs.get("api_key", ""):
173+
api_key = os.getenv("SiliconFLOW_API_KEY", "")
174+
else:
175+
api_key = kwargs.get("api_key")
176+
if not kwargs.get("base_url", ""):
177+
base_url = os.getenv("SiliconFLOW_ENDPOINT", "")
178+
else:
179+
base_url = kwargs.get("base_url")
180+
return SiliconFlowChat(
181+
api_key=api_key,
182+
base_url=base_url,
183+
model_name=kwargs.get("model_name", "Qwen/QwQ-32B"),
184+
temperature=kwargs.get("temperature", 0.0),
185+
max_tokens=kwargs.get("max_tokens", 512),
186+
frequency_penalty=kwargs.get("frequency_penalty", 0.5),
187+
)
171188
else:
172189
raise ValueError(f"Unsupported provider: {provider}")
173190

@@ -185,7 +202,40 @@ def get_llm_model(provider: str, **kwargs):
185202
"mistral": ["pixtral-large-latest", "mistral-large-latest", "mistral-small-latest", "ministral-8b-latest"],
186203
"alibaba": ["qwen-plus", "qwen-max", "qwen-turbo", "qwen-long"],
187204
"moonshot": ["moonshot-v1-32k-vision-preview", "moonshot-v1-8k-vision-preview"],
188-
"unbound": ["gemini-2.0-flash","gpt-4o-mini", "gpt-4o", "gpt-4.5-preview"]
205+
"unbound": ["gemini-2.0-flash", "gpt-4o-mini", "gpt-4o", "gpt-4.5-preview"],
206+
"siliconflow": [
207+
"deepseek-ai/DeepSeek-R1",
208+
"deepseek-ai/DeepSeek-V3",
209+
"deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
210+
"deepseek-ai/DeepSeek-R1-Distill-Qwen-14B",
211+
"deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",
212+
"deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B",
213+
"deepseek-ai/DeepSeek-V2.5",
214+
"deepseek-ai/deepseek-vl2",
215+
"Qwen/Qwen2.5-72B-Instruct-128K",
216+
"Qwen/Qwen2.5-72B-Instruct",
217+
"Qwen/Qwen2.5-32B-Instruct",
218+
"Qwen/Qwen2.5-14B-Instruct",
219+
"Qwen/Qwen2.5-7B-Instruct",
220+
"Qwen/Qwen2.5-Coder-32B-Instruct",
221+
"Qwen/Qwen2.5-Coder-7B-Instruct",
222+
"Qwen/Qwen2-7B-Instruct",
223+
"Qwen/Qwen2-1.5B-Instruct",
224+
"Qwen/QwQ-32B-Preview",
225+
"Qwen/Qwen2-VL-72B-Instruct",
226+
"Qwen/Qwen2.5-VL-32B-Instruct",
227+
"Qwen/Qwen2.5-VL-72B-Instruct",
228+
"TeleAI/TeleChat2",
229+
"THUDM/glm-4-9b-chat",
230+
"Vendor-A/Qwen/Qwen2.5-72B-Instruct",
231+
"internlm/internlm2_5-7b-chat",
232+
"internlm/internlm2_5-20b-chat",
233+
"Pro/Qwen/Qwen2.5-7B-Instruct",
234+
"Pro/Qwen/Qwen2-7B-Instruct",
235+
"Pro/Qwen/Qwen2-1.5B-Instruct",
236+
"Pro/THUDM/chatglm3-6b",
237+
"Pro/THUDM/glm-4-9b-chat",
238+
],
189239
}
190240

191241

0 commit comments

Comments
 (0)