fix: resolve git conflicts in LLM abstraction layer

- Fix gui() function import in __init__.py (use cli.selector)
- Fix prompt builder system message merging logic
- Add default max_tokens for Anthropic API in claude.py
- Fix openai tool_call arguments parsing with json.loads
- Fix test_builder.py PromptConfig import and assertions
This commit is contained in:
Anish
2026-04-12 07:10:54 +00:00
6 changed files with 501 additions and 495 deletions

View File

@@ -1,84 +1,78 @@
[project] [project]
name = "llm-abstraction" name = "llm-abstraction"
version = "0.1.0" version = "0.1.0"
description = "Provider-agnostic LLM abstraction layer" description = "Provider-agnostic LLM abstraction layer"
readme = "README.md" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"
license = {text = "MIT"} license = {text = "MIT"}
authors = [ authors = [
{name = "Affaan Mustafa", email = "affaan@example.com"} {name = "Affaan Mustafa", email = "affaan@example.com"}
] ]
keywords = ["llm", "openai", "anthropic", "ollama", "ai"] keywords = ["llm", "openai", "anthropic", "ollama", "ai"]
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
] ]
dependencies = [ dependencies = [
"anthropic>=0.25.0", "anthropic>=0.25.0",
"openai>=1.30.0", "openai>=1.30.0",
] ]
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"pytest>=8.0", "pytest>=8.0",
"pytest-asyncio>=0.23", "pytest-asyncio>=0.23",
"pytest-cov>=4.1", "pytest-cov>=4.1",
"ruff>=0.4", "pytest-mock>=3.12",
"mypy>=1.10", "ruff>=0.4",
"ruff>=0.4", "mypy>=1.10",
] ]
test = [
"pytest>=8.0", [project.urls]
"pytest-asyncio>=0.23", Homepage = "https://github.com/affaan-m/everything-claude-code"
"pytest-cov>=4.1", Repository = "https://github.com/affaan-m/everything-claude-code"
"pytest-mock>=3.12",
] [project.scripts]
llm-select = "llm.cli.selector:main"
[project.urls]
Homepage = "https://github.com/affaan-m/everything-claude-code" [build-system]
Repository = "https://github.com/affaan-m/everything-claude-code" requires = ["hatchling"]
build-backend = "hatchling.build"
[project.scripts]
llm-select = "llm.cli.selector:main" [tool.hatch.build.targets.wheel]
packages = ["src/llm"]
[build-system]
requires = ["hatchling"] [tool.pytest.ini_options]
build-backend = "hatchling.build" testpaths = ["tests"]
asyncio_mode = "auto"
[tool.hatch.build.targets.wheel] filterwarnings = ["ignore::DeprecationWarning"]
packages = ["src/llm"]
[tool.coverage.run]
[tool.pytest.ini_options] source = ["src/llm"]
testpaths = ["tests"] branch = true
asyncio_mode = "auto"
filterwarnings = ["ignore::DeprecationWarning"] [tool.coverage.report]
exclude_lines = [
[tool.coverage.run] "pragma: no cover",
source = ["src/llm"] "if TYPE_CHECKING:",
branch = true "raise NotImplementedError",
]
[tool.coverage.report]
exclude_lines = [ [tool.ruff]
"pragma: no cover", src-path = ["src"]
"if TYPE_CHECKING:", target-version = "py311"
"raise NotImplementedError",
] [tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
[tool.ruff] ignore = ["E501"]
src-path = ["src"]
target-version = "py311" [tool.mypy]
python_version = "3.11"
[tool.ruff.lint] src_paths = ["src"]
select = ["E", "F", "I", "N", "W", "UP"] warn_return_any = true
ignore = ["E501"] warn_unused_ignores = true
[tool.mypy]
python_version = "3.11"
src_paths = ["src"]
warn_return_any = true
warn_unused_ignores = true

View File

@@ -1,33 +1,33 @@
""" """
LLM Abstraction Layer LLM Abstraction Layer
Provider-agnostic interface for multiple LLM backends. Provider-agnostic interface for multiple LLM backends.
""" """
from llm.core.interface import LLMProvider from llm.core.interface import LLMProvider
from llm.core.types import LLMInput, LLMOutput, Message, ToolCall, ToolDefinition, ToolResult from llm.core.types import LLMInput, LLMOutput, Message, ToolCall, ToolDefinition, ToolResult
from llm.providers import get_provider from llm.providers import get_provider
from llm.tools import ToolExecutor, ToolRegistry from llm.tools import ToolExecutor, ToolRegistry
from llm.cli.selector import interactive_select from llm.cli.selector import interactive_select
__version__ = "0.1.0" __version__ = "0.1.0"
__all__ = [ __all__ = [
"LLMProvider", "LLMProvider",
"LLMInput", "LLMInput",
"LLMOutput", "LLMOutput",
"Message", "Message",
"get_provider", "get_provider",
"ToolCall", "ToolCall",
"ToolDefinition", "ToolDefinition",
"ToolResult", "ToolResult",
"ToolExecutor", "ToolExecutor",
"ToolRegistry", "ToolRegistry",
"interactive_select", "interactive_select",
] ]
def gui() -> None: def gui() -> None:
from llm.gui.selector import main from llm.cli.selector import main
main() main()

View File

@@ -1,101 +1,102 @@
"""Prompt builder for normalizing prompts across providers.""" """Prompt builder for normalizing prompts across providers."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
from llm.core.types import LLMInput, Message, Role, ToolDefinition from llm.core.types import LLMInput, Message, Role, ToolDefinition
from llm.providers.claude import ClaudeProvider from llm.providers.claude import ClaudeProvider
from llm.providers.openai import OpenAIProvider from llm.providers.openai import OpenAIProvider
from llm.providers.ollama import OllamaProvider from llm.providers.ollama import OllamaProvider
@dataclass @dataclass
class PromptConfig: class PromptConfig:
system_template: str | None = None system_template: str | None = None
user_template: str | None = None user_template: str | None = None
include_tools_in_system: bool = True include_tools_in_system: bool = True
tool_format: str = "native" tool_format: str = "native"
class PromptBuilder: class PromptBuilder:
def __init__(self, config: PromptConfig | None = None) -> None: def __init__(self, config: PromptConfig | None = None) -> None:
self.config = config or PromptConfig() self.config = config or PromptConfig()
def build(self, messages: list[Message], tools: list[ToolDefinition] | None = None) -> list[Message]: def build(self, messages: list[Message], tools: list[ToolDefinition] | None = None) -> list[Message]:
if not messages: if not messages:
return [] return []
result: list[Message] = [] result: list[Message] = []
system_parts: list[str] = [] system_parts: list[str] = []
if self.config.system_template: if self.config.system_template:
system_parts.append(self.config.system_template) system_parts.append(self.config.system_template)
if tools and self.config.include_tools_in_system: if tools and self.config.include_tools_in_system:
tools_desc = self._format_tools(tools) tools_desc = self._format_tools(tools)
system_parts.append(f"\n\n## Available Tools\n{tools_desc}") system_parts.append(f"\n\n## Available Tools\n{tools_desc}")
if messages[0].role == Role.SYSTEM: if messages[0].role == Role.SYSTEM:
system_parts.insert(0, messages[0].content) system_parts.insert(0, messages[0].content)
result.extend(messages[1:]) result.insert(0, Message(role=Role.SYSTEM, content="\n\n".join(system_parts)))
else: result.extend(messages[1:])
if system_parts: else:
result.insert(0, Message(role=Role.SYSTEM, content="\n\n".join(system_parts))) if system_parts:
result.extend(messages) result.insert(0, Message(role=Role.SYSTEM, content="\n\n".join(system_parts)))
result.extend(messages)
return result
return result
def _format_tools(self, tools: list[ToolDefinition]) -> str:
lines = [] def _format_tools(self, tools: list[ToolDefinition]) -> str:
for tool in tools: lines = []
lines.append(f"### {tool.name}") for tool in tools:
lines.append(tool.description) lines.append(f"### {tool.name}")
if tool.parameters: lines.append(tool.description)
lines.append("Parameters:") if tool.parameters:
lines.append(self._format_parameters(tool.parameters)) lines.append("Parameters:")
return "\n".join(lines) lines.append(self._format_parameters(tool.parameters))
return "\n".join(lines)
def _format_parameters(self, params: dict[str, Any]) -> str:
if "properties" not in params: def _format_parameters(self, params: dict[str, Any]) -> str:
return str(params) if "properties" not in params:
lines = [] return str(params)
required = params.get("required", []) lines = []
for name, spec in params["properties"].items(): required = params.get("required", [])
prop_type = spec.get("type", "any") for name, spec in params["properties"].items():
desc = spec.get("description", "") prop_type = spec.get("type", "any")
required_mark = "(required)" if name in required else "(optional)" desc = spec.get("description", "")
lines.append(f" - {name}: {prop_type} {required_mark} - {desc}") required_mark = "(required)" if name in required else "(optional)"
return "\n".join(lines) if lines else str(params) lines.append(f" - {name}: {prop_type} {required_mark} - {desc}")
return "\n".join(lines) if lines else str(params)
_PROVIDER_TEMPLATE_MAP: dict[str, dict[str, Any]] = {
"claude": { _PROVIDER_TEMPLATE_MAP: dict[str, dict[str, Any]] = {
"include_tools_in_system": False, "claude": {
"tool_format": "anthropic", "include_tools_in_system": False,
}, "tool_format": "anthropic",
"openai": { },
"include_tools_in_system": False, "openai": {
"tool_format": "openai", "include_tools_in_system": False,
}, "tool_format": "openai",
"ollama": { },
"include_tools_in_system": True, "ollama": {
"tool_format": "text", "include_tools_in_system": True,
}, "tool_format": "text",
} },
}
def get_provider_builder(provider_name: str) -> PromptBuilder:
config_dict = _PROVIDER_TEMPLATE_MAP.get(provider_name.lower(), {}) def get_provider_builder(provider_name: str) -> PromptBuilder:
config = PromptConfig(**config_dict) config_dict = _PROVIDER_TEMPLATE_MAP.get(provider_name.lower(), {})
return PromptBuilder(config) config = PromptConfig(**config_dict)
return PromptBuilder(config)
def adapt_messages_for_provider(
messages: list[Message], def adapt_messages_for_provider(
provider: str, messages: list[Message],
tools: list[ToolDefinition] | None = None, provider: str,
) -> list[Message]: tools: list[ToolDefinition] | None = None,
builder = get_provider_builder(provider) ) -> list[Message]:
return builder.build(messages, tools) builder = get_provider_builder(provider)
return builder.build(messages, tools)

View File

@@ -1,103 +1,105 @@
"""Claude provider adapter.""" """Claude provider adapter."""
from __future__ import annotations from __future__ import annotations
import os import os
from typing import Any from typing import Any
from anthropic import Anthropic from anthropic import Anthropic
from llm.core.interface import ( from llm.core.interface import (
AuthenticationError, AuthenticationError,
ContextLengthError, ContextLengthError,
LLMProvider, LLMProvider,
RateLimitError, RateLimitError,
) )
from llm.core.types import LLMInput, LLMOutput, Message, ModelInfo, ProviderType, ToolCall from llm.core.types import LLMInput, LLMOutput, Message, ModelInfo, ProviderType, ToolCall
class ClaudeProvider(LLMProvider): class ClaudeProvider(LLMProvider):
provider_type = ProviderType.CLAUDE provider_type = ProviderType.CLAUDE
def __init__(self, api_key: str | None = None, base_url: str | None = None) -> None: def __init__(self, api_key: str | None = None, base_url: str | None = None) -> None:
self.client = Anthropic(api_key=api_key or os.environ.get("ANTHROPIC_API_KEY"), base_url=base_url) self.client = Anthropic(api_key=api_key or os.environ.get("ANTHROPIC_API_KEY"), base_url=base_url)
self._models = [ self._models = [
ModelInfo( ModelInfo(
name="claude-opus-4-5", name="claude-opus-4-5",
provider=ProviderType.CLAUDE, provider=ProviderType.CLAUDE,
supports_tools=True, supports_tools=True,
supports_vision=True, supports_vision=True,
max_tokens=8192, max_tokens=8192,
context_window=200000, context_window=200000,
), ),
ModelInfo( ModelInfo(
name="claude-sonnet-4-7", name="claude-sonnet-4-7",
provider=ProviderType.CLAUDE, provider=ProviderType.CLAUDE,
supports_tools=True, supports_tools=True,
supports_vision=True, supports_vision=True,
max_tokens=8192, max_tokens=8192,
context_window=200000, context_window=200000,
), ),
ModelInfo( ModelInfo(
name="claude-haiku-4-7", name="claude-haiku-4-7",
provider=ProviderType.CLAUDE, provider=ProviderType.CLAUDE,
supports_tools=True, supports_tools=True,
supports_vision=False, supports_vision=False,
max_tokens=4096, max_tokens=4096,
context_window=200000, context_window=200000,
), ),
] ]
def generate(self, input: LLMInput) -> LLMOutput: def generate(self, input: LLMInput) -> LLMOutput:
try: try:
params: dict[str, Any] = { params: dict[str, Any] = {
"model": input.model or "claude-sonnet-4-7", "model": input.model or "claude-sonnet-4-7",
"messages": [msg.to_dict() for msg in input.messages], "messages": [msg.to_dict() for msg in input.messages],
"temperature": input.temperature, "temperature": input.temperature,
} }
if input.max_tokens: if input.max_tokens:
params["max_tokens"] = input.max_tokens params["max_tokens"] = input.max_tokens
if input.tools: else:
params["tools"] = [tool.to_dict() for tool in input.tools] params["max_tokens"] = 8192 # required by Anthropic API
if input.tools:
response = self.client.messages.create(**params) params["tools"] = [tool.to_dict() for tool in input.tools]
tool_calls = None response = self.client.messages.create(**params)
if response.content and hasattr(response.content[0], "type"):
if response.content[0].type == "tool_use": tool_calls = None
tool_calls = [ if response.content and hasattr(response.content[0], "type"):
ToolCall( if response.content[0].type == "tool_use":
id=getattr(response.content[0], "id", ""), tool_calls = [
name=getattr(response.content[0], "name", ""), ToolCall(
arguments=getattr(response.content[0].input, "__dict__", {}), id=getattr(response.content[0], "id", ""),
) name=getattr(response.content[0], "name", ""),
] arguments=getattr(response.content[0].input, "__dict__", {}),
)
return LLMOutput( ]
content=response.content[0].text if response.content else "",
tool_calls=tool_calls, return LLMOutput(
model=response.model, content=response.content[0].text if response.content else "",
usage={ tool_calls=tool_calls,
"input_tokens": response.usage.input_tokens, model=response.model,
"output_tokens": response.usage.output_tokens, usage={
}, "input_tokens": response.usage.input_tokens,
stop_reason=response.stop_reason, "output_tokens": response.usage.output_tokens,
) },
except Exception as e: stop_reason=response.stop_reason,
msg = str(e) )
if "401" in msg or "authentication" in msg.lower(): except Exception as e:
raise AuthenticationError(msg, provider=ProviderType.CLAUDE) from e msg = str(e)
if "429" in msg or "rate_limit" in msg.lower(): if "401" in msg or "authentication" in msg.lower():
raise RateLimitError(msg, provider=ProviderType.CLAUDE) from e raise AuthenticationError(msg, provider=ProviderType.CLAUDE) from e
if "context" in msg.lower() and "length" in msg.lower(): if "429" in msg or "rate_limit" in msg.lower():
raise ContextLengthError(msg, provider=ProviderType.CLAUDE) from e raise RateLimitError(msg, provider=ProviderType.CLAUDE) from e
raise if "context" in msg.lower() and "length" in msg.lower():
raise ContextLengthError(msg, provider=ProviderType.CLAUDE) from e
def list_models(self) -> list[ModelInfo]: raise
return self._models.copy()
def list_models(self) -> list[ModelInfo]:
def validate_config(self) -> bool: return self._models.copy()
return bool(self.client.api_key)
def validate_config(self) -> bool:
def get_default_model(self) -> str: return bool(self.client.api_key)
return "claude-sonnet-4-7"
def get_default_model(self) -> str:
return "claude-sonnet-4-7"

View File

@@ -1,113 +1,114 @@
"""OpenAI provider adapter.""" """OpenAI provider adapter."""
from __future__ import annotations from __future__ import annotations
import os import json
from typing import Any import os
from typing import Any
from openai import OpenAI
from openai import OpenAI
from llm.core.interface import (
AuthenticationError, from llm.core.interface import (
ContextLengthError, AuthenticationError,
LLMProvider, ContextLengthError,
RateLimitError, LLMProvider,
) RateLimitError,
from llm.core.types import LLMInput, LLMOutput, Message, ModelInfo, ProviderType, ToolCall )
from llm.core.types import LLMInput, LLMOutput, Message, ModelInfo, ProviderType, ToolCall
class OpenAIProvider(LLMProvider):
provider_type = ProviderType.OPENAI class OpenAIProvider(LLMProvider):
provider_type = ProviderType.OPENAI
def __init__(self, api_key: str | None = None, base_url: str | None = None) -> None:
self.client = OpenAI(api_key=api_key or os.environ.get("OPENAI_API_KEY"), base_url=base_url) def __init__(self, api_key: str | None = None, base_url: str | None = None) -> None:
self._models = [ self.client = OpenAI(api_key=api_key or os.environ.get("OPENAI_API_KEY"), base_url=base_url)
ModelInfo( self._models = [
name="gpt-4o", ModelInfo(
provider=ProviderType.OPENAI, name="gpt-4o",
supports_tools=True, provider=ProviderType.OPENAI,
supports_vision=True, supports_tools=True,
max_tokens=4096, supports_vision=True,
context_window=128000, max_tokens=4096,
), context_window=128000,
ModelInfo( ),
name="gpt-4o-mini", ModelInfo(
provider=ProviderType.OPENAI, name="gpt-4o-mini",
supports_tools=True, provider=ProviderType.OPENAI,
supports_vision=True, supports_tools=True,
max_tokens=4096, supports_vision=True,
context_window=128000, max_tokens=4096,
), context_window=128000,
ModelInfo( ),
name="gpt-4-turbo", ModelInfo(
provider=ProviderType.OPENAI, name="gpt-4-turbo",
supports_tools=True, provider=ProviderType.OPENAI,
supports_vision=True, supports_tools=True,
max_tokens=4096, supports_vision=True,
context_window=128000, max_tokens=4096,
), context_window=128000,
ModelInfo( ),
name="gpt-3.5-turbo", ModelInfo(
provider=ProviderType.OPENAI, name="gpt-3.5-turbo",
supports_tools=True, provider=ProviderType.OPENAI,
supports_vision=False, supports_tools=True,
max_tokens=4096, supports_vision=False,
context_window=16385, max_tokens=4096,
), context_window=16385,
] ),
]
def generate(self, input: LLMInput) -> LLMOutput:
try: def generate(self, input: LLMInput) -> LLMOutput:
params: dict[str, Any] = { try:
"model": input.model or "gpt-4o-mini", params: dict[str, Any] = {
"messages": [msg.to_dict() for msg in input.messages], "model": input.model or "gpt-4o-mini",
"temperature": input.temperature, "messages": [msg.to_dict() for msg in input.messages],
} "temperature": input.temperature,
if input.max_tokens: }
params["max_tokens"] = input.max_tokens if input.max_tokens:
if input.tools: params["max_tokens"] = input.max_tokens
params["tools"] = [tool.to_dict() for tool in input.tools] if input.tools:
params["tools"] = [tool.to_dict() for tool in input.tools]
response = self.client.chat.completions.create(**params)
choice = response.choices[0] response = self.client.chat.completions.create(**params)
choice = response.choices[0]
tool_calls = None
if choice.message.tool_calls: tool_calls = None
tool_calls = [ if choice.message.tool_calls:
ToolCall( tool_calls = [
id=tc.id or "", ToolCall(
name=tc.function.name, id=tc.id or "",
arguments={} if tc.function.arguments == "" else tc.function.arguments, name=tc.function.name,
) arguments={} if not tc.function.arguments else json.loads(tc.function.arguments),
for tc in choice.message.tool_calls )
] for tc in choice.message.tool_calls
]
return LLMOutput(
content=choice.message.content or "", return LLMOutput(
tool_calls=tool_calls, content=choice.message.content or "",
model=response.model, tool_calls=tool_calls,
usage={ model=response.model,
"prompt_tokens": response.usage.prompt_tokens, usage={
"completion_tokens": response.usage.completion_tokens, "prompt_tokens": response.usage.prompt_tokens,
"total_tokens": response.usage.total_tokens, "completion_tokens": response.usage.completion_tokens,
}, "total_tokens": response.usage.total_tokens,
stop_reason=choice.finish_reason, },
) stop_reason=choice.finish_reason,
except Exception as e: )
msg = str(e) except Exception as e:
if "401" in msg or "authentication" in msg.lower(): msg = str(e)
raise AuthenticationError(msg, provider=ProviderType.OPENAI) from e if "401" in msg or "authentication" in msg.lower():
if "429" in msg or "rate_limit" in msg.lower(): raise AuthenticationError(msg, provider=ProviderType.OPENAI) from e
raise RateLimitError(msg, provider=ProviderType.OPENAI) from e if "429" in msg or "rate_limit" in msg.lower():
if "context" in msg.lower() and "length" in msg.lower(): raise RateLimitError(msg, provider=ProviderType.OPENAI) from e
raise ContextLengthError(msg, provider=ProviderType.OPENAI) from e if "context" in msg.lower() and "length" in msg.lower():
raise raise ContextLengthError(msg, provider=ProviderType.OPENAI) from e
raise
def list_models(self) -> list[ModelInfo]:
return self._models.copy() def list_models(self) -> list[ModelInfo]:
return self._models.copy()
def validate_config(self) -> bool:
return bool(self.client.api_key) def validate_config(self) -> bool:
return bool(self.client.api_key)
def get_default_model(self) -> str:
return "gpt-4o-mini" def get_default_model(self) -> str:
return "gpt-4o-mini"

View File

@@ -1,61 +1,69 @@
import pytest import pytest
from llm.core.types import LLMInput, Message, Role, ToolDefinition from llm.core.types import LLMInput, Message, Role, ToolDefinition
from llm.prompt import PromptBuilder, adapt_messages_for_provider from llm.prompt import PromptBuilder, adapt_messages_for_provider
from llm.prompt.builder import PromptConfig
class TestPromptBuilder:
def test_build_without_system(self): class TestPromptBuilder:
messages = [Message(role=Role.USER, content="Hello")] def test_build_without_system(self):
builder = PromptBuilder() messages = [Message(role=Role.USER, content="Hello")]
result = builder.build(messages) builder = PromptBuilder()
result = builder.build(messages)
assert len(result) == 1
assert result[0].role == Role.USER assert len(result) == 1
assert result[0].role == Role.USER
def test_build_with_system(self):
messages = [ def test_build_with_system(self):
Message(role=Role.SYSTEM, content="You are helpful."), messages = [
Message(role=Role.USER, content="Hello"), Message(role=Role.SYSTEM, content="You are helpful."),
] Message(role=Role.USER, content="Hello"),
builder = PromptBuilder() ]
result = builder.build(messages) builder = PromptBuilder()
result = builder.build(messages)
assert len(result) == 2
assert result[0].role == Role.SYSTEM assert len(result) == 2
assert result[0].role == Role.SYSTEM
def test_build_adds_system_from_config(self):
messages = [Message(role=Role.USER, content="Hello")] def test_build_adds_system_from_config(self):
builder = PromptBuilder(system_template="You are a pirate.") messages = [Message(role=Role.USER, content="Hello")]
result = builder.build(messages) builder = PromptBuilder(system_template="You are a pirate.")
result = builder.build(messages)
assert len(result) == 2
assert "pirate" in result[0].content assert len(result) == 2
assert "pirate" in result[0].content
def test_build_with_tools(self):
messages = [Message(role=Role.USER, content="Search for something")] def test_build_adds_system_from_config(self):
tools = [ messages = [Message(role=Role.USER, content="Hello")]
ToolDefinition(name="search", description="Search the web", parameters={}), builder = PromptBuilder(config=PromptConfig(system_template="You are a pirate."))
] result = builder.build(messages)
builder = PromptBuilder(include_tools_in_system=True)
result = builder.build(messages, tools) assert len(result) == 2
assert "pirate" in result[0].content
assert len(result) == 2 def test_build_with_tools(self):
assert "search" in result[0].content messages = [Message(role=Role.USER, content="Search for something")]
assert "Available Tools" in result[0].content tools = [
ToolDefinition(name="search", description="Search the web", parameters={}),
]
class TestAdaptMessagesForProvider: builder = PromptBuilder(include_tools_in_system=True)
def test_adapt_for_claude(self): result = builder.build(messages, tools)
messages = [Message(role=Role.USER, content="Hello")]
result = adapt_messages_for_provider(messages, "claude") assert len(result) == 2
assert len(result) == 1 assert "search" in result[0].content
assert "Available Tools" in result[0].content
def test_adapt_for_openai(self):
messages = [Message(role=Role.USER, content="Hello")]
result = adapt_messages_for_provider(messages, "openai") class TestAdaptMessagesForProvider:
assert len(result) == 1 def test_adapt_for_claude(self):
messages = [Message(role=Role.USER, content="Hello")]
def test_adapt_for_ollama(self): result = adapt_messages_for_provider(messages, "claude")
messages = [Message(role=Role.USER, content="Hello")] assert len(result) == 1
result = adapt_messages_for_provider(messages, "ollama")
assert len(result) == 1 def test_adapt_for_openai(self):
messages = [Message(role=Role.USER, content="Hello")]
result = adapt_messages_for_provider(messages, "openai")
assert len(result) == 1
def test_adapt_for_ollama(self):
messages = [Message(role=Role.USER, content="Hello")]
result = adapt_messages_for_provider(messages, "ollama")
assert len(result) == 1