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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightmodel
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Ultra-light Model adapter and web search tool for smolagents - works with any OpenAI-compatible API
5
5
  Author-email: PlimbAi <plimb.ai.contact@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lightmodel"
7
- version = "0.1.2"
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightmodel
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Ultra-light Model adapter and web search tool for smolagents - works with any OpenAI-compatible API
5
5
  Author-email: PlimbAi <plimb.ai.contact@gmail.com>
6
6
  License: MIT
@@ -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