mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 10:17:00 +00:00
test(fireworks): stabilize integration tests with rate limiting and retries (#37590)
Fireworks integration tests have been flaky against the live API with 429s. Adds a shared, xdist-aware rate limiter and a global retry policy so transient rate-limit errors no longer fail the suite. Mirrors the same fix recently applied to `langchain-mistralai`.
This commit is contained in:
@@ -15,7 +15,7 @@ test tests:
|
||||
uv run --group test pytest $(PYTEST_EXTRA) --disable-socket --allow-unix-socket $(TEST_FILE)
|
||||
|
||||
integration_test integration_tests:
|
||||
uv run --group test --group test_integration pytest -v --tb=short -n auto $(TEST_FILE)
|
||||
uv run --group test --group test_integration pytest -v --tb=short -n auto --retries 3 --retry-delay 2 $(TEST_FILE)
|
||||
|
||||
test_watch:
|
||||
uv run --group test ptw --snapshot-update --now . -- -vv $(TEST_FILE)
|
||||
|
||||
@@ -48,6 +48,7 @@ test = [
|
||||
"syrupy>=5.0.0,<6.0.0",
|
||||
"pytest-watcher>=0.3.4,<1.0.0",
|
||||
"pytest-asyncio>=1.3.0,<2.0.0",
|
||||
"pytest-retry>=1.7.0,<1.8.0",
|
||||
"pytest-socket>=0.7.0,<1.0.0",
|
||||
"pytest-xdist>=3.8.0,<4.0.0",
|
||||
"langchain-core>=1.4.0,<2.0.0",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
"""Shared rate limiter for Fireworks integration tests.
|
||||
|
||||
Scaled by ``PYTEST_XDIST_WORKER_COUNT`` so aggregate QPS across all xdist
|
||||
workers stays bounded near the target rate.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from langchain_core.rate_limiters import InMemoryRateLimiter
|
||||
|
||||
_TARGET_REQUESTS_PER_SECOND = 0.5
|
||||
_WORKER_COUNT = max(1, int(os.environ.get("PYTEST_XDIST_WORKER_COUNT", "1")))
|
||||
|
||||
rate_limiter = InMemoryRateLimiter(
|
||||
requests_per_second=_TARGET_REQUESTS_PER_SECOND / _WORKER_COUNT,
|
||||
)
|
||||
@@ -14,6 +14,7 @@ from pydantic import BaseModel, Field
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langchain_fireworks import ChatFireworks
|
||||
from tests.integration_tests._rate_limiter import rate_limiter
|
||||
|
||||
_MODEL = "accounts/fireworks/models/gpt-oss-120b"
|
||||
|
||||
@@ -21,7 +22,9 @@ _MODEL = "accounts/fireworks/models/gpt-oss-120b"
|
||||
@pytest.mark.parametrize("strict", [None, True, False])
|
||||
def test_tool_choice_bool(strict: bool | None) -> None: # noqa: FBT001
|
||||
"""Test that tool choice is respected with different strict values."""
|
||||
llm = ChatFireworks(model="accounts/fireworks/models/kimi-k2p6")
|
||||
llm = ChatFireworks(
|
||||
model="accounts/fireworks/models/kimi-k2p6", rate_limiter=rate_limiter
|
||||
)
|
||||
|
||||
class MyTool(BaseModel):
|
||||
name: str
|
||||
@@ -59,7 +62,9 @@ def test_tool_choice_bool(strict: bool | None) -> None: # noqa: FBT001
|
||||
|
||||
async def test_astream() -> None:
|
||||
"""Test streaming tokens from ChatFireworks."""
|
||||
llm = ChatFireworks(model="accounts/fireworks/models/kimi-k2p6")
|
||||
llm = ChatFireworks(
|
||||
model="accounts/fireworks/models/kimi-k2p6", rate_limiter=rate_limiter
|
||||
)
|
||||
|
||||
full: BaseMessageChunk | None = None
|
||||
chunks_with_token_counts = 0
|
||||
@@ -96,7 +101,7 @@ async def test_astream() -> None:
|
||||
|
||||
async def test_abatch_tags() -> None:
|
||||
"""Test batch tokens from ChatFireworks."""
|
||||
llm = ChatFireworks(model=_MODEL)
|
||||
llm = ChatFireworks(model=_MODEL, rate_limiter=rate_limiter)
|
||||
|
||||
result = await llm.abatch(
|
||||
["I'm Pickle Rick", "I'm not Pickle Rick"], config={"tags": ["foo"]}
|
||||
@@ -107,7 +112,7 @@ async def test_abatch_tags() -> None:
|
||||
|
||||
async def test_ainvoke() -> None:
|
||||
"""Test invoke tokens from ChatFireworks."""
|
||||
llm = ChatFireworks(model=_MODEL)
|
||||
llm = ChatFireworks(model=_MODEL, rate_limiter=rate_limiter)
|
||||
|
||||
result = await llm.ainvoke("I'm Pickle Rick", config={"tags": ["foo"]})
|
||||
assert isinstance(result.content, str)
|
||||
@@ -115,7 +120,7 @@ async def test_ainvoke() -> None:
|
||||
|
||||
def test_invoke() -> None:
|
||||
"""Test invoke tokens from ChatFireworks."""
|
||||
llm = ChatFireworks(model=_MODEL)
|
||||
llm = ChatFireworks(model=_MODEL, rate_limiter=rate_limiter)
|
||||
|
||||
result = llm.invoke("I'm Pickle Rick", config={"tags": ["foo"]})
|
||||
assert isinstance(result.content, str)
|
||||
@@ -157,7 +162,9 @@ def _get_joke_class(
|
||||
|
||||
@pytest.mark.parametrize("schema_type", ["pydantic", "typeddict", "json_schema"])
|
||||
def test_structured_output_json_schema(schema_type: str) -> None:
|
||||
llm = ChatFireworks(model="accounts/fireworks/models/kimi-k2p6")
|
||||
llm = ChatFireworks(
|
||||
model="accounts/fireworks/models/kimi-k2p6", rate_limiter=rate_limiter
|
||||
)
|
||||
schema, validation_function = _get_joke_class(schema_type) # type: ignore[arg-type]
|
||||
chat = llm.with_structured_output(schema, method="json_schema")
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from langchain_tests.integration_tests import ( # type: ignore[import-not-found
|
||||
)
|
||||
|
||||
from langchain_fireworks import ChatFireworks
|
||||
from tests.integration_tests._rate_limiter import rate_limiter
|
||||
|
||||
|
||||
class TestFireworksStandard(ChatModelIntegrationTests):
|
||||
@@ -20,6 +21,7 @@ class TestFireworksStandard(ChatModelIntegrationTests):
|
||||
return {
|
||||
"model": "accounts/fireworks/models/kimi-k2p6",
|
||||
"temperature": 0,
|
||||
"rate_limiter": rate_limiter,
|
||||
}
|
||||
|
||||
@pytest.mark.xfail(reason="Not yet implemented.")
|
||||
|
||||
14
libs/partners/fireworks/uv.lock
generated
14
libs/partners/fireworks/uv.lock
generated
@@ -778,6 +778,7 @@ test = [
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
{ name = "pytest-mock" },
|
||||
{ name = "pytest-retry" },
|
||||
{ name = "pytest-socket" },
|
||||
{ name = "pytest-watcher" },
|
||||
{ name = "pytest-xdist" },
|
||||
@@ -808,6 +809,7 @@ test = [
|
||||
{ name = "pytest", specifier = ">=9.0.3,<10.0.0" },
|
||||
{ name = "pytest-asyncio", specifier = ">=1.3.0,<2.0.0" },
|
||||
{ name = "pytest-mock", specifier = ">=3.10.0,<4.0.0" },
|
||||
{ name = "pytest-retry", specifier = ">=1.7.0,<1.8.0" },
|
||||
{ name = "pytest-socket", specifier = ">=0.7.0,<1.0.0" },
|
||||
{ name = "pytest-watcher", specifier = ">=0.3.4,<1.0.0" },
|
||||
{ name = "pytest-xdist", specifier = ">=3.8.0,<4.0.0" },
|
||||
@@ -1691,6 +1693,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/42/c2/ce34735972cc42d912173e79f200fe66530225190c06655c5632a9d88f1e/pytest_recording-0.13.4-py3-none-any.whl", hash = "sha256:ad49a434b51b1c4f78e85b1e6b74fdcc2a0a581ca16e52c798c6ace971f7f439", size = 13723, upload-time = "2025-05-08T10:41:09.684Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-retry"
|
||||
version = "1.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c5/5b/607b017994cca28de3a1ad22a3eee8418e5d428dcd8ec25b26b18e995a73/pytest_retry-1.7.0.tar.gz", hash = "sha256:f8d52339f01e949df47c11ba9ee8d5b362f5824dff580d3870ec9ae0057df80f", size = 19977, upload-time = "2025-01-19T01:56:13.115Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/ff/3266c8a73b9b93c4b14160a7e2b31d1e1088e28ed29f4c2d93ae34093bfd/pytest_retry-1.7.0-py3-none-any.whl", hash = "sha256:a2dac85b79a4e2375943f1429479c65beb6c69553e7dae6b8332be47a60954f4", size = 13775, upload-time = "2025-01-19T01:56:11.199Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-socket"
|
||||
version = "0.7.0"
|
||||
|
||||
Reference in New Issue
Block a user