lightmodel 0.1.3__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.
@@ -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.1.3"
8
- description = "Ultra-light Model adapter and web search tool for smolagents - works with any OpenAI-compatible API"
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 = {text = "MIT"}
12
- authors = [{name = "PlimbAi", email = "plimb.ai.contact@gmail.com"}]
13
- keywords = ["smolagents", "llm", "ai", "agent", "openai", "nvidia-nim", "duckduckgo", "web-search"]
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,7 @@
1
+ """LightModel - Ultra-light autonomous AI agents."""
2
+ from .model import LightModel
3
+ from .tools import Tool, SimpleDuckDuckGoSearchTool
4
+ from .agent import Agent
5
+
6
+ __version__ = "0.2.0"
7
+ __all__ = ["LightModel", "Tool", "SimpleDuckDuckGoSearchTool", "Agent"]
@@ -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
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/lightmodel/__init__.py
5
+ src/lightmodel/agent.py
5
6
  src/lightmodel/model.py
6
7
  src/lightmodel/tools.py
7
8
  src/lightmodel.egg-info/PKG-INFO
@@ -2,6 +2,3 @@ requests>=2.28
2
2
 
3
3
  [search]
4
4
  beautifulsoup4>=4.12
5
-
6
- [smolagents]
7
- smolagents>=1.0
lightmodel-0.1.3/PKG-INFO DELETED
@@ -1,126 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: lightmodel
3
- Version: 0.1.3
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
@@ -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,6 +0,0 @@
1
- """LightModel - Ultra-light Model adapter and web search tool for smolagents."""
2
- from .model import LightModel
3
- from .tools import SimpleDuckDuckGoSearchTool
4
-
5
- __version__ = "0.1.0"
6
- __all__ = ["LightModel", "SimpleDuckDuckGoSearchTool"]
@@ -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": "user",
62
- "tool": "user",
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
@@ -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.3
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