Skip to content

Commit b9b1ae3

Browse files
committed
✨ feat: migrate SiliconFlow integration from ChatOpenAI to ChatSiliconFlow
Replace ChatOpenAI workaround with native ChatSiliconFlow implementation to enable proper function calling support (bind_tools) for ReAct agent workflows. Key changes: - Use ChatSiliconFlow from langchain-siliconflow>=0.1.3 instead of ChatOpenAI - Remove hardcoded base_url defaults to let ChatSiliconFlow handle configuration - Update all unit tests to mock ChatSiliconFlow - Upgrade evaluation judge to GLM-Z1 for better tool support All tests pass: 84/84 unit, 14/14 integration, 14/16 E2E, 6/6 evaluation
1 parent 5e6b1cd commit b9b1ae3

File tree

12 files changed

+172
-166
lines changed

12 files changed

+172
-166
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ dependencies = [
1818
"langchain-tavily>=0.1",
1919
"langchain-qwq>=0.2.1",
2020
"langchain-mcp-adapters>=0.1.9",
21+
"langchain-siliconflow>=0.1.3",
2122
]
2223

2324

src/common/models/siliconflow.py

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
import os
44
from typing import Any, Optional
55

6-
# NOTE: Using ChatOpenAI instead of ChatSiliconFlow because langchain-siliconflow v0.1.1
7-
# does not support function calling (bind_tools raises NotImplementedError).
8-
# We'll switch back to ChatSiliconFlow once they add function calling support.
9-
from langchain_openai import ChatOpenAI
6+
from langchain_siliconflow import ChatSiliconFlow
107

118
from ..utils import normalize_region
129

@@ -17,12 +14,8 @@ def create_siliconflow_model(
1714
base_url: Optional[str] = None,
1815
region: Optional[str] = None,
1916
**kwargs: Any,
20-
) -> ChatOpenAI:
21-
"""Create a SiliconFlow model using ChatOpenAI (OpenAI-compatible API).
22-
23-
NOTE: Using ChatOpenAI instead of ChatSiliconFlow because langchain-siliconflow v0.1.1
24-
does not support function calling (bind_tools raises NotImplementedError).
25-
SiliconFlow provides OpenAI-compatible API endpoints, so we use ChatOpenAI directly.
17+
) -> ChatSiliconFlow:
18+
"""Create a SiliconFlow model using ChatSiliconFlow.
2619
2720
Args:
2821
model_name: The model name (e.g., 'Qwen/Qwen3-8B', 'THUDM/GLM-4.1V-9B-Thinking')
@@ -33,7 +26,7 @@ def create_siliconflow_model(
3326
**kwargs: Additional model parameters
3427
3528
Returns:
36-
Configured ChatOpenAI instance pointing to SiliconFlow API
29+
Configured ChatSiliconFlow instance
3730
"""
3831
# Get API key from env if not provided
3932
if api_key is None:
@@ -52,16 +45,11 @@ def create_siliconflow_model(
5245
elif normalized_region == "international":
5346
base_url = "https://api.siliconflow.com/v1"
5447

55-
# Default to PRC endpoint if no region specified
56-
if base_url is None:
57-
base_url = "https://api.siliconflow.cn/v1"
48+
# Create ChatSiliconFlow configuration
49+
config = {"model": model_name, "api_key": api_key, **kwargs}
5850

59-
# Create ChatOpenAI configuration for SiliconFlow
60-
config = {
61-
"model": model_name,
62-
"api_key": api_key,
63-
"base_url": base_url,
64-
**kwargs
65-
}
51+
# Only add base_url if explicitly provided
52+
if base_url is not None:
53+
config["base_url"] = base_url
6654

67-
return ChatOpenAI(**config)
55+
return ChatSiliconFlow(**config)

src/common/utils.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010

1111
def normalize_region(region: str) -> Optional[str]:
1212
"""Normalize region aliases to standard values.
13-
13+
1414
Args:
1515
region: Region string to normalize
16-
16+
1717
Returns:
1818
Normalized region ('prc' or 'international') or None if invalid
1919
"""
@@ -54,11 +54,13 @@ def load_chat_model(
5454
# Handle Qwen models specially with dashscope integration
5555
if provider_lower == "qwen":
5656
from .models import create_qwen_model
57+
5758
return create_qwen_model(model)
5859

5960
# Handle SiliconFlow models
6061
if provider_lower == "siliconflow":
6162
from .models import create_siliconflow_model
63+
6264
return create_siliconflow_model(model)
6365

6466
# Use standard langchain initialization for other providers

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ async def assistant_id(langgraph_client):
4141
},
4242
)
4343
assistant_id = assistant["assistant_id"]
44-
44+
4545
yield assistant_id
46-
46+
4747
# Cleanup
4848
try:
4949
await langgraph_client.assistants.delete(assistant_id)

tests/e2e_tests/test_siliconflow.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
# Test Models
88
QWEN3_8B = "siliconflow:Qwen/Qwen3-8B" # Traditional text model
9-
GLM_Z1 = "siliconflow:THUDM/GLM-Z1-9B-0414" # Reasoning model (supports function calling)
9+
GLM_Z1 = (
10+
"siliconflow:THUDM/GLM-Z1-9B-0414" # Reasoning model (supports function calling)
11+
)
1012

1113
# Test Data
1214
STUDIO_UI_PATH = os.path.join(
@@ -288,9 +290,9 @@ async def test_glm_z1_reasoning_task(langgraph_client) -> None:
288290
"feet",
289291
"heads",
290292
]
291-
assert any(
292-
term in final_response for term in expected_terms
293-
), f"Expected reasoning terms in response: {final_response[:200]}..."
293+
assert any(term in final_response for term in expected_terms), (
294+
f"Expected reasoning terms in response: {final_response[:200]}..."
295+
)
294296

295297
finally:
296298
await langgraph_client.assistants.delete(assistant_id)
@@ -340,8 +342,6 @@ async def test_glm4_complex_reasoning_task(langgraph_client) -> None:
340342
await langgraph_client.assistants.delete(assistant_id)
341343

342344

343-
344-
345345
@pytest.mark.e2e
346346
async def test_glm4_reasoning_chain(langgraph_client) -> None:
347347
"""Test GLM-4.1V reasoning chain capabilities."""
@@ -394,5 +394,3 @@ async def test_glm4_reasoning_chain(langgraph_client) -> None:
394394

395395
finally:
396396
await langgraph_client.assistants.delete(assistant_id)
397-
398-

tests/evaluations/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"""Evaluation tests for the ReAct agent using AgentEvals framework."""
1+
"""Evaluation tests for the ReAct agent using AgentEvals framework."""

tests/evaluations/conftest.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,9 @@ def pytest_configure(config):
77
"""Configure pytest for evaluation tests."""
88
# Register custom markers
99
config.addinivalue_line(
10-
"markers",
11-
"slow: marks tests as slow (deselect with '-m \"not slow\"')"
12-
)
13-
config.addinivalue_line(
14-
"markers",
15-
"evaluation: marks tests as evaluation tests"
10+
"markers", "slow: marks tests as slow (deselect with '-m \"not slow\"')"
1611
)
12+
config.addinivalue_line("markers", "evaluation: marks tests as evaluation tests")
1713

1814

1915
@pytest.fixture(scope="session")
@@ -29,4 +25,4 @@ def evaluation_config():
2925
def evaluation_thread_id(request):
3026
"""Generate unique thread ID for evaluation tests."""
3127
test_name = request.node.name
32-
return f"eval_test_{test_name}_{id(request)}"
28+
return f"eval_test_{test_name}_{id(request)}"

0 commit comments

Comments
 (0)