lmcore 0.1.1__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.
- lmcore-0.1.1/PKG-INFO +242 -0
- lmcore-0.1.1/README.md +215 -0
- lmcore-0.1.1/lmcore/__init__.py +13 -0
- lmcore-0.1.1/lmcore/config.py +24 -0
- lmcore-0.1.1/lmcore/llm.py +102 -0
- lmcore-0.1.1/lmcore/logging_utils.py +49 -0
- lmcore-0.1.1/lmcore.egg-info/PKG-INFO +242 -0
- lmcore-0.1.1/lmcore.egg-info/SOURCES.txt +11 -0
- lmcore-0.1.1/lmcore.egg-info/dependency_links.txt +1 -0
- lmcore-0.1.1/lmcore.egg-info/requires.txt +16 -0
- lmcore-0.1.1/lmcore.egg-info/top_level.txt +1 -0
- lmcore-0.1.1/pyproject.toml +36 -0
- lmcore-0.1.1/setup.cfg +4 -0
lmcore-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lmcore
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Personal core toolkit for AI projects — config loading, LLM client factory, and logging.
|
|
5
|
+
Author-email: Luigi Medrano <luigimedrano03@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/lm45562/lmcore
|
|
8
|
+
Project-URL: Repository, https://github.com/lm45562/lmcore
|
|
9
|
+
Keywords: ai,llm,openai,anthropic,ollama,config,logging
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
16
|
+
Requires-Dist: pyyaml>=6.0
|
|
17
|
+
Provides-Extra: ollama
|
|
18
|
+
Requires-Dist: ollama>=0.2.0; extra == "ollama"
|
|
19
|
+
Provides-Extra: openai
|
|
20
|
+
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
21
|
+
Provides-Extra: anthropic
|
|
22
|
+
Requires-Dist: anthropic>=0.25.0; extra == "anthropic"
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: ollama>=0.2.0; extra == "all"
|
|
25
|
+
Requires-Dist: openai>=1.0.0; extra == "all"
|
|
26
|
+
Requires-Dist: anthropic>=0.25.0; extra == "all"
|
|
27
|
+
|
|
28
|
+
# lmcore
|
|
29
|
+
|
|
30
|
+
Personal core toolkit for AI projects. Install once, call with inputs, never edit the package.
|
|
31
|
+
|
|
32
|
+
Covers three things every project needs:
|
|
33
|
+
- **Config** — load and validate `.env` secrets
|
|
34
|
+
- **LLM** — build AI clients from a `models.yaml` file
|
|
35
|
+
- **Logging** — colored, consistent logging across all modules
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install lmcore
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
With specific provider dependencies:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install lmcore[openai]
|
|
49
|
+
pip install lmcore[anthropic]
|
|
50
|
+
pip install lmcore[ollama]
|
|
51
|
+
pip install lmcore[all] # all three providers
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Config
|
|
57
|
+
|
|
58
|
+
Call `load_config` once at startup. Pass the keys your project needs — the package handles loading from `.env`, validating, and returning a plain dict.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from lmcore import load_config
|
|
62
|
+
|
|
63
|
+
cfg = load_config(
|
|
64
|
+
required=["OPENAI_API_KEY", "CHROMA_API_KEY"],
|
|
65
|
+
optional={"ACTIVE_ARCH": "arch_1", "LOG_LEVEL": "INFO"}
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `required` — must exist in `.env`, raises `EnvironmentError` if any are missing
|
|
70
|
+
- `optional` — uses the default value if not set in `.env`
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## LLM
|
|
75
|
+
|
|
76
|
+
### models.yaml
|
|
77
|
+
|
|
78
|
+
Lives in your **project root**. Define only what your project uses — `chat` and `embed` are both optional. Each section is a list so you can define multiple clients and reference them by index.
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
arch_1:
|
|
82
|
+
chat:
|
|
83
|
+
- provider: openai
|
|
84
|
+
model: gpt-4o
|
|
85
|
+
api_key_env: OPENAI_API_KEY
|
|
86
|
+
- provider: anthropic
|
|
87
|
+
model: claude-sonnet-4-6
|
|
88
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
89
|
+
embed:
|
|
90
|
+
- provider: ollama
|
|
91
|
+
model: nomic-embed-text
|
|
92
|
+
base_url_env: OLLAMA_URL
|
|
93
|
+
|
|
94
|
+
arch_2:
|
|
95
|
+
chat:
|
|
96
|
+
- provider: anthropic
|
|
97
|
+
model: claude-opus-4-8
|
|
98
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Supported fields per client:**
|
|
102
|
+
|
|
103
|
+
| Field | Required | Description |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `provider` | yes | `ollama`, `openai`, or `anthropic` |
|
|
106
|
+
| `model` | yes | model name string |
|
|
107
|
+
| `api_key_env` | when needed | env var name that holds the API key |
|
|
108
|
+
| `base_url` | no | override default endpoint (e.g. local Ollama) |
|
|
109
|
+
| `base_url_env` | no | env var name that holds the base URL (alternative to `base_url`) |
|
|
110
|
+
|
|
111
|
+
### Getting clients
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from lmcore import get_chat_client, get_embed_client
|
|
115
|
+
|
|
116
|
+
# index defaults to 0 — covers most single-client projects
|
|
117
|
+
chat = get_chat_client("models.yaml", arch="arch_1")
|
|
118
|
+
embed = get_embed_client("models.yaml", arch="arch_1")
|
|
119
|
+
|
|
120
|
+
# specify index for multiple clients
|
|
121
|
+
openai_client = get_chat_client("models.yaml", arch="arch_1", index=0)
|
|
122
|
+
anthropic_client = get_chat_client("models.yaml", arch="arch_1", index=1)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Adding a custom provider
|
|
126
|
+
|
|
127
|
+
If you need a provider not built into the package, register it in your project:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from lmcore import register_provider
|
|
131
|
+
|
|
132
|
+
def _build_groq(cfg):
|
|
133
|
+
from groq import Groq
|
|
134
|
+
return Groq(api_key=cfg["api_key"])
|
|
135
|
+
|
|
136
|
+
register_provider("groq", _build_groq)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Once registered, `groq` works as a `provider:` value in `models.yaml` like any built-in.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Logging
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from lmcore import configure_logging, get_logger
|
|
147
|
+
|
|
148
|
+
configure_logging() # call once at app startup
|
|
149
|
+
configure_logging("DEBUG") # optional level override
|
|
150
|
+
|
|
151
|
+
logger = get_logger(__name__)
|
|
152
|
+
|
|
153
|
+
logger.info("Starting up")
|
|
154
|
+
logger.warning("Something looks off")
|
|
155
|
+
logger.error("Connection failed")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Color scheme:
|
|
159
|
+
|
|
160
|
+
| Color | Level |
|
|
161
|
+
|---|---|
|
|
162
|
+
| Cyan | INFO |
|
|
163
|
+
| Yellow | WARNING |
|
|
164
|
+
| Red | ERROR / CRITICAL |
|
|
165
|
+
| Dim | DEBUG |
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Full Example
|
|
170
|
+
|
|
171
|
+
**Project layout:**
|
|
172
|
+
```
|
|
173
|
+
my-project/
|
|
174
|
+
├── .env
|
|
175
|
+
├── models.yaml
|
|
176
|
+
└── app/
|
|
177
|
+
└── core/
|
|
178
|
+
├── config.py
|
|
179
|
+
└── llm.py
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**`.env`:**
|
|
183
|
+
```
|
|
184
|
+
OPENAI_API_KEY=sk-...
|
|
185
|
+
ACTIVE_ARCH=arch_1
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**`models.yaml`:**
|
|
189
|
+
```yaml
|
|
190
|
+
arch_1:
|
|
191
|
+
chat:
|
|
192
|
+
- provider: openai
|
|
193
|
+
model: gpt-4o
|
|
194
|
+
api_key_env: OPENAI_API_KEY
|
|
195
|
+
- provider: ollama
|
|
196
|
+
model: llama3
|
|
197
|
+
base_url_env: OLLAMA_URL
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**`app/core/config.py`:**
|
|
201
|
+
```python
|
|
202
|
+
from lmcore import load_config, configure_logging
|
|
203
|
+
|
|
204
|
+
configure_logging()
|
|
205
|
+
|
|
206
|
+
cfg = load_config(
|
|
207
|
+
required=["OPENAI_API_KEY"],
|
|
208
|
+
optional={"ACTIVE_ARCH": "arch_1"}
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**`app/core/llm.py`:**
|
|
213
|
+
```python
|
|
214
|
+
from lmcore import get_chat_client
|
|
215
|
+
from app.core.config import cfg
|
|
216
|
+
|
|
217
|
+
chat = get_chat_client("models.yaml", arch=cfg["ACTIVE_ARCH"])
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Anywhere in the project:**
|
|
221
|
+
```python
|
|
222
|
+
from lmcore import get_logger
|
|
223
|
+
from app.core.llm import chat
|
|
224
|
+
|
|
225
|
+
logger = get_logger(__name__)
|
|
226
|
+
|
|
227
|
+
response = chat.chat(
|
|
228
|
+
model="gpt-4o",
|
|
229
|
+
messages=[{"role": "user", "content": "Summarize this document."}]
|
|
230
|
+
)
|
|
231
|
+
logger.info(f"Done: {response.choices[0].message.content}")
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Built-in Providers
|
|
237
|
+
|
|
238
|
+
| Provider | `provider:` value | Install extra |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| Ollama | `ollama` | `pip install lmcore[ollama]` |
|
|
241
|
+
| OpenAI | `openai` | `pip install lmcore[openai]` |
|
|
242
|
+
| Anthropic | `anthropic` | `pip install lmcore[anthropic]` |
|
lmcore-0.1.1/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# lmcore
|
|
2
|
+
|
|
3
|
+
Personal core toolkit for AI projects. Install once, call with inputs, never edit the package.
|
|
4
|
+
|
|
5
|
+
Covers three things every project needs:
|
|
6
|
+
- **Config** — load and validate `.env` secrets
|
|
7
|
+
- **LLM** — build AI clients from a `models.yaml` file
|
|
8
|
+
- **Logging** — colored, consistent logging across all modules
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install lmcore
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
With specific provider dependencies:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install lmcore[openai]
|
|
22
|
+
pip install lmcore[anthropic]
|
|
23
|
+
pip install lmcore[ollama]
|
|
24
|
+
pip install lmcore[all] # all three providers
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Config
|
|
30
|
+
|
|
31
|
+
Call `load_config` once at startup. Pass the keys your project needs — the package handles loading from `.env`, validating, and returning a plain dict.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from lmcore import load_config
|
|
35
|
+
|
|
36
|
+
cfg = load_config(
|
|
37
|
+
required=["OPENAI_API_KEY", "CHROMA_API_KEY"],
|
|
38
|
+
optional={"ACTIVE_ARCH": "arch_1", "LOG_LEVEL": "INFO"}
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- `required` — must exist in `.env`, raises `EnvironmentError` if any are missing
|
|
43
|
+
- `optional` — uses the default value if not set in `.env`
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## LLM
|
|
48
|
+
|
|
49
|
+
### models.yaml
|
|
50
|
+
|
|
51
|
+
Lives in your **project root**. Define only what your project uses — `chat` and `embed` are both optional. Each section is a list so you can define multiple clients and reference them by index.
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
arch_1:
|
|
55
|
+
chat:
|
|
56
|
+
- provider: openai
|
|
57
|
+
model: gpt-4o
|
|
58
|
+
api_key_env: OPENAI_API_KEY
|
|
59
|
+
- provider: anthropic
|
|
60
|
+
model: claude-sonnet-4-6
|
|
61
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
62
|
+
embed:
|
|
63
|
+
- provider: ollama
|
|
64
|
+
model: nomic-embed-text
|
|
65
|
+
base_url_env: OLLAMA_URL
|
|
66
|
+
|
|
67
|
+
arch_2:
|
|
68
|
+
chat:
|
|
69
|
+
- provider: anthropic
|
|
70
|
+
model: claude-opus-4-8
|
|
71
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Supported fields per client:**
|
|
75
|
+
|
|
76
|
+
| Field | Required | Description |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `provider` | yes | `ollama`, `openai`, or `anthropic` |
|
|
79
|
+
| `model` | yes | model name string |
|
|
80
|
+
| `api_key_env` | when needed | env var name that holds the API key |
|
|
81
|
+
| `base_url` | no | override default endpoint (e.g. local Ollama) |
|
|
82
|
+
| `base_url_env` | no | env var name that holds the base URL (alternative to `base_url`) |
|
|
83
|
+
|
|
84
|
+
### Getting clients
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from lmcore import get_chat_client, get_embed_client
|
|
88
|
+
|
|
89
|
+
# index defaults to 0 — covers most single-client projects
|
|
90
|
+
chat = get_chat_client("models.yaml", arch="arch_1")
|
|
91
|
+
embed = get_embed_client("models.yaml", arch="arch_1")
|
|
92
|
+
|
|
93
|
+
# specify index for multiple clients
|
|
94
|
+
openai_client = get_chat_client("models.yaml", arch="arch_1", index=0)
|
|
95
|
+
anthropic_client = get_chat_client("models.yaml", arch="arch_1", index=1)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Adding a custom provider
|
|
99
|
+
|
|
100
|
+
If you need a provider not built into the package, register it in your project:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from lmcore import register_provider
|
|
104
|
+
|
|
105
|
+
def _build_groq(cfg):
|
|
106
|
+
from groq import Groq
|
|
107
|
+
return Groq(api_key=cfg["api_key"])
|
|
108
|
+
|
|
109
|
+
register_provider("groq", _build_groq)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Once registered, `groq` works as a `provider:` value in `models.yaml` like any built-in.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Logging
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from lmcore import configure_logging, get_logger
|
|
120
|
+
|
|
121
|
+
configure_logging() # call once at app startup
|
|
122
|
+
configure_logging("DEBUG") # optional level override
|
|
123
|
+
|
|
124
|
+
logger = get_logger(__name__)
|
|
125
|
+
|
|
126
|
+
logger.info("Starting up")
|
|
127
|
+
logger.warning("Something looks off")
|
|
128
|
+
logger.error("Connection failed")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Color scheme:
|
|
132
|
+
|
|
133
|
+
| Color | Level |
|
|
134
|
+
|---|---|
|
|
135
|
+
| Cyan | INFO |
|
|
136
|
+
| Yellow | WARNING |
|
|
137
|
+
| Red | ERROR / CRITICAL |
|
|
138
|
+
| Dim | DEBUG |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Full Example
|
|
143
|
+
|
|
144
|
+
**Project layout:**
|
|
145
|
+
```
|
|
146
|
+
my-project/
|
|
147
|
+
├── .env
|
|
148
|
+
├── models.yaml
|
|
149
|
+
└── app/
|
|
150
|
+
└── core/
|
|
151
|
+
├── config.py
|
|
152
|
+
└── llm.py
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**`.env`:**
|
|
156
|
+
```
|
|
157
|
+
OPENAI_API_KEY=sk-...
|
|
158
|
+
ACTIVE_ARCH=arch_1
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**`models.yaml`:**
|
|
162
|
+
```yaml
|
|
163
|
+
arch_1:
|
|
164
|
+
chat:
|
|
165
|
+
- provider: openai
|
|
166
|
+
model: gpt-4o
|
|
167
|
+
api_key_env: OPENAI_API_KEY
|
|
168
|
+
- provider: ollama
|
|
169
|
+
model: llama3
|
|
170
|
+
base_url_env: OLLAMA_URL
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**`app/core/config.py`:**
|
|
174
|
+
```python
|
|
175
|
+
from lmcore import load_config, configure_logging
|
|
176
|
+
|
|
177
|
+
configure_logging()
|
|
178
|
+
|
|
179
|
+
cfg = load_config(
|
|
180
|
+
required=["OPENAI_API_KEY"],
|
|
181
|
+
optional={"ACTIVE_ARCH": "arch_1"}
|
|
182
|
+
)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**`app/core/llm.py`:**
|
|
186
|
+
```python
|
|
187
|
+
from lmcore import get_chat_client
|
|
188
|
+
from app.core.config import cfg
|
|
189
|
+
|
|
190
|
+
chat = get_chat_client("models.yaml", arch=cfg["ACTIVE_ARCH"])
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Anywhere in the project:**
|
|
194
|
+
```python
|
|
195
|
+
from lmcore import get_logger
|
|
196
|
+
from app.core.llm import chat
|
|
197
|
+
|
|
198
|
+
logger = get_logger(__name__)
|
|
199
|
+
|
|
200
|
+
response = chat.chat(
|
|
201
|
+
model="gpt-4o",
|
|
202
|
+
messages=[{"role": "user", "content": "Summarize this document."}]
|
|
203
|
+
)
|
|
204
|
+
logger.info(f"Done: {response.choices[0].message.content}")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Built-in Providers
|
|
210
|
+
|
|
211
|
+
| Provider | `provider:` value | Install extra |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| Ollama | `ollama` | `pip install lmcore[ollama]` |
|
|
214
|
+
| OpenAI | `openai` | `pip install lmcore[openai]` |
|
|
215
|
+
| Anthropic | `anthropic` | `pip install lmcore[anthropic]` |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .config import load_config
|
|
2
|
+
from .llm import get_chat_client, get_embed_client, register_provider
|
|
3
|
+
from .logging_utils import configure_logging, get_logger, Colors
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"load_config",
|
|
7
|
+
"get_chat_client",
|
|
8
|
+
"get_embed_client",
|
|
9
|
+
"register_provider",
|
|
10
|
+
"configure_logging",
|
|
11
|
+
"get_logger",
|
|
12
|
+
"Colors",
|
|
13
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from dotenv import load_dotenv
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def load_config(required: list[str], optional: dict[str, str] = {}) -> dict:
|
|
6
|
+
load_dotenv()
|
|
7
|
+
|
|
8
|
+
config = {}
|
|
9
|
+
missing = []
|
|
10
|
+
|
|
11
|
+
for key in required:
|
|
12
|
+
val = os.getenv(key)
|
|
13
|
+
if val is None:
|
|
14
|
+
missing.append(key)
|
|
15
|
+
else:
|
|
16
|
+
config[key] = val
|
|
17
|
+
|
|
18
|
+
if missing:
|
|
19
|
+
raise EnvironmentError(f"Missing required env vars: {missing}")
|
|
20
|
+
|
|
21
|
+
for key, default in optional.items():
|
|
22
|
+
config[key] = os.getenv(key, default)
|
|
23
|
+
|
|
24
|
+
return config
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import yaml
|
|
3
|
+
import os
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
_registry: dict[str, Callable] = {}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register_provider(name: str, factory_fn: Callable) -> None:
|
|
10
|
+
_registry[name] = factory_fn
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _load_arch(models_yaml_path: str, arch: str) -> dict:
|
|
14
|
+
if not os.path.exists(models_yaml_path):
|
|
15
|
+
raise FileNotFoundError(f"models.yaml not found at: {models_yaml_path}")
|
|
16
|
+
|
|
17
|
+
with open(models_yaml_path, "r") as f:
|
|
18
|
+
data = yaml.safe_load(f)
|
|
19
|
+
|
|
20
|
+
if arch not in data:
|
|
21
|
+
available = list(data.keys())
|
|
22
|
+
raise KeyError(f"Arch '{arch}' not found in {models_yaml_path}. Available: {available}")
|
|
23
|
+
|
|
24
|
+
return data[arch]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _resolve_api_key(cfg: dict) -> dict:
|
|
28
|
+
if "api_key_env" in cfg:
|
|
29
|
+
key = os.getenv(cfg["api_key_env"])
|
|
30
|
+
if key is None:
|
|
31
|
+
raise EnvironmentError(f"Env var '{cfg['api_key_env']}' is not set")
|
|
32
|
+
cfg = {**cfg, "api_key": key}
|
|
33
|
+
if "base_url_env" in cfg:
|
|
34
|
+
url = os.getenv(cfg["base_url_env"])
|
|
35
|
+
if url is None:
|
|
36
|
+
raise EnvironmentError(f"Env var '{cfg['base_url_env']}' is not set")
|
|
37
|
+
cfg = {**cfg, "base_url": url}
|
|
38
|
+
return cfg
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_chat_client(models_yaml_path: str, arch: str, index: int = 0):
|
|
42
|
+
arch_config = _load_arch(models_yaml_path, arch)
|
|
43
|
+
|
|
44
|
+
if "chat" not in arch_config:
|
|
45
|
+
raise ValueError(f"Arch '{arch}' has no chat config in {models_yaml_path}")
|
|
46
|
+
|
|
47
|
+
clients = arch_config["chat"]
|
|
48
|
+
if index >= len(clients):
|
|
49
|
+
raise IndexError(
|
|
50
|
+
f"Arch '{arch}' has {len(clients)} chat client(s), index {index} is out of range"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
cfg = _resolve_api_key(clients[index])
|
|
54
|
+
provider = cfg["provider"]
|
|
55
|
+
|
|
56
|
+
if provider not in _registry:
|
|
57
|
+
raise ValueError(f"Provider '{provider}' is not registered. Use register_provider() to add it.")
|
|
58
|
+
|
|
59
|
+
return _registry[provider](cfg)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_embed_client(models_yaml_path: str, arch: str, index: int = 0):
|
|
63
|
+
arch_config = _load_arch(models_yaml_path, arch)
|
|
64
|
+
|
|
65
|
+
if "embed" not in arch_config:
|
|
66
|
+
raise ValueError(f"Arch '{arch}' has no embed config in {models_yaml_path}")
|
|
67
|
+
|
|
68
|
+
clients = arch_config["embed"]
|
|
69
|
+
if index >= len(clients):
|
|
70
|
+
raise IndexError(
|
|
71
|
+
f"Arch '{arch}' has {len(clients)} embed client(s), index {index} is out of range"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
cfg = _resolve_api_key(clients[index])
|
|
75
|
+
provider = cfg["provider"]
|
|
76
|
+
|
|
77
|
+
if provider not in _registry:
|
|
78
|
+
raise ValueError(f"Provider '{provider}' is not registered. Use register_provider() to add it.")
|
|
79
|
+
|
|
80
|
+
return _registry[provider](cfg)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# --- Built-in provider factories ---
|
|
84
|
+
|
|
85
|
+
def _build_ollama(cfg: dict):
|
|
86
|
+
from ollama import Client
|
|
87
|
+
return Client(host=cfg.get("base_url", "http://localhost:11434"))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _build_openai(cfg: dict):
|
|
91
|
+
from openai import OpenAI
|
|
92
|
+
return OpenAI(api_key=cfg["api_key"], base_url=cfg.get("base_url"))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _build_anthropic(cfg: dict):
|
|
96
|
+
from anthropic import Anthropic
|
|
97
|
+
return Anthropic(api_key=cfg["api_key"])
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
register_provider("ollama", _build_ollama)
|
|
101
|
+
register_provider("openai", _build_openai)
|
|
102
|
+
register_provider("anthropic", _build_anthropic)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Colors:
|
|
5
|
+
HEADER = '\033[95m'
|
|
6
|
+
OKBLUE = '\033[94m'
|
|
7
|
+
OKCYAN = '\033[96m'
|
|
8
|
+
OKGREEN = '\033[92m'
|
|
9
|
+
WARNING = '\033[93m'
|
|
10
|
+
FAIL = '\033[91m'
|
|
11
|
+
ENDC = '\033[0m'
|
|
12
|
+
BOLD = '\033[1m'
|
|
13
|
+
UNDERLINE = '\033[4m'
|
|
14
|
+
DIM = '\033[2m'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _ColorFormatter(logging.Formatter):
|
|
18
|
+
_LEVEL_COLORS = {
|
|
19
|
+
logging.DEBUG: Colors.DIM,
|
|
20
|
+
logging.INFO: Colors.OKCYAN,
|
|
21
|
+
logging.WARNING: Colors.WARNING,
|
|
22
|
+
logging.ERROR: Colors.FAIL,
|
|
23
|
+
logging.CRITICAL: Colors.FAIL + Colors.BOLD,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
27
|
+
color = self._LEVEL_COLORS.get(record.levelno, "")
|
|
28
|
+
record.levelname = f"{color}{record.levelname}{Colors.ENDC}"
|
|
29
|
+
record.name = f"{Colors.OKBLUE}{record.name}{Colors.ENDC}"
|
|
30
|
+
return super().format(record)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def configure_logging(level: str = "INFO") -> None:
|
|
34
|
+
numeric = getattr(logging, level.upper(), logging.INFO)
|
|
35
|
+
|
|
36
|
+
handler = logging.StreamHandler()
|
|
37
|
+
handler.setFormatter(
|
|
38
|
+
_ColorFormatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
root = logging.getLogger()
|
|
42
|
+
if not root.handlers:
|
|
43
|
+
root.addHandler(handler)
|
|
44
|
+
|
|
45
|
+
root.setLevel(numeric)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_logger(name: str) -> logging.Logger:
|
|
49
|
+
return logging.getLogger(name)
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lmcore
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Personal core toolkit for AI projects — config loading, LLM client factory, and logging.
|
|
5
|
+
Author-email: Luigi Medrano <luigimedrano03@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/lm45562/lmcore
|
|
8
|
+
Project-URL: Repository, https://github.com/lm45562/lmcore
|
|
9
|
+
Keywords: ai,llm,openai,anthropic,ollama,config,logging
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
16
|
+
Requires-Dist: pyyaml>=6.0
|
|
17
|
+
Provides-Extra: ollama
|
|
18
|
+
Requires-Dist: ollama>=0.2.0; extra == "ollama"
|
|
19
|
+
Provides-Extra: openai
|
|
20
|
+
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
21
|
+
Provides-Extra: anthropic
|
|
22
|
+
Requires-Dist: anthropic>=0.25.0; extra == "anthropic"
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: ollama>=0.2.0; extra == "all"
|
|
25
|
+
Requires-Dist: openai>=1.0.0; extra == "all"
|
|
26
|
+
Requires-Dist: anthropic>=0.25.0; extra == "all"
|
|
27
|
+
|
|
28
|
+
# lmcore
|
|
29
|
+
|
|
30
|
+
Personal core toolkit for AI projects. Install once, call with inputs, never edit the package.
|
|
31
|
+
|
|
32
|
+
Covers three things every project needs:
|
|
33
|
+
- **Config** — load and validate `.env` secrets
|
|
34
|
+
- **LLM** — build AI clients from a `models.yaml` file
|
|
35
|
+
- **Logging** — colored, consistent logging across all modules
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install lmcore
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
With specific provider dependencies:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install lmcore[openai]
|
|
49
|
+
pip install lmcore[anthropic]
|
|
50
|
+
pip install lmcore[ollama]
|
|
51
|
+
pip install lmcore[all] # all three providers
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Config
|
|
57
|
+
|
|
58
|
+
Call `load_config` once at startup. Pass the keys your project needs — the package handles loading from `.env`, validating, and returning a plain dict.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from lmcore import load_config
|
|
62
|
+
|
|
63
|
+
cfg = load_config(
|
|
64
|
+
required=["OPENAI_API_KEY", "CHROMA_API_KEY"],
|
|
65
|
+
optional={"ACTIVE_ARCH": "arch_1", "LOG_LEVEL": "INFO"}
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `required` — must exist in `.env`, raises `EnvironmentError` if any are missing
|
|
70
|
+
- `optional` — uses the default value if not set in `.env`
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## LLM
|
|
75
|
+
|
|
76
|
+
### models.yaml
|
|
77
|
+
|
|
78
|
+
Lives in your **project root**. Define only what your project uses — `chat` and `embed` are both optional. Each section is a list so you can define multiple clients and reference them by index.
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
arch_1:
|
|
82
|
+
chat:
|
|
83
|
+
- provider: openai
|
|
84
|
+
model: gpt-4o
|
|
85
|
+
api_key_env: OPENAI_API_KEY
|
|
86
|
+
- provider: anthropic
|
|
87
|
+
model: claude-sonnet-4-6
|
|
88
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
89
|
+
embed:
|
|
90
|
+
- provider: ollama
|
|
91
|
+
model: nomic-embed-text
|
|
92
|
+
base_url_env: OLLAMA_URL
|
|
93
|
+
|
|
94
|
+
arch_2:
|
|
95
|
+
chat:
|
|
96
|
+
- provider: anthropic
|
|
97
|
+
model: claude-opus-4-8
|
|
98
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Supported fields per client:**
|
|
102
|
+
|
|
103
|
+
| Field | Required | Description |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `provider` | yes | `ollama`, `openai`, or `anthropic` |
|
|
106
|
+
| `model` | yes | model name string |
|
|
107
|
+
| `api_key_env` | when needed | env var name that holds the API key |
|
|
108
|
+
| `base_url` | no | override default endpoint (e.g. local Ollama) |
|
|
109
|
+
| `base_url_env` | no | env var name that holds the base URL (alternative to `base_url`) |
|
|
110
|
+
|
|
111
|
+
### Getting clients
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from lmcore import get_chat_client, get_embed_client
|
|
115
|
+
|
|
116
|
+
# index defaults to 0 — covers most single-client projects
|
|
117
|
+
chat = get_chat_client("models.yaml", arch="arch_1")
|
|
118
|
+
embed = get_embed_client("models.yaml", arch="arch_1")
|
|
119
|
+
|
|
120
|
+
# specify index for multiple clients
|
|
121
|
+
openai_client = get_chat_client("models.yaml", arch="arch_1", index=0)
|
|
122
|
+
anthropic_client = get_chat_client("models.yaml", arch="arch_1", index=1)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Adding a custom provider
|
|
126
|
+
|
|
127
|
+
If you need a provider not built into the package, register it in your project:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from lmcore import register_provider
|
|
131
|
+
|
|
132
|
+
def _build_groq(cfg):
|
|
133
|
+
from groq import Groq
|
|
134
|
+
return Groq(api_key=cfg["api_key"])
|
|
135
|
+
|
|
136
|
+
register_provider("groq", _build_groq)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Once registered, `groq` works as a `provider:` value in `models.yaml` like any built-in.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Logging
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from lmcore import configure_logging, get_logger
|
|
147
|
+
|
|
148
|
+
configure_logging() # call once at app startup
|
|
149
|
+
configure_logging("DEBUG") # optional level override
|
|
150
|
+
|
|
151
|
+
logger = get_logger(__name__)
|
|
152
|
+
|
|
153
|
+
logger.info("Starting up")
|
|
154
|
+
logger.warning("Something looks off")
|
|
155
|
+
logger.error("Connection failed")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Color scheme:
|
|
159
|
+
|
|
160
|
+
| Color | Level |
|
|
161
|
+
|---|---|
|
|
162
|
+
| Cyan | INFO |
|
|
163
|
+
| Yellow | WARNING |
|
|
164
|
+
| Red | ERROR / CRITICAL |
|
|
165
|
+
| Dim | DEBUG |
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Full Example
|
|
170
|
+
|
|
171
|
+
**Project layout:**
|
|
172
|
+
```
|
|
173
|
+
my-project/
|
|
174
|
+
├── .env
|
|
175
|
+
├── models.yaml
|
|
176
|
+
└── app/
|
|
177
|
+
└── core/
|
|
178
|
+
├── config.py
|
|
179
|
+
└── llm.py
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**`.env`:**
|
|
183
|
+
```
|
|
184
|
+
OPENAI_API_KEY=sk-...
|
|
185
|
+
ACTIVE_ARCH=arch_1
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**`models.yaml`:**
|
|
189
|
+
```yaml
|
|
190
|
+
arch_1:
|
|
191
|
+
chat:
|
|
192
|
+
- provider: openai
|
|
193
|
+
model: gpt-4o
|
|
194
|
+
api_key_env: OPENAI_API_KEY
|
|
195
|
+
- provider: ollama
|
|
196
|
+
model: llama3
|
|
197
|
+
base_url_env: OLLAMA_URL
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**`app/core/config.py`:**
|
|
201
|
+
```python
|
|
202
|
+
from lmcore import load_config, configure_logging
|
|
203
|
+
|
|
204
|
+
configure_logging()
|
|
205
|
+
|
|
206
|
+
cfg = load_config(
|
|
207
|
+
required=["OPENAI_API_KEY"],
|
|
208
|
+
optional={"ACTIVE_ARCH": "arch_1"}
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**`app/core/llm.py`:**
|
|
213
|
+
```python
|
|
214
|
+
from lmcore import get_chat_client
|
|
215
|
+
from app.core.config import cfg
|
|
216
|
+
|
|
217
|
+
chat = get_chat_client("models.yaml", arch=cfg["ACTIVE_ARCH"])
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Anywhere in the project:**
|
|
221
|
+
```python
|
|
222
|
+
from lmcore import get_logger
|
|
223
|
+
from app.core.llm import chat
|
|
224
|
+
|
|
225
|
+
logger = get_logger(__name__)
|
|
226
|
+
|
|
227
|
+
response = chat.chat(
|
|
228
|
+
model="gpt-4o",
|
|
229
|
+
messages=[{"role": "user", "content": "Summarize this document."}]
|
|
230
|
+
)
|
|
231
|
+
logger.info(f"Done: {response.choices[0].message.content}")
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Built-in Providers
|
|
237
|
+
|
|
238
|
+
| Provider | `provider:` value | Install extra |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| Ollama | `ollama` | `pip install lmcore[ollama]` |
|
|
241
|
+
| OpenAI | `openai` | `pip install lmcore[openai]` |
|
|
242
|
+
| Anthropic | `anthropic` | `pip install lmcore[anthropic]` |
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
lmcore/__init__.py
|
|
4
|
+
lmcore/config.py
|
|
5
|
+
lmcore/llm.py
|
|
6
|
+
lmcore/logging_utils.py
|
|
7
|
+
lmcore.egg-info/PKG-INFO
|
|
8
|
+
lmcore.egg-info/SOURCES.txt
|
|
9
|
+
lmcore.egg-info/dependency_links.txt
|
|
10
|
+
lmcore.egg-info/requires.txt
|
|
11
|
+
lmcore.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lmcore
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lmcore"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Personal core toolkit for AI projects — config loading, LLM client factory, and logging."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Luigi Medrano", email = "luigimedrano03@gmail.com" }]
|
|
13
|
+
keywords = ["ai", "llm", "openai", "anthropic", "ollama", "config", "logging"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"python-dotenv>=1.0.0",
|
|
21
|
+
"pyyaml>=6.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
ollama = ["ollama>=0.2.0"]
|
|
26
|
+
openai = ["openai>=1.0.0"]
|
|
27
|
+
anthropic = ["anthropic>=0.25.0"]
|
|
28
|
+
all = ["ollama>=0.2.0", "openai>=1.0.0", "anthropic>=0.25.0"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/lm45562/lmcore"
|
|
32
|
+
Repository = "https://github.com/lm45562/lmcore"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["."]
|
|
36
|
+
include = ["lmcore*"]
|
lmcore-0.1.1/setup.cfg
ADDED