From 9c71c8fa3bcefa0a5bf6efe36f17f37b840c8d28 Mon Sep 17 00:00:00 2001 From: HAL9000 Date: Sat, 18 Apr 2026 19:18:36 +0000 Subject: [PATCH 1/6] feat(providers): implement OllamaProvider and MistralProvider - Implemented OllamaChatProvider to enable local Ollama model support. - Implemented MistralChatProvider to integrate with the Mistral API. - Added Behave BDD tests for both providers. - Updated dependencies: langchain-mistralai and ollama. - Updated provider exports to include the new providers. ISSUES CLOSED: #5257 --- features/mistral_provider.feature | 71 ++++ features/ollama_provider.feature | 70 ++++ features/steps/mistral_provider_steps.py | 333 ++++++++++++++++++ features/steps/ollama_provider_steps.py | 321 +++++++++++++++++ pyproject.toml | 2 + src/cleveragents/providers/llm/__init__.py | 4 + .../providers/llm/mistral_provider.py | 46 +++ .../providers/llm/ollama_provider.py | 37 ++ 8 files changed, 884 insertions(+) create mode 100644 features/mistral_provider.feature create mode 100644 features/ollama_provider.feature create mode 100644 features/steps/mistral_provider_steps.py create mode 100644 features/steps/ollama_provider_steps.py create mode 100644 src/cleveragents/providers/llm/mistral_provider.py create mode 100644 src/cleveragents/providers/llm/ollama_provider.py diff --git a/features/mistral_provider.feature b/features/mistral_provider.feature new file mode 100644 index 000000000..ef788522d --- /dev/null +++ b/features/mistral_provider.feature @@ -0,0 +1,71 @@ +Feature: Mistral chat provider coverage + As a maintainer integrating Mistral API support + I want unit-level Behave scenarios for the Mistral chat provider + So that the Mistral adapter stays documented and regression tested + + @unit @providers @mistral + Scenario: Mistral provider instantiates ChatMistralAI with provided credentials + Given I have sample provider domain inputs + When I create a Mistral chat provider with API key "test-key-123" and model "mistral-large-latest" + And I request plan generation from the Mistral provider + Then the Mistral provider should construct ChatMistralAI with api key "test-key-123" and model "mistral-large-latest" + And the Mistral provider metadata should report name "mistral" and model "mistral-large-latest" + + @unit @providers @mistral + Scenario: Mistral provider stubbed response reports metadata + Given I have sample provider domain inputs + And I create a Mistral chat provider with API key "test-key-123" and model "mistral-large-latest" + When I request plan generation from the Mistral provider + Then the Mistral provider response should contain no generated changes + And the Mistral provider response should report the requested model without errors + + @unit @providers @mistral + Scenario: Mistral provider rejects missing API key + Given I have sample provider domain inputs + When I attempt to create a Mistral chat provider without an API key + Then the Mistral provider creation should fail with error containing "Mistral API key is required" + + @unit @providers @mistral + Scenario: Mistral provider reads API key from environment variable + Given I have sample provider domain inputs + And I set the MISTRAL_API_KEY environment variable to "env-key-456" + When I create a Mistral chat provider without explicit API key and model "mistral-large-latest" + And I request plan generation from the Mistral provider + Then the Mistral provider should construct ChatMistralAI with api key "env-key-456" and model "mistral-large-latest" + + @unit @providers @mistral + Scenario: Mistral provider forwards extra kwargs + Given I have sample provider domain inputs + And I set the Mistral provider extra kwargs "temperature=0.5,max_tokens=512" + When I create a Mistral chat provider with API key "test-key-123" and model "mistral-large-latest" + And I request plan generation from the Mistral provider + Then the Mistral provider should construct ChatMistralAI with api key "test-key-123" and model "mistral-large-latest" + And the Mistral provider should include kwargs "temperature=0.5,max_tokens=512" in the ChatMistralAI call + + @unit @providers @mistral + Scenario: Mistral provider reports runtime errors + Given I have sample provider domain inputs + And I create a Mistral chat provider with API key "test-key-123" and model "mistral-large-latest" + And the plan generation graph raises RuntimeError "API rate limit exceeded" + When I request plan generation from the Mistral provider + Then the Mistral provider response should report error "API rate limit exceeded" + And the Mistral provider response should contain no generated changes + + @unit @providers @mistral + Scenario: Mistral provider streaming yields workflow events + Given I have sample provider domain inputs + And the plan generation graph returns a generated change for "app/mistral.py" + And the plan generation graph emits streaming nodes "load_context,analyze_requirements,generate_plan,validate" + And I create a Mistral chat provider with API key "test-key-123" and model "mistral-large-latest" + When I stream plan generation from the Mistral provider + Then the Mistral provider streaming events should include nodes "load_context,analyze_requirements,generate_plan,validate" + And the Mistral provider streaming result should finish with a response containing 1 generated change + + @unit @providers @mistral + Scenario: Mistral provider surfaces plan generation errors + Given I have sample provider domain inputs + And the plan generation graph raises ValueError "invalid request" + And I create a Mistral chat provider with API key "test-key-123" and model "mistral-large-latest" + When I request plan generation from the Mistral provider + Then the Mistral provider response should report error "invalid request" + And the Mistral provider response should contain no generated changes diff --git a/features/ollama_provider.feature b/features/ollama_provider.feature new file mode 100644 index 000000000..386f153fb --- /dev/null +++ b/features/ollama_provider.feature @@ -0,0 +1,70 @@ +Feature: Ollama chat provider coverage + As a maintainer integrating local model support + I want unit-level Behave scenarios for the Ollama chat provider + So that the local model adapter stays documented and regression tested + + @unit @providers @ollama + Scenario: Ollama provider instantiates ChatOllama with provided credentials + Given I have sample provider domain inputs + When I create an Ollama chat provider with model "llama2" and base_url "http://localhost:11434" + And I request plan generation from the Ollama provider + Then the Ollama provider should construct ChatOllama with model "llama2" and base_url "http://localhost:11434" + And the Ollama provider metadata should report name "ollama" and model "llama2" + + @unit @providers @ollama + Scenario: Ollama provider stubbed response reports metadata + Given I have sample provider domain inputs + And I create an Ollama chat provider with model "llama2" and base_url "http://localhost:11434" + When I request plan generation from the Ollama provider + Then the Ollama provider response should contain no generated changes + And the Ollama provider response should report the requested model without errors + + @unit @providers @ollama + Scenario: Ollama provider rejects missing model name + Given I have sample provider domain inputs + When I attempt to create an Ollama chat provider without a model name + Then the Ollama provider creation should fail with error "Ollama model name is required" + + @unit @providers @ollama + Scenario: Ollama provider uses default base URL + Given I have sample provider domain inputs + When I create an Ollama chat provider with model "llama2" and default base_url + And I request plan generation from the Ollama provider + Then the Ollama provider should construct ChatOllama with model "llama2" and base_url "http://localhost:11434" + + @unit @providers @ollama + Scenario: Ollama provider forwards extra kwargs + Given I have sample provider domain inputs + And I set the Ollama provider extra kwargs "temperature=0.7,top_p=0.9" + When I create an Ollama chat provider with model "llama2" and base_url "http://localhost:11434" + And I request plan generation from the Ollama provider + Then the Ollama provider should construct ChatOllama with model "llama2" and base_url "http://localhost:11434" + And the Ollama provider should include kwargs "temperature=0.7,top_p=0.9" in the ChatOllama call + + @unit @providers @ollama + Scenario: Ollama provider reports runtime errors + Given I have sample provider domain inputs + And I create an Ollama chat provider with model "llama2" and base_url "http://localhost:11434" + And the plan generation graph raises RuntimeError "connection refused" + When I request plan generation from the Ollama provider + Then the Ollama provider response should report error "connection refused" + And the Ollama provider response should contain no generated changes + + @unit @providers @ollama + Scenario: Ollama provider streaming yields workflow events + Given I have sample provider domain inputs + And the plan generation graph returns a generated change for "app/stream.py" + And the plan generation graph emits streaming nodes "load_context,analyze_requirements,generate_plan,validate" + And I create an Ollama chat provider with model "llama2" and base_url "http://localhost:11434" + When I stream plan generation from the Ollama provider + Then the Ollama provider streaming events should include nodes "load_context,analyze_requirements,generate_plan,validate" + And the Ollama provider streaming result should finish with a response containing 1 generated change + + @unit @providers @ollama + Scenario: Ollama provider surfaces plan generation errors + Given I have sample provider domain inputs + And the plan generation graph raises ValueError "model not found" + And I create an Ollama chat provider with model "llama2" and base_url "http://localhost:11434" + When I request plan generation from the Ollama provider + Then the Ollama provider response should report error "model not found" + And the Ollama provider response should contain no generated changes diff --git a/features/steps/mistral_provider_steps.py b/features/steps/mistral_provider_steps.py new file mode 100644 index 000000000..38707435c --- /dev/null +++ b/features/steps/mistral_provider_steps.py @@ -0,0 +1,333 @@ +from __future__ import annotations + +import ast +import os +from typing import Any +from unittest.mock import MagicMock, patch + +from behave import given, then, when + +from cleveragents.domain.models.core import ( + Context, + OperationType, + Plan, + Project, +) +from cleveragents.providers.llm.mistral_provider import MistralChatProvider + + +def _register_cleanup(context, cleanup): + if hasattr(context, "add_cleanup"): + context.add_cleanup(cleanup) + else: + cleanup_handlers = getattr(context, "_cleanup_handlers", []) + cleanup_handlers.append(cleanup) + context._cleanup_handlers = cleanup_handlers + + +def _parse_kwargs_string(kwargs_string: str) -> dict[str, Any]: + if not kwargs_string: + return {} + result: dict[str, Any] = {} + for entry in kwargs_string.split(","): + entry = entry.strip() + if not entry: + continue + if "=" not in entry: + result[entry] = True + continue + key, value = entry.split("=", 1) + key = key.strip() + value = value.strip() + try: + parsed_value = ast.literal_eval(value) + except Exception: + parsed_value = value + result[key] = parsed_value + return result + + +def _setup_plan_generation_graph(context) -> MagicMock: + patcher = patch( + "cleveragents.providers.llm.langchain_chat_provider.PlanGenerationGraph" + ) + context.plan_generation_patcher = patcher + mock_graph_class = patcher.start() + _register_cleanup(context, patcher.stop) + mock_graph_instance = MagicMock(name="PlanGenerationGraphInstance") + + default_state = { + "generated_changes": [], + "validation_result": {"status": "PASS"}, + "error": None, + } + state_override = getattr(context, "plan_generation_state_override", None) + mock_graph_instance.invoke.return_value = state_override or default_state + + invoke_side_effect = getattr(context, "plan_generation_invoke_side_effect", None) + if invoke_side_effect is not None: + mock_graph_instance.invoke.side_effect = invoke_side_effect + + stream_events = getattr(context, "plan_generation_stream_events", None) + if stream_events is None: + mock_graph_instance.stream.return_value = iter(()) + else: + events_copy = list(stream_events) + + def _stream(*_args, **_kwargs): + yield from events_copy + + mock_graph_instance.stream.side_effect = _stream + + mock_graph_class.return_value = mock_graph_instance + context.plan_generation_graph = mock_graph_instance + context.plan_generation_graph_class = mock_graph_class + return mock_graph_instance + + +@given("I have sample provider domain inputs") +def step_sample_provider_inputs(context): + context.project = MagicMock(spec=Project) + context.plan = MagicMock(spec=Plan) + context.plan.prompt = "Add placeholder coverage" + context.contexts = [MagicMock(spec=Context)] + context.contexts[0].content = "Sample context entry" + + +@given('the plan generation graph returns a generated change for "{file_path}"') +def step_plan_generation_returns_change(context, file_path): + change = { + "plan_id": 1, + "file_path": file_path, + "operation": OperationType.MODIFY.value, + "new_content": "# updated content", + } + context.plan_generation_state_override = { + "generated_changes": [change], + "validation_result": {"status": "PASS"}, + "error": None, + } + + +@given('the plan generation graph emits streaming nodes "{node_list}"') +def step_plan_generation_stream_nodes(context, node_list): + nodes = [node.strip() for node in node_list.split(",") if node.strip()] + state = getattr(context, "plan_generation_state_override", {}) or {} + events: list[dict[str, Any]] = [] + for node in nodes: + payload: dict[str, Any] = {"status": "completed"} + if node == "generate_plan" and isinstance(state.get("generated_changes"), list): + payload = { + "generated_changes": state["generated_changes"], + "status": "completed", + } + elif node == "validate" and isinstance(state.get("validation_result"), dict): + payload = { + "validation_result": state["validation_result"], + "status": "completed", + } + events.append({node: payload}) + context.plan_generation_stream_events = events + + +@given('the plan generation graph raises ValueError "{message}"') +def step_plan_generation_raises_value_error(context, message): + context.plan_generation_invoke_side_effect = ValueError(message) + + +@given('the plan generation graph raises RuntimeError "{message}"') +def step_plan_generation_runtime_error(context, message): + context.plan_generation_invoke_side_effect = RuntimeError(message) + + +@given('I set the MISTRAL_API_KEY environment variable to "{api_key}"') +def step_set_mistral_env_var(context, api_key): + context.mistral_env_api_key = api_key + os.environ["MISTRAL_API_KEY"] = api_key + _register_cleanup(context, lambda: os.environ.pop("MISTRAL_API_KEY", None)) + + +@given('I set the Mistral provider extra kwargs "{kwargs_string}"') +def step_set_mistral_provider_kwargs(context, kwargs_string): + context.mistral_provider_kwargs = _parse_kwargs_string(kwargs_string) + + +@given( + 'I create a Mistral chat provider with API key "{api_key}" and model "{model}"' +) +@when( + 'I create a Mistral chat provider with API key "{api_key}" and model "{model}"' +) +def step_create_mistral_provider(context, api_key, model): + patcher = patch("cleveragents.providers.llm.mistral_provider.ChatMistralAI") + context.chat_mistral_patcher = patcher + mock_chat_mistral_class = patcher.start() + _register_cleanup(context, patcher.stop) + mock_chat_mistral_instance = MagicMock(name="ChatMistralAIInstance") + mock_chat_mistral_instance.get_num_tokens = MagicMock(return_value=0) + mock_chat_mistral_class.return_value = mock_chat_mistral_instance + + extra_kwargs = dict(getattr(context, "mistral_provider_kwargs", {})) + + context.provider = MistralChatProvider( + api_key=api_key, + model=model, + **extra_kwargs, + ) + context.chat_mistral_class = mock_chat_mistral_class + context.chat_mistral_instance = mock_chat_mistral_instance + + +@when( + 'I create a Mistral chat provider without explicit API key and model "{model}"' +) +def step_create_mistral_provider_from_env(context, model): + patcher = patch("cleveragents.providers.llm.mistral_provider.ChatMistralAI") + context.chat_mistral_patcher = patcher + mock_chat_mistral_class = patcher.start() + _register_cleanup(context, patcher.stop) + mock_chat_mistral_instance = MagicMock(name="ChatMistralAIInstance") + mock_chat_mistral_instance.get_num_tokens = MagicMock(return_value=0) + mock_chat_mistral_class.return_value = mock_chat_mistral_instance + + extra_kwargs = dict(getattr(context, "mistral_provider_kwargs", {})) + + context.provider = MistralChatProvider( + model=model, + **extra_kwargs, + ) + context.chat_mistral_class = mock_chat_mistral_class + context.chat_mistral_instance = mock_chat_mistral_instance + + +@when("I attempt to create a Mistral chat provider without an API key") +def step_mistral_provider_without_api_key(context): + context.mistral_provider_error = None + # Make sure env var is not set + os.environ.pop("MISTRAL_API_KEY", None) + try: + MistralChatProvider(api_key="", model="mistral-large-latest") + except Exception as exc: # pragma: no cover - defensive logging only + context.mistral_provider_error = exc + + +@when("I request plan generation from the Mistral provider") +def step_request_plan_generation(context): + _setup_plan_generation_graph(context) + + context.response = context.provider.generate_changes( + context.project, + context.plan, + context.contexts, + ) + context.chat_mistral_call = context.chat_mistral_class.call_args + context.plan_generation_graph_call = context.plan_generation_graph_class.call_args + + +@when("I stream plan generation from the Mistral provider") +def step_stream_plan_generation(context): + _setup_plan_generation_graph(context) + context.streamed_events = list( + context.provider.stream_changes( + context.project, + context.plan, + context.contexts, + ) + ) + context.chat_mistral_call = context.chat_mistral_class.call_args + context.plan_generation_graph_call = context.plan_generation_graph_class.call_args + + +@then( + 'the Mistral provider should construct ChatMistralAI with api key "{api_key}" and model "{model}"' +) +def step_assert_chat_mistral_constructor(context, api_key, model): + assert context.chat_mistral_call is not None, "ChatMistralAI should have been called" + call_args, call_kwargs = context.chat_mistral_call + assert call_args == (), "ChatMistralAI should be called with keyword arguments" + assert call_kwargs["api_key"] == api_key + assert call_kwargs["model"] == model + + assert context.plan_generation_graph_call is not None, ( + "PlanGenerationGraph should receive the ChatMistralAI instance" + ) + _, graph_kwargs = context.plan_generation_graph_call + assert graph_kwargs["llm"] is context.chat_mistral_instance + + +@then( + 'the Mistral provider should include kwargs "{kwargs_string}" in the ChatMistralAI call' +) +def step_assert_chat_mistral_kwargs(context, kwargs_string): + assert context.chat_mistral_call is not None, "ChatMistralAI should have been called" + _, call_kwargs = context.chat_mistral_call + expected_kwargs = _parse_kwargs_string(kwargs_string) + for key, value in expected_kwargs.items(): + assert key in call_kwargs, f"Expected {key} in ChatMistralAI kwargs" + assert call_kwargs[key] == value, ( + f"Expected ChatMistralAI kwargs[{key!r}] to equal {value!r}" + ) + + +@then( + 'the Mistral provider metadata should report name "{expected_name}" and model "{expected_model}"' +) +def step_assert_provider_metadata(context, expected_name, expected_model): + provider = getattr(context, "provider", None) + assert provider is not None, "Provider should exist" + assert provider.name == expected_name + assert provider.model_id == expected_model + response = getattr(context, "response", None) + if response is not None: + assert response.model_used == expected_model + + +@then("the Mistral provider response should report the requested model without errors") +def step_assert_placeholder_metadata(context): + assert context.response is not None + assert context.response.model_used == context.provider.model_id + assert context.response.error_message in (None, "") + + +@then("the Mistral provider response should contain no generated changes") +def step_assert_no_changes(context): + assert context.response is not None, "Provider response should exist" + assert len(context.response.changes) == 0, "Expected no generated changes" + + +@then('the Mistral provider response should report error "{message}"') +def step_assert_response_error(context, message): + assert context.response is not None + assert context.response.error_message == message + + +@then('the Mistral provider streaming events should include nodes "{node_list}"') +def step_assert_stream_events(context, node_list): + expected = [node.strip() for node in node_list.split(",") if node.strip()] + actual = [ + next(iter(event.keys())) + for event in getattr(context, "streamed_events", []) + if "__end__" not in event + ] + assert actual == expected + + +@then( + "the Mistral provider streaming result should finish with a response containing {count:d} generated change" +) +def step_assert_stream_final_response(context, count): + events = getattr(context, "streamed_events", []) + assert events, "Expected streamed events" + final_event = events[-1] + assert "__end__" in final_event, "Expected final __end__ event" + response = final_event["__end__"].get("response") + assert response is not None, "Expected ProviderResponse in __end__ event" + assert len(response.changes) == count + + +@then('the Mistral provider creation should fail with error containing "{message}"') +def step_assert_mistral_provider_error(context, message): + error = getattr(context, "mistral_provider_error", None) + assert error is not None, "Expected the provider to raise an error" + assert isinstance(error, ValueError) + assert message in str(error) diff --git a/features/steps/ollama_provider_steps.py b/features/steps/ollama_provider_steps.py new file mode 100644 index 000000000..792439d88 --- /dev/null +++ b/features/steps/ollama_provider_steps.py @@ -0,0 +1,321 @@ +from __future__ import annotations + +import ast +from typing import Any +from unittest.mock import MagicMock, patch + +from behave import given, then, when + +from cleveragents.domain.models.core import ( + Context, + OperationType, + Plan, + Project, +) +from cleveragents.providers.llm.ollama_provider import OllamaChatProvider + + +def _register_cleanup(context, cleanup): + if hasattr(context, "add_cleanup"): + context.add_cleanup(cleanup) + else: + cleanup_handlers = getattr(context, "_cleanup_handlers", []) + cleanup_handlers.append(cleanup) + context._cleanup_handlers = cleanup_handlers + + +def _parse_kwargs_string(kwargs_string: str) -> dict[str, Any]: + if not kwargs_string: + return {} + result: dict[str, Any] = {} + for entry in kwargs_string.split(","): + entry = entry.strip() + if not entry: + continue + if "=" not in entry: + result[entry] = True + continue + key, value = entry.split("=", 1) + key = key.strip() + value = value.strip() + try: + parsed_value = ast.literal_eval(value) + except Exception: + parsed_value = value + result[key] = parsed_value + return result + + +def _setup_plan_generation_graph(context) -> MagicMock: + patcher = patch( + "cleveragents.providers.llm.langchain_chat_provider.PlanGenerationGraph" + ) + context.plan_generation_patcher = patcher + mock_graph_class = patcher.start() + _register_cleanup(context, patcher.stop) + mock_graph_instance = MagicMock(name="PlanGenerationGraphInstance") + + default_state = { + "generated_changes": [], + "validation_result": {"status": "PASS"}, + "error": None, + } + state_override = getattr(context, "plan_generation_state_override", None) + mock_graph_instance.invoke.return_value = state_override or default_state + + invoke_side_effect = getattr(context, "plan_generation_invoke_side_effect", None) + if invoke_side_effect is not None: + mock_graph_instance.invoke.side_effect = invoke_side_effect + + stream_events = getattr(context, "plan_generation_stream_events", None) + if stream_events is None: + mock_graph_instance.stream.return_value = iter(()) + else: + events_copy = list(stream_events) + + def _stream(*_args, **_kwargs): + yield from events_copy + + mock_graph_instance.stream.side_effect = _stream + + mock_graph_class.return_value = mock_graph_instance + context.plan_generation_graph = mock_graph_instance + context.plan_generation_graph_class = mock_graph_class + return mock_graph_instance + + +@given("I have sample provider domain inputs") +def step_sample_provider_inputs(context): + context.project = MagicMock(spec=Project) + context.plan = MagicMock(spec=Plan) + context.plan.prompt = "Add placeholder coverage" + context.contexts = [MagicMock(spec=Context)] + context.contexts[0].content = "Sample context entry" + + +@given('the plan generation graph returns a generated change for "{file_path}"') +def step_plan_generation_returns_change(context, file_path): + change = { + "plan_id": 1, + "file_path": file_path, + "operation": OperationType.MODIFY.value, + "new_content": "# updated content", + } + context.plan_generation_state_override = { + "generated_changes": [change], + "validation_result": {"status": "PASS"}, + "error": None, + } + + +@given('the plan generation graph emits streaming nodes "{node_list}"') +def step_plan_generation_stream_nodes(context, node_list): + nodes = [node.strip() for node in node_list.split(",") if node.strip()] + state = getattr(context, "plan_generation_state_override", {}) or {} + events: list[dict[str, Any]] = [] + for node in nodes: + payload: dict[str, Any] = {"status": "completed"} + if node == "generate_plan" and isinstance(state.get("generated_changes"), list): + payload = { + "generated_changes": state["generated_changes"], + "status": "completed", + } + elif node == "validate" and isinstance(state.get("validation_result"), dict): + payload = { + "validation_result": state["validation_result"], + "status": "completed", + } + events.append({node: payload}) + context.plan_generation_stream_events = events + + +@given('the plan generation graph raises ValueError "{message}"') +def step_plan_generation_raises_value_error(context, message): + context.plan_generation_invoke_side_effect = ValueError(message) + + +@given('the plan generation graph raises RuntimeError "{message}"') +def step_plan_generation_runtime_error(context, message): + context.plan_generation_invoke_side_effect = RuntimeError(message) + + +@given('I set the Ollama provider extra kwargs "{kwargs_string}"') +def step_set_ollama_provider_kwargs(context, kwargs_string): + context.ollama_provider_kwargs = _parse_kwargs_string(kwargs_string) + + +@given( + 'I create an Ollama chat provider with model "{model}" and base_url "{base_url}"' +) +@when( + 'I create an Ollama chat provider with model "{model}" and base_url "{base_url}"' +) +def step_create_ollama_provider(context, model, base_url): + patcher = patch("cleveragents.providers.llm.ollama_provider.ChatOllama") + context.chat_ollama_patcher = patcher + mock_chat_ollama_class = patcher.start() + _register_cleanup(context, patcher.stop) + mock_chat_ollama_instance = MagicMock(name="ChatOllamaInstance") + mock_chat_ollama_instance.get_num_tokens = MagicMock(return_value=0) + mock_chat_ollama_class.return_value = mock_chat_ollama_instance + + extra_kwargs = dict(getattr(context, "ollama_provider_kwargs", {})) + + context.provider = OllamaChatProvider( + model=model, + base_url=base_url, + **extra_kwargs, + ) + context.chat_ollama_class = mock_chat_ollama_class + context.chat_ollama_instance = mock_chat_ollama_instance + + +@when('I create an Ollama chat provider with model "{model}" and default base_url') +def step_create_ollama_provider_default_url(context, model): + patcher = patch("cleveragents.providers.llm.ollama_provider.ChatOllama") + context.chat_ollama_patcher = patcher + mock_chat_ollama_class = patcher.start() + _register_cleanup(context, patcher.stop) + mock_chat_ollama_instance = MagicMock(name="ChatOllamaInstance") + mock_chat_ollama_instance.get_num_tokens = MagicMock(return_value=0) + mock_chat_ollama_class.return_value = mock_chat_ollama_instance + + extra_kwargs = dict(getattr(context, "ollama_provider_kwargs", {})) + + context.provider = OllamaChatProvider( + model=model, + **extra_kwargs, + ) + context.chat_ollama_class = mock_chat_ollama_class + context.chat_ollama_instance = mock_chat_ollama_instance + + +@when("I attempt to create an Ollama chat provider without a model name") +def step_ollama_provider_without_model(context): + context.ollama_provider_error = None + try: + OllamaChatProvider(model="") + except Exception as exc: # pragma: no cover - defensive logging only + context.ollama_provider_error = exc + + +@when("I request plan generation from the Ollama provider") +def step_request_plan_generation(context): + _setup_plan_generation_graph(context) + + context.response = context.provider.generate_changes( + context.project, + context.plan, + context.contexts, + ) + context.chat_ollama_call = context.chat_ollama_class.call_args + context.plan_generation_graph_call = context.plan_generation_graph_class.call_args + + +@when("I stream plan generation from the Ollama provider") +def step_stream_plan_generation(context): + _setup_plan_generation_graph(context) + context.streamed_events = list( + context.provider.stream_changes( + context.project, + context.plan, + context.contexts, + ) + ) + context.chat_ollama_call = context.chat_ollama_class.call_args + context.plan_generation_graph_call = context.plan_generation_graph_class.call_args + + +@then( + 'the Ollama provider should construct ChatOllama with model "{model}" and base_url "{base_url}"' +) +def step_assert_chat_ollama_constructor(context, model, base_url): + assert context.chat_ollama_call is not None, "ChatOllama should have been called" + call_args, call_kwargs = context.chat_ollama_call + assert call_args == (), "ChatOllama should be called with keyword arguments" + assert call_kwargs["model"] == model + assert call_kwargs["base_url"] == base_url + + assert context.plan_generation_graph_call is not None, ( + "PlanGenerationGraph should receive the ChatOllama instance" + ) + _, graph_kwargs = context.plan_generation_graph_call + assert graph_kwargs["llm"] is context.chat_ollama_instance + + +@then( + 'the Ollama provider should include kwargs "{kwargs_string}" in the ChatOllama call' +) +def step_assert_chat_ollama_kwargs(context, kwargs_string): + assert context.chat_ollama_call is not None, "ChatOllama should have been called" + _, call_kwargs = context.chat_ollama_call + expected_kwargs = _parse_kwargs_string(kwargs_string) + for key, value in expected_kwargs.items(): + assert key in call_kwargs, f"Expected {key} in ChatOllama kwargs" + assert call_kwargs[key] == value, ( + f"Expected ChatOllama kwargs[{key!r}] to equal {value!r}" + ) + + +@then( + 'the Ollama provider metadata should report name "{expected_name}" and model "{expected_model}"' +) +def step_assert_provider_metadata(context, expected_name, expected_model): + provider = getattr(context, "provider", None) + assert provider is not None, "Provider should exist" + assert provider.name == expected_name + assert provider.model_id == expected_model + response = getattr(context, "response", None) + if response is not None: + assert response.model_used == expected_model + + +@then("the Ollama provider response should report the requested model without errors") +def step_assert_placeholder_metadata(context): + assert context.response is not None + assert context.response.model_used == context.provider.model_id + assert context.response.error_message in (None, "") + + +@then("the Ollama provider response should contain no generated changes") +def step_assert_no_changes(context): + assert context.response is not None, "Provider response should exist" + assert len(context.response.changes) == 0, "Expected no generated changes" + + +@then('the Ollama provider response should report error "{message}"') +def step_assert_response_error(context, message): + assert context.response is not None + assert context.response.error_message == message + + +@then('the Ollama provider streaming events should include nodes "{node_list}"') +def step_assert_stream_events(context, node_list): + expected = [node.strip() for node in node_list.split(",") if node.strip()] + actual = [ + next(iter(event.keys())) + for event in getattr(context, "streamed_events", []) + if "__end__" not in event + ] + assert actual == expected + + +@then( + "the Ollama provider streaming result should finish with a response containing {count:d} generated change" +) +def step_assert_stream_final_response(context, count): + events = getattr(context, "streamed_events", []) + assert events, "Expected streamed events" + final_event = events[-1] + assert "__end__" in final_event, "Expected final __end__ event" + response = final_event["__end__"].get("response") + assert response is not None, "Expected ProviderResponse in __end__ event" + assert len(response.changes) == count + + +@then('the Ollama provider creation should fail with error "{message}"') +def step_assert_ollama_provider_error(context, message): + error = getattr(context, "ollama_provider_error", None) + assert error is not None, "Expected the provider to raise an error" + assert isinstance(error, ValueError) + assert str(error) == message diff --git a/pyproject.toml b/pyproject.toml index dbeafd732..fcc9e44a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ "langchain-community>=0.2.14", "langchain-openai>=0.2.0", "langchain-google-genai>=0.2.0", + "langchain-mistralai>=0.1.0", # Mistral API integration "jinja2>=3.1.0", "alembic>=1.13.1", "numpy>=2.1.0", @@ -50,6 +51,7 @@ dependencies = [ "aiohttp>=3.13.4", # CVE-2026-34515 mitigation: open redirect vulnerability "pyyaml>=6.0.3", # CVE-2017-18342 / CVE-2025-8045 mitigation: explicit pin for runtime YAML actor config loading; safe_load enforced throughout codebase "a2a-sdk>=0.3.0,<1.0.0", # A2A Python SDK — required transport for local (stdio) and server (HTTP) modes (ADR-047); pinned <1.0.0 (removed legacy A2AClient) + "ollama>=0.1.0", # Ollama local model support ] [project.optional-dependencies] diff --git a/src/cleveragents/providers/llm/__init__.py b/src/cleveragents/providers/llm/__init__.py index 9a07b2bed..489744d87 100644 --- a/src/cleveragents/providers/llm/__init__.py +++ b/src/cleveragents/providers/llm/__init__.py @@ -6,12 +6,16 @@ import them directly from the ``cleveragents.providers.llm`` subpackage. from cleveragents.providers.llm.anthropic_provider import AnthropicChatProvider from cleveragents.providers.llm.google_provider import GoogleChatProvider +from cleveragents.providers.llm.mistral_provider import MistralChatProvider +from cleveragents.providers.llm.ollama_provider import OllamaChatProvider from cleveragents.providers.llm.openai_provider import OpenAIChatProvider from cleveragents.providers.llm.openrouter_provider import OpenRouterChatProvider __all__ = [ "AnthropicChatProvider", "GoogleChatProvider", + "MistralChatProvider", + "OllamaChatProvider", "OpenAIChatProvider", "OpenRouterChatProvider", ] diff --git a/src/cleveragents/providers/llm/mistral_provider.py b/src/cleveragents/providers/llm/mistral_provider.py new file mode 100644 index 000000000..5fd022c90 --- /dev/null +++ b/src/cleveragents/providers/llm/mistral_provider.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import os +from typing import Any, cast + +from langchain_core.language_models import BaseLanguageModel +from langchain_mistralai import ChatMistralAI + +from cleveragents.providers.llm.langchain_chat_provider import LangChainChatProvider + + +class MistralChatProvider(LangChainChatProvider): + """Concrete LangChain-backed provider for Mistral API models.""" + + def __init__( + self, + *, + api_key: str | None = None, + model: str = "mistral-large-latest", + max_retries: int = 3, + **llm_kwargs: Any, + ) -> None: + # Use provided API key or fall back to environment variable + resolved_api_key = api_key or os.environ.get("MISTRAL_API_KEY") + if not resolved_api_key: + raise ValueError( + "Mistral API key is required. " + "Provide it via api_key parameter or MISTRAL_API_KEY env var" + ) + + def factory(resolved_model: str) -> BaseLanguageModel: + kwargs: dict[str, Any] = { + "api_key": resolved_api_key, + "model": resolved_model, + } + if llm_kwargs: + kwargs.update(llm_kwargs) + return cast(BaseLanguageModel, ChatMistralAI(**kwargs)) + + super().__init__( + name="mistral", + model_id=model, + llm_factory=factory, + max_retries=max_retries, + supports_streaming=True, + ) diff --git a/src/cleveragents/providers/llm/ollama_provider.py b/src/cleveragents/providers/llm/ollama_provider.py new file mode 100644 index 000000000..4f313215e --- /dev/null +++ b/src/cleveragents/providers/llm/ollama_provider.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from typing import Any, cast + +from langchain_core.language_models import BaseLanguageModel +from langchain_community.chat_models import ChatOllama + +from cleveragents.providers.llm.langchain_chat_provider import LangChainChatProvider + + +class OllamaChatProvider(LangChainChatProvider): + """Concrete LangChain-backed provider for Ollama local models.""" + + def __init__( + self, + *, + model: str = "llama2", + base_url: str = "http://localhost:11434", + max_retries: int = 3, + **llm_kwargs: Any, + ) -> None: + if not model: + raise ValueError("Ollama model name is required") + + def factory(resolved_model: str) -> BaseLanguageModel: + kwargs: dict[str, Any] = {"model": resolved_model, "base_url": base_url} + if llm_kwargs: + kwargs.update(llm_kwargs) + return cast(BaseLanguageModel, ChatOllama(**kwargs)) + + super().__init__( + name="ollama", + model_id=model, + llm_factory=factory, + max_retries=max_retries, + supports_streaming=True, + ) -- 2.52.0 From b3978841851fb06916922317d802ac200d0d84c9 Mon Sep 17 00:00:00 2001 From: HAL9000 Date: Thu, 23 Apr 2026 12:46:55 +0000 Subject: [PATCH 2/6] fix(providers): resolve lint and unit test failures in OllamaProvider and MistralProvider - Fix import order in ollama_provider.py (langchain_community before langchain_core) - Remove duplicate shared step definitions from ollama_provider_steps.py - Remove duplicate shared step definitions from mistral_provider_steps.py The duplicate @given step definitions caused AmbiguousStep errors when running the full Behave test suite. Shared steps (provider domain inputs, plan generation graph setup) are already defined in openai_provider_steps.py and loaded by Behave for all feature files. --- features/steps/mistral_provider_steps.py | 81 +++---------------- features/steps/ollama_provider_steps.py | 65 +-------------- .../providers/llm/ollama_provider.py | 2 +- 3 files changed, 11 insertions(+), 137 deletions(-) diff --git a/features/steps/mistral_provider_steps.py b/features/steps/mistral_provider_steps.py index 38707435c..3dba0c48e 100644 --- a/features/steps/mistral_provider_steps.py +++ b/features/steps/mistral_provider_steps.py @@ -7,12 +7,6 @@ from unittest.mock import MagicMock, patch from behave import given, then, when -from cleveragents.domain.models.core import ( - Context, - OperationType, - Plan, - Project, -) from cleveragents.providers.llm.mistral_provider import MistralChatProvider @@ -85,61 +79,6 @@ def _setup_plan_generation_graph(context) -> MagicMock: return mock_graph_instance -@given("I have sample provider domain inputs") -def step_sample_provider_inputs(context): - context.project = MagicMock(spec=Project) - context.plan = MagicMock(spec=Plan) - context.plan.prompt = "Add placeholder coverage" - context.contexts = [MagicMock(spec=Context)] - context.contexts[0].content = "Sample context entry" - - -@given('the plan generation graph returns a generated change for "{file_path}"') -def step_plan_generation_returns_change(context, file_path): - change = { - "plan_id": 1, - "file_path": file_path, - "operation": OperationType.MODIFY.value, - "new_content": "# updated content", - } - context.plan_generation_state_override = { - "generated_changes": [change], - "validation_result": {"status": "PASS"}, - "error": None, - } - - -@given('the plan generation graph emits streaming nodes "{node_list}"') -def step_plan_generation_stream_nodes(context, node_list): - nodes = [node.strip() for node in node_list.split(",") if node.strip()] - state = getattr(context, "plan_generation_state_override", {}) or {} - events: list[dict[str, Any]] = [] - for node in nodes: - payload: dict[str, Any] = {"status": "completed"} - if node == "generate_plan" and isinstance(state.get("generated_changes"), list): - payload = { - "generated_changes": state["generated_changes"], - "status": "completed", - } - elif node == "validate" and isinstance(state.get("validation_result"), dict): - payload = { - "validation_result": state["validation_result"], - "status": "completed", - } - events.append({node: payload}) - context.plan_generation_stream_events = events - - -@given('the plan generation graph raises ValueError "{message}"') -def step_plan_generation_raises_value_error(context, message): - context.plan_generation_invoke_side_effect = ValueError(message) - - -@given('the plan generation graph raises RuntimeError "{message}"') -def step_plan_generation_runtime_error(context, message): - context.plan_generation_invoke_side_effect = RuntimeError(message) - - @given('I set the MISTRAL_API_KEY environment variable to "{api_key}"') def step_set_mistral_env_var(context, api_key): context.mistral_env_api_key = api_key @@ -152,12 +91,8 @@ def step_set_mistral_provider_kwargs(context, kwargs_string): context.mistral_provider_kwargs = _parse_kwargs_string(kwargs_string) -@given( - 'I create a Mistral chat provider with API key "{api_key}" and model "{model}"' -) -@when( - 'I create a Mistral chat provider with API key "{api_key}" and model "{model}"' -) +@given('I create a Mistral chat provider with API key "{api_key}" and model "{model}"') +@when('I create a Mistral chat provider with API key "{api_key}" and model "{model}"') def step_create_mistral_provider(context, api_key, model): patcher = patch("cleveragents.providers.llm.mistral_provider.ChatMistralAI") context.chat_mistral_patcher = patcher @@ -178,9 +113,7 @@ def step_create_mistral_provider(context, api_key, model): context.chat_mistral_instance = mock_chat_mistral_instance -@when( - 'I create a Mistral chat provider without explicit API key and model "{model}"' -) +@when('I create a Mistral chat provider without explicit API key and model "{model}"') def step_create_mistral_provider_from_env(context, model): patcher = patch("cleveragents.providers.llm.mistral_provider.ChatMistralAI") context.chat_mistral_patcher = patcher @@ -242,7 +175,9 @@ def step_stream_plan_generation(context): 'the Mistral provider should construct ChatMistralAI with api key "{api_key}" and model "{model}"' ) def step_assert_chat_mistral_constructor(context, api_key, model): - assert context.chat_mistral_call is not None, "ChatMistralAI should have been called" + assert context.chat_mistral_call is not None, ( + "ChatMistralAI should have been called" + ) call_args, call_kwargs = context.chat_mistral_call assert call_args == (), "ChatMistralAI should be called with keyword arguments" assert call_kwargs["api_key"] == api_key @@ -259,7 +194,9 @@ def step_assert_chat_mistral_constructor(context, api_key, model): 'the Mistral provider should include kwargs "{kwargs_string}" in the ChatMistralAI call' ) def step_assert_chat_mistral_kwargs(context, kwargs_string): - assert context.chat_mistral_call is not None, "ChatMistralAI should have been called" + assert context.chat_mistral_call is not None, ( + "ChatMistralAI should have been called" + ) _, call_kwargs = context.chat_mistral_call expected_kwargs = _parse_kwargs_string(kwargs_string) for key, value in expected_kwargs.items(): diff --git a/features/steps/ollama_provider_steps.py b/features/steps/ollama_provider_steps.py index 792439d88..2cfe0bbd2 100644 --- a/features/steps/ollama_provider_steps.py +++ b/features/steps/ollama_provider_steps.py @@ -6,12 +6,6 @@ from unittest.mock import MagicMock, patch from behave import given, then, when -from cleveragents.domain.models.core import ( - Context, - OperationType, - Plan, - Project, -) from cleveragents.providers.llm.ollama_provider import OllamaChatProvider @@ -84,61 +78,6 @@ def _setup_plan_generation_graph(context) -> MagicMock: return mock_graph_instance -@given("I have sample provider domain inputs") -def step_sample_provider_inputs(context): - context.project = MagicMock(spec=Project) - context.plan = MagicMock(spec=Plan) - context.plan.prompt = "Add placeholder coverage" - context.contexts = [MagicMock(spec=Context)] - context.contexts[0].content = "Sample context entry" - - -@given('the plan generation graph returns a generated change for "{file_path}"') -def step_plan_generation_returns_change(context, file_path): - change = { - "plan_id": 1, - "file_path": file_path, - "operation": OperationType.MODIFY.value, - "new_content": "# updated content", - } - context.plan_generation_state_override = { - "generated_changes": [change], - "validation_result": {"status": "PASS"}, - "error": None, - } - - -@given('the plan generation graph emits streaming nodes "{node_list}"') -def step_plan_generation_stream_nodes(context, node_list): - nodes = [node.strip() for node in node_list.split(",") if node.strip()] - state = getattr(context, "plan_generation_state_override", {}) or {} - events: list[dict[str, Any]] = [] - for node in nodes: - payload: dict[str, Any] = {"status": "completed"} - if node == "generate_plan" and isinstance(state.get("generated_changes"), list): - payload = { - "generated_changes": state["generated_changes"], - "status": "completed", - } - elif node == "validate" and isinstance(state.get("validation_result"), dict): - payload = { - "validation_result": state["validation_result"], - "status": "completed", - } - events.append({node: payload}) - context.plan_generation_stream_events = events - - -@given('the plan generation graph raises ValueError "{message}"') -def step_plan_generation_raises_value_error(context, message): - context.plan_generation_invoke_side_effect = ValueError(message) - - -@given('the plan generation graph raises RuntimeError "{message}"') -def step_plan_generation_runtime_error(context, message): - context.plan_generation_invoke_side_effect = RuntimeError(message) - - @given('I set the Ollama provider extra kwargs "{kwargs_string}"') def step_set_ollama_provider_kwargs(context, kwargs_string): context.ollama_provider_kwargs = _parse_kwargs_string(kwargs_string) @@ -147,9 +86,7 @@ def step_set_ollama_provider_kwargs(context, kwargs_string): @given( 'I create an Ollama chat provider with model "{model}" and base_url "{base_url}"' ) -@when( - 'I create an Ollama chat provider with model "{model}" and base_url "{base_url}"' -) +@when('I create an Ollama chat provider with model "{model}" and base_url "{base_url}"') def step_create_ollama_provider(context, model, base_url): patcher = patch("cleveragents.providers.llm.ollama_provider.ChatOllama") context.chat_ollama_patcher = patcher diff --git a/src/cleveragents/providers/llm/ollama_provider.py b/src/cleveragents/providers/llm/ollama_provider.py index 4f313215e..cbc548394 100644 --- a/src/cleveragents/providers/llm/ollama_provider.py +++ b/src/cleveragents/providers/llm/ollama_provider.py @@ -2,8 +2,8 @@ from __future__ import annotations from typing import Any, cast -from langchain_core.language_models import BaseLanguageModel from langchain_community.chat_models import ChatOllama +from langchain_core.language_models import BaseLanguageModel from cleveragents.providers.llm.langchain_chat_provider import LangChainChatProvider -- 2.52.0 From c2903f8b23f64ebda0aa4ebee71d1dc1f59a8237 Mon Sep 17 00:00:00 2001 From: CleverThis Date: Thu, 4 Jun 2026 10:26:54 -0400 Subject: [PATCH 3/6] fix(providers): use langchain-ollama package for ChatOllama import langchain-community 0.4.2 removed ChatOllama from its chat_models module. Switch to the standalone langchain-ollama package which is the official replacement. - ollama_provider.py: import ChatOllama from langchain_ollama - pyproject.toml: replace ollama>=0.1.0 with langchain-ollama>=0.1.0 - uv.lock: regenerated to include langchain-ollama v1.1.0 --- pyproject.toml | 2 +- .../providers/llm/ollama_provider.py | 2 +- uv.lock | 266 +++++++++++++++++- 3 files changed, 261 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fcc9e44a6..53f3f788f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ dependencies = [ "aiohttp>=3.13.4", # CVE-2026-34515 mitigation: open redirect vulnerability "pyyaml>=6.0.3", # CVE-2017-18342 / CVE-2025-8045 mitigation: explicit pin for runtime YAML actor config loading; safe_load enforced throughout codebase "a2a-sdk>=0.3.0,<1.0.0", # A2A Python SDK — required transport for local (stdio) and server (HTTP) modes (ADR-047); pinned <1.0.0 (removed legacy A2AClient) - "ollama>=0.1.0", # Ollama local model support + "langchain-ollama>=0.1.0", # Ollama local model support via LangChain integration ] [project.optional-dependencies] diff --git a/src/cleveragents/providers/llm/ollama_provider.py b/src/cleveragents/providers/llm/ollama_provider.py index cbc548394..576806895 100644 --- a/src/cleveragents/providers/llm/ollama_provider.py +++ b/src/cleveragents/providers/llm/ollama_provider.py @@ -2,7 +2,7 @@ from __future__ import annotations from typing import Any, cast -from langchain_community.chat_models import ChatOllama +from langchain_ollama import ChatOllama from langchain_core.language_models import BaseLanguageModel from cleveragents.providers.llm.langchain_chat_provider import LangChainChatProvider diff --git a/uv.lock b/uv.lock index 5bf228b21..4c330bed1 100644 --- a/uv.lock +++ b/uv.lock @@ -282,6 +282,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/a7/1a31561d10a089fcb46fe286766dd4e053a12f6e23b4fd1c26478aff2475/boltons-21.0.0-py2.py3-none-any.whl", hash = "sha256:b9bb7b58b2b420bbe11a6025fdef6d3e5edc9f76a42fb467afe7ca212ef9948b", size = 193723, upload-time = "2021-05-17T01:20:20.023Z" }, ] +[[package]] +name = "boto3" +version = "1.43.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/31/32388d5ec332ffe81d8f3650860f94b66294009172d188c390cad58c6f5f/boto3-1.43.22.tar.gz", hash = "sha256:2a7fe12d8e0731bb8aa7c1e59b4ccc770fda031b8659c2f6f497393bdcec3051", size = 113203, upload-time = "2026-06-03T19:33:13.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/6b/c2fb3b91e849882df5426e68cc15eb2b5ba6ac28325ab2aa3a1065da5884/boto3-1.43.22-py3-none-any.whl", hash = "sha256:0597fb9fe1613e636ac55219a5a54ad0fcb7c15e6be32c799301f7fb53ff04e1", size = 140536, upload-time = "2026-06-03T19:33:10.939Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/cf/840f1b8db16d45e3807c23d1ea779723eed1cd9cf3b6c49e16f372d2a777/botocore-1.43.22.tar.gz", hash = "sha256:b00de525e538289ed4a7a85263f1be4e47473c124cec87be6b23be49356bf745", size = 15458781, upload-time = "2026-06-03T19:33:02.882Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/6b/576a1b0f915871e35f14a33104f2bcae635f19c6a72486ba639db0d1fc70/botocore-1.43.22-py3-none-any.whl", hash = "sha256:ceec9f81d0891abe7b28ca2b2ee47e32de7b3360ad11e80d351470f015217379", size = 15141375, upload-time = "2026-06-03T19:32:58.324Z" }, +] + [[package]] name = "bracex" version = "2.6" @@ -442,6 +470,8 @@ dependencies = [ { name = "langchain-anthropic" }, { name = "langchain-community" }, { name = "langchain-google-genai" }, + { name = "langchain-mistralai" }, + { name = "langchain-ollama" }, { name = "langchain-openai" }, { name = "numpy" }, { name = "pydantic" }, @@ -459,6 +489,10 @@ dependencies = [ ] [package.optional-dependencies] +aws = [ + { name = "boto3" }, + { name = "botocore" }, +] dev = [ { name = "bandit" }, { name = "behave" }, @@ -485,6 +519,9 @@ docs = [ { name = "mkdocstrings", extra = ["python"] }, { name = "ruff" }, ] +server = [ + { name = "psycopg2-binary" }, +] tests = [ { name = "asv" }, { name = "behave" }, @@ -506,6 +543,8 @@ requires-dist = [ { name = "bandit", extras = ["toml"], marker = "extra == 'dev'", specifier = ">=1.7.5" }, { name = "behave", marker = "extra == 'dev'", specifier = "==1.3.3" }, { name = "behave", marker = "extra == 'tests'", specifier = "==1.3.3" }, + { name = "boto3", marker = "extra == 'aws'", specifier = ">=1.34.0" }, + { name = "botocore", marker = "extra == 'aws'", specifier = ">=1.34.0" }, { name = "dependency-injector", specifier = ">=4.41.0" }, { name = "faiss-cpu", specifier = ">=1.7.4" }, { name = "faker", marker = "extra == 'tests'", specifier = ">=20.0.0" }, @@ -517,6 +556,8 @@ requires-dist = [ { name = "langchain-anthropic", specifier = ">=0.2.0" }, { name = "langchain-community", specifier = ">=0.2.14" }, { name = "langchain-google-genai", specifier = ">=0.2.0" }, + { name = "langchain-mistralai", specifier = ">=0.1.0" }, + { name = "langchain-ollama", specifier = ">=0.1.0" }, { name = "langchain-openai", specifier = ">=0.2.0" }, { name = "mkdocs", marker = "extra == 'docs'", specifier = ">=1.6.1" }, { name = "mkdocs-click", marker = "extra == 'docs'", specifier = ">=0.8.0" }, @@ -527,6 +568,7 @@ requires-dist = [ { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'", specifier = ">=0.24.0" }, { name = "numpy", specifier = ">=2.1.0" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.6.0" }, + { name = "psycopg2-binary", marker = "extra == 'server'", specifier = ">=2.9.9" }, { name = "pydantic", specifier = ">=2.7.0" }, { name = "pydantic-settings", specifier = ">=2.11.0" }, { name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.350" }, @@ -555,7 +597,7 @@ requires-dist = [ { name = "vulture", marker = "extra == 'dev'", specifier = ">=2.10" }, { name = "watchdog", specifier = ">=4.0.0" }, ] -provides-extras = ["tui", "dev", "tests", "docs"] +provides-extras = ["server", "tui", "aws", "dev", "tests", "docs"] [[package]] name = "click" @@ -838,14 +880,30 @@ wheels = [ [[package]] name = "faker" -version = "40.15.0" +version = "40.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7f/13/6741787bd91c4109c7bed047d68273965cd52ce8a5f773c471b949334b6d/faker-40.15.0.tar.gz", hash = "sha256:20f3a6ec8c266b74d4c554e34118b21c3c2056c0b4a519d15c8decb3a4e6e795", size = 1967447, upload-time = "2026-04-17T20:05:27.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/6f/d7b251fb31de7dce0e482680bf7ca876aa0043f475c04aeefa1459ea80d4/faker-40.21.0.tar.gz", hash = "sha256:2fdee1b650a723a54432db9c6dfe17cfa29d1adc8bd60520444a07698524ba4d", size = 1970295, upload-time = "2026-06-02T17:53:46.27Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/a7/a600f8f30d4505e89166de51dd121bd540ab8e560e8cf0901de00a81de8c/faker-40.15.0-py3-none-any.whl", hash = "sha256:71ab3c3370da9d2205ab74ffb0fd51273063ad562b3a3bb69d0026a20923e318", size = 2004447, upload-time = "2026-04-17T20:05:25.437Z" }, + { url = "https://files.pythonhosted.org/packages/bb/77/6adb5a9dcd028f687f81fc9f789591f9572cb5a46454337122add004e134/faker-40.21.0-py3-none-any.whl", hash = "sha256:cb6601b2ae8e128895dc96814d271eab6b930a2d2d7932c6f9ff26785c24ee18", size = 2008808, upload-time = "2026-06-02T17:53:44.346Z" }, +] + +[[package]] +name = "fastapi" +version = "0.136.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" }, ] [[package]] @@ -955,6 +1013,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] +[[package]] +name = "fsspec" +version = "2026.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/8d/1c51c094345df128ca4a990d633fe1a0ff28726c9e6b3c41ba65087bba1d/fsspec-2026.4.0.tar.gz", hash = "sha256:301d8ac70ae90ef3ad05dcf94d6c3754a097f9b5fe4667d2787aa359ec7df7e4", size = 312760, upload-time = "2026-04-29T20:42:38.635Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl", hash = "sha256:11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2", size = 203402, upload-time = "2026-04-29T20:42:36.842Z" }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -1109,6 +1176,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "hf-xet" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/d8/5c06fc76461418326a7decf8367480c35be11a41fd938633929c60a9ec6b/hf_xet-1.5.0.tar.gz", hash = "sha256:e0fb0a34d9f406eed88233e829a67ec016bec5af19e480eac65a233ea289a948", size = 837196, upload-time = "2026-05-06T06:18:15.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/9b/6912c99070915a4f28119e3c5b52a9abd1eec0ad5cb293b8c967a0c6f5a2/hf_xet-1.5.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7d70fe2ce97b9db73b9c9b9c81fe3693640aec83416a966c446afea54acfae3c", size = 4023383, upload-time = "2026-05-06T06:17:53.947Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6d/9563cfde59b5d8128a9c7ec972a087f4c782e4f7bac5a85234edfd5d5e49/hf_xet-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:73a0dae8c71de3b0633a45c73f4a4a5ed09e94b43441d82981a781d4f12baa42", size = 3792751, upload-time = "2026-05-06T06:17:51.791Z" }, + { url = "https://files.pythonhosted.org/packages/07/a5/ed5a0cf35b49a0571af5a8f53416dad1877a718c021c9937c3a53cb45781/hf_xet-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a60290ec57e9b71767fba7c3645ddafdd0759974b540441510c629c6db6db24a", size = 4456058, upload-time = "2026-05-06T06:17:40.735Z" }, + { url = "https://files.pythonhosted.org/packages/60/fb/3ae8bf2a7a37a4197d0195d7247fd25b3952e15cb8a599e285dfaa6f52b3/hf_xet-1.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e5de0f6deada0dada870bb376a11bcd1f08abf3a968a6d118f33e72d1b1eb480", size = 4250783, upload-time = "2026-05-06T06:17:38.412Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/8bae40d4d91525085137196e84eb0ed49cf65b5e96e5c3ecdadd8bd0fac2/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c799d49f1a5544a0ef7591c0ee75e0d6b93d6f56dc7a4979f59f7518d2872216", size = 4445594, upload-time = "2026-05-06T06:18:04.219Z" }, + { url = "https://files.pythonhosted.org/packages/13/59/c74efbbd4e8728172b2cc72a2bc014d2947a4b7bdced932fbd3f5da1a4e5/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2baea1b0b989e5c152fe81425f7745ddc8901280ba3d97c98d8cdece7b706c60", size = 4663995, upload-time = "2026-05-06T06:18:06.1Z" }, + { url = "https://files.pythonhosted.org/packages/73/32/8e1e0410af64cda9b139d1dcebdc993a8ff9c8c7c0e2696ae356d75ccc0d/hf_xet-1.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:526345b3ed45f374f6317349df489167606736c876241ba984105afe7fd4839d", size = 3966608, upload-time = "2026-05-06T06:18:19.74Z" }, + { url = "https://files.pythonhosted.org/packages/fc/34/a8febc8f4edbea8b3e21b02ebc8b628679b84ba7e45cde624a7736b51500/hf_xet-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:786d28e2eb8315d5035544b9d137b4a842d600c434bb91bf7d0d953cce906ad4", size = 3796946, upload-time = "2026-05-06T06:18:17.568Z" }, + { url = "https://files.pythonhosted.org/packages/2a/20/8fc8996afe5815fa1a6be8e9e5c02f24500f409d599e905800d498a4e14d/hf_xet-1.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:872d5601e6deea30d15865ede55d29eac6daf5a534ab417b99b6ef6b076dd96c", size = 4023495, upload-time = "2026-05-06T06:18:01.94Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/93d84463c00cecb561a7508aa6303e35ee2894294eac14245526924415fe/hf_xet-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9929561f5abf4581c8ea79587881dfef6b8abb2a0d8a51915936fc2a614f4e73", size = 3792731, upload-time = "2026-05-06T06:18:00.021Z" }, + { url = "https://files.pythonhosted.org/packages/9d/5a/8ec8e0c863b382d00b3c2e2af6ded6b06371be617144a625903a6d562f4b/hf_xet-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7b7bbae318e583a86fb21e5a4a175d6721d628a2874f4bd022d0e660c32a682", size = 4456738, upload-time = "2026-05-06T06:17:49.574Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ca/f7effa1a67717da2bcc6b6c28f71c6ca648c77acaec4e2c32f40cbe16d85/hf_xet-1.5.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cf7b2dc6f31a4ea754bb50f74cde482dcf5d366d184076d8530b9872787f3761", size = 4251622, upload-time = "2026-05-06T06:17:47.096Z" }, + { url = "https://files.pythonhosted.org/packages/65/f2/19247dba3e231cf77dec59ddfb878f00057635ff773d099c9b59d37812c3/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8dbcbab554c9ef158ef2c991545c3e970ddd8cc7acdcd0a78c5a41095dab4ded", size = 4445667, upload-time = "2026-05-06T06:18:11.983Z" }, + { url = "https://files.pythonhosted.org/packages/7f/64/6f116801a3bcfb6f59f5c251f48cadc47ea54026441c4a385079286a94fa/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5906bf7718d3636dc13402914736abe723492cb730f744834f5f5b67d3a12702", size = 4664619, upload-time = "2026-05-06T06:18:13.771Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e8/069542d37946ed08669b127e1496fa99e78196d71de8d41eda5e9f1b7a58/hf_xet-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f3dc2248fc01cc0a00cd392ab497f1ca373fcbc7e3f2da1f452480b384e839e", size = 3966802, upload-time = "2026-05-06T06:18:28.162Z" }, + { url = "https://files.pythonhosted.org/packages/f9/91/fc6fdec27b14d04e88c386ac0a0129732b53fa23f7c4a78f4b83a039c567/hf_xet-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b285cea1b5bab46b758772716ba8d6854a1a0310fed1c249d678a8b38601e5a0", size = 3797168, upload-time = "2026-05-06T06:18:26.287Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fb/69ff198a82cae7eb1a69fb84d93b3a3e4816564d76817fe541ddc96874eb/hf_xet-1.5.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dad0dc84e941b8ba3c860659fe1fdc35c049d47cce293f003287757e971a8f56", size = 4030814, upload-time = "2026-05-06T06:17:57.933Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ff/edcc2b40162bef3ff78e14ab637e5f3b89243d6aee72f5949d3bb6a5af83/hf_xet-1.5.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fd6e5a9b0fdac4ed03ed45ef79254a655b1aaab514a02202617fbf643f5fdf7a", size = 3798444, upload-time = "2026-05-06T06:17:55.79Z" }, + { url = "https://files.pythonhosted.org/packages/49/4d/103f76b04310e5e57656696cc184690d20c466af0bca3ca88f8c8ea5d4f3/hf_xet-1.5.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3531b1823a0e6d77d80f9ed15ca0e00f0d115094f8ac033d5cae88f4564cc949", size = 4465986, upload-time = "2026-05-06T06:17:44.886Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a2/546f47f464737b3edbab6f8ddb57f2599b93d2cbb66f06abb475ccb48651/hf_xet-1.5.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a0ee58cd18d5ea799f7ed11290bbccbe56bdd8b1d97ca74b9cc49a3945d7a3b", size = 4259865, upload-time = "2026-05-06T06:17:42.639Z" }, + { url = "https://files.pythonhosted.org/packages/95/7f/1be593c1f28613be2e196473481cd81bfc5910795e30a34e8f744f6cac4f/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e60df5a42e9bed8628b6416af2cba4cba57ae9f02de226a06b020d98e1aab18", size = 4459835, upload-time = "2026-05-06T06:18:08.026Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b2/703569fc881f3284487e68cda7b42179978480da3c438042a6bbbb4a671c/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4b35549ce62601b84da4ff9b24d970032ace3d4430f52d91bcbb26c901d6c690", size = 4672414, upload-time = "2026-05-06T06:18:09.864Z" }, + { url = "https://files.pythonhosted.org/packages/af/37/1b6def445c567286b50aa3b33828158e135b1be44938dde59f11382a500c/hf_xet-1.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:2806c7c17b4d23f8d88f7c4814f838c3b6150773fe339c20af23e1cfaf2797e4", size = 3977238, upload-time = "2026-05-06T06:18:23.621Z" }, + { url = "https://files.pythonhosted.org/packages/62/94/3b66b148778ee100dcfd69c2ca22b57b41b44d3063ceec934f209e9184ce/hf_xet-1.5.0-cp37-abi3-win_arm64.whl", hash = "sha256:b6c9df403040248c76d808d3e047d64db2d923bae593eb244c41e425cf6cd7be", size = 3806916, upload-time = "2026-05-06T06:18:21.7Z" }, +] + [[package]] name = "httpcore" version = "1.0.9" @@ -1146,6 +1245,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, ] +[[package]] +name = "huggingface-hub" +version = "1.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/0f/ed994dbade67a54407c28cab96ef845e0e6d25500be56aca6394f8bfc9dd/huggingface_hub-1.16.1.tar.gz", hash = "sha256:7f1dc4c5ec21aed69be630ad0c3378616be16f3de1a47b141c0e812965d9c832", size = 792534, upload-time = "2026-05-21T18:40:00.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/79/621a7dbb80c70974f73a597275351ebe03ce5bc65cb5f8f4acb5859252bc/huggingface_hub-1.16.1-py3-none-any.whl", hash = "sha256:64340de934b9ce37857ef85a82de72f5629e8a270f9119eabb12bf495eb53c22", size = 668176, upload-time = "2026-05-21T18:39:58.596Z" }, +] + [[package]] name = "identify" version = "2.6.18" @@ -1248,6 +1367,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, ] +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + [[package]] name = "json5" version = "0.14.0" @@ -1376,10 +1504,11 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.2.26" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, + { name = "langchain-protocol" }, { name = "langsmith" }, { name = "packaging" }, { name = "pydantic" }, @@ -1388,9 +1517,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/b0/30ed29e5820580bc13d70b1f8a212b4fe0609a9737164ed1a90167941ca2/langchain_core-1.2.26.tar.gz", hash = "sha256:ba025ec70e19b56467f46b9109de19d30d169d328a174986b353cb23fd0ff0fe", size = 844795, upload-time = "2026-04-03T23:30:32.567Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/de/679a53472c25860837e32c0442c962fa86e95317a36460e2c9d5c91b17c2/langchain_core-1.4.0.tar.gz", hash = "sha256:1dc341eed802ed9c117c0df3923c991e5e9e226571e5725c194eeb5bd93d1a7f", size = 920260, upload-time = "2026-05-11T18:42:35.919Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/8b/c184205a52b37a4a3166b3567323495701929b1dbfd528e5ba1df62bd404/langchain_core-1.2.26-py3-none-any.whl", hash = "sha256:3d0a3913dff77a930b017a05afe979e4959d27bec0c77ee51f9a100754510509", size = 508298, upload-time = "2026-04-03T23:30:30.253Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1a/86c38c27b81913a1c6c12448cab55defb5a1097c7dc9a4cea83f55477a2d/langchain_core-1.4.0-py3-none-any.whl", hash = "sha256:23cbbdb46e38ddd1dd5247e6167e96013eae74bea4c5949c550809970a9e565c", size = 548120, upload-time = "2026-05-11T18:42:33.992Z" }, ] [[package]] @@ -1408,6 +1537,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/7e/46c5973bd8b10a5c4c8a77136cf536e658796380a17c740246074901b038/langchain_google_genai-4.2.1-py3-none-any.whl", hash = "sha256:a7735289cf94ca3a684d830e09196aac8f6e75e647e3a0a1c3c9dc534ceb985e", size = 66500, upload-time = "2026-02-19T19:29:18.002Z" }, ] +[[package]] +name = "langchain-mistralai" +version = "1.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "langchain-core" }, + { name = "pydantic" }, + { name = "tokenizers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/c3/ca9538a9d2f4ceedccb084802c83647371cae21ad4e6095ee2c7b793cdce/langchain_mistralai-1.1.4.tar.gz", hash = "sha256:af1815c7b70051afb93c091c1c3b49cc680cf756dc5feb77344f9e73201660c1", size = 132673, upload-time = "2026-05-05T15:29:02.397Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/6b/23d26a9584d78c1d53891178aab37c6b2e25ff360231beca80f3a90120de/langchain_mistralai-1.1.4-py3-none-any.whl", hash = "sha256:1d9c2c27a83b3d4020cdc3a81fa5b021200054a984a4c8e8fa30c2f802f781b6", size = 21113, upload-time = "2026-05-05T15:29:00.839Z" }, +] + +[[package]] +name = "langchain-ollama" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ollama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/9b/6641afe8a5bf807e454fd464eddfc7eb2f2df53cb0b29744381171f9c609/langchain_ollama-1.1.0.tar.gz", hash = "sha256:f776f56f6782ae4da7692579b94a6575906118318d1023b455d7207f9d059811", size = 133075, upload-time = "2026-04-07T02:48:00.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/b2/c2acb076590a98bee2816ed5f285e00df162a34238f9e276e175e14ebc35/langchain_ollama-1.1.0-py3-none-any.whl", hash = "sha256:43ac83a6eacb0f43855810739794dd55019e0d9b17bdcf3ecb3b1991ac3b59dd", size = 31413, upload-time = "2026-04-07T02:47:59.642Z" }, +] + [[package]] name = "langchain-openai" version = "1.1.12" @@ -1422,6 +1580,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/a6/68fb22e3604015e6f546fa1d3677d24378b482855ae74710cbf4aec44132/langchain_openai-1.1.12-py3-none-any.whl", hash = "sha256:da71ca3f2d18c16f7a2443cc306aa195ad2a07054335ac9b0626dcae02b6a0c5", size = 88487, upload-time = "2026-03-23T18:59:17.978Z" }, ] +[[package]] +name = "langchain-protocol" +version = "0.0.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/e7/8300ba22d968653051fd06e3117d783872dddf3dcebdd6b1d386836eb43c/langchain_protocol-0.0.16.tar.gz", hash = "sha256:806c7cdd951b1c4f692fa40fce60821ff0f221d4360e27673ddf2c2b99c2b7ff", size = 5969, upload-time = "2026-05-28T23:05:11.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/9c/06dfcc88d02a6364e8d864c421ddd3736305cb0a6c853f75c302c80fe17c/langchain_protocol-0.0.16-py3-none-any.whl", hash = "sha256:3658c142c5d0fb3a023a4be442ce4c15c6d626aab6135eb79a76dc64ad19c3c3", size = 7037, upload-time = "2026-05-28T23:05:10.163Z" }, +] + [[package]] name = "langchain-text-splitters" version = "1.1.1" @@ -2017,6 +2187,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, ] +[[package]] +name = "ollama" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/72/5f12423b6b39ca8430fbe56f77fcf4ef60f63067c7c4a2e30e200ed9ec16/ollama-0.6.2.tar.gz", hash = "sha256:936d55daa684f474364c098611c933626f8d6c7d67065c5b7ae0c477b508b07f", size = 53145, upload-time = "2026-04-29T21:21:15.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ab/d6722beeb2d10f7a3b9ff49375708904fde18f82b5609a0bc4aeb5996a4d/ollama-0.6.2-py3-none-any.whl", hash = "sha256:3ad7daab28e5a973445c36a73882a3ef698c2ebb00e21e308652741577509f7d", size = 15115, upload-time = "2026-04-29T21:21:13.794Z" }, +] + [[package]] name = "openai" version = "2.30.0" @@ -2450,6 +2633,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, ] +[[package]] +name = "psycopg2-binary" +version = "2.9.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/60/a3624f79acea344c16fbef3a94d28b89a8042ddfb8f3e4ca83f538671409/psycopg2_binary-2.9.12.tar.gz", hash = "sha256:5ac9444edc768c02a6b6a591f070b8aae28ff3a99be57560ac996001580f294c", size = 379686, upload-time = "2026-04-21T09:40:34.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/bb/4608c96f970f6e0c56572e87027ef4404f709382a3503e9934526d7ba051/psycopg2_binary-2.9.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7c729a73c7b1b84de3582f73cdd27d905121dc2c531f3d9a3c32a3011033b965", size = 3712419, upload-time = "2026-04-20T23:34:58.754Z" }, + { url = "https://files.pythonhosted.org/packages/5e/af/48f76af9d50d61cf390f8cd657b503168b089e2e9298e48465d029fcc713/psycopg2_binary-2.9.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4413d0caef93c5cf50b96863df4c2efe8c269bf2267df353225595e7e15e8df7", size = 3822990, upload-time = "2026-04-20T23:35:00.821Z" }, + { url = "https://files.pythonhosted.org/packages/7a/df/aba0f99397cd811d32e06fc0cc781f1f3ce98bc0e729cb423925085d781a/psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4dfcf8e45ebb0c663be34a3442f65e17311f3367089cd4e5e3a3e8e62c978777", size = 4578696, upload-time = "2026-04-20T23:35:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/eaa74021ac4e4d5c2f83d82fc6615a63f4fe6c94dc4e94c3990427053f67/psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c41321a14dd74aceb6a9a643b9253a334521babfa763fa873e33d89cfa122fb5", size = 4274982, upload-time = "2026-04-20T23:35:05.583Z" }, + { url = "https://files.pythonhosted.org/packages/35/ed/c25deff98bd26187ba48b3b250a3ffc3037c46c5b89362534a15d200e0db/psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83946ba43979ebfdc99a3cd0ee775c89f221df026984ba19d46133d8d75d3cd9", size = 5894867, upload-time = "2026-04-20T23:35:07.902Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/8d0e21ca77373c6c9589e5c4528f6e8f0c08c62cafc76fb0bddb7a2cee22/psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:411e85815652d13560fbe731878daa5d92378c4995a22302071890ec3397d019", size = 4110578, upload-time = "2026-04-20T23:35:10.149Z" }, + { url = "https://files.pythonhosted.org/packages/00/fc/f481e2435bd8f742d0123309174aae4165160ad3ef17c1b99c3622c241d2/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c8ad4c08e00f7679559eaed7aff1edfffc60c086b976f93972f686384a95e2c", size = 3655816, upload-time = "2026-04-20T23:35:12.56Z" }, + { url = "https://files.pythonhosted.org/packages/53/79/b9f46466bdbe9f239c96cde8be33c1aace4842f06013b47b730dc9759187/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:00814e40fa23c2b37ef0a1e3c749d89982c73a9cb5046137f0752a22d432e82f", size = 3301307, upload-time = "2026-04-20T23:35:15.029Z" }, + { url = "https://files.pythonhosted.org/packages/3f/19/7dc003b32fe35024df89b658104f7c8538a8b2dcbde7a4e746ce929742e7/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:98062447aebc20ed20add1f547a364fd0ef8933640d5372ff1873f8deb9b61be", size = 3048968, upload-time = "2026-04-20T23:35:16.757Z" }, + { url = "https://files.pythonhosted.org/packages/91/58/2dbd7db5c604d45f4950d988506aae672a14126ec22998ced5021cbb76bb/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66a7685d7e548f10fb4ce32fb01a7b7f4aa702134de92a292c7bd9e0d3dbd290", size = 3351369, upload-time = "2026-04-20T23:35:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/42/ee/dee8dcaad07f735824de3d6563bc67119fa6c28257b17977a8d624f02fab/psycopg2_binary-2.9.12-cp313-cp313-win_amd64.whl", hash = "sha256:b6937f5fe4e180aeee87de907a2fa982ded6f7f15d7218f78a083e4e1d68f2a0", size = 2757347, upload-time = "2026-04-20T23:35:21.283Z" }, + { url = "https://files.pythonhosted.org/packages/13/1b/708c0dca874acfad6d65314271859899a79007686f3a1f74e82a2ed4b645/psycopg2_binary-2.9.12-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f3b3de8a74ef8db215f22edffb19e32dc6fa41340456de7ec99efdc8a7b3ec2", size = 3712428, upload-time = "2026-04-20T23:35:23.453Z" }, + { url = "https://files.pythonhosted.org/packages/d6/39/ddbea9d4b4de6aca9431b6ed253f530f8a02d3b8f9bcfd0dbfe2b3de6fe4/psycopg2_binary-2.9.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1006fb62f0f0bc5ce256a832356c6262e91be43f5e4eb15b5eaf38079464caf2", size = 3823184, upload-time = "2026-04-20T23:35:25.92Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a0/bc2fef74b106fa345567122a0659e6d94512ed7dc0131ec44c9e5aba3725/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:840066105706cd2eb29b9a1c2329620056582a4bf3e8169dec5c447042d0869f", size = 4579157, upload-time = "2026-04-20T23:35:28.542Z" }, + { url = "https://files.pythonhosted.org/packages/57/d7/d4e3b2005d3de607ca4fbb0e8742e248056e52184a6b94ebda3c1c2c329b/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:863f5d12241ebe1c76a72a04c2113b6dc905f90b9cef0e9be0efd994affd9354", size = 4274970, upload-time = "2026-04-20T23:35:30.418Z" }, + { url = "https://files.pythonhosted.org/packages/2e/42/c9853f8db3967fe08bcde11f53d53b85d351750cae726ce001cb68afa9c1/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a99eaab34a9010f1a086b126de467466620a750634d114d20455f3a824aae033", size = 5895175, upload-time = "2026-04-20T23:35:33.584Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fd/b82b5601a97630308bef079f545ffec481bbbc795c2ba5ec416a01d03f60/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ffdd7dc5463ccd61845ac37b7012d0f35a1548df9febe14f8dd549be4a0bc81e", size = 4110658, upload-time = "2026-04-20T23:35:35.638Z" }, + { url = "https://files.pythonhosted.org/packages/62/8c/32ca69b0389ef25dd22937bf9e8fbe2ce27aea20b05ded48c4ce4cb42475/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54a0dfecab1b48731f934e06139dfe11e24219fb6d0ceb32177cf0375f14c7b5", size = 3656251, upload-time = "2026-04-20T23:35:37.854Z" }, + { url = "https://files.pythonhosted.org/packages/c4/29/96992a2b59e3b9d730fcf9612d0a387305025dc867a9fc490a9e496e074e/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:96937c9c5d891f772430f418a7a8b4691a90c3e6b93cf72b5bd7cad8cbca32a5", size = 3301810, upload-time = "2026-04-20T23:35:39.927Z" }, + { url = "https://files.pythonhosted.org/packages/56/ad/44b06659949b243ae10112cd3b20a197f9bf3e81d5651379b9eb889bfaad/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:77b348775efd4cdab410ec6609d81ccecd1139c90265fa583a7255c8064bc03d", size = 3048977, upload-time = "2026-04-20T23:35:41.806Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f2/10a1bcebadb6aa55e280e1f58975c36a7b560ea525184c7aa4064c466633/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:527e6342b3e44c2f0544f6b8e927d60de7f163f5723b8f1dfa7d2a84298738cd", size = 3351466, upload-time = "2026-04-20T23:35:43.993Z" }, + { url = "https://files.pythonhosted.org/packages/20/be/b732c8418ffa5bcfda002890f5dc4c869fc17db66ff11f53b17cfe44afc0/psycopg2_binary-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:f12ae41fcafadb39b2785e64a40f9db05d6de2ac114077457e0e7c597f3af980", size = 2848762, upload-time = "2026-04-20T23:35:46.421Z" }, +] + [[package]] name = "pyasn1" version = "0.6.3" @@ -3097,6 +3310,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e2/a9/efeaeca4928a9a56d04d609b5730994d610c82cf4d9dd7aa173e6ef4233e/Rx-3.2.0-py3-none-any.whl", hash = "sha256:922c5f4edb3aa1beaa47bf61d65d5380011ff6adcd527f26377d05cb73ed8ec8", size = 199245, upload-time = "2021-04-25T15:57:07.487Z" }, ] +[[package]] +name = "s3transfer" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/1f/12417f7f493fc45e1f9fd5d4a9b6c125cf8d2cf3f8ddbdfab3e76406e9d6/s3transfer-0.18.0.tar.gz", hash = "sha256:3760b8b7ec1315da54048b2d626276732bee4300d054d492d4e1d43e20d4ecbd", size = 160560, upload-time = "2026-05-28T19:39:09.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/58/a58fc997655386daa2e25784e30c288aa3e3819e401f77029ee4899fb55a/s3transfer-0.18.0-py3-none-any.whl", hash = "sha256:239c13b09e65ad0346e1be7348b8a202dcad44ac7ea7c6eb858fc881dce739b6", size = 88572, upload-time = "2026-05-28T19:39:07.999Z" }, +] + [[package]] name = "semantic-version" version = "2.10.0" @@ -3347,6 +3572,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, ] +[[package]] +name = "tokenizers" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/60/21f715d9faba5f5407ff759472ade058ec4a507ad62bcea47cb847239a73/tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa", size = 365748, upload-time = "2026-04-27T14:43:25.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/39/b87a87d5bb9470610b80a2d31df42fcffeaf35118b8b97952b2aff598cc7/tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224", size = 3146732, upload-time = "2026-04-27T14:43:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6a/068ed9f6e444c9d7e9d55ce134181325700f3d7f30410721bdc8f848d727/tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e", size = 3054954, upload-time = "2026-04-27T14:43:13.745Z" }, + { url = "https://files.pythonhosted.org/packages/6c/36/e006edf031154cba92b8416057d92c3abe3635e4c4b0aa0b5b9bb39dde70/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3", size = 3374081, upload-time = "2026-04-27T14:43:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ef/7735d226f9c7f874a6bee5e3f27fb25ecabdf207d37b8cf45286d0795893/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f836ca703b89ae07919a309f9651f7a88fd5a33d5f718ba5ad0870ec0256bad6", size = 3247641, upload-time = "2026-04-27T14:43:03.856Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d9/24827036f6e21297bfffda0768e58eb6096a4f411e932964a01707857931/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae848657742035523fdf261773630cb819a26995fcd3d9ecae0c1daf6e5a4959", size = 3585624, upload-time = "2026-04-27T14:43:10.664Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/22f3582b3a4f49358293a5206e25317621ee4526bfe9cdaa0f07a12e770e/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b09e85775d5187941e7bab30e941b4134ab4a7dd8c68e783d231fb7ca27c51", size = 3844062, upload-time = "2026-04-27T14:43:05.643Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/b8f8814eef95800f20721384136d9a1d22241d50b2874357cb70542c392f/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea5a0ce170074329faaa8ea3f6400ecde604b6678192688533af80980daae71a", size = 3460098, upload-time = "2026-04-27T14:43:08.854Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d5/1353e5f677ec27c2494fb6a6725e82d56c985f53e90ec511369e7e4f02c6/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4", size = 3346235, upload-time = "2026-04-27T14:43:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/71/89/39b6b8fc073fb6d413d0147aa333dc7eff7be65639ac9d19930a0b21bf33/tokenizers-0.23.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:56f3a77de629917652f876294dc9fe6bad4a0c43bc229dc72e59bb23a0f4729a", size = 3426398, upload-time = "2026-04-27T14:43:07.264Z" }, + { url = "https://files.pythonhosted.org/packages/0f/80/127c854da64827e5b79264ce524993a90dddcb320e5cd42412c5c02f9e8a/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d10a6d957ef01896dc274e890eee27d41bd0e74ef31e60616f0fc311345184e", size = 9823279, upload-time = "2026-04-27T14:43:17.222Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ba/44c2502feb1a058f096ddfb4e0996ef3225a01a388e1a9b094e91689fe93/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1974288a609c343774f1b897c8b482c791ab17b75ab5c8c2b1737565c1d82288", size = 9644986, upload-time = "2026-04-27T14:43:19.45Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c1/464019a9fb059870bfe4eebb4ba12208f3042035e258bf5e782906bd3847/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:120468fb4c24faf0543c835a4fabafa4deb3f20a035c9b6e83d0b553a97615d4", size = 9976181, upload-time = "2026-04-27T14:43:21.463Z" }, + { url = "https://files.pythonhosted.org/packages/79/94/3ac1432bda31626071e9b6a12709b97ae05131c804b94c8f3ac622c5da32/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3d8f40ea6268047de7046906326abed5134f27d4e8447b23763afe5808c8a96", size = 10113853, upload-time = "2026-04-27T14:43:23.617Z" }, + { url = "https://files.pythonhosted.org/packages/6a/dd/631b21433c771b1382535326f0eca80b9c9cee2e64961dd993bc9ac4669e/tokenizers-0.23.1-cp310-abi3-win32.whl", hash = "sha256:93120a930b919416da7cd10a2f606ac9919cc69cacae7980fa2140e277660948", size = 2536263, upload-time = "2026-04-27T14:43:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/2553f72aaf65a2797d4229e37fa7fbe38ffbf3e32912d31bdd78b3323e59/tokenizers-0.23.1-cp310-abi3-win_amd64.whl", hash = "sha256:e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7", size = 2798223, upload-time = "2026-04-27T14:43:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2b/2be299bab55fc595e3d38567edb1a87f86e594842968fa9515a07bdcf422/tokenizers-0.23.1-cp310-abi3-win_arm64.whl", hash = "sha256:a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9", size = 2664127, upload-time = "2026-04-27T14:43:26.949Z" }, +] + [[package]] name = "tomli" version = "2.0.2" -- 2.52.0 From 24966911f5ac832eb590e182d0ae0b832aba8b5e Mon Sep 17 00:00:00 2001 From: cleveragents-auto Date: Thu, 4 Jun 2026 10:33:05 -0400 Subject: [PATCH 4/6] chore: worker ruff auto-fix (pre-push lint gate) --- src/cleveragents/providers/llm/ollama_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cleveragents/providers/llm/ollama_provider.py b/src/cleveragents/providers/llm/ollama_provider.py index 576806895..2d4907167 100644 --- a/src/cleveragents/providers/llm/ollama_provider.py +++ b/src/cleveragents/providers/llm/ollama_provider.py @@ -2,8 +2,8 @@ from __future__ import annotations from typing import Any, cast -from langchain_ollama import ChatOllama from langchain_core.language_models import BaseLanguageModel +from langchain_ollama import ChatOllama from cleveragents.providers.llm.langchain_chat_provider import LangChainChatProvider -- 2.52.0 From 0ccb5f9eed94fa14ffae1fa8d7cb0d45f0ee0e1e Mon Sep 17 00:00:00 2001 From: controller-ci-rerun Date: Thu, 4 Jun 2026 13:07:30 -0400 Subject: [PATCH 5/6] chore: re-trigger CI [controller] -- 2.52.0 From 5100a40006b763d2f287708523b5476d0390ef30 Mon Sep 17 00:00:00 2001 From: controller-ci-rerun Date: Wed, 17 Jun 2026 22:12:21 -0400 Subject: [PATCH 6/6] chore: re-trigger CI [controller] -- 2.52.0