lightmodel 0.1.2__tar.gz → 0.1.4__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.
- {lightmodel-0.1.2/src/lightmodel.egg-info → lightmodel-0.1.4}/PKG-INFO +1 -1
- {lightmodel-0.1.2 → lightmodel-0.1.4}/pyproject.toml +1 -1
- lightmodel-0.1.4/src/lightmodel/model.py +126 -0
- {lightmodel-0.1.2 → lightmodel-0.1.4/src/lightmodel.egg-info}/PKG-INFO +1 -1
- lightmodel-0.1.2/src/lightmodel/model.py +0 -75
- {lightmodel-0.1.2 → lightmodel-0.1.4}/LICENSE +0 -0
- {lightmodel-0.1.2 → lightmodel-0.1.4}/README.md +0 -0
- {lightmodel-0.1.2 → lightmodel-0.1.4}/setup.cfg +0 -0
- {lightmodel-0.1.2 → lightmodel-0.1.4}/src/lightmodel/__init__.py +0 -0
- {lightmodel-0.1.2 → lightmodel-0.1.4}/src/lightmodel/tools.py +0 -0
- {lightmodel-0.1.2 → lightmodel-0.1.4}/src/lightmodel.egg-info/SOURCES.txt +0 -0
- {lightmodel-0.1.2 → lightmodel-0.1.4}/src/lightmodel.egg-info/dependency_links.txt +0 -0
- {lightmodel-0.1.2 → lightmodel-0.1.4}/src/lightmodel.egg-info/requires.txt +0 -0
- {lightmodel-0.1.2 → lightmodel-0.1.4}/src/lightmodel.egg-info/top_level.txt +0 -0
- {lightmodel-0.1.2 → lightmodel-0.1.4}/tests/test_model.py +0 -0
- {lightmodel-0.1.2 → lightmodel-0.1.4}/tests/test_tools.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lightmodel"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "Ultra-light Model adapter and web search tool for smolagents - works with any OpenAI-compatible API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""LightModel - lightweight Model adapter for smolagents."""
|
|
2
|
+
import json
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from smolagents import Model
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from smolagents.models import ChatMessage, MessageRole
|
|
9
|
+
ROLE_ASSISTANT = MessageRole.ASSISTANT
|
|
10
|
+
except ImportError:
|
|
11
|
+
from smolagents.models import ChatMessage
|
|
12
|
+
ROLE_ASSISTANT = "assistant"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LightModel(Model):
|
|
16
|
+
"""Universal adapter for any OpenAI-compatible API with tool calling."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, model_id, api_base, api_key="not-needed",
|
|
19
|
+
temperature=0.7, max_tokens=2048, timeout=120, extra=None):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.model_id = model_id
|
|
22
|
+
base = api_base.rstrip("/")
|
|
23
|
+
if not base.endswith("/chat/completions"):
|
|
24
|
+
base += "/chat/completions"
|
|
25
|
+
self.api_url = base
|
|
26
|
+
self.api_key = api_key
|
|
27
|
+
self.temperature = temperature
|
|
28
|
+
self.max_tokens = max_tokens
|
|
29
|
+
self.timeout = timeout
|
|
30
|
+
self.extra = extra or {}
|
|
31
|
+
|
|
32
|
+
def generate(self, messages, stop_sequences=None, response_format=None,
|
|
33
|
+
tools_to_call_from=None, **kwargs):
|
|
34
|
+
headers = {"Content-Type": "application/json"}
|
|
35
|
+
if self.api_key:
|
|
36
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
37
|
+
|
|
38
|
+
payload = {
|
|
39
|
+
"model": self.model_id,
|
|
40
|
+
"messages": self._to_openai(messages),
|
|
41
|
+
"temperature": self.temperature,
|
|
42
|
+
"max_tokens": self.max_tokens,
|
|
43
|
+
**self.extra,
|
|
44
|
+
}
|
|
45
|
+
if tools_to_call_from:
|
|
46
|
+
payload["tools"] = self._tools_to_openai(tools_to_call_from)
|
|
47
|
+
payload["tool_choice"] = "auto"
|
|
48
|
+
if stop_sequences:
|
|
49
|
+
payload["stop"] = stop_sequences
|
|
50
|
+
|
|
51
|
+
r = requests.post(self.api_url, json=payload, headers=headers,
|
|
52
|
+
timeout=self.timeout)
|
|
53
|
+
if r.status_code >= 400:
|
|
54
|
+
raise RuntimeError(f"API {r.status_code}: {r.text[:300]}")
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
msg = r.json()["choices"][0]["message"]
|
|
58
|
+
except (KeyError, IndexError):
|
|
59
|
+
raise RuntimeError(f"Invalid response: {r.text[:300]}")
|
|
60
|
+
|
|
61
|
+
content = msg.get("content") or ""
|
|
62
|
+
raw_calls = msg.get("tool_calls") or []
|
|
63
|
+
chat_msg = ChatMessage(role=ROLE_ASSISTANT, content=content)
|
|
64
|
+
if raw_calls:
|
|
65
|
+
chat_msg.tool_calls = [
|
|
66
|
+
{
|
|
67
|
+
"id": tc.get("id", f"call_{i}"),
|
|
68
|
+
"type": "function",
|
|
69
|
+
"function": {
|
|
70
|
+
"name": tc["function"]["name"],
|
|
71
|
+
"arguments": tc["function"]["arguments"],
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
for i, tc in enumerate(raw_calls)
|
|
75
|
+
]
|
|
76
|
+
return chat_msg
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def _tools_to_openai(tools):
|
|
80
|
+
out = []
|
|
81
|
+
for t in tools:
|
|
82
|
+
props, required = {}, []
|
|
83
|
+
for name, spec in (t.inputs or {}).items():
|
|
84
|
+
props[name] = {
|
|
85
|
+
"type": spec.get("type", "string"),
|
|
86
|
+
"description": spec.get("description", ""),
|
|
87
|
+
}
|
|
88
|
+
required.append(name)
|
|
89
|
+
out.append({
|
|
90
|
+
"type": "function",
|
|
91
|
+
"function": {
|
|
92
|
+
"name": t.name,
|
|
93
|
+
"description": t.description,
|
|
94
|
+
"parameters": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"properties": props,
|
|
97
|
+
"required": required,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
return out
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def _to_openai(messages):
|
|
105
|
+
ROLE_MAP = {"tool-call": "assistant"}
|
|
106
|
+
out = []
|
|
107
|
+
for m in messages:
|
|
108
|
+
if isinstance(m, dict):
|
|
109
|
+
role, content = m.get("role", "user"), m.get("content", "")
|
|
110
|
+
tool_call_id = m.get("tool_call_id")
|
|
111
|
+
else:
|
|
112
|
+
role = getattr(m, "role", "user")
|
|
113
|
+
content = getattr(m, "content", "") or ""
|
|
114
|
+
tool_call_id = getattr(m, "tool_call_id", None)
|
|
115
|
+
if hasattr(role, "value"):
|
|
116
|
+
role = role.value
|
|
117
|
+
role = str(role)
|
|
118
|
+
if role in ("tool-response", "tool"):
|
|
119
|
+
msg = {"role": "tool", "content": str(content)}
|
|
120
|
+
if tool_call_id:
|
|
121
|
+
msg["tool_call_id"] = tool_call_id
|
|
122
|
+
out.append(msg)
|
|
123
|
+
continue
|
|
124
|
+
role = ROLE_MAP.get(role, role)
|
|
125
|
+
out.append({"role": role, "content": str(content)})
|
|
126
|
+
return out
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
"""LightModel - lightweight Model adapter for smolagents."""
|
|
2
|
-
import requests
|
|
3
|
-
|
|
4
|
-
from smolagents import Model
|
|
5
|
-
|
|
6
|
-
try:
|
|
7
|
-
from smolagents.models import ChatMessage, MessageRole
|
|
8
|
-
ROLE_ASSISTANT = MessageRole.ASSISTANT
|
|
9
|
-
except ImportError:
|
|
10
|
-
from smolagents.models import ChatMessage
|
|
11
|
-
ROLE_ASSISTANT = "assistant"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class LightModel(Model):
|
|
15
|
-
"""Universal adapter for any OpenAI-compatible API."""
|
|
16
|
-
|
|
17
|
-
def __init__(self, model_id, api_base, api_key="not-needed",
|
|
18
|
-
temperature=0.7, max_tokens=2048, timeout=120, extra=None):
|
|
19
|
-
super().__init__()
|
|
20
|
-
self.model_id = model_id
|
|
21
|
-
base = api_base.rstrip("/")
|
|
22
|
-
if not base.endswith("/chat/completions"):
|
|
23
|
-
base += "/chat/completions"
|
|
24
|
-
self.api_url = base
|
|
25
|
-
self.api_key = api_key
|
|
26
|
-
self.temperature = temperature
|
|
27
|
-
self.max_tokens = max_tokens
|
|
28
|
-
self.timeout = timeout
|
|
29
|
-
self.extra = extra or {}
|
|
30
|
-
|
|
31
|
-
def generate(self, messages, stop_sequences=None, response_format=None,
|
|
32
|
-
tools_to_call_from=None, **kwargs):
|
|
33
|
-
headers = {"Content-Type": "application/json"}
|
|
34
|
-
if self.api_key:
|
|
35
|
-
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
36
|
-
|
|
37
|
-
payload = {
|
|
38
|
-
"model": self.model_id,
|
|
39
|
-
"messages": self._to_openai(messages),
|
|
40
|
-
"temperature": self.temperature,
|
|
41
|
-
"max_tokens": self.max_tokens,
|
|
42
|
-
**self.extra,
|
|
43
|
-
}
|
|
44
|
-
if stop_sequences:
|
|
45
|
-
payload["stop"] = stop_sequences
|
|
46
|
-
|
|
47
|
-
r = requests.post(self.api_url, json=payload, headers=headers,
|
|
48
|
-
timeout=self.timeout)
|
|
49
|
-
if r.status_code >= 400:
|
|
50
|
-
raise RuntimeError(f"API {r.status_code}: {r.text[:300]}")
|
|
51
|
-
try:
|
|
52
|
-
text = r.json()["choices"][0]["message"]["content"] or ""
|
|
53
|
-
except (KeyError, IndexError):
|
|
54
|
-
raise RuntimeError(f"Invalid response: {r.text[:300]}")
|
|
55
|
-
return ChatMessage(role=ROLE_ASSISTANT, content=text)
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def _to_openai(messages):
|
|
59
|
-
ROLE_MAP = {
|
|
60
|
-
"tool-call": "assistant",
|
|
61
|
-
"tool-response": "tool",
|
|
62
|
-
"tool": "tool",
|
|
63
|
-
}
|
|
64
|
-
out = []
|
|
65
|
-
for m in messages:
|
|
66
|
-
if isinstance(m, dict):
|
|
67
|
-
role, content = m.get("role", "user"), m.get("content", "")
|
|
68
|
-
else:
|
|
69
|
-
role = getattr(m, "role", "user")
|
|
70
|
-
content = getattr(m, "content", "")
|
|
71
|
-
if hasattr(role, "value"):
|
|
72
|
-
role = role.value
|
|
73
|
-
role = ROLE_MAP.get(str(role), str(role))
|
|
74
|
-
out.append({"role": role, "content": str(content)})
|
|
75
|
-
return out
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|