HowdenLLM 1.6.2__tar.gz → 3.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-1.6.2 → howdenllm-3.0.0}/HowdenLLM/manager.py +4 -2
- howdenllm-3.0.0/HowdenLLM/providers/base_provider.py +7 -0
- howdenllm-3.0.0/HowdenLLM/providers/provider_anthropic.py +42 -0
- {howdenllm-1.6.2 → howdenllm-3.0.0}/HowdenLLM/providers/provider_factory.py +4 -4
- {howdenllm-1.6.2 → howdenllm-3.0.0}/HowdenLLM/providers/provider_huggingface.py +4 -2
- howdenllm-3.0.0/HowdenLLM/providers/provider_openai.py +53 -0
- {howdenllm-1.6.2 → howdenllm-3.0.0}/PKG-INFO +1 -1
- {howdenllm-1.6.2 → howdenllm-3.0.0}/pyproject.toml +2 -1
- howdenllm-1.6.2/HowdenLLM/providers/base_provider.py +0 -7
- howdenllm-1.6.2/HowdenLLM/providers/provider_anthropic.py +0 -27
- howdenllm-1.6.2/HowdenLLM/providers/provider_openai.py +0 -34
- {howdenllm-1.6.2 → howdenllm-3.0.0}/HowdenLLM/__init__.py +0 -0
- {howdenllm-1.6.2 → howdenllm-3.0.0}/HowdenLLM/providers/__init__.py +0 -0
- {howdenllm-1.6.2 → howdenllm-3.0.0}/HowdenLLM/providers/known_providers.py +0 -0
- {howdenllm-1.6.2 → howdenllm-3.0.0}/HowdenLLM/providers/provider_meta.py +0 -0
- {howdenllm-1.6.2 → howdenllm-3.0.0}/README.md +0 -0
|
@@ -35,6 +35,7 @@ class LLM:
|
|
|
35
35
|
provider_and_model: str,
|
|
36
36
|
template: Template,
|
|
37
37
|
use_web_search_tool: bool,
|
|
38
|
+
upload_attachment: str|None,
|
|
38
39
|
system: str = None,
|
|
39
40
|
name: str = None
|
|
40
41
|
):
|
|
@@ -53,6 +54,7 @@ class LLM:
|
|
|
53
54
|
self.system: str = system
|
|
54
55
|
self.provider = ProviderFactory.create(self.provider_name)
|
|
55
56
|
self.use_web_search_tool = use_web_search_tool
|
|
57
|
+
self.upload_attachment = upload_attachment
|
|
56
58
|
self.hashed = self.compute_hash(self.input_params)
|
|
57
59
|
|
|
58
60
|
self.total_input_tokens = 0
|
|
@@ -68,7 +70,7 @@ class LLM:
|
|
|
68
70
|
except (KeyError, LookupError, AttributeError, ImportError):
|
|
69
71
|
return len(text.split())
|
|
70
72
|
|
|
71
|
-
def __call__(self, path_or_content: Path | str) -> str:
|
|
73
|
+
async def __call__(self, path_or_content: Path | str) -> str:
|
|
72
74
|
"""
|
|
73
75
|
Execute one completion round and return:
|
|
74
76
|
(output_text, input_token_count, output_token_count)
|
|
@@ -87,7 +89,7 @@ class LLM:
|
|
|
87
89
|
input_tokens = self._count_tokens(input_text)
|
|
88
90
|
|
|
89
91
|
# --- run model ---
|
|
90
|
-
output = self.provider.complete(self.system, prompt, self.model, self.use_web_search_tool)
|
|
92
|
+
output = await self.provider.complete(self.system, prompt, self.model, self.use_web_search_tool,self.upload_attachment)
|
|
91
93
|
|
|
92
94
|
# --- count output tokens ---
|
|
93
95
|
output_tokens = self._count_tokens(output)
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from HowdenLLM.providers.base_provider import BaseProvider
|
|
3
|
+
from anthropic import AsyncAnthropic
|
|
4
|
+
from .known_providers import KnownProviders
|
|
5
|
+
|
|
6
|
+
class AnthropicProvider(BaseProvider, ABC):
|
|
7
|
+
provider = KnownProviders.Anthropic
|
|
8
|
+
|
|
9
|
+
def __init__(self,client: AsyncAnthropic):
|
|
10
|
+
self.client = client
|
|
11
|
+
|
|
12
|
+
async def complete(self, system: str, prompt: str, model: str, use_web_search_tool: bool, upload_attachment: str) -> str:
|
|
13
|
+
|
|
14
|
+
if use_web_search_tool:
|
|
15
|
+
tools=[{"type": "web_search_20260209","name": "web_search","max_uses": 5}]
|
|
16
|
+
else:
|
|
17
|
+
tools = []
|
|
18
|
+
|
|
19
|
+
if model in ["claude-opus-4-6","claude-opus-4-5-20251101","claude-sonnet-4-6","claude-sonnet-4-5-20250929"]:
|
|
20
|
+
message = await self.client.messages.create(
|
|
21
|
+
model=model,
|
|
22
|
+
system=system,
|
|
23
|
+
messages=[
|
|
24
|
+
{"role": "user","content": prompt}
|
|
25
|
+
],
|
|
26
|
+
max_tokens=16000,
|
|
27
|
+
temperature=0.0,
|
|
28
|
+
tools=tools,
|
|
29
|
+
stream=False,
|
|
30
|
+
# thinking={"type": "adaptive"},
|
|
31
|
+
# output_config={"effort": "high"}, # or max, medium, low
|
|
32
|
+
)
|
|
33
|
+
else:
|
|
34
|
+
raise Exception(f"Unsupported model: {model}")
|
|
35
|
+
|
|
36
|
+
# print(f"Content: {message}")
|
|
37
|
+
clean_response = "\n".join(
|
|
38
|
+
block.text for block in message.content if block.type == "text"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
return clean_response
|
|
@@ -41,13 +41,13 @@ class ProviderFactory:
|
|
|
41
41
|
if key not in ProviderFactory.clients_alive.keys():
|
|
42
42
|
load_dotenv()
|
|
43
43
|
if key == KnownProviders.OpenAI:
|
|
44
|
-
from openai import
|
|
45
|
-
ProviderFactory.clients_alive[key] =
|
|
44
|
+
from openai import AsyncOpenAI
|
|
45
|
+
ProviderFactory.clients_alive[key] = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
|
46
46
|
elif key == KnownProviders.HuggingFace:
|
|
47
47
|
from huggingface_hub import InferenceClient
|
|
48
48
|
ProviderFactory.clients_alive[key] = InferenceClient(token=os.getenv("HUGGINGFACE_API_KEY"))
|
|
49
49
|
elif key == KnownProviders.Anthropic:
|
|
50
|
-
from anthropic import
|
|
51
|
-
ProviderFactory.clients_alive[key] =
|
|
50
|
+
from anthropic import AsyncAnthropic
|
|
51
|
+
ProviderFactory.clients_alive[key] = AsyncAnthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
|
|
52
52
|
|
|
53
53
|
return providers[key](ProviderFactory.clients_alive[key])
|
|
@@ -3,6 +3,7 @@ from dotenv import load_dotenv
|
|
|
3
3
|
import os
|
|
4
4
|
from huggingface_hub import InferenceClient
|
|
5
5
|
from .known_providers import KnownProviders
|
|
6
|
+
import asyncio
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class HuggingFaceProvider(BaseProvider):
|
|
@@ -11,9 +12,10 @@ class HuggingFaceProvider(BaseProvider):
|
|
|
11
12
|
def __init__(self,client: InferenceClient):
|
|
12
13
|
self.client = client
|
|
13
14
|
|
|
14
|
-
def complete(self, system: str, prompt: str, model: str, use_web_search_tool: bool) -> str:
|
|
15
|
+
async def complete(self, system: str, prompt: str, model: str, use_web_search_tool: bool) -> str:
|
|
15
16
|
full_prompt = f"[System]: {system}\n[User]: {prompt}\n"
|
|
16
|
-
response =
|
|
17
|
+
response = await asyncio.to_thread(
|
|
18
|
+
self.client.text_generation,
|
|
17
19
|
model=model,
|
|
18
20
|
prompt=full_prompt,
|
|
19
21
|
max_new_tokens=500,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
|
|
3
|
+
from sympy.codegen.ast import continue_
|
|
4
|
+
|
|
5
|
+
from HowdenLLM.providers.base_provider import BaseProvider
|
|
6
|
+
from openai import AsyncOpenAI
|
|
7
|
+
from .known_providers import KnownProviders
|
|
8
|
+
|
|
9
|
+
class OpenAIProvider(BaseProvider, ABC):
|
|
10
|
+
provider = KnownProviders.OpenAI
|
|
11
|
+
|
|
12
|
+
def __init__(self,client: AsyncOpenAI):
|
|
13
|
+
self.client = client
|
|
14
|
+
|
|
15
|
+
async def complete(self, system: str, prompt: str, model: str, use_web_search_tool: bool, upload_attachment: str|None) -> str:
|
|
16
|
+
|
|
17
|
+
if use_web_search_tool:
|
|
18
|
+
tools = [
|
|
19
|
+
{ "type": "web_search" }
|
|
20
|
+
]
|
|
21
|
+
else:
|
|
22
|
+
tools = None
|
|
23
|
+
|
|
24
|
+
if upload_attachment:
|
|
25
|
+
with open(upload_attachment, "rb") as f:
|
|
26
|
+
uploaded = self.client.files.create(
|
|
27
|
+
file=f,
|
|
28
|
+
purpose="user_data",
|
|
29
|
+
)
|
|
30
|
+
input=[
|
|
31
|
+
{"role": "system", "content": system},
|
|
32
|
+
{"role": "user", "content": [
|
|
33
|
+
{"type": "input_text", "text": prompt},
|
|
34
|
+
{"type": "input_file", "file_id": uploaded.id},
|
|
35
|
+
]}
|
|
36
|
+
]
|
|
37
|
+
else:
|
|
38
|
+
input=[
|
|
39
|
+
{"role": "system", "content": system},
|
|
40
|
+
{"role": "user", "content": prompt}
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
if model in ["gpt-5", "gpt-5.2", "gpt-5-mini", "gpt-5.4-mini", "gpt-5-nano"]:
|
|
44
|
+
response = await self.client.responses.create(
|
|
45
|
+
model=model,
|
|
46
|
+
input=input,
|
|
47
|
+
tools=tools,
|
|
48
|
+
max_output_tokens=16000
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
raise Exception(f"Unsupported model: {model}")
|
|
52
|
+
|
|
53
|
+
return response.output_text
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "HowdenLLM"
|
|
3
|
+
version = "3.0.0"
|
|
3
4
|
description = ""
|
|
4
5
|
readme = "README.md"
|
|
5
6
|
requires-python = ">=3.12,<3.14"
|
|
@@ -14,7 +15,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
14
15
|
|
|
15
16
|
[tool.poetry]
|
|
16
17
|
name = "HowdenLLM"
|
|
17
|
-
version = "1.
|
|
18
|
+
version = "1.7.0"
|
|
18
19
|
description = "A simple configuration manager with Pydantic and JSON export."
|
|
19
20
|
authors = [ "JesperThoftIllemannJ <jesper.jaeger@howdendanmark.dk>",]
|
|
20
21
|
readme = "README.md"
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
from HowdenLLM.providers.base_provider import BaseProvider
|
|
3
|
-
from anthropic import Anthropic
|
|
4
|
-
from .known_providers import KnownProviders
|
|
5
|
-
|
|
6
|
-
class AnthropicProvider(BaseProvider, ABC):
|
|
7
|
-
provider = KnownProviders.Anthropic
|
|
8
|
-
|
|
9
|
-
def __init__(self,client: Anthropic):
|
|
10
|
-
self.client = client
|
|
11
|
-
|
|
12
|
-
def complete(self, system: str, prompt: str, model: str, use_web_search_tool: bool) -> str:
|
|
13
|
-
if model in ["claude-opus-4-6","claude-sonnet-4-6"]:
|
|
14
|
-
message = self.client.messages.create(
|
|
15
|
-
model=model,
|
|
16
|
-
system=system,
|
|
17
|
-
messages=[
|
|
18
|
-
{"role": "user","content": prompt}
|
|
19
|
-
],
|
|
20
|
-
max_tokens=16000,
|
|
21
|
-
temperature=0.0
|
|
22
|
-
|
|
23
|
-
)
|
|
24
|
-
else:
|
|
25
|
-
raise Exception(f"Unsupported model: {model}")
|
|
26
|
-
|
|
27
|
-
return message.content[0].text
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
from HowdenLLM.providers.base_provider import BaseProvider
|
|
3
|
-
from openai import OpenAI
|
|
4
|
-
from .known_providers import KnownProviders
|
|
5
|
-
|
|
6
|
-
class OpenAIProvider(BaseProvider, ABC):
|
|
7
|
-
provider = KnownProviders.OpenAI
|
|
8
|
-
|
|
9
|
-
def __init__(self,client: OpenAI):
|
|
10
|
-
self.client = client
|
|
11
|
-
|
|
12
|
-
def complete(self, system: str, prompt: str, model: str, use_web_search_tool: bool) -> str:
|
|
13
|
-
|
|
14
|
-
if use_web_search_tool:
|
|
15
|
-
tools = [
|
|
16
|
-
{ "type": "web_search" }
|
|
17
|
-
]
|
|
18
|
-
else:
|
|
19
|
-
tools = None
|
|
20
|
-
|
|
21
|
-
if model in ["gpt-5", "gpt-5.2", "gpt-5-mini", "gpt-5.4-mini", "gpt-5-nano"]:
|
|
22
|
-
response = self.client.responses.create(
|
|
23
|
-
model=model,
|
|
24
|
-
input=[
|
|
25
|
-
{"role": "system", "content": system},
|
|
26
|
-
{"role": "user", "content": prompt}
|
|
27
|
-
],
|
|
28
|
-
tools=tools,
|
|
29
|
-
max_output_tokens=16000
|
|
30
|
-
)
|
|
31
|
-
else:
|
|
32
|
-
raise Exception(f"Unsupported model: {model}")
|
|
33
|
-
|
|
34
|
-
return response.output_text
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|