From 5e80f0ac75bbe76583575f8d34e34b5114a017be Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Tue, 18 Nov 2025 09:50:13 -0800 Subject: [PATCH 1/9] Refactor libs/oci to meet mypy stardard Removed unnecessary type: ignore comments, improved type annotations, and updated function signatures for better type safety across chat models, embeddings, and tests. Also enhanced mypy configuration for stricter type checking and plugin support. --- .../chat_models/oci_data_science.py | 11 ++++-- .../chat_models/oci_generative_ai.py | 29 ++++++++------- ..._data_science_model_deployment_endpoint.py | 9 ++--- .../embeddings/oci_generative_ai.py | 2 +- .../langchain_oci/llms/oci_generative_ai.py | 2 +- libs/oci/pyproject.toml | 34 ++++++++++-------- .../chat_models/test_tool_calling.py | 2 +- .../chat_models/test_oci_data_science.py | 2 +- .../chat_models/test_oci_generative_ai.py | 35 +++++++++++-------- 9 files changed, 69 insertions(+), 57 deletions(-) diff --git a/libs/oci/langchain_oci/chat_models/oci_data_science.py b/libs/oci/langchain_oci/chat_models/oci_data_science.py index b6c5eb1..7d6f579 100644 --- a/libs/oci/langchain_oci/chat_models/oci_data_science.py +++ b/libs/oci/langchain_oci/chat_models/oci_data_science.py @@ -31,7 +31,12 @@ agenerate_from_stream, generate_from_stream, ) -from langchain_core.messages import AIMessageChunk, BaseMessage, BaseMessageChunk +from langchain_core.messages import ( + AIMessage, + AIMessageChunk, + BaseMessage, + BaseMessageChunk, +) from langchain_core.output_parsers import ( JsonOutputParser, PydanticOutputParser, @@ -598,7 +603,7 @@ def with_structured_output( if method == "json_mode": llm = self.bind(response_format={"type": "json_object"}) output_parser = ( - PydanticOutputParser(pydantic_object=schema) # type: ignore[type-var, arg-type] + PydanticOutputParser(pydantic_object=schema) if is_pydantic_schema else JsonOutputParser() ) @@ -767,7 +772,7 @@ def bind_tools( self, tools: Sequence[Union[Dict[str, Any], Type[BaseModel], Callable, BaseTool]], **kwargs: Any, - ) -> Runnable[LanguageModelInput, BaseMessage]: + ) -> Runnable[LanguageModelInput, AIMessage]: formatted_tools = [convert_to_openai_tool(tool) for tool in tools] return super().bind(tools=formatted_tools, **kwargs) diff --git a/libs/oci/langchain_oci/chat_models/oci_generative_ai.py b/libs/oci/langchain_oci/chat_models/oci_generative_ai.py index bd38516..c21da5f 100644 --- a/libs/oci/langchain_oci/chat_models/oci_generative_ai.py +++ b/libs/oci/langchain_oci/chat_models/oci_generative_ai.py @@ -31,7 +31,6 @@ AIMessage, AIMessageChunk, BaseMessage, - ChatMessage, HumanMessage, SystemMessage, ToolCall, @@ -350,7 +349,7 @@ def get_role(self, message: BaseMessage) -> str: raise ValueError(f"Unknown message type: {type(message)}") def messages_to_oci_params( - self, messages: Sequence[ChatMessage], **kwargs: Any + self, messages: Sequence[BaseMessage], **kwargs: Any ) -> Dict[str, Any]: """ Convert LangChain messages to OCI parameters for Cohere. @@ -417,7 +416,7 @@ def messages_to_oci_params( current_turn = list(reversed(current_turn)) # Process tool results from the current turn - oci_tool_results: List[Any] = [] + oci_tool_results: Optional[List[Any]] = [] for message in current_turn: if isinstance(message, ToolMessage): tool_msg = message @@ -434,7 +433,7 @@ def messages_to_oci_params( parameters=lc_tool_call["args"], ) tool_result.outputs = [{"output": tool_msg.content}] - oci_tool_results.append(tool_result) + oci_tool_results.append(tool_result) # type: ignore[union-attr] if not oci_tool_results: oci_tool_results = None @@ -552,7 +551,7 @@ def process_stream_tool_calls( Returns: List of ToolCallChunk objects """ - tool_call_chunks = [] + tool_call_chunks: List[ToolCallChunk] = [] tool_call_response = self.chat_stream_tool_calls(event_data) if not tool_call_response: @@ -813,7 +812,7 @@ def _should_allow_more_tool_calls( return False # Detect infinite loop: same tool called with same arguments in succession - recent_calls = [] + recent_calls: list = [] for msg in reversed(messages): if hasattr(msg, "tool_calls") and msg.tool_calls: for tc in msg.tool_calls: @@ -895,7 +894,7 @@ def _process_message_content( def convert_to_oci_tool( self, - tool: Union[Type[BaseModel], Callable, BaseTool], + tool: Union[Dict[str, Any], Type[BaseModel], Callable, BaseTool], ) -> Dict[str, Any]: """Convert a BaseTool instance, TypedDict or BaseModel type to a OCI tool in Meta's format. @@ -1016,7 +1015,7 @@ def process_stream_tool_calls( Returns: List of ToolCallChunk objects """ - tool_call_chunks = [] + tool_call_chunks: List[ToolCallChunk] = [] tool_call_response = self.chat_stream_tool_calls(event_data) if not tool_call_response: @@ -1142,7 +1141,7 @@ def _prepare_request( stop: Optional[List[str]], stream: bool, **kwargs: Any, - ) -> Dict[str, Any]: + ) -> Any: """ Prepare the OCI chat request from LangChain messages. @@ -1206,7 +1205,7 @@ def bind_tools( Union[dict, str, Literal["auto", "none", "required", "any"], bool] ] = None, **kwargs: Any, - ) -> Runnable[LanguageModelInput, BaseMessage]: + ) -> Runnable[LanguageModelInput, AIMessage]: """Bind tool-like objects to this chat model. Assumes model is compatible with Meta's tool-calling API. @@ -1299,8 +1298,8 @@ def with_structured_output( tool_name = getattr(self._provider.convert_to_oci_tool(schema), "name") if is_pydantic_schema: output_parser: OutputParserLike = PydanticToolsParser( - tools=[schema], # type: ignore[list-item] - first_tool_only=True, # type: ignore[list-item] + tools=[schema], + first_tool_only=True, ) else: output_parser = JsonOutputKeyToolsParser( @@ -1309,15 +1308,15 @@ def with_structured_output( elif method == "json_mode": llm = self.bind(response_format={"type": "JSON_OBJECT"}) output_parser = ( - PydanticOutputParser(pydantic_object=schema) # type: ignore[type-var, arg-type] + PydanticOutputParser(pydantic_object=schema) if is_pydantic_schema else JsonOutputParser() ) elif method == "json_schema": - json_schema_dict = ( + json_schema_dict: Dict[str, Any] = ( schema.model_json_schema() # type: ignore[union-attr] if is_pydantic_schema - else schema + else schema # type: ignore[assignment] ) response_json_schema = self._provider.oci_response_json_schema( diff --git a/libs/oci/langchain_oci/embeddings/oci_data_science_model_deployment_endpoint.py b/libs/oci/langchain_oci/embeddings/oci_data_science_model_deployment_endpoint.py index a81d3d1..ce06ab5 100644 --- a/libs/oci/langchain_oci/embeddings/oci_data_science_model_deployment_endpoint.py +++ b/libs/oci/langchain_oci/embeddings/oci_data_science_model_deployment_endpoint.py @@ -104,12 +104,9 @@ def _completion_with_retry(**kwargs: Any) -> Any: response.raise_for_status() return response except requests.exceptions.HTTPError as http_err: - if response.status_code == 401 and self._refresh_signer(): - raise TokenExpiredError() from http_err - else: - raise ValueError( - f"Server error: {str(http_err)}. Message: {response.text}" - ) from http_err + raise ValueError( + f"Server error: {str(http_err)}. Message: {response.text}" + ) from http_err except Exception as e: raise ValueError(f"Error occurs by inference endpoint: {str(e)}") from e diff --git a/libs/oci/langchain_oci/embeddings/oci_generative_ai.py b/libs/oci/langchain_oci/embeddings/oci_generative_ai.py index 9bde3d5..4254982 100644 --- a/libs/oci/langchain_oci/embeddings/oci_generative_ai.py +++ b/libs/oci/langchain_oci/embeddings/oci_generative_ai.py @@ -125,7 +125,7 @@ def validate_environment(cls, values: Dict) -> Dict: # pylint: disable=no-self- client_kwargs.pop("signer", None) elif values["auth_type"] == OCIAuthType(2).name: - def make_security_token_signer(oci_config): # type: ignore[no-untyped-def] + def make_security_token_signer(oci_config): pk = oci.signer.load_private_key_from_file( oci_config.get("key_file"), None ) diff --git a/libs/oci/langchain_oci/llms/oci_generative_ai.py b/libs/oci/langchain_oci/llms/oci_generative_ai.py index 793a338..e5843c4 100644 --- a/libs/oci/langchain_oci/llms/oci_generative_ai.py +++ b/libs/oci/langchain_oci/llms/oci_generative_ai.py @@ -151,7 +151,7 @@ def validate_environment(cls, values: Dict) -> Dict: client_kwargs.pop("signer", None) elif values["auth_type"] == OCIAuthType(2).name: - def make_security_token_signer(oci_config): # type: ignore[no-untyped-def] + def make_security_token_signer(oci_config): pk = oci.signer.load_private_key_from_file( oci_config.get("key_file"), None ) diff --git a/libs/oci/pyproject.toml b/libs/oci/pyproject.toml index 2e22d52..b997b4f 100644 --- a/libs/oci/pyproject.toml +++ b/libs/oci/pyproject.toml @@ -77,20 +77,26 @@ ignore = [ ] [tool.mypy] -ignore_missing_imports = "True" - -# Disable specific error codes that are causing issues -disallow_untyped_defs = "False" -disable_error_code = ["attr-defined", "assignment", "var-annotated", "override", "union-attr", "arg-type"] - -# TODO: LangChain Google settings -# plugins = ["pydantic.mypy"] -# strict = true -# disallow_untyped_defs = true - -# # TODO: activate for 'strict' checking -# disallow_any_generics = false -# warn_return_any = false +plugins = ["pydantic.mypy"] +check_untyped_defs = true +error_summary = false +pretty = true +show_column_numbers = true +show_error_codes = true +show_error_context = true +warn_redundant_casts = true +warn_unreachable = true +warn_unused_configs = true +warn_unused_ignores = true + +# Ignore missing imports only for specific untyped packages +[[tool.mypy.overrides]] +module = [ + "oci.*", + "ads.*", + "langchain_openai.*", +] +ignore_missing_imports = true [tool.coverage.run] omit = ["tests/*"] diff --git a/libs/oci/tests/integration_tests/chat_models/test_tool_calling.py b/libs/oci/tests/integration_tests/chat_models/test_tool_calling.py index 0936afe..40cdfd4 100644 --- a/libs/oci/tests/integration_tests/chat_models/test_tool_calling.py +++ b/libs/oci/tests/integration_tests/chat_models/test_tool_calling.py @@ -428,7 +428,7 @@ def should_continue(state: MessagesState): # Invoke agent with a diagnostic scenario result = agent.invoke( - { + { # type: ignore[arg-type] "messages": [ SystemMessage(content=system_prompt), HumanMessage( diff --git a/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py b/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py index 0bb01c8..e6cb12c 100644 --- a/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py +++ b/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py @@ -80,7 +80,7 @@ def __init__(self, json_data: Dict, status_code: int = 200): def raise_for_status(self) -> None: """Mocked raise for status.""" if 400 <= self.status_code < 600: - raise HTTPError() # type: ignore[call-arg] + raise HTTPError() def json(self) -> Dict: """Returns mocked json data.""" diff --git a/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py b/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py index aa50198..3a6f7c7 100644 --- a/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py +++ b/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py @@ -13,12 +13,12 @@ class MockResponseDict(dict): - def __getattr__(self, val): # type: ignore[no-untyped-def] + def __getattr__(self, val): return self.get(val) class MockToolCall(dict): - def __getattr__(self, val): # type: ignore[no-untyped-def] + def __getattr__(self, val): return self[val] @@ -37,7 +37,7 @@ def test_llm_chat(monkeypatch: MonkeyPatch, test_model_id: str) -> None: provider = model_id.split(".")[0].lower() - def mocked_response(*args): # type: ignore[no-untyped-def] + def mocked_response(*args): response_text = "Assistant chat reply." response = None if provider == "cohere": @@ -166,7 +166,7 @@ def test_meta_tool_calling(monkeypatch: MonkeyPatch) -> None: oci_gen_ai_client = MagicMock() llm = ChatOCIGenAI(model_id="meta.llama-3-70b-instruct", client=oci_gen_ai_client) - def mocked_response(*args, **kwargs): # type: ignore[no-untyped-def] + def mocked_response(*args, **kwargs): # Mock response with tool calls return MockResponseDict( { @@ -231,7 +231,7 @@ def get_weather(location: str) -> str: messages = [HumanMessage(content="What's the weather like?")] # Test different tool choice options - tool_choices = [ + tool_choices: list[str | bool | dict[str, str | dict[str, str]]] = [ "get_weather", # Specific tool "auto", # Auto mode "none", # No tools @@ -254,7 +254,7 @@ def get_weather(location: str) -> str: assert tool_call["function"]["name"] == "get_weather" # Test escaped JSON arguments (issue #52) - def mocked_response_escaped(*args, **kwargs): # type: ignore[no-untyped-def] + def mocked_response_escaped(*args, **kwargs): """Mock response with escaped JSON arguments.""" return MockResponseDict( { @@ -310,6 +310,7 @@ def mocked_response_escaped(*args, **kwargs): # type: ignore[no-untyped-def] response_escaped = llm.bind_tools(tools=[get_weather]).invoke(messages) # Verify escaped JSON was correctly parsed to a dict + assert isinstance(response_escaped, AIMessage) assert len(response_escaped.tool_calls) == 1 assert response_escaped.tool_calls[0]["name"] == "get_weather" assert response_escaped.tool_calls[0]["args"] == {"location": "San Francisco"} @@ -337,7 +338,7 @@ def get_weather(location: str) -> str: ).invoke(messages) # Mock response for the case without tool choice - def mocked_response(*args, **kwargs): # type: ignore[no-untyped-def] + def mocked_response(*args, **kwargs): return MockResponseDict( { "status": 200, @@ -378,7 +379,7 @@ def test_meta_tool_conversion(monkeypatch: MonkeyPatch) -> None: oci_gen_ai_client = MagicMock() llm = ChatOCIGenAI(model_id="meta.llama-3.3-70b-instruct", client=oci_gen_ai_client) - def mocked_response(*args, **kwargs): # type: ignore[no-untyped-def] + def mocked_response(*args, **kwargs): request = args[0] # Check the conversion of tools to oci generic API spec # Function tool @@ -467,6 +468,7 @@ class PydanticTool(BaseModel): # For tool calls, the response content should be empty. assert response.content == "" + assert isinstance(response, AIMessage) assert len(response.tool_calls) == 1 assert response.tool_calls[0]["name"] == "function_tool" @@ -483,7 +485,7 @@ class WeatherResponse(BaseModel): oci_gen_ai_client = MagicMock() llm = ChatOCIGenAI(model_id="cohere.command-r-16k", client=oci_gen_ai_client) - def mocked_response(*args, **kwargs): # type: ignore[no-untyped-def] + def mocked_response(*args, **kwargs): return MockResponseDict( { "status": 200, @@ -533,7 +535,7 @@ class WeatherResponse(BaseModel): oci_gen_ai_client = MagicMock() llm = ChatOCIGenAI(model_id="cohere.command-latest", client=oci_gen_ai_client) - def mocked_response(*args, **kwargs): # type: ignore[no-untyped-def] + def mocked_response(*args, **kwargs): # Verify that response_format is a JsonSchemaResponseFormat object request = args[0] assert hasattr(request.chat_request, "response_format") @@ -610,7 +612,7 @@ class WeatherResponse(BaseModel): oci_gen_ai_client = MagicMock() llm = ChatOCIGenAI(model_id="cohere.command-r-16k", client=oci_gen_ai_client) - def mocked_response(*args, **kwargs): # type: ignore[no-untyped-def] + def mocked_response(*args, **kwargs): return MockResponseDict( { "status": 200, @@ -663,7 +665,7 @@ def test_ai_message_tool_calls_direct_field(monkeypatch: MonkeyPatch) -> None: # Track if the tool_calls processing branch is executed tool_calls_processed = False - def mocked_response(*args, **kwargs): # type: ignore[no-untyped-def] + def mocked_response(*args, **kwargs): nonlocal tool_calls_processed # Check if the request contains tool_calls in the message request = args[0] @@ -746,7 +748,7 @@ def test_ai_message_tool_calls_additional_kwargs(monkeypatch: MonkeyPatch) -> No oci_gen_ai_client = MagicMock() llm = ChatOCIGenAI(model_id="meta.llama-3.3-70b-instruct", client=oci_gen_ai_client) - def mocked_response(*args, **kwargs): # type: ignore[no-untyped-def] + def mocked_response(*args, **kwargs): return MockResponseDict( { "status": 200, @@ -884,8 +886,11 @@ def get_weather(city: str) -> str: ] # Prepare the request - need to pass tools from the bound model kwargs - request = llm_with_tools._prepare_request( - messages, stop=None, stream=False, **llm_with_tools.kwargs + request = llm._prepare_request( + messages, + stop=None, + stream=False, + **llm_with_tools.kwargs, # type: ignore[attr-defined] ) # Verify that tool_choice is set to 'none' because limit was reached From f94678bfb2dcec3342fa35b18c3557bf68b4ef73 Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Tue, 18 Nov 2025 14:10:31 -0800 Subject: [PATCH 2/9] Refactor libs/oracledb to meet mypy standard --- .../document_loaders/oracleadb_loader.py | 5 +++- .../document_loaders/oracleai.py | 6 ++--- .../langchain_oracledb/embeddings/oracleai.py | 3 --- .../langchain_oracledb/utilities/oracleai.py | 4 +-- .../vectorstores/oraclevs.py | 4 +-- libs/oracledb/pyproject.toml | 25 ++++++++----------- .../vectorstores/test_oraclevs.py | 12 ++++----- 7 files changed, 28 insertions(+), 31 deletions(-) diff --git a/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py b/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py index 7181387..8594bc0 100644 --- a/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py +++ b/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py @@ -95,7 +95,10 @@ def _run_query(self) -> List[Dict[str, Any]]: cursor.execute(self.query, self.parameter) else: cursor.execute(self.query) - columns = [col[0] for col in cursor.description] + + columns = ( + [col[0] for col in cursor.description] if cursor.description else [] + ) data = cursor.fetchall() data = [ { diff --git a/libs/oracledb/langchain_oracledb/document_loaders/oracleai.py b/libs/oracledb/langchain_oracledb/document_loaders/oracleai.py index 637792f..20646ae 100644 --- a/libs/oracledb/langchain_oracledb/document_loaders/oracleai.py +++ b/libs/oracledb/langchain_oracledb/document_loaders/oracleai.py @@ -130,7 +130,7 @@ def read_file( with open(file_path, "rb") as f: data = f.read() - if data is None: + if not data: return Document(page_content="", metadata=metadata) mdata = cursor.var(oracledb.DB_TYPE_CLOB) @@ -151,7 +151,7 @@ def read_file( ) cursor.close() - if mdata is None: + if mdata.getvalue() is None: metadata = {} else: doc_data = str(mdata.getvalue()) @@ -166,7 +166,7 @@ def read_file( metadata["_oid"] = doc_id metadata["_file"] = file_path - if text is None: + if text.getvalue() is None: return Document(page_content="", metadata=metadata) else: return Document(page_content=str(text.getvalue()), metadata=metadata) diff --git a/libs/oracledb/langchain_oracledb/embeddings/oracleai.py b/libs/oracledb/langchain_oracledb/embeddings/oracleai.py index b0fbedf..cbca892 100644 --- a/libs/oracledb/langchain_oracledb/embeddings/oracleai.py +++ b/libs/oracledb/langchain_oracledb/embeddings/oracleai.py @@ -102,9 +102,6 @@ def embed_documents(self, texts: List[str]) -> List[List[float]]: List of embeddings, one for each input text. """ - if texts is None: - return None - embeddings: List[List[float]] = [] try: # returns strings or bytes instead of a locator diff --git a/libs/oracledb/langchain_oracledb/utilities/oracleai.py b/libs/oracledb/langchain_oracledb/utilities/oracleai.py index 860fd1b..f0e637c 100644 --- a/libs/oracledb/langchain_oracledb/utilities/oracleai.py +++ b/libs/oracledb/langchain_oracledb/utilities/oracleai.py @@ -92,7 +92,7 @@ def get_summary(self, docs: Any) -> List[str]: summ=summary, ) - if summary is None: + if summary.getvalue() is None: results.append("") else: results.append(str(summary.getvalue())) @@ -114,7 +114,7 @@ def get_summary(self, docs: Any) -> List[str]: summ=summary, ) - if summary is None: + if summary.getvalue() is None: results.append("") else: results.append(str(summary.getvalue())) diff --git a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py index 7ca2f4c..e43ddf5 100644 --- a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py +++ b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py @@ -1330,7 +1330,7 @@ async def _aembed_documents(self, texts: List[str]) -> List[List[float]]: if isinstance(self.embedding_function, Embeddings): return await self.embedding_function.aembed_documents(texts) elif inspect.isawaitable(self.embedding_function): - return [await self.embedding_function(text) for text in texts] + return [await self.embedding_function(text) for text in texts] # type: ignore[unreachable] elif callable(self.embedding_function): return [self.embedding_function(text) for text in texts] else: @@ -1348,7 +1348,7 @@ async def _aembed_query(self, text: str) -> List[float]: if isinstance(self.embedding_function, Embeddings): return await self.embedding_function.aembed_query(text) elif inspect.isawaitable(self.embedding_function): - return await self.embedding_function(text) + return await self.embedding_function(text) # type: ignore[unreachable] else: return self.embedding_function(text) diff --git a/libs/oracledb/pyproject.toml b/libs/oracledb/pyproject.toml index 0fbd2e2..3da9eff 100644 --- a/libs/oracledb/pyproject.toml +++ b/libs/oracledb/pyproject.toml @@ -72,20 +72,17 @@ ignore = [ docstring-code-format = true [tool.mypy] -ignore_missing_imports = "True" - -# Disable specific error codes that are causing issues -disallow_untyped_defs = "False" -disable_error_code = ["attr-defined", "assignment", "var-annotated", "override", "union-attr", "arg-type"] - -# TODO: LangChain Google settings -# plugins = ["pydantic.mypy"] -# strict = true -# disallow_untyped_defs = true - -# # TODO: activate for 'strict' checking -# disallow_any_generics = false -# warn_return_any = false +plugins = ["pydantic.mypy"] +check_untyped_defs = true +error_summary = false +pretty = true +show_column_numbers = true +show_error_codes = true +show_error_context = true +warn_redundant_casts = true +warn_unreachable = true +warn_unused_configs = true +warn_unused_ignores = true [tool.coverage.run] omit = ["tests/*"] diff --git a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py index 708e08b..2e0eb69 100644 --- a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py +++ b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py @@ -1918,7 +1918,7 @@ def test_db_filter_test() -> None: exception_occurred = False try: - db_filter_exc: dict = { # type: ignore[typeddict-unknown-key] + db_filter_exc: dict = { "_xor": [ # Incorrect operation _xor {"order": {"$lte": 4}}, {"id": "st"}, @@ -2055,7 +2055,7 @@ async def test_db_filter_test_async() -> None: exception_occurred = False try: - db_filter_exc: dict = { # type: ignore[typeddict-unknown-key] + db_filter_exc: dict = { "_xor": [ # Incorrect operation _xor {"order": {"$lte": 4}}, {"id": "st"}, @@ -2213,11 +2213,11 @@ def test_from_texts_lobs() -> None: metadatas = [ { "id": "cncpt_15.5.3.2.2_P4", - "link": "https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/logical-storage-structures.html#GUID-5387D7B2-C0CA-4C1E-811B-C7EB9B636442", # type: ignore[E501] + "link": "https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/logical-storage-structures.html#GUID-5387D7B2-C0CA-4C1E-811B-C7EB9B636442", }, { "id": "cncpt_15.5.5_P1", - "link": "https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/logical-storage-structures.html#GUID-D02B2220-E6F5-40D9-AFB5-BC69BCEF6CD4", # type: ignore[E501] + "link": "https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/logical-storage-structures.html#GUID-D02B2220-E6F5-40D9-AFB5-BC69BCEF6CD4", }, ] @@ -2613,7 +2613,7 @@ def test_filters() -> None: except Exception: sys.exit(1) - def model1(_) -> list[float]: # type: ignore[no-untyped-def] + def model1(_) -> list[float]: return [0.1, 0.2, 0.3] # model1 = lambda x: [0.1, 0.2, 0.3] @@ -2804,7 +2804,7 @@ async def test_filters_async() -> None: except Exception: sys.exit(1) - def model1(_) -> list[float]: # type: ignore[no-untyped-def] + def model1(_) -> list[float]: return [0.1, 0.2, 0.3] # model1 = lambda x: [0.1, 0.2, 0.3] From 966a0f13aa86933d09b4667d4b37e54787025ec5 Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Wed, 19 Nov 2025 09:25:14 -0800 Subject: [PATCH 3/9] Fix more lint in libs/oci --- libs/oci/langchain_oci/chat_models/oci_data_science.py | 9 ++------- libs/oci/langchain_oci/chat_models/oci_generative_ai.py | 6 +++--- .../unit_tests/chat_models/test_oci_data_science.py | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/libs/oci/langchain_oci/chat_models/oci_data_science.py b/libs/oci/langchain_oci/chat_models/oci_data_science.py index 7d6f579..364d8b4 100644 --- a/libs/oci/langchain_oci/chat_models/oci_data_science.py +++ b/libs/oci/langchain_oci/chat_models/oci_data_science.py @@ -31,12 +31,7 @@ agenerate_from_stream, generate_from_stream, ) -from langchain_core.messages import ( - AIMessage, - AIMessageChunk, - BaseMessage, - BaseMessageChunk, -) +from langchain_core.messages import AIMessageChunk, BaseMessage, BaseMessageChunk from langchain_core.output_parsers import ( JsonOutputParser, PydanticOutputParser, @@ -772,7 +767,7 @@ def bind_tools( self, tools: Sequence[Union[Dict[str, Any], Type[BaseModel], Callable, BaseTool]], **kwargs: Any, - ) -> Runnable[LanguageModelInput, AIMessage]: + ) -> Runnable[LanguageModelInput, BaseMessage]: formatted_tools = [convert_to_openai_tool(tool) for tool in tools] return super().bind(tools=formatted_tools, **kwargs) diff --git a/libs/oci/langchain_oci/chat_models/oci_generative_ai.py b/libs/oci/langchain_oci/chat_models/oci_generative_ai.py index c21da5f..46945bc 100644 --- a/libs/oci/langchain_oci/chat_models/oci_generative_ai.py +++ b/libs/oci/langchain_oci/chat_models/oci_generative_ai.py @@ -924,8 +924,8 @@ def convert_to_oci_tool( "required": parameters.get("required", []), }, ) - elif isinstance(tool, BaseTool): - return self.oci_function_definition( + elif isinstance(tool, BaseTool): # type: ignore[unreachable] + return self.oci_function_definition( # type: ignore[unreachable] name=tool.name, description=OCIUtils.remove_signature_from_tool_description( tool.name, tool.description @@ -1205,7 +1205,7 @@ def bind_tools( Union[dict, str, Literal["auto", "none", "required", "any"], bool] ] = None, **kwargs: Any, - ) -> Runnable[LanguageModelInput, AIMessage]: + ) -> Runnable[LanguageModelInput, BaseMessage]: """Bind tool-like objects to this chat model. Assumes model is compatible with Meta's tool-calling API. diff --git a/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py b/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py index e6cb12c..9b4892a 100644 --- a/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py +++ b/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py @@ -155,7 +155,7 @@ def test_stream_vllm(*args: Any) -> None: if output is None: output = chunk else: - output += chunk + output += chunk # type: ignore[assignment] count += 1 assert count == 5 assert output is not None From 3ba2a8442c89f61a3a7f219adc6a87a44fe055cf Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Wed, 19 Nov 2025 09:30:57 -0800 Subject: [PATCH 4/9] Remove unnecessary type ignore --- .../tests/integration_tests/chat_models/test_tool_calling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/oci/tests/integration_tests/chat_models/test_tool_calling.py b/libs/oci/tests/integration_tests/chat_models/test_tool_calling.py index 40cdfd4..0936afe 100644 --- a/libs/oci/tests/integration_tests/chat_models/test_tool_calling.py +++ b/libs/oci/tests/integration_tests/chat_models/test_tool_calling.py @@ -428,7 +428,7 @@ def should_continue(state: MessagesState): # Invoke agent with a diagnostic scenario result = agent.invoke( - { # type: ignore[arg-type] + { "messages": [ SystemMessage(content=system_prompt), HumanMessage( From 54008bdd02c1a0cd046a0412b9f0ffded5d4c58d Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Wed, 19 Nov 2025 09:49:17 -0800 Subject: [PATCH 5/9] Add type: ignore[call-arg] in /Users/panxia/Documents/GitHub/langchain-oracle/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py --- libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py | 2 +- .../oci/tests/unit_tests/chat_models/test_oci_generative_ai.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py b/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py index 9b4892a..ca0a9b5 100644 --- a/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py +++ b/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py @@ -80,7 +80,7 @@ def __init__(self, json_data: Dict, status_code: int = 200): def raise_for_status(self) -> None: """Mocked raise for status.""" if 400 <= self.status_code < 600: - raise HTTPError() + raise HTTPError() # type: ignore[call-arg] def json(self) -> Dict: """Returns mocked json data.""" diff --git a/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py b/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py index 3a6f7c7..0954c61 100644 --- a/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py +++ b/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py @@ -3,6 +3,7 @@ """Test OCI Generative AI LLM service""" +from typing import Union from unittest.mock import MagicMock import pytest @@ -231,7 +232,7 @@ def get_weather(location: str) -> str: messages = [HumanMessage(content="What's the weather like?")] # Test different tool choice options - tool_choices: list[str | bool | dict[str, str | dict[str, str]]] = [ + tool_choices: list[Union[str, bool, dict[str, Union[str, dict[str, str]]]]] = [ "get_weather", # Specific tool "auto", # Auto mode "none", # No tools From 4a4ed71c33e7d62b5e2e45190525641ce7ede454 Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Wed, 19 Nov 2025 10:32:23 -0800 Subject: [PATCH 6/9] Pass response to HTTPError in mock raise_for_status --- libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py b/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py index ca0a9b5..f3bf9d1 100644 --- a/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py +++ b/libs/oci/tests/unit_tests/chat_models/test_oci_data_science.py @@ -80,7 +80,7 @@ def __init__(self, json_data: Dict, status_code: int = 200): def raise_for_status(self) -> None: """Mocked raise for status.""" if 400 <= self.status_code < 600: - raise HTTPError() # type: ignore[call-arg] + raise HTTPError(response=self) # type: ignore[arg-type] def json(self) -> Dict: """Returns mocked json data.""" From 3170d1d3c87f920b3cfffeb2d86fab89e01e81eb Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Wed, 19 Nov 2025 13:40:13 -0800 Subject: [PATCH 7/9] Remove test dependencies not used in _lint.yml Removes 'test' and 'test_integration' groups from the Poetry install step in the lint workflow, ensuring only linting and typing dependencies are installed during lint jobs. --- .github/workflows/_lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_lint.yml b/.github/workflows/_lint.yml index b2ba920..0c2e5a4 100644 --- a/.github/workflows/_lint.yml +++ b/.github/workflows/_lint.yml @@ -58,7 +58,7 @@ jobs: # It doesn't matter how you change it, any change will cause a cache-bust. working-directory: ${{ inputs.working-directory }} run: | - poetry install --with lint,typing,test,test_integration + poetry install --with lint,typing - name: Run linting working-directory: ${{ inputs.working-directory }} From d48ea78f56e5f6c09726b3e216be213bd1115d53 Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Wed, 19 Nov 2025 13:50:37 -0800 Subject: [PATCH 8/9] Revert "Remove test dependencies not used in _lint.yml" This reverts commit 3170d1d3c87f920b3cfffeb2d86fab89e01e81eb. --- .github/workflows/_lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_lint.yml b/.github/workflows/_lint.yml index 0c2e5a4..b2ba920 100644 --- a/.github/workflows/_lint.yml +++ b/.github/workflows/_lint.yml @@ -58,7 +58,7 @@ jobs: # It doesn't matter how you change it, any change will cause a cache-bust. working-directory: ${{ inputs.working-directory }} run: | - poetry install --with lint,typing + poetry install --with lint,typing,test,test_integration - name: Run linting working-directory: ${{ inputs.working-directory }} From 61f355389854695e6b99799fadf1065c1d69fe48 Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Wed, 19 Nov 2025 13:55:51 -0800 Subject: [PATCH 9/9] Add disk space cleanup to CI workflows Introduced steps to free disk space in both lint and test GitHub Actions workflows by removing unused Android and CodeQL directories and pruning Docker images. This helps prevent CI failures due to insufficient disk space. --- .github/workflows/_lint.yml | 6 ++++++ .github/workflows/_test.yml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/_lint.yml b/.github/workflows/_lint.yml index b2ba920..142cdf2 100644 --- a/.github/workflows/_lint.yml +++ b/.github/workflows/_lint.yml @@ -34,6 +34,12 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Free Disk Space + run: | + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo docker image prune --all --force + - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }} uses: "./.github/actions/poetry_setup" with: diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml index 337fefa..e29aad3 100644 --- a/.github/workflows/_test.yml +++ b/.github/workflows/_test.yml @@ -26,6 +26,12 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Free Disk Space + run: | + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo docker image prune --all --force + - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }} uses: "./.github/actions/poetry_setup" with: