firts agent concept

This commit is contained in:
2026-02-03 21:20:00 -05:00
parent daa9893d2e
commit aff0a588c5
12 changed files with 2030 additions and 1 deletions

1
.env.example Normal file
View File

@@ -0,0 +1 @@
DEEPSEEK_API_KEY="sk-..."

1828
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,14 @@ authors = [
{name = "aserrador",email = "alejandro.ayala@onecluster.org"} {name = "aserrador",email = "alejandro.ayala@onecluster.org"}
] ]
readme = "README.md" readme = "README.md"
requires-python = ">=3.13" requires-python = ">=3.13,<4.0"
dependencies = [ dependencies = [
"pytest (>=9.0.2,<10.0.0)",
"langchain[standard] (>=1.2.6,<2.0.0)",
"langgraph[standard] (>=1.0.6,<2.0.0)",
"langchain-anthropic (>=1.3.1,<2.0.0)",
"langchain-deepseek (>=1.0.1,<2.0.0)",
"dotenv (>=0.9.9,<0.10.0)"
] ]
[tool.poetry] [tool.poetry]

View File

@@ -0,0 +1,5 @@
class Agent:
pass
agent = Agent()

View File

@@ -0,0 +1,81 @@
from langgraph.graph import StateGraph, START, END
from langchain.messages import SystemMessage, ToolMessage
from typing import Literal
from .schemas import MessagesState
class Agent:
def __init__(self, model):
self._model = model
self._tools = []
self._tools_by_name = []
self.model_with_tools = self._model.bind_tools(
self._tools)
self.agent = self._compiler_agent()
def invoke(self, messages):
self.agent.invoke(
messages
)
def _compiler_agent(self):
agent = self._build_agent()
return agent.compile()
def _build_agent(self):
agent_builder = StateGraph(MessagesState)
agent_builder.add_node("llm_call", self.llm_call)
agent_builder.add_node("tool_node", self.tool_node)
agent_builder.add_edge(START, "llm_call")
return agent_builder
def llm_call(self, state: dict):
"""LLM decides whether to call a tool or not"""
return {
"messages": [
self.model_with_tools.invoke(
[
SystemMessage(
content=(
"You are a helpful assistant tasked with "
"performing arithmetic on a set of inputs."
)
)
]
+ state["messages"]
)
],
"llm_calls": state.get('llm_calls', 0) + 1
}
def tool_node(self, state: dict):
"""Performs the tool call"""
result = []
for tool_call in state["messages"][-1].tool_calls:
tool = self._tools_by_name[tool_call["name"]]
observation = tool.invoke(tool_call["args"])
result.append(
ToolMessage(
content=observation,
tool_call_id=tool_call["id"]
))
return {"messages": result}
def should_continue(state: MessagesState) -> Literal["tool_node", END]:
"""
Decide if we should continue the loop or stop base
d upon whether the LLM made a tool call
"""
messages = state["messages"]
last_message = messages[-1]
if last_message.tool_calls:
return "tool_node"
return END

View File

@@ -0,0 +1,8 @@
from langchain.messages import AnyMessage
from typing_extensions import TypedDict, Annotated
import operator
class MessagesState(TypedDict):
messages: Annotated[list[AnyMessage], operator.add]
llm_calls: int

View File

@@ -0,0 +1,4 @@
class NotFoundProviderError(Exception):
"""LLM Provider is not supported."""
pass

View File

View File

@@ -0,0 +1,48 @@
from enum import Enum
from langchain_anthropic import ChatAnthropic
from langchain_deepseek import ChatDeepSeek
from src.naliiabot.bot.exceptions import NotFoundProviderError
from dotenv import load_dotenv
load_dotenv()
class LLMProvider(str, Enum):
"""Supported Providers."""
ANTHROPIC = "anthropic"
DEEPSEEK = "deepseek"
class LLMFactory:
def __init__(self, provider):
self.provider = provider
def get_model(self):
model = None
if self.provider == LLMProvider.ANTHROPIC:
model = self._create_anthropic()
elif self.provider == LLMProvider.DEEPSEEK:
model = self._create_deepseek()
else:
raise NotFoundProviderError(
f"{self.provider.upper()} Not Provider Found"
)
return model
def _create_anthropic(self):
model_name = "claude-sonnet-4-5-20250929"
model = ChatAnthropic(model=model_name)
return model
def _create_deepseek(self):
model_name = "deepseek-chat"
model = ChatDeepSeek(
model=model_name
)
return model

16
tests/conftest.py Normal file
View File

@@ -0,0 +1,16 @@
import pytest
from src.naliiabot.bot.agent.agent import Agent
from src.naliiabot.bot.factories.llm_factory import LLMFactory
@pytest.fixture
def anthropic_model():
llmFactory = LLMFactory("anthropic")
anthropic_model = llmFactory.get_model()
return anthropic_model
@pytest.fixture
def agent(anthropic_model):
return Agent(model=anthropic_model)

9
tests/test_agent.py Normal file
View File

@@ -0,0 +1,9 @@
# import pytest
from langchain.messages import HumanMessage
def test_invoke_agent(agent):
messages = [HumanMessage(content="divide 3 and 4.")]
messages = agent.invoke({"messages": messages})
assert messages is not None

23
tests/test_factories.py Normal file
View File

@@ -0,0 +1,23 @@
import pytest
from src.naliiabot.bot.factories.llm_factory import LLMFactory
from src.naliiabot.bot.exceptions import NotFoundProviderError
from langchain_anthropic import ChatAnthropic
from langchain_deepseek import ChatDeepSeek
def test_llm_factory():
antrophicModel = LLMFactory("anthropic").get_model()
assert isinstance(antrophicModel, ChatAnthropic) is True
deepseekModel = LLMFactory("deepseek").get_model()
assert isinstance(deepseekModel, ChatDeepSeek)
def test_get_model_raises_exception_when_provider_not_found():
factory = LLMFactory(provider="openai")
with pytest.raises(NotFoundProviderError) as exc_info:
factory.get_model()
assert str(exc_info.value) == "OPENAI Not Provider Found"