From e849a8b2bd39778c9adafa10c29556047030c0be Mon Sep 17 00:00:00 2001 From: nerdai Date: Tue, 24 Jun 2025 01:17:34 -0400 Subject: [PATCH 1/4] ollama dep and tools param to chat method --- pyproject.toml | 1 + src/llm_agents_from_scratch/base/llm.py | 7 ++++++- tests/conftest.py | 5 ++++- tests/llm/test_base.py | 1 + uv.lock | 15 +++++++++++++++ 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ab13378..8e6f2c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,5 +31,6 @@ requires-python = ">=3.10" dependencies = [ "asyncio>=3.4.3", "mcp[cli]>=1.9.4", + "ollama>=0.5.1", "pydantic>=2.11.7" ] diff --git a/src/llm_agents_from_scratch/base/llm.py b/src/llm_agents_from_scratch/base/llm.py index 0a972b6..8ab4eb3 100644 --- a/src/llm_agents_from_scratch/base/llm.py +++ b/src/llm_agents_from_scratch/base/llm.py @@ -4,6 +4,8 @@ from llm_agents_from_scratch.data_structures import ChatMessage, CompleteResult +from .tool import BaseTool + class BaseLLM(ABC): """Base LLM Class.""" @@ -20,11 +22,14 @@ async def complete(self, prompt: str) -> CompleteResult: """ @abstractmethod - async def chat(self, chat_messages: list[ChatMessage]) -> ChatMessage: + async def chat( + self, chat_messages: list[ChatMessage], tools: list[BaseTool] = [] + ) -> ChatMessage: """Chat interface. Args: chat_messages (list[ChatMessage]): chat history. + tools (list[BaseTool]): tools that the LLM can call. Returns: ChatMessage: The response of the LLM structured as a `ChatMessage`. diff --git a/tests/conftest.py b/tests/conftest.py index b9855a2..eec3698 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ import pytest from llm_agents_from_scratch.base.llm import BaseLLM +from llm_agents_from_scratch.base.tool import BaseTool from llm_agents_from_scratch.data_structures import ChatMessage, CompleteResult @@ -11,7 +12,9 @@ async def complete(self, prompt: str) -> CompleteResult: response=result, full_response=f"{prompt} {result}" ) - async def chat(self, chat_messages: list[ChatMessage]) -> ChatMessage: + async def chat( + self, chat_messages: list[ChatMessage], tools: list[BaseTool] = [] + ) -> ChatMessage: return ChatMessage(role="assistant", content="mock chat response") diff --git a/tests/llm/test_base.py b/tests/llm/test_base.py index fc359d3..5ef6272 100644 --- a/tests/llm/test_base.py +++ b/tests/llm/test_base.py @@ -2,6 +2,7 @@ def test_base_abstract_attr() -> None: + """Tests abstract methods in base class.""" abstract_methods = BaseLLM.__abstractmethods__ assert "complete" in abstract_methods diff --git a/uv.lock b/uv.lock index 7b8eac8..219bd84 100644 --- a/uv.lock +++ b/uv.lock @@ -600,6 +600,7 @@ source = { editable = "." } dependencies = [ { name = "asyncio" }, { name = "mcp", extra = ["cli"] }, + { name = "ollama" }, { name = "pydantic" }, ] @@ -624,6 +625,7 @@ dev = [ requires-dist = [ { name = "asyncio", specifier = ">=3.4.3" }, { name = "mcp", extras = ["cli"], specifier = ">=1.9.4" }, + { name = "ollama", specifier = ">=0.5.1" }, { name = "pydantic", specifier = ">=2.11.7" }, ] @@ -778,6 +780,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] +[[package]] +name = "ollama" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/96/c7fe0d2d1b3053be614822a7b722c7465161b3672ce90df71515137580a0/ollama-0.5.1.tar.gz", hash = "sha256:5a799e4dc4e7af638b11e3ae588ab17623ee019e496caaf4323efbaa8feeff93", size = 41112, upload-time = "2025-05-30T21:32:48.679Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/76/3f96c8cdbf3955d7a73ee94ce3e0db0755d6de1e0098a70275940d1aff2f/ollama-0.5.1-py3-none-any.whl", hash = "sha256:4c8839f35bc173c7057b1eb2cbe7f498c1a7e134eafc9192824c8aecb3617506", size = 13369, upload-time = "2025-05-30T21:32:47.429Z" }, +] + [[package]] name = "packaging" version = "25.0" From d9b5c3f131c60c983d683d62f3b58248a533406f Mon Sep 17 00:00:00 2001 From: nerdai Date: Tue, 24 Jun 2025 01:21:28 -0400 Subject: [PATCH 2/4] add tool calls to ChatMessage data structure --- src/llm_agents_from_scratch/data_structures/llm.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/llm_agents_from_scratch/data_structures/llm.py b/src/llm_agents_from_scratch/data_structures/llm.py index 2955c9e..d4bf309 100644 --- a/src/llm_agents_from_scratch/data_structures/llm.py +++ b/src/llm_agents_from_scratch/data_structures/llm.py @@ -1,6 +1,7 @@ """Data Structures for LLMs""" from enum import Enum +from typing import Any from pydantic import BaseModel, ConfigDict @@ -9,13 +10,14 @@ class ChatRole(str, Enum): USER = "user" ASSISTANT = "assistant" SYSTEM = "system" - TOOL_CALL = "tool_call" + TOOL = "tool" class ChatMessage(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) role: ChatRole content: str + tool_calls: list[dict[str, Any]] | None = None class CompleteResult(BaseModel): From ee4932bbd881be42ac48e2590b9229dd8de624bc Mon Sep 17 00:00:00 2001 From: nerdai Date: Tue, 24 Jun 2025 01:25:55 -0400 Subject: [PATCH 3/4] docstrings --- .../data_structures/llm.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/llm_agents_from_scratch/data_structures/llm.py b/src/llm_agents_from_scratch/data_structures/llm.py index d4bf309..5457444 100644 --- a/src/llm_agents_from_scratch/data_structures/llm.py +++ b/src/llm_agents_from_scratch/data_structures/llm.py @@ -7,6 +7,8 @@ class ChatRole(str, Enum): + """Roles for chat messages.""" + USER = "user" ASSISTANT = "assistant" SYSTEM = "system" @@ -14,6 +16,14 @@ class ChatRole(str, Enum): class ChatMessage(BaseModel): + """The chat message data model. + + Attributes: + role: The role of the message. + content: The content of the message. + tool_calls: Tool calls associated with the message. + """ + model_config = ConfigDict(arbitrary_types_allowed=True) role: ChatRole content: str @@ -21,5 +31,12 @@ class ChatMessage(BaseModel): class CompleteResult(BaseModel): + """The llm completion result data model. + + Attributes: + response: The completion response provided by the LLM. + full_response: Input prompt and completion text. + """ + response: str full_response: str From 148595547b24666cdef55f7547d53766acf917cf Mon Sep 17 00:00:00 2001 From: nerdai Date: Tue, 24 Jun 2025 01:31:46 -0400 Subject: [PATCH 4/4] flat import and api tests --- src/llm_agents_from_scratch/__init__.py | 8 ++++++-- tests/api/__init__.py | 0 tests/api/test_data_structure_imports.py | 17 +++++++++++++++++ tests/api/test_root_imports.py | 15 +++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 tests/api/__init__.py create mode 100644 tests/api/test_data_structure_imports.py create mode 100644 tests/api/test_root_imports.py diff --git a/src/llm_agents_from_scratch/__init__.py b/src/llm_agents_from_scratch/__init__.py index ed44406..935136a 100644 --- a/src/llm_agents_from_scratch/__init__.py +++ b/src/llm_agents_from_scratch/__init__.py @@ -1,2 +1,6 @@ -def hello() -> str: - return "Hello from llm-agents-from-scratch!" +# Disable the F403 warning for wildcard imports +# ruff: noqa: F403, F401 +from .core import * +from .core import __all__ as _core_all + +__all__ = sorted(_core_all) diff --git a/tests/api/__init__.py b/tests/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/api/test_data_structure_imports.py b/tests/api/test_data_structure_imports.py new file mode 100644 index 0000000..c18529c --- /dev/null +++ b/tests/api/test_data_structure_imports.py @@ -0,0 +1,17 @@ +import importlib + +import pytest + +from llm_agents_from_scratch.data_structures import ( + __all__ as _data_structures_all, +) + + +@pytest.mark.parametrize("name", _data_structures_all) +def test_data_structures_all_importable(name: str) -> None: + """Tests that all names listed in generators __all__ are importable.""" + mod = importlib.import_module("llm_agents_from_scratch.data_structures") + attr = getattr(mod, name) + + assert hasattr(mod, name) + assert attr is not None diff --git a/tests/api/test_root_imports.py b/tests/api/test_root_imports.py new file mode 100644 index 0000000..06f2d6f --- /dev/null +++ b/tests/api/test_root_imports.py @@ -0,0 +1,15 @@ +import importlib + +import pytest + +from llm_agents_from_scratch import __all__ as _root_all + + +@pytest.mark.parametrize("name", _root_all) +def test_root_names_all_importable(name: str) -> None: + """Tests that all names listed in root __all__ are importable.""" + mod = importlib.import_module("llm_agents_from_scratch") + attr = getattr(mod, name) + + assert hasattr(mod, name) + assert attr is not None