From bc160597385778020ea44081779e85cf1d5aba4b Mon Sep 17 00:00:00 2001 From: Juan Date: Sun, 10 May 2026 10:28:40 +0200 Subject: [PATCH] =?UTF-8?q?feat(llm):=20a=C3=B1ade=20Protocol=20LLMProvide?= =?UTF-8?q?r=20con=20Message=20y=20CompletionResult?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- core/src/agentforge_core/llm/base.py | 38 ++++++++++++++++++++++++++++ tests/unit/test_llm_base.py | 19 ++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 core/src/agentforge_core/llm/base.py create mode 100644 tests/unit/test_llm_base.py diff --git a/core/src/agentforge_core/llm/base.py b/core/src/agentforge_core/llm/base.py new file mode 100644 index 0000000..effed53 --- /dev/null +++ b/core/src/agentforge_core/llm/base.py @@ -0,0 +1,38 @@ +"""Tipos y Protocol del proveedor LLM (Strategy pattern).""" + +from __future__ import annotations + +from typing import Any, Literal, Protocol + +from pydantic import BaseModel + + +class Message(BaseModel): + """Mensaje individual de la conversación enviada al LLM.""" + + role: Literal["system", "user", "assistant"] + content: str + + +class CompletionResult(BaseModel): + """Resultado normalizado de una llamada a un proveedor LLM.""" + + content: str + model: str + tokens_in: int + tokens_out: int + latency_ms: int + + +class LLMProvider(Protocol): + """Interfaz mínima común a todos los proveedores LLM.""" + + name: str + + async def complete( + self, + messages: list[Message], + schema: dict[str, Any] | None = None, + temperature: float = 0.2, + max_tokens: int = 2000, + ) -> CompletionResult: ... diff --git a/tests/unit/test_llm_base.py b/tests/unit/test_llm_base.py new file mode 100644 index 0000000..deefc98 --- /dev/null +++ b/tests/unit/test_llm_base.py @@ -0,0 +1,19 @@ +"""Test de los modelos base del proveedor LLM.""" + +from agentforge_core.llm.base import CompletionResult, Message + + +def test_message_serializable() -> None: + m = Message(role="user", content="hola") + assert m.model_dump()["role"] == "user" + + +def test_completion_result_campos_basicos() -> None: + r = CompletionResult( + content='{"a": 1}', + model="gpt-4o-mock", + tokens_in=10, + tokens_out=20, + latency_ms=42, + ) + assert r.tokens_in == 10