lightmodel 0.1.4__tar.gz → 0.2.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.
- lightmodel-0.2.0/PKG-INFO +58 -0
- lightmodel-0.2.0/README.md +41 -0
- {lightmodel-0.1.4 → lightmodel-0.2.0}/pyproject.toml +5 -7
- lightmodel-0.2.0/src/lightmodel/__init__.py +7 -0
- lightmodel-0.2.0/src/lightmodel/agent.py +71 -0
- lightmodel-0.2.0/src/lightmodel/model.py +45 -0
- lightmodel-0.2.0/src/lightmodel/tools.py +61 -0
- lightmodel-0.2.0/src/lightmodel.egg-info/PKG-INFO +58 -0
- {lightmodel-0.1.4 → lightmodel-0.2.0}/src/lightmodel.egg-info/SOURCES.txt +1 -0
- {lightmodel-0.1.4 → lightmodel-0.2.0}/src/lightmodel.egg-info/requires.txt +0 -3
- lightmodel-0.1.4/PKG-INFO +0 -126
- lightmodel-0.1.4/README.md +0 -106
- lightmodel-0.1.4/src/lightmodel/__init__.py +0 -6
- lightmodel-0.1.4/src/lightmodel/model.py +0 -126
- lightmodel-0.1.4/src/lightmodel/tools.py +0 -39
- lightmodel-0.1.4/src/lightmodel.egg-info/PKG-INFO +0 -126
- {lightmodel-0.1.4 → lightmodel-0.2.0}/LICENSE +0 -0
- {lightmodel-0.1.4 → lightmodel-0.2.0}/setup.cfg +0 -0
- {lightmodel-0.1.4 → lightmodel-0.2.0}/src/lightmodel.egg-info/dependency_links.txt +0 -0
- {lightmodel-0.1.4 → lightmodel-0.2.0}/src/lightmodel.egg-info/top_level.txt +0 -0
- {lightmodel-0.1.4 → lightmodel-0.2.0}/tests/test_model.py +0 -0
- {lightmodel-0.1.4 → lightmodel-0.2.0}/tests/test_tools.py +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lightmodel
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Ultra-light autonomous AI agents for any OpenAI-compatible API
|
|
5
|
+
Author-email: Your Name <you@example.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: llm,ai,agent,openai,nvidia-nim,tool-calling
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: requests>=2.28
|
|
14
|
+
Provides-Extra: search
|
|
15
|
+
Requires-Dist: beautifulsoup4>=4.12; extra == "search"
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# LightModel
|
|
19
|
+
|
|
20
|
+
Ultra-light **autonomous AI agents** for any OpenAI-compatible API.
|
|
21
|
+
No `smolagents`, no `litellm`, no `openai` — just `requests`.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
pip install lightmodel
|
|
26
|
+
pip install lightmodel[search] # with beautifulsoup4 for web search
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
from lightmodel import Agent, LightModel, SimpleDuckDuckGoSearchTool
|
|
31
|
+
|
|
32
|
+
model = LightModel(
|
|
33
|
+
model_id="meta/llama-3.3-70b-instruct",
|
|
34
|
+
api_base="https://integrate.api.nvidia.com/v1",
|
|
35
|
+
api_key="your_key",
|
|
36
|
+
)
|
|
37
|
+
agent = Agent(
|
|
38
|
+
model=model,
|
|
39
|
+
tools=[SimpleDuckDuckGoSearchTool()],
|
|
40
|
+
system_prompt="You are a helpful research assistant.",
|
|
41
|
+
)
|
|
42
|
+
print(agent.run("What are the latest AI news in 2026?"))
|
|
43
|
+
|
|
44
|
+
## Custom tools
|
|
45
|
+
|
|
46
|
+
from lightmodel import Tool
|
|
47
|
+
|
|
48
|
+
class CalculatorTool(Tool):
|
|
49
|
+
name = "calculator"
|
|
50
|
+
description = "Evaluate a basic arithmetic expression."
|
|
51
|
+
inputs = {"expression": {"type": "string", "description": "e.g. 2+2"}}
|
|
52
|
+
|
|
53
|
+
def forward(self, expression):
|
|
54
|
+
return str(eval(expression, {"__builtins__": {}}, {}))
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# LightModel
|
|
2
|
+
|
|
3
|
+
Ultra-light **autonomous AI agents** for any OpenAI-compatible API.
|
|
4
|
+
No `smolagents`, no `litellm`, no `openai` — just `requests`.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
pip install lightmodel
|
|
9
|
+
pip install lightmodel[search] # with beautifulsoup4 for web search
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
from lightmodel import Agent, LightModel, SimpleDuckDuckGoSearchTool
|
|
14
|
+
|
|
15
|
+
model = LightModel(
|
|
16
|
+
model_id="meta/llama-3.3-70b-instruct",
|
|
17
|
+
api_base="https://integrate.api.nvidia.com/v1",
|
|
18
|
+
api_key="your_key",
|
|
19
|
+
)
|
|
20
|
+
agent = Agent(
|
|
21
|
+
model=model,
|
|
22
|
+
tools=[SimpleDuckDuckGoSearchTool()],
|
|
23
|
+
system_prompt="You are a helpful research assistant.",
|
|
24
|
+
)
|
|
25
|
+
print(agent.run("What are the latest AI news in 2026?"))
|
|
26
|
+
|
|
27
|
+
## Custom tools
|
|
28
|
+
|
|
29
|
+
from lightmodel import Tool
|
|
30
|
+
|
|
31
|
+
class CalculatorTool(Tool):
|
|
32
|
+
name = "calculator"
|
|
33
|
+
description = "Evaluate a basic arithmetic expression."
|
|
34
|
+
inputs = {"expression": {"type": "string", "description": "e.g. 2+2"}}
|
|
35
|
+
|
|
36
|
+
def forward(self, expression):
|
|
37
|
+
return str(eval(expression, {"__builtins__": {}}, {}))
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT
|
|
@@ -4,22 +4,20 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lightmodel"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Ultra-light
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Ultra-light autonomous AI agents for any OpenAI-compatible API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
|
-
license =
|
|
12
|
-
authors = [{name = "
|
|
13
|
-
keywords = ["
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{name = "Your Name", email = "you@example.com"}]
|
|
13
|
+
keywords = ["llm", "ai", "agent", "openai", "nvidia-nim", "tool-calling"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
|
-
"License :: OSI Approved :: MIT License",
|
|
17
16
|
"Operating System :: OS Independent",
|
|
18
17
|
]
|
|
19
18
|
dependencies = ["requests>=2.28"]
|
|
20
19
|
|
|
21
20
|
[project.optional-dependencies]
|
|
22
|
-
smolagents = ["smolagents>=1.0"]
|
|
23
21
|
search = ["beautifulsoup4>=4.12"]
|
|
24
22
|
|
|
25
23
|
[tool.setuptools.packages.find]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""LightModel Agent - autonomous tool-calling loop."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Agent:
|
|
6
|
+
"""Autonomous agent using native OpenAI tool calling."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, model, tools=None, system_prompt=None,
|
|
9
|
+
max_steps=10, verbose=False):
|
|
10
|
+
self.model = model
|
|
11
|
+
self.tools = {t.name: t for t in (tools or [])}
|
|
12
|
+
self.system_prompt = system_prompt
|
|
13
|
+
self.max_steps = max_steps
|
|
14
|
+
self.verbose = verbose
|
|
15
|
+
|
|
16
|
+
def run(self, task):
|
|
17
|
+
messages = []
|
|
18
|
+
if self.system_prompt:
|
|
19
|
+
messages.append({"role": "system", "content": self.system_prompt})
|
|
20
|
+
messages.append({"role": "user", "content": task})
|
|
21
|
+
|
|
22
|
+
openai_tools = [t.to_openai() for t in self.tools.values()] or None
|
|
23
|
+
|
|
24
|
+
for step in range(1, self.max_steps + 1):
|
|
25
|
+
msg = self.model.chat(messages, tools=openai_tools)
|
|
26
|
+
|
|
27
|
+
assistant_msg = {"role": "assistant"}
|
|
28
|
+
if msg.get("content"):
|
|
29
|
+
assistant_msg["content"] = msg["content"]
|
|
30
|
+
if msg.get("tool_calls"):
|
|
31
|
+
assistant_msg["tool_calls"] = msg["tool_calls"]
|
|
32
|
+
messages.append(assistant_msg)
|
|
33
|
+
|
|
34
|
+
tool_calls = msg.get("tool_calls") or []
|
|
35
|
+
if not tool_calls:
|
|
36
|
+
return msg.get("content") or ""
|
|
37
|
+
|
|
38
|
+
if self.verbose:
|
|
39
|
+
print(f"[step {step}] {len(tool_calls)} tool call(s)")
|
|
40
|
+
|
|
41
|
+
for tc in tool_calls:
|
|
42
|
+
name = tc["function"]["name"]
|
|
43
|
+
raw_args = tc["function"]["arguments"]
|
|
44
|
+
try:
|
|
45
|
+
args = json.loads(raw_args) if isinstance(raw_args, str) else raw_args
|
|
46
|
+
except json.JSONDecodeError:
|
|
47
|
+
args = {}
|
|
48
|
+
|
|
49
|
+
if self.verbose:
|
|
50
|
+
print(f" → {name}({args})")
|
|
51
|
+
|
|
52
|
+
tool = self.tools.get(name)
|
|
53
|
+
if tool is None:
|
|
54
|
+
result = f"Unknown tool: {name}"
|
|
55
|
+
else:
|
|
56
|
+
try:
|
|
57
|
+
result = tool.forward(**args)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
result = f"Tool error: {e}"
|
|
60
|
+
|
|
61
|
+
if self.verbose:
|
|
62
|
+
preview = str(result)[:120].replace("\n", " ")
|
|
63
|
+
print(f" ← {preview}...")
|
|
64
|
+
|
|
65
|
+
messages.append({
|
|
66
|
+
"role": "tool",
|
|
67
|
+
"tool_call_id": tc["id"],
|
|
68
|
+
"content": str(result),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return "[max_steps reached without final answer]"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""LightModel - pure HTTP client for OpenAI-compatible APIs."""
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class LightModel:
|
|
6
|
+
"""Client for any OpenAI-compatible /chat/completions API."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, model_id, api_base, api_key="not-needed",
|
|
9
|
+
temperature=0.7, max_tokens=2048, timeout=120, extra=None):
|
|
10
|
+
self.model_id = model_id
|
|
11
|
+
base = api_base.rstrip("/")
|
|
12
|
+
if not base.endswith("/chat/completions"):
|
|
13
|
+
base += "/chat/completions"
|
|
14
|
+
self.api_url = base
|
|
15
|
+
self.api_key = api_key
|
|
16
|
+
self.temperature = temperature
|
|
17
|
+
self.max_tokens = max_tokens
|
|
18
|
+
self.timeout = timeout
|
|
19
|
+
self.extra = extra or {}
|
|
20
|
+
|
|
21
|
+
def chat(self, messages, tools=None):
|
|
22
|
+
"""Send messages to the API. Returns the assistant message dict."""
|
|
23
|
+
headers = {"Content-Type": "application/json"}
|
|
24
|
+
if self.api_key:
|
|
25
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
26
|
+
|
|
27
|
+
payload = {
|
|
28
|
+
"model": self.model_id,
|
|
29
|
+
"messages": messages,
|
|
30
|
+
"temperature": self.temperature,
|
|
31
|
+
"max_tokens": self.max_tokens,
|
|
32
|
+
**self.extra,
|
|
33
|
+
}
|
|
34
|
+
if tools:
|
|
35
|
+
payload["tools"] = tools
|
|
36
|
+
payload["tool_choice"] = "auto"
|
|
37
|
+
|
|
38
|
+
r = requests.post(self.api_url, json=payload, headers=headers,
|
|
39
|
+
timeout=self.timeout)
|
|
40
|
+
if r.status_code >= 400:
|
|
41
|
+
raise RuntimeError(f"API {r.status_code}: {r.text[:300]}")
|
|
42
|
+
try:
|
|
43
|
+
return r.json()["choices"][0]["message"]
|
|
44
|
+
except (KeyError, IndexError):
|
|
45
|
+
raise RuntimeError(f"Invalid response: {r.text[:300]}")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Lightweight tools for LightModel agents."""
|
|
2
|
+
import requests
|
|
3
|
+
from bs4 import BeautifulSoup
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Tool:
|
|
7
|
+
"""Base class for all tools. Subclass and override forward()."""
|
|
8
|
+
name = ""
|
|
9
|
+
description = ""
|
|
10
|
+
inputs = {} # {"arg_name": {"type": "string", "description": "..."}}
|
|
11
|
+
|
|
12
|
+
def forward(self, **kwargs):
|
|
13
|
+
raise NotImplementedError
|
|
14
|
+
|
|
15
|
+
def to_openai(self):
|
|
16
|
+
props, required = {}, []
|
|
17
|
+
for name, spec in self.inputs.items():
|
|
18
|
+
props[name] = {
|
|
19
|
+
"type": spec.get("type", "string"),
|
|
20
|
+
"description": spec.get("description", ""),
|
|
21
|
+
}
|
|
22
|
+
required.append(name)
|
|
23
|
+
return {
|
|
24
|
+
"type": "function",
|
|
25
|
+
"function": {
|
|
26
|
+
"name": self.name,
|
|
27
|
+
"description": self.description,
|
|
28
|
+
"parameters": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": props,
|
|
31
|
+
"required": required,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SimpleDuckDuckGoSearchTool(Tool):
|
|
38
|
+
"""Lightweight DuckDuckGo web search (HTML scraping, no API key)."""
|
|
39
|
+
name = "web_search"
|
|
40
|
+
description = "Search the web via DuckDuckGo. Returns the top 5 results."
|
|
41
|
+
inputs = {
|
|
42
|
+
"query": {"type": "string", "description": "The search query."}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def forward(self, query):
|
|
46
|
+
url = "https://html.duckduckgo.com/html/"
|
|
47
|
+
headers = {"User-Agent": "Mozilla/5.0"}
|
|
48
|
+
try:
|
|
49
|
+
r = requests.post(url, data={"q": query}, headers=headers, timeout=10)
|
|
50
|
+
r.raise_for_status()
|
|
51
|
+
soup = BeautifulSoup(r.text, "html.parser")
|
|
52
|
+
out = []
|
|
53
|
+
for res in soup.select(".result")[:5]:
|
|
54
|
+
title = res.select_one(".result__a")
|
|
55
|
+
snippet = res.select_one(".result__snippet")
|
|
56
|
+
if title and snippet:
|
|
57
|
+
out.append(f"- {title.get_text(strip=True)}\n "
|
|
58
|
+
f"{title['href']}\n {snippet.get_text(strip=True)}")
|
|
59
|
+
return "\n\n".join(out) if out else "No results found."
|
|
60
|
+
except Exception as e:
|
|
61
|
+
return f"Search error: {e}"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lightmodel
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Ultra-light autonomous AI agents for any OpenAI-compatible API
|
|
5
|
+
Author-email: Your Name <you@example.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: llm,ai,agent,openai,nvidia-nim,tool-calling
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: requests>=2.28
|
|
14
|
+
Provides-Extra: search
|
|
15
|
+
Requires-Dist: beautifulsoup4>=4.12; extra == "search"
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# LightModel
|
|
19
|
+
|
|
20
|
+
Ultra-light **autonomous AI agents** for any OpenAI-compatible API.
|
|
21
|
+
No `smolagents`, no `litellm`, no `openai` — just `requests`.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
pip install lightmodel
|
|
26
|
+
pip install lightmodel[search] # with beautifulsoup4 for web search
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
from lightmodel import Agent, LightModel, SimpleDuckDuckGoSearchTool
|
|
31
|
+
|
|
32
|
+
model = LightModel(
|
|
33
|
+
model_id="meta/llama-3.3-70b-instruct",
|
|
34
|
+
api_base="https://integrate.api.nvidia.com/v1",
|
|
35
|
+
api_key="your_key",
|
|
36
|
+
)
|
|
37
|
+
agent = Agent(
|
|
38
|
+
model=model,
|
|
39
|
+
tools=[SimpleDuckDuckGoSearchTool()],
|
|
40
|
+
system_prompt="You are a helpful research assistant.",
|
|
41
|
+
)
|
|
42
|
+
print(agent.run("What are the latest AI news in 2026?"))
|
|
43
|
+
|
|
44
|
+
## Custom tools
|
|
45
|
+
|
|
46
|
+
from lightmodel import Tool
|
|
47
|
+
|
|
48
|
+
class CalculatorTool(Tool):
|
|
49
|
+
name = "calculator"
|
|
50
|
+
description = "Evaluate a basic arithmetic expression."
|
|
51
|
+
inputs = {"expression": {"type": "string", "description": "e.g. 2+2"}}
|
|
52
|
+
|
|
53
|
+
def forward(self, expression):
|
|
54
|
+
return str(eval(expression, {"__builtins__": {}}, {}))
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
lightmodel-0.1.4/PKG-INFO
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: lightmodel
|
|
3
|
-
Version: 0.1.4
|
|
4
|
-
Summary: Ultra-light Model adapter and web search tool for smolagents - works with any OpenAI-compatible API
|
|
5
|
-
Author-email: PlimbAi <plimb.ai.contact@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Keywords: smolagents,llm,ai,agent,openai,nvidia-nim,duckduckgo,web-search
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.9
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENSE
|
|
14
|
-
Requires-Dist: requests>=2.28
|
|
15
|
-
Provides-Extra: smolagents
|
|
16
|
-
Requires-Dist: smolagents>=1.0; extra == "smolagents"
|
|
17
|
-
Provides-Extra: search
|
|
18
|
-
Requires-Dist: beautifulsoup4>=4.12; extra == "search"
|
|
19
|
-
Dynamic: license-file
|
|
20
|
-
|
|
21
|
-
# LightModel
|
|
22
|
-
|
|
23
|
-
Ultra-light **Model adapter** and **web search tool** for [smolagents](https://github.com/huggingface/smolagents).
|
|
24
|
-
|
|
25
|
-
Works with **any** OpenAI-compatible API: NVIDIA NIM, Ollama, vLLM, LM Studio,
|
|
26
|
-
Groq, OpenRouter, Together, DeepSeek, or any custom URL. No heavy dependencies,
|
|
27
|
-
no Rust compilation, runs everywhere (including Termux on Android).
|
|
28
|
-
|
|
29
|
-
## Features
|
|
30
|
-
|
|
31
|
-
- `LightModel`: a drop-in `Model` adapter for smolagents using only `requests`.
|
|
32
|
-
- `SimpleDuckDuckGoSearchTool`: a lightweight web search tool that scrapes
|
|
33
|
-
DuckDuckGo HTML and does not require `ddgs`, `primp`, or Rust toolchain.
|
|
34
|
-
- Zero heavy dependencies: only `requests` (and `beautifulsoup4` for search).
|
|
35
|
-
- Works with any endpoint exposing an OpenAI-compatible `/chat/completions` API.
|
|
36
|
-
|
|
37
|
-
## Installation
|
|
38
|
-
|
|
39
|
-
pip install lightmodel
|
|
40
|
-
pip install lightmodel[smolagents] # with smolagents
|
|
41
|
-
pip install lightmodel[search] # with beautifulsoup4 for web search
|
|
42
|
-
|
|
43
|
-
On Termux or restricted environments, install `smolagents` without its heavy
|
|
44
|
-
dependencies:
|
|
45
|
-
|
|
46
|
-
export HF_HUB_DISABLE_XET=1
|
|
47
|
-
pip install --no-deps smolagents
|
|
48
|
-
pip install --no-deps "huggingface-hub==0.31.2"
|
|
49
|
-
pip install rich jinja2 pillow python-dotenv requests beautifulsoup4
|
|
50
|
-
pip install lightmodel
|
|
51
|
-
|
|
52
|
-
## Usage
|
|
53
|
-
|
|
54
|
-
### Model only
|
|
55
|
-
|
|
56
|
-
from smolagents import CodeAgent
|
|
57
|
-
from lightmodel import LightModel
|
|
58
|
-
|
|
59
|
-
model = LightModel(
|
|
60
|
-
model_id="meta/llama3-70b-instruct",
|
|
61
|
-
api_base="https://integrate.api.nvidia.com/v1",
|
|
62
|
-
api_key="your_nvidia_key",
|
|
63
|
-
)
|
|
64
|
-
agent = CodeAgent(tools=[], model=model)
|
|
65
|
-
agent.run("Explain quantum entanglement in one sentence.")
|
|
66
|
-
|
|
67
|
-
### Model + web search
|
|
68
|
-
|
|
69
|
-
from smolagents import CodeAgent
|
|
70
|
-
from lightmodel import LightModel, SimpleDuckDuckGoSearchTool
|
|
71
|
-
|
|
72
|
-
model = LightModel(
|
|
73
|
-
model_id="meta/llama3-70b-instruct",
|
|
74
|
-
api_base="https://integrate.api.nvidia.com/v1",
|
|
75
|
-
api_key="your_nvidia_key",
|
|
76
|
-
)
|
|
77
|
-
agent = CodeAgent(
|
|
78
|
-
tools=[SimpleDuckDuckGoSearchTool()],
|
|
79
|
-
model=model,
|
|
80
|
-
)
|
|
81
|
-
agent.run("What are the latest AI news in 2026?")
|
|
82
|
-
|
|
83
|
-
### With Ollama (local)
|
|
84
|
-
|
|
85
|
-
model = LightModel(
|
|
86
|
-
model_id="llama3",
|
|
87
|
-
api_base="http://localhost:11434/v1",
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
### With any custom URL
|
|
91
|
-
|
|
92
|
-
model = LightModel(
|
|
93
|
-
model_id="my-model",
|
|
94
|
-
api_base="https://my-server.example.com/v1",
|
|
95
|
-
api_key="my-key",
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
## API reference
|
|
99
|
-
|
|
100
|
-
### `LightModel`
|
|
101
|
-
|
|
102
|
-
LightModel(
|
|
103
|
-
model_id: str,
|
|
104
|
-
api_base: str,
|
|
105
|
-
api_key: str = "not-needed",
|
|
106
|
-
temperature: float = 0.7,
|
|
107
|
-
max_tokens: int = 2048,
|
|
108
|
-
timeout: int = 120,
|
|
109
|
-
extra: dict | None = None,
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
- `model_id`: model name sent to the API.
|
|
113
|
-
- `api_base`: base URL. `/chat/completions` is appended automatically.
|
|
114
|
-
- `api_key`: bearer token (optional for local servers).
|
|
115
|
-
- `extra`: extra JSON fields merged into the request payload.
|
|
116
|
-
|
|
117
|
-
### `SimpleDuckDuckGoSearchTool`
|
|
118
|
-
|
|
119
|
-
SimpleDuckDuckGoSearchTool()
|
|
120
|
-
|
|
121
|
-
Scrapes DuckDuckGo HTML results and returns the top 5 as formatted text.
|
|
122
|
-
No API key required. No Rust dependency.
|
|
123
|
-
|
|
124
|
-
## License
|
|
125
|
-
|
|
126
|
-
MIT
|
lightmodel-0.1.4/README.md
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# LightModel
|
|
2
|
-
|
|
3
|
-
Ultra-light **Model adapter** and **web search tool** for [smolagents](https://github.com/huggingface/smolagents).
|
|
4
|
-
|
|
5
|
-
Works with **any** OpenAI-compatible API: NVIDIA NIM, Ollama, vLLM, LM Studio,
|
|
6
|
-
Groq, OpenRouter, Together, DeepSeek, or any custom URL. No heavy dependencies,
|
|
7
|
-
no Rust compilation, runs everywhere (including Termux on Android).
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
- `LightModel`: a drop-in `Model` adapter for smolagents using only `requests`.
|
|
12
|
-
- `SimpleDuckDuckGoSearchTool`: a lightweight web search tool that scrapes
|
|
13
|
-
DuckDuckGo HTML and does not require `ddgs`, `primp`, or Rust toolchain.
|
|
14
|
-
- Zero heavy dependencies: only `requests` (and `beautifulsoup4` for search).
|
|
15
|
-
- Works with any endpoint exposing an OpenAI-compatible `/chat/completions` API.
|
|
16
|
-
|
|
17
|
-
## Installation
|
|
18
|
-
|
|
19
|
-
pip install lightmodel
|
|
20
|
-
pip install lightmodel[smolagents] # with smolagents
|
|
21
|
-
pip install lightmodel[search] # with beautifulsoup4 for web search
|
|
22
|
-
|
|
23
|
-
On Termux or restricted environments, install `smolagents` without its heavy
|
|
24
|
-
dependencies:
|
|
25
|
-
|
|
26
|
-
export HF_HUB_DISABLE_XET=1
|
|
27
|
-
pip install --no-deps smolagents
|
|
28
|
-
pip install --no-deps "huggingface-hub==0.31.2"
|
|
29
|
-
pip install rich jinja2 pillow python-dotenv requests beautifulsoup4
|
|
30
|
-
pip install lightmodel
|
|
31
|
-
|
|
32
|
-
## Usage
|
|
33
|
-
|
|
34
|
-
### Model only
|
|
35
|
-
|
|
36
|
-
from smolagents import CodeAgent
|
|
37
|
-
from lightmodel import LightModel
|
|
38
|
-
|
|
39
|
-
model = LightModel(
|
|
40
|
-
model_id="meta/llama3-70b-instruct",
|
|
41
|
-
api_base="https://integrate.api.nvidia.com/v1",
|
|
42
|
-
api_key="your_nvidia_key",
|
|
43
|
-
)
|
|
44
|
-
agent = CodeAgent(tools=[], model=model)
|
|
45
|
-
agent.run("Explain quantum entanglement in one sentence.")
|
|
46
|
-
|
|
47
|
-
### Model + web search
|
|
48
|
-
|
|
49
|
-
from smolagents import CodeAgent
|
|
50
|
-
from lightmodel import LightModel, SimpleDuckDuckGoSearchTool
|
|
51
|
-
|
|
52
|
-
model = LightModel(
|
|
53
|
-
model_id="meta/llama3-70b-instruct",
|
|
54
|
-
api_base="https://integrate.api.nvidia.com/v1",
|
|
55
|
-
api_key="your_nvidia_key",
|
|
56
|
-
)
|
|
57
|
-
agent = CodeAgent(
|
|
58
|
-
tools=[SimpleDuckDuckGoSearchTool()],
|
|
59
|
-
model=model,
|
|
60
|
-
)
|
|
61
|
-
agent.run("What are the latest AI news in 2026?")
|
|
62
|
-
|
|
63
|
-
### With Ollama (local)
|
|
64
|
-
|
|
65
|
-
model = LightModel(
|
|
66
|
-
model_id="llama3",
|
|
67
|
-
api_base="http://localhost:11434/v1",
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
### With any custom URL
|
|
71
|
-
|
|
72
|
-
model = LightModel(
|
|
73
|
-
model_id="my-model",
|
|
74
|
-
api_base="https://my-server.example.com/v1",
|
|
75
|
-
api_key="my-key",
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
## API reference
|
|
79
|
-
|
|
80
|
-
### `LightModel`
|
|
81
|
-
|
|
82
|
-
LightModel(
|
|
83
|
-
model_id: str,
|
|
84
|
-
api_base: str,
|
|
85
|
-
api_key: str = "not-needed",
|
|
86
|
-
temperature: float = 0.7,
|
|
87
|
-
max_tokens: int = 2048,
|
|
88
|
-
timeout: int = 120,
|
|
89
|
-
extra: dict | None = None,
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
- `model_id`: model name sent to the API.
|
|
93
|
-
- `api_base`: base URL. `/chat/completions` is appended automatically.
|
|
94
|
-
- `api_key`: bearer token (optional for local servers).
|
|
95
|
-
- `extra`: extra JSON fields merged into the request payload.
|
|
96
|
-
|
|
97
|
-
### `SimpleDuckDuckGoSearchTool`
|
|
98
|
-
|
|
99
|
-
SimpleDuckDuckGoSearchTool()
|
|
100
|
-
|
|
101
|
-
Scrapes DuckDuckGo HTML results and returns the top 5 as formatted text.
|
|
102
|
-
No API key required. No Rust dependency.
|
|
103
|
-
|
|
104
|
-
## License
|
|
105
|
-
|
|
106
|
-
MIT
|
|
@@ -1,126 +0,0 @@
|
|
|
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,39 +0,0 @@
|
|
|
1
|
-
"""Lightweight tools for smolagents."""
|
|
2
|
-
import requests
|
|
3
|
-
from bs4 import BeautifulSoup
|
|
4
|
-
from smolagents import Tool
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class SimpleDuckDuckGoSearchTool(Tool):
|
|
8
|
-
"""Lightweight DuckDuckGo web search tool (HTML scraping, no Rust)."""
|
|
9
|
-
|
|
10
|
-
name = "web_search"
|
|
11
|
-
description = "Search the web for information via DuckDuckGo."
|
|
12
|
-
inputs = {
|
|
13
|
-
"query": {
|
|
14
|
-
"type": "string",
|
|
15
|
-
"description": "The search query.",
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
output_type = "string"
|
|
19
|
-
|
|
20
|
-
def forward(self, query: str) -> str:
|
|
21
|
-
url = "https://html.duckduckgo.com/html/"
|
|
22
|
-
headers = {"User-Agent": "Mozilla/5.0"}
|
|
23
|
-
params = {"q": query}
|
|
24
|
-
try:
|
|
25
|
-
response = requests.post(url, data=params, headers=headers, timeout=10)
|
|
26
|
-
response.raise_for_status()
|
|
27
|
-
soup = BeautifulSoup(response.text, "html.parser")
|
|
28
|
-
results = []
|
|
29
|
-
for result in soup.select(".result")[:5]:
|
|
30
|
-
title_tag = result.select_one(".result__a")
|
|
31
|
-
snippet_tag = result.select_one(".result__snippet")
|
|
32
|
-
if title_tag and snippet_tag:
|
|
33
|
-
title = title_tag.get_text(strip=True)
|
|
34
|
-
link = title_tag["href"]
|
|
35
|
-
snippet = snippet_tag.get_text(strip=True)
|
|
36
|
-
results.append(f"- {title}\n {link}\n {snippet}")
|
|
37
|
-
return "\n\n".join(results) if results else "No results found."
|
|
38
|
-
except Exception as e:
|
|
39
|
-
return f"Search error: {e}"
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: lightmodel
|
|
3
|
-
Version: 0.1.4
|
|
4
|
-
Summary: Ultra-light Model adapter and web search tool for smolagents - works with any OpenAI-compatible API
|
|
5
|
-
Author-email: PlimbAi <plimb.ai.contact@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Keywords: smolagents,llm,ai,agent,openai,nvidia-nim,duckduckgo,web-search
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.9
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENSE
|
|
14
|
-
Requires-Dist: requests>=2.28
|
|
15
|
-
Provides-Extra: smolagents
|
|
16
|
-
Requires-Dist: smolagents>=1.0; extra == "smolagents"
|
|
17
|
-
Provides-Extra: search
|
|
18
|
-
Requires-Dist: beautifulsoup4>=4.12; extra == "search"
|
|
19
|
-
Dynamic: license-file
|
|
20
|
-
|
|
21
|
-
# LightModel
|
|
22
|
-
|
|
23
|
-
Ultra-light **Model adapter** and **web search tool** for [smolagents](https://github.com/huggingface/smolagents).
|
|
24
|
-
|
|
25
|
-
Works with **any** OpenAI-compatible API: NVIDIA NIM, Ollama, vLLM, LM Studio,
|
|
26
|
-
Groq, OpenRouter, Together, DeepSeek, or any custom URL. No heavy dependencies,
|
|
27
|
-
no Rust compilation, runs everywhere (including Termux on Android).
|
|
28
|
-
|
|
29
|
-
## Features
|
|
30
|
-
|
|
31
|
-
- `LightModel`: a drop-in `Model` adapter for smolagents using only `requests`.
|
|
32
|
-
- `SimpleDuckDuckGoSearchTool`: a lightweight web search tool that scrapes
|
|
33
|
-
DuckDuckGo HTML and does not require `ddgs`, `primp`, or Rust toolchain.
|
|
34
|
-
- Zero heavy dependencies: only `requests` (and `beautifulsoup4` for search).
|
|
35
|
-
- Works with any endpoint exposing an OpenAI-compatible `/chat/completions` API.
|
|
36
|
-
|
|
37
|
-
## Installation
|
|
38
|
-
|
|
39
|
-
pip install lightmodel
|
|
40
|
-
pip install lightmodel[smolagents] # with smolagents
|
|
41
|
-
pip install lightmodel[search] # with beautifulsoup4 for web search
|
|
42
|
-
|
|
43
|
-
On Termux or restricted environments, install `smolagents` without its heavy
|
|
44
|
-
dependencies:
|
|
45
|
-
|
|
46
|
-
export HF_HUB_DISABLE_XET=1
|
|
47
|
-
pip install --no-deps smolagents
|
|
48
|
-
pip install --no-deps "huggingface-hub==0.31.2"
|
|
49
|
-
pip install rich jinja2 pillow python-dotenv requests beautifulsoup4
|
|
50
|
-
pip install lightmodel
|
|
51
|
-
|
|
52
|
-
## Usage
|
|
53
|
-
|
|
54
|
-
### Model only
|
|
55
|
-
|
|
56
|
-
from smolagents import CodeAgent
|
|
57
|
-
from lightmodel import LightModel
|
|
58
|
-
|
|
59
|
-
model = LightModel(
|
|
60
|
-
model_id="meta/llama3-70b-instruct",
|
|
61
|
-
api_base="https://integrate.api.nvidia.com/v1",
|
|
62
|
-
api_key="your_nvidia_key",
|
|
63
|
-
)
|
|
64
|
-
agent = CodeAgent(tools=[], model=model)
|
|
65
|
-
agent.run("Explain quantum entanglement in one sentence.")
|
|
66
|
-
|
|
67
|
-
### Model + web search
|
|
68
|
-
|
|
69
|
-
from smolagents import CodeAgent
|
|
70
|
-
from lightmodel import LightModel, SimpleDuckDuckGoSearchTool
|
|
71
|
-
|
|
72
|
-
model = LightModel(
|
|
73
|
-
model_id="meta/llama3-70b-instruct",
|
|
74
|
-
api_base="https://integrate.api.nvidia.com/v1",
|
|
75
|
-
api_key="your_nvidia_key",
|
|
76
|
-
)
|
|
77
|
-
agent = CodeAgent(
|
|
78
|
-
tools=[SimpleDuckDuckGoSearchTool()],
|
|
79
|
-
model=model,
|
|
80
|
-
)
|
|
81
|
-
agent.run("What are the latest AI news in 2026?")
|
|
82
|
-
|
|
83
|
-
### With Ollama (local)
|
|
84
|
-
|
|
85
|
-
model = LightModel(
|
|
86
|
-
model_id="llama3",
|
|
87
|
-
api_base="http://localhost:11434/v1",
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
### With any custom URL
|
|
91
|
-
|
|
92
|
-
model = LightModel(
|
|
93
|
-
model_id="my-model",
|
|
94
|
-
api_base="https://my-server.example.com/v1",
|
|
95
|
-
api_key="my-key",
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
## API reference
|
|
99
|
-
|
|
100
|
-
### `LightModel`
|
|
101
|
-
|
|
102
|
-
LightModel(
|
|
103
|
-
model_id: str,
|
|
104
|
-
api_base: str,
|
|
105
|
-
api_key: str = "not-needed",
|
|
106
|
-
temperature: float = 0.7,
|
|
107
|
-
max_tokens: int = 2048,
|
|
108
|
-
timeout: int = 120,
|
|
109
|
-
extra: dict | None = None,
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
- `model_id`: model name sent to the API.
|
|
113
|
-
- `api_base`: base URL. `/chat/completions` is appended automatically.
|
|
114
|
-
- `api_key`: bearer token (optional for local servers).
|
|
115
|
-
- `extra`: extra JSON fields merged into the request payload.
|
|
116
|
-
|
|
117
|
-
### `SimpleDuckDuckGoSearchTool`
|
|
118
|
-
|
|
119
|
-
SimpleDuckDuckGoSearchTool()
|
|
120
|
-
|
|
121
|
-
Scrapes DuckDuckGo HTML results and returns the top 5 as formatted text.
|
|
122
|
-
No API key required. No Rust dependency.
|
|
123
|
-
|
|
124
|
-
## License
|
|
125
|
-
|
|
126
|
-
MIT
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|