HowdenLLM 3.1.2__tar.gz → 4.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {howdenllm-3.1.2 → howdenllm-4.0.0}/HowdenLLM/manager.py +14 -35
- howdenllm-4.0.0/HowdenLLM/providers/base_provider.py +8 -0
- {howdenllm-3.1.2 → howdenllm-4.0.0}/HowdenLLM/providers/provider_anthropic.py +14 -7
- {howdenllm-3.1.2 → howdenllm-4.0.0}/HowdenLLM/providers/provider_openai.py +15 -4
- {howdenllm-3.1.2 → howdenllm-4.0.0}/PKG-INFO +2 -1
- {howdenllm-3.1.2 → howdenllm-4.0.0}/pyproject.toml +2 -2
- howdenllm-3.1.2/HowdenLLM/providers/base_provider.py +0 -7
- {howdenllm-3.1.2 → howdenllm-4.0.0}/HowdenLLM/__init__.py +0 -0
- {howdenllm-3.1.2 → howdenllm-4.0.0}/HowdenLLM/providers/__init__.py +0 -0
- {howdenllm-3.1.2 → howdenllm-4.0.0}/HowdenLLM/providers/known_providers.py +0 -0
- {howdenllm-3.1.2 → howdenllm-4.0.0}/HowdenLLM/providers/provider_factory.py +0 -0
- {howdenllm-3.1.2 → howdenllm-4.0.0}/HowdenLLM/providers/provider_huggingface.py +0 -0
- {howdenllm-3.1.2 → howdenllm-4.0.0}/HowdenLLM/providers/provider_meta.py +0 -0
- {howdenllm-3.1.2 → howdenllm-4.0.0}/README.md +0 -0
|
@@ -3,6 +3,7 @@ from typing import Callable
|
|
|
3
3
|
from dotenv import load_dotenv
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from HowdenLLM.providers.provider_factory import ProviderFactory
|
|
6
|
+
from HowdenCommonObjects.howden_result import HowdenResult
|
|
6
7
|
from typing import Any
|
|
7
8
|
import hashlib
|
|
8
9
|
import os
|
|
@@ -61,16 +62,7 @@ class LLM:
|
|
|
61
62
|
self.total_output_tokens = 0
|
|
62
63
|
self.total_runs = 0
|
|
63
64
|
|
|
64
|
-
def
|
|
65
|
-
"""Try to count tokens with tiktoken; fallback to rough word count."""
|
|
66
|
-
try:
|
|
67
|
-
import tiktoken
|
|
68
|
-
enc = tiktoken.encoding_for_model(self.model)
|
|
69
|
-
return len(enc.encode(text))
|
|
70
|
-
except (KeyError, LookupError, AttributeError, ImportError):
|
|
71
|
-
return len(text.split())
|
|
72
|
-
|
|
73
|
-
async def __call__(self, path_or_content: Path | str) -> str:
|
|
65
|
+
async def __call__(self, path_or_content: Path | str) -> HowdenResult:
|
|
74
66
|
"""
|
|
75
67
|
Execute one completion round and return:
|
|
76
68
|
(output_text, input_token_count, output_token_count)
|
|
@@ -84,29 +76,10 @@ class LLM:
|
|
|
84
76
|
|
|
85
77
|
prompt = self.template.substitute(content=content)
|
|
86
78
|
|
|
87
|
-
# --- count input tokens ---
|
|
88
|
-
input_text = f"{self.system or ''}\n{prompt}"
|
|
89
|
-
input_tokens = self._count_tokens(input_text)
|
|
90
|
-
|
|
91
79
|
# --- run model ---
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# --- count output tokens ---
|
|
95
|
-
output_tokens = self._count_tokens(output)
|
|
96
|
-
|
|
97
|
-
# --- update totals ---
|
|
98
|
-
self.total_input_tokens += input_tokens
|
|
99
|
-
self.total_output_tokens += output_tokens
|
|
100
|
-
self.total_runs += 1
|
|
101
|
-
print(f"[{self.name or 'LLM'}] "
|
|
102
|
-
f"Input tokens: {input_tokens}, "
|
|
103
|
-
f"Output tokens: {output_tokens}, "
|
|
104
|
-
f"Total_input: {self.total_input_tokens}, "
|
|
105
|
-
f"Total_output: {self.total_output_tokens}, "
|
|
106
|
-
f"Total_input_average: {round(self.total_input_tokens / self.total_runs, 2)}, "
|
|
107
|
-
f"Total_output_average: {round(self.total_output_tokens / self.total_runs, 2)}")
|
|
80
|
+
result = await self.provider.complete(self.name, self.system, prompt, self.model, self.use_web_search_tool,self.upload_attachment)
|
|
108
81
|
|
|
109
|
-
return
|
|
82
|
+
return result
|
|
110
83
|
|
|
111
84
|
def make_serializable(self, obj: Any) -> Any:
|
|
112
85
|
if obj is None or isinstance(obj, (str, int, float, bool)):
|
|
@@ -158,7 +131,7 @@ class LLMSwitch:
|
|
|
158
131
|
A class that inspects a file to determine which LLM instance to invoke based on file content.
|
|
159
132
|
Changes made to this class by Casper 19/11-25
|
|
160
133
|
"""
|
|
161
|
-
def __init__(self, LLMs: list[LLM], name: str, json_key: str = None) -> None:
|
|
134
|
+
def __init__(self, LLMs: list[LLM], name: str = None, json_key: str = None) -> None:
|
|
162
135
|
"""
|
|
163
136
|
Parameters
|
|
164
137
|
----------
|
|
@@ -172,6 +145,9 @@ class LLMSwitch:
|
|
|
172
145
|
self.input_parameter = {llm.name: llm.input_params for llm in LLMs}
|
|
173
146
|
self.json_key = json_key
|
|
174
147
|
self.name = name
|
|
148
|
+
self.system: str # Set during __call__ to the params of the called LLM
|
|
149
|
+
self.template: str # Set during __call__ to the params of the called LLM
|
|
150
|
+
self.model: str # Set during __call__ to the params of the called LLM
|
|
175
151
|
|
|
176
152
|
def _get_json_content(self, filepath: Path) -> str:
|
|
177
153
|
"""
|
|
@@ -203,7 +179,7 @@ class LLMSwitch:
|
|
|
203
179
|
else:
|
|
204
180
|
raise NotJsonException(filepath)
|
|
205
181
|
|
|
206
|
-
def __call__(self, content_path: Path, filepath: Path) ->
|
|
182
|
+
async def __call__(self, content_path: Path, filepath: Path) -> HowdenResult:
|
|
207
183
|
"""
|
|
208
184
|
Reads a file to determine which of the provided LLM objects to call. The method checks each model in
|
|
209
185
|
` self.LLMS` and calls the first model whose `name` appears in the content.
|
|
@@ -234,11 +210,14 @@ class LLMSwitch:
|
|
|
234
210
|
content = filepath.read_text(encoding="utf-8")
|
|
235
211
|
else:
|
|
236
212
|
raise UnsupportedFiletypeException(filepath)
|
|
237
|
-
result = ""
|
|
213
|
+
result = HowdenResult("")
|
|
238
214
|
|
|
239
215
|
for model in self.LLMS:
|
|
240
216
|
if model.name in content:
|
|
241
|
-
|
|
217
|
+
self.system = model.system
|
|
218
|
+
self.template = model.template
|
|
219
|
+
self.model = model.model
|
|
220
|
+
result = await model(content_path)
|
|
242
221
|
return result
|
|
243
222
|
|
|
244
223
|
def make_serializable(self, obj: Any) -> Any:
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from .provider_meta import ProviderMeta
|
|
3
|
+
from HowdenCommonObjects.howden_result import HowdenResult
|
|
4
|
+
|
|
5
|
+
class BaseProvider(ABC, metaclass=ProviderMeta):
|
|
6
|
+
@abstractmethod
|
|
7
|
+
async def complete(self, name:str, system: str, prompt: str, model: str, use_web_search_tool: bool, upload_attachment: str|None) -> HowdenResult:
|
|
8
|
+
pass
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
2
|
from HowdenLLM.providers.base_provider import BaseProvider
|
|
3
3
|
from anthropic import AsyncAnthropic
|
|
4
|
+
from HowdenCommonObjects.howden_result import HowdenResult
|
|
5
|
+
from HowdenCommonObjects.usage import Usage
|
|
4
6
|
from .known_providers import KnownProviders
|
|
5
7
|
|
|
6
8
|
class AnthropicProvider(BaseProvider, ABC):
|
|
@@ -9,7 +11,7 @@ class AnthropicProvider(BaseProvider, ABC):
|
|
|
9
11
|
def __init__(self,client: AsyncAnthropic):
|
|
10
12
|
self.client = client
|
|
11
13
|
|
|
12
|
-
async def complete(self, system: str, prompt: str, model: str, use_web_search_tool: bool, upload_attachment: str) ->
|
|
14
|
+
async def complete(self, name:str, system: str, prompt: str, model: str, use_web_search_tool: bool, upload_attachment: str|None) -> HowdenResult:
|
|
13
15
|
|
|
14
16
|
if use_web_search_tool:
|
|
15
17
|
tools=[{"type": "web_search_20260209","name": "web_search","max_uses": 5}]
|
|
@@ -17,7 +19,7 @@ class AnthropicProvider(BaseProvider, ABC):
|
|
|
17
19
|
tools = []
|
|
18
20
|
|
|
19
21
|
if model in ["claude-opus-4-7", "claude-opus-4-8","claude-opus-4-6","claude-opus-4-5-20251101","claude-sonnet-4-6","claude-sonnet-4-5-20250929"]:
|
|
20
|
-
|
|
22
|
+
response = await self.client.messages.create(
|
|
21
23
|
model=model,
|
|
22
24
|
system=system,
|
|
23
25
|
messages=[
|
|
@@ -33,10 +35,15 @@ class AnthropicProvider(BaseProvider, ABC):
|
|
|
33
35
|
else:
|
|
34
36
|
raise Exception(f"Unsupported model: {model}")
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
clean_response = "\n".join(block.text for block in response.content if block.type == "text")
|
|
39
|
+
usage = Usage(
|
|
40
|
+
type="llm",
|
|
41
|
+
provider=self.provider,
|
|
42
|
+
model=model,
|
|
43
|
+
operation=name,
|
|
44
|
+
input_tokens=response.usage.input_tokens,
|
|
45
|
+
cached_tokens=response.usage.cache_read_input_tokens,
|
|
46
|
+
output_tokens=response.usage.output_tokens,
|
|
39
47
|
)
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
return clean_response
|
|
49
|
+
return HowdenResult(clean_response, usage)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
2
|
|
|
3
3
|
from sympy.codegen.ast import continue_
|
|
4
|
-
|
|
4
|
+
from HowdenCommonObjects.howden_result import HowdenResult
|
|
5
|
+
from HowdenCommonObjects.usage import Usage
|
|
5
6
|
from HowdenLLM.providers.base_provider import BaseProvider
|
|
6
7
|
from openai import AsyncOpenAI
|
|
7
8
|
from .known_providers import KnownProviders
|
|
@@ -12,7 +13,7 @@ class OpenAIProvider(BaseProvider, ABC):
|
|
|
12
13
|
def __init__(self,client: AsyncOpenAI):
|
|
13
14
|
self.client = client
|
|
14
15
|
|
|
15
|
-
async def complete(self, system: str, prompt: str, model: str, use_web_search_tool: bool, upload_attachment: str|None) ->
|
|
16
|
+
async def complete(self, name:str, system: str, prompt: str, model: str, use_web_search_tool: bool, upload_attachment: str|None) -> HowdenResult:
|
|
16
17
|
|
|
17
18
|
if use_web_search_tool:
|
|
18
19
|
tools = [
|
|
@@ -23,7 +24,7 @@ class OpenAIProvider(BaseProvider, ABC):
|
|
|
23
24
|
|
|
24
25
|
if upload_attachment:
|
|
25
26
|
with open(upload_attachment, "rb") as f:
|
|
26
|
-
uploaded = self.client.files.create(
|
|
27
|
+
uploaded = await self.client.files.create(
|
|
27
28
|
file=f,
|
|
28
29
|
purpose="user_data",
|
|
29
30
|
)
|
|
@@ -50,4 +51,14 @@ class OpenAIProvider(BaseProvider, ABC):
|
|
|
50
51
|
else:
|
|
51
52
|
raise Exception(f"Unsupported model: {model}")
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
usage = Usage(
|
|
55
|
+
type="llm",
|
|
56
|
+
provider=self.provider,
|
|
57
|
+
model=model,
|
|
58
|
+
operation=name,
|
|
59
|
+
input_tokens=response.usage.input_tokens,
|
|
60
|
+
cached_tokens=response.usage.input_tokens_details.cached_tokens,
|
|
61
|
+
output_tokens=response.usage.output_tokens,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return HowdenResult(response.output_text, usage)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: HowdenLLM
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: A simple configuration manager with Pydantic and JSON export.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: config,configuration,pydantic,json
|
|
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Requires-Dist: accelerate (>=1.10.0,<2.0.0)
|
|
15
15
|
Requires-Dist: anthropic (>=0.85.0,<0.86.0)
|
|
16
|
+
Requires-Dist: howdencommonobjects (>=1.1.0)
|
|
16
17
|
Requires-Dist: langchain (>=1.1.3,<2.0.0)
|
|
17
18
|
Requires-Dist: langchain-community (>=0.3.27,<0.4.0)
|
|
18
19
|
Requires-Dist: openai (>=1.99.9,<2.0.0)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "HowdenLLM"
|
|
3
|
-
version = "
|
|
3
|
+
version = "4.0.0"
|
|
4
4
|
description = ""
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12,<3.14"
|
|
7
|
-
dependencies = [ "transformers (>=4.55.0,<5.0.0)", "pydantic (>=2.11.7,<3.0.0)", "python-dotenv (>=1.1.1,<2.0.0)", "langchain-community (>=0.3.27,<0.4.0)", "openai (>=1.99.9,<2.0.0)", "accelerate (>=1.10.0,<2.0.0)", "tiktoken (>=0.12.0,<0.13.0)", "langchain (>=1.1.3,<2.0.0)", "pytest (>=9.0.2,<10.0.0)", "anthropic (>=0.85.0,<0.86.0)",]
|
|
7
|
+
dependencies = [ "transformers (>=4.55.0,<5.0.0)", "pydantic (>=2.11.7,<3.0.0)", "python-dotenv (>=1.1.1,<2.0.0)", "langchain-community (>=0.3.27,<0.4.0)", "openai (>=1.99.9,<2.0.0)", "accelerate (>=1.10.0,<2.0.0)", "tiktoken (>=0.12.0,<0.13.0)", "langchain (>=1.1.3,<2.0.0)", "pytest (>=9.0.2,<10.0.0)", "anthropic (>=0.85.0,<0.86.0)","howdencommonobjects>=1.1.0"]
|
|
8
8
|
[[project.authors]]
|
|
9
9
|
name = "JesperThoftIllemannJ"
|
|
10
10
|
email = "jesper.jaeger@howdendanmark.dk"
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from .provider_meta import ProviderMeta
|
|
3
|
-
|
|
4
|
-
class BaseProvider(ABC, metaclass=ProviderMeta):
|
|
5
|
-
@abstractmethod
|
|
6
|
-
async def complete(self, system: str, prompt: str, model: str, use_web_search_tool: bool, upload_attachment: str|None) -> str:
|
|
7
|
-
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|