llm-agent-base 0.1.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.
- llm_agent_base-0.1.0/PKG-INFO +182 -0
- llm_agent_base-0.1.0/README.md +157 -0
- llm_agent_base-0.1.0/llm_agent_base/__init__.py +15 -0
- llm_agent_base-0.1.0/llm_agent_base/agent_base.py +70 -0
- llm_agent_base-0.1.0/llm_agent_base/agent_pipeline_base.py +12 -0
- llm_agent_base-0.1.0/llm_agent_base/knowledge_base.py +145 -0
- llm_agent_base-0.1.0/llm_agent_base/llm_connection_config.py +22 -0
- llm_agent_base-0.1.0/llm_agent_base/tool_calling.py +100 -0
- llm_agent_base-0.1.0/llm_agent_base.egg-info/PKG-INFO +182 -0
- llm_agent_base-0.1.0/llm_agent_base.egg-info/SOURCES.txt +13 -0
- llm_agent_base-0.1.0/llm_agent_base.egg-info/dependency_links.txt +1 -0
- llm_agent_base-0.1.0/llm_agent_base.egg-info/requires.txt +5 -0
- llm_agent_base-0.1.0/llm_agent_base.egg-info/top_level.txt +1 -0
- llm_agent_base-0.1.0/pyproject.toml +39 -0
- llm_agent_base-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llm-agent-base
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight Python framework for building LLM agents with tool calling and RAG
|
|
5
|
+
Author-email: Paweł Gryka <pawel.m.gryka@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mksochota16/llm-agent-base
|
|
8
|
+
Project-URL: Repository, https://github.com/mksochota16/llm-agent-base
|
|
9
|
+
Keywords: llm,agent,rag,openai,tool-calling
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: openai>=2.0.0
|
|
21
|
+
Requires-Dist: faiss-cpu>=1.7.0
|
|
22
|
+
Requires-Dist: pypdf>=4.0.0
|
|
23
|
+
Requires-Dist: numpy>=1.24.0
|
|
24
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
25
|
+
|
|
26
|
+
# llm-agent-base
|
|
27
|
+
|
|
28
|
+
A lightweight Python base for building LLM agents with tool calling and retrieval-augmented generation (RAG). It works with any OpenAI-compatible API (OpenRouter, OpenAI, local models via Ollama, etc.).
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- **Tool calling** — register plain Python functions as LLM-callable tools; schemas are built automatically from type hints and docstrings
|
|
33
|
+
- **RAG** — ingest a folder of documents (`.txt`, `.md`, `.json`, `.pdf`) into a FAISS vector index and inject relevant chunks into every prompt
|
|
34
|
+
- **Pipelines** — chain multiple agents so each agent's output becomes the next agent's input
|
|
35
|
+
- **Debug mode** — optional logging of tool calls and knowledge retrievals
|
|
36
|
+
|
|
37
|
+
## Project structure
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
agent_base.py # AgentBase class
|
|
41
|
+
agent_pipeline_base.py # AgentPipelineBase class
|
|
42
|
+
tool_calling.py # Schema building and tool-call execution loop
|
|
43
|
+
knowledge_base.py # Document ingestion, embedding, and FAISS retrieval
|
|
44
|
+
llm_connection_config.py# LLM client configuration
|
|
45
|
+
knowledge/ # Knowledge files (subdirectory per topic)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Setup
|
|
49
|
+
|
|
50
|
+
**1. Install dependencies**
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install -r requirements.txt
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**2. Configure environment**
|
|
57
|
+
|
|
58
|
+
Copy `.env` and fill in your credentials:
|
|
59
|
+
|
|
60
|
+
```env
|
|
61
|
+
OPENROUTER_API_KEY=your-api-key-here
|
|
62
|
+
MODEL=openai/gpt-4o-mini
|
|
63
|
+
BASE_URL=https://openrouter.ai/api/v1
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The default `BASE_URL` points to [OpenRouter](https://openrouter.ai), which gives access to many models through a single API key. You can swap it for the OpenAI base URL or any other compatible endpoint.
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
### Basic agent
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from agent_base import AgentBase
|
|
74
|
+
from llm_connection_config import LLMConnectionConfig
|
|
75
|
+
|
|
76
|
+
config = LLMConnectionConfig(model="openai/gpt-4o-mini", api_key="...")
|
|
77
|
+
|
|
78
|
+
agent = AgentBase(
|
|
79
|
+
system_prompt="You are a helpful assistant.",
|
|
80
|
+
llm_config=config,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
print(agent.run("What is the capital of France?"))
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Tool calling
|
|
87
|
+
|
|
88
|
+
Register any Python function as a tool. The function name becomes the tool name, the docstring becomes its description, and the type hints define the parameter schema.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
def get_weather(city: str) -> str:
|
|
92
|
+
"""Return the current weather for a given city."""
|
|
93
|
+
return f"The weather in {city} is sunny and 22°C."
|
|
94
|
+
|
|
95
|
+
def add(a: int, b: int) -> int:
|
|
96
|
+
"""Add two integers and return the result."""
|
|
97
|
+
return a + b
|
|
98
|
+
|
|
99
|
+
agent = AgentBase(
|
|
100
|
+
system_prompt="You are a helpful assistant. Use the available tools when needed.",
|
|
101
|
+
llm_config=config,
|
|
102
|
+
)
|
|
103
|
+
agent.register_tool(get_weather)
|
|
104
|
+
agent.register_tool(add)
|
|
105
|
+
|
|
106
|
+
print(agent.run("What is the weather in Tokyo and what is 10 + 20?"))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`register_tool` can also be used as a decorator:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
@agent.register_tool
|
|
113
|
+
def search_orders(order_id: str) -> str:
|
|
114
|
+
"""Look up an order by ID."""
|
|
115
|
+
...
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### RAG (knowledge base)
|
|
119
|
+
|
|
120
|
+
Place your documents in a folder (organised into subdirectories by topic). Call `ingest_knowledge` once to embed and index them, then `run` as normal — relevant chunks are automatically injected into the system prompt.
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
knowledge/
|
|
124
|
+
├── topic/
|
|
125
|
+
│ └── overview.txt
|
|
126
|
+
├── products/
|
|
127
|
+
│ ├── faq.md
|
|
128
|
+
│ └── pricing.json
|
|
129
|
+
└── support/
|
|
130
|
+
└── sla.md
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
agent = AgentBase(
|
|
135
|
+
system_prompt="You are a product assistant. Answer using only the provided context.",
|
|
136
|
+
llm_config=config,
|
|
137
|
+
knowledge_folder_path="knowledge",
|
|
138
|
+
knowledge_index_dir=".kb_index", # where the FAISS index is saved
|
|
139
|
+
knowledge_top_k=3, # number of chunks injected per prompt
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Build and persist the index (run once, or when documents change)
|
|
143
|
+
agent.ingest_knowledge(save=True)
|
|
144
|
+
|
|
145
|
+
# On subsequent runs, load from disk instead of re-embedding
|
|
146
|
+
# agent.load_knowledge()
|
|
147
|
+
|
|
148
|
+
print(agent.run("Who founded the company and when?"))
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Agent pipelines
|
|
152
|
+
|
|
153
|
+
Chain agents so the output of one becomes the input of the next:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from agent_pipeline_base import AgentPipelineBase
|
|
157
|
+
|
|
158
|
+
researcher = AgentBase(
|
|
159
|
+
system_prompt="Extract the key facts from the user's question.",
|
|
160
|
+
llm_config=config,
|
|
161
|
+
)
|
|
162
|
+
writer = AgentBase(
|
|
163
|
+
system_prompt="Turn the provided facts into a concise, friendly summary.",
|
|
164
|
+
llm_config=config,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
pipeline = AgentPipelineBase(agents=[researcher, writer])
|
|
168
|
+
print(pipeline.run("Tell me about the Acme Corp product lineup."))
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Debug mode
|
|
172
|
+
|
|
173
|
+
Pass `debug=True` to any agent to print tool invocations and knowledge retrievals to stdout:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
agent = AgentBase(..., debug=True)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
[debug] Retrieving knowledge
|
|
181
|
+
[debug] tool 'get_weather' args={'city': 'Tokyo'} result=The weather in Tokyo is sunny and 22°C.
|
|
182
|
+
```
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# llm-agent-base
|
|
2
|
+
|
|
3
|
+
A lightweight Python base for building LLM agents with tool calling and retrieval-augmented generation (RAG). It works with any OpenAI-compatible API (OpenRouter, OpenAI, local models via Ollama, etc.).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Tool calling** — register plain Python functions as LLM-callable tools; schemas are built automatically from type hints and docstrings
|
|
8
|
+
- **RAG** — ingest a folder of documents (`.txt`, `.md`, `.json`, `.pdf`) into a FAISS vector index and inject relevant chunks into every prompt
|
|
9
|
+
- **Pipelines** — chain multiple agents so each agent's output becomes the next agent's input
|
|
10
|
+
- **Debug mode** — optional logging of tool calls and knowledge retrievals
|
|
11
|
+
|
|
12
|
+
## Project structure
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
agent_base.py # AgentBase class
|
|
16
|
+
agent_pipeline_base.py # AgentPipelineBase class
|
|
17
|
+
tool_calling.py # Schema building and tool-call execution loop
|
|
18
|
+
knowledge_base.py # Document ingestion, embedding, and FAISS retrieval
|
|
19
|
+
llm_connection_config.py# LLM client configuration
|
|
20
|
+
knowledge/ # Knowledge files (subdirectory per topic)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
**1. Install dependencies**
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install -r requirements.txt
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**2. Configure environment**
|
|
32
|
+
|
|
33
|
+
Copy `.env` and fill in your credentials:
|
|
34
|
+
|
|
35
|
+
```env
|
|
36
|
+
OPENROUTER_API_KEY=your-api-key-here
|
|
37
|
+
MODEL=openai/gpt-4o-mini
|
|
38
|
+
BASE_URL=https://openrouter.ai/api/v1
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The default `BASE_URL` points to [OpenRouter](https://openrouter.ai), which gives access to many models through a single API key. You can swap it for the OpenAI base URL or any other compatible endpoint.
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Basic agent
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from agent_base import AgentBase
|
|
49
|
+
from llm_connection_config import LLMConnectionConfig
|
|
50
|
+
|
|
51
|
+
config = LLMConnectionConfig(model="openai/gpt-4o-mini", api_key="...")
|
|
52
|
+
|
|
53
|
+
agent = AgentBase(
|
|
54
|
+
system_prompt="You are a helpful assistant.",
|
|
55
|
+
llm_config=config,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
print(agent.run("What is the capital of France?"))
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Tool calling
|
|
62
|
+
|
|
63
|
+
Register any Python function as a tool. The function name becomes the tool name, the docstring becomes its description, and the type hints define the parameter schema.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
def get_weather(city: str) -> str:
|
|
67
|
+
"""Return the current weather for a given city."""
|
|
68
|
+
return f"The weather in {city} is sunny and 22°C."
|
|
69
|
+
|
|
70
|
+
def add(a: int, b: int) -> int:
|
|
71
|
+
"""Add two integers and return the result."""
|
|
72
|
+
return a + b
|
|
73
|
+
|
|
74
|
+
agent = AgentBase(
|
|
75
|
+
system_prompt="You are a helpful assistant. Use the available tools when needed.",
|
|
76
|
+
llm_config=config,
|
|
77
|
+
)
|
|
78
|
+
agent.register_tool(get_weather)
|
|
79
|
+
agent.register_tool(add)
|
|
80
|
+
|
|
81
|
+
print(agent.run("What is the weather in Tokyo and what is 10 + 20?"))
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`register_tool` can also be used as a decorator:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
@agent.register_tool
|
|
88
|
+
def search_orders(order_id: str) -> str:
|
|
89
|
+
"""Look up an order by ID."""
|
|
90
|
+
...
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### RAG (knowledge base)
|
|
94
|
+
|
|
95
|
+
Place your documents in a folder (organised into subdirectories by topic). Call `ingest_knowledge` once to embed and index them, then `run` as normal — relevant chunks are automatically injected into the system prompt.
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
knowledge/
|
|
99
|
+
├── topic/
|
|
100
|
+
│ └── overview.txt
|
|
101
|
+
├── products/
|
|
102
|
+
│ ├── faq.md
|
|
103
|
+
│ └── pricing.json
|
|
104
|
+
└── support/
|
|
105
|
+
└── sla.md
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
agent = AgentBase(
|
|
110
|
+
system_prompt="You are a product assistant. Answer using only the provided context.",
|
|
111
|
+
llm_config=config,
|
|
112
|
+
knowledge_folder_path="knowledge",
|
|
113
|
+
knowledge_index_dir=".kb_index", # where the FAISS index is saved
|
|
114
|
+
knowledge_top_k=3, # number of chunks injected per prompt
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Build and persist the index (run once, or when documents change)
|
|
118
|
+
agent.ingest_knowledge(save=True)
|
|
119
|
+
|
|
120
|
+
# On subsequent runs, load from disk instead of re-embedding
|
|
121
|
+
# agent.load_knowledge()
|
|
122
|
+
|
|
123
|
+
print(agent.run("Who founded the company and when?"))
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Agent pipelines
|
|
127
|
+
|
|
128
|
+
Chain agents so the output of one becomes the input of the next:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from agent_pipeline_base import AgentPipelineBase
|
|
132
|
+
|
|
133
|
+
researcher = AgentBase(
|
|
134
|
+
system_prompt="Extract the key facts from the user's question.",
|
|
135
|
+
llm_config=config,
|
|
136
|
+
)
|
|
137
|
+
writer = AgentBase(
|
|
138
|
+
system_prompt="Turn the provided facts into a concise, friendly summary.",
|
|
139
|
+
llm_config=config,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
pipeline = AgentPipelineBase(agents=[researcher, writer])
|
|
143
|
+
print(pipeline.run("Tell me about the Acme Corp product lineup."))
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Debug mode
|
|
147
|
+
|
|
148
|
+
Pass `debug=True` to any agent to print tool invocations and knowledge retrievals to stdout:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
agent = AgentBase(..., debug=True)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
[debug] Retrieving knowledge
|
|
156
|
+
[debug] tool 'get_weather' args={'city': 'Tokyo'} result=The weather in Tokyo is sunny and 22°C.
|
|
157
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .agent_base import AgentBase
|
|
2
|
+
from .agent_pipeline_base import AgentPipelineBase
|
|
3
|
+
from .knowledge_base import DocumentChunk, KnowledgeBase
|
|
4
|
+
from .llm_connection_config import LLMConnectionConfig
|
|
5
|
+
from .tool_calling import build_tool_schema, execute_tool_loop
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"AgentBase",
|
|
9
|
+
"AgentPipelineBase",
|
|
10
|
+
"DocumentChunk",
|
|
11
|
+
"KnowledgeBase",
|
|
12
|
+
"LLMConnectionConfig",
|
|
13
|
+
"build_tool_schema",
|
|
14
|
+
"execute_tool_loop",
|
|
15
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import Callable, Optional
|
|
2
|
+
|
|
3
|
+
from .knowledge_base import DocumentChunk, KnowledgeBase
|
|
4
|
+
from .llm_connection_config import LLMConnectionConfig
|
|
5
|
+
from .tool_calling import build_tool_schema, execute_tool_loop
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AgentBase:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
system_prompt: str,
|
|
12
|
+
llm_config: LLMConnectionConfig,
|
|
13
|
+
knowledge_folder_path: Optional[str] = None,
|
|
14
|
+
knowledge_index_dir: str = ".kb_index",
|
|
15
|
+
knowledge_top_k: int = 5,
|
|
16
|
+
debug: bool = False,
|
|
17
|
+
):
|
|
18
|
+
self.system_prompt = system_prompt
|
|
19
|
+
self.llm_config = llm_config
|
|
20
|
+
self.knowledge_top_k = knowledge_top_k
|
|
21
|
+
self.debug = debug
|
|
22
|
+
self._client = llm_config.build_client()
|
|
23
|
+
self._kb: Optional[KnowledgeBase] = None
|
|
24
|
+
self._tools: dict[str, tuple[Callable, dict]] = {}
|
|
25
|
+
|
|
26
|
+
if knowledge_folder_path:
|
|
27
|
+
self._kb = KnowledgeBase(
|
|
28
|
+
folder_path=knowledge_folder_path,
|
|
29
|
+
llm_config=llm_config,
|
|
30
|
+
index_dir=knowledge_index_dir,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def register_tool(self, fn: Callable) -> Callable:
|
|
34
|
+
"""Register a Python function as a callable tool. Can be used as a decorator."""
|
|
35
|
+
self._tools[fn.__name__] = (fn, build_tool_schema(fn))
|
|
36
|
+
return fn
|
|
37
|
+
|
|
38
|
+
def ingest_knowledge(self, save: bool = True) -> int:
|
|
39
|
+
if self._kb is None:
|
|
40
|
+
return 0
|
|
41
|
+
count = self._kb.ingest()
|
|
42
|
+
if save:
|
|
43
|
+
self._kb.save()
|
|
44
|
+
return count
|
|
45
|
+
|
|
46
|
+
def load_knowledge(self):
|
|
47
|
+
if self._kb is not None:
|
|
48
|
+
self._kb.load()
|
|
49
|
+
|
|
50
|
+
def retrieve_knowledge(self, query: str) -> list[DocumentChunk]:
|
|
51
|
+
if self._kb is None:
|
|
52
|
+
return []
|
|
53
|
+
if self.debug:
|
|
54
|
+
print("[debug] Retrieving knowledge")
|
|
55
|
+
return self._kb.retrieve(query, top_k=self.knowledge_top_k)
|
|
56
|
+
|
|
57
|
+
def run(self, prompt: str) -> str:
|
|
58
|
+
chunks = self.retrieve_knowledge(prompt)
|
|
59
|
+
|
|
60
|
+
system = self.system_prompt
|
|
61
|
+
if chunks:
|
|
62
|
+
context = "\n\n".join(f"[{c.source}]\n{c.text}" for c in chunks)
|
|
63
|
+
system = f"{system}\n\n<context>\n{context}\n</context>"
|
|
64
|
+
|
|
65
|
+
messages = [
|
|
66
|
+
{"role": "system", "content": system},
|
|
67
|
+
{"role": "user", "content": prompt},
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
return execute_tool_loop(self._client, self.llm_config.model, messages, self._tools, debug=self.debug)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .agent_base import AgentBase
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AgentPipelineBase:
|
|
5
|
+
def __init__(self, agents: list[AgentBase]):
|
|
6
|
+
self._agents = agents
|
|
7
|
+
|
|
8
|
+
def run(self, prompt: str) -> str:
|
|
9
|
+
result = prompt
|
|
10
|
+
for agent in self._agents:
|
|
11
|
+
result = agent.run(result)
|
|
12
|
+
return result
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import pickle
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import faiss
|
|
9
|
+
|
|
10
|
+
from .llm_connection_config import LLMConnectionConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class DocumentChunk:
|
|
15
|
+
text: str
|
|
16
|
+
source: str
|
|
17
|
+
chunk_index: int
|
|
18
|
+
metadata: dict = field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class KnowledgeBase:
|
|
22
|
+
SUPPORTED_EXTENSIONS = {".txt", ".md", ".json", ".pdf"}
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
folder_path: str,
|
|
27
|
+
llm_config: LLMConnectionConfig,
|
|
28
|
+
index_dir: str = ".kb_index",
|
|
29
|
+
chunk_size: int = 1000,
|
|
30
|
+
chunk_overlap: int = 100,
|
|
31
|
+
):
|
|
32
|
+
self.folder_path = Path(folder_path)
|
|
33
|
+
self.index_dir = Path(index_dir)
|
|
34
|
+
self.chunk_size = chunk_size
|
|
35
|
+
self.chunk_overlap = chunk_overlap
|
|
36
|
+
self.embedding_model = llm_config.embedding_model
|
|
37
|
+
self._client = llm_config.build_client()
|
|
38
|
+
|
|
39
|
+
self._chunks: list[DocumentChunk] = []
|
|
40
|
+
self._index: Optional[faiss.IndexFlatIP] = None
|
|
41
|
+
self._dim: Optional[int] = None
|
|
42
|
+
|
|
43
|
+
# ------------------------------------------------------------------ #
|
|
44
|
+
# Public API #
|
|
45
|
+
# ------------------------------------------------------------------ #
|
|
46
|
+
|
|
47
|
+
def ingest(self) -> int:
|
|
48
|
+
"""Parse all files in folder_path, chunk, embed, and build the index."""
|
|
49
|
+
self._chunks = []
|
|
50
|
+
vectors = []
|
|
51
|
+
|
|
52
|
+
for path in sorted(self.folder_path.rglob("*")):
|
|
53
|
+
if path.suffix.lower() not in self.SUPPORTED_EXTENSIONS:
|
|
54
|
+
continue
|
|
55
|
+
text = self._parse_file(path)
|
|
56
|
+
if not text:
|
|
57
|
+
continue
|
|
58
|
+
for i, chunk_text in enumerate(self._chunk_text(text)):
|
|
59
|
+
chunk = DocumentChunk(
|
|
60
|
+
text=chunk_text,
|
|
61
|
+
source=str(path.relative_to(self.folder_path)),
|
|
62
|
+
chunk_index=i,
|
|
63
|
+
)
|
|
64
|
+
self._chunks.append(chunk)
|
|
65
|
+
vectors.append(self._embed(chunk_text))
|
|
66
|
+
|
|
67
|
+
if not vectors:
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
matrix = np.array(vectors, dtype="float32")
|
|
71
|
+
self._dim = matrix.shape[1]
|
|
72
|
+
faiss.normalize_L2(matrix)
|
|
73
|
+
self._index = faiss.IndexFlatIP(self._dim)
|
|
74
|
+
self._index.add(matrix)
|
|
75
|
+
return len(self._chunks)
|
|
76
|
+
|
|
77
|
+
def save(self):
|
|
78
|
+
"""Persist the FAISS index and chunk catalog to disk."""
|
|
79
|
+
self.index_dir.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
faiss.write_index(self._index, str(self.index_dir / "index.faiss"))
|
|
81
|
+
with open(self.index_dir / "catalog.pkl", "wb") as f:
|
|
82
|
+
pickle.dump({"chunks": self._chunks, "dim": self._dim}, f)
|
|
83
|
+
|
|
84
|
+
def load(self):
|
|
85
|
+
"""Restore a previously saved index and catalog from disk."""
|
|
86
|
+
index_path = self.index_dir / "index.faiss"
|
|
87
|
+
catalog_path = self.index_dir / "catalog.pkl"
|
|
88
|
+
if not index_path.exists() or not catalog_path.exists():
|
|
89
|
+
raise FileNotFoundError(f"No saved index found in {self.index_dir}")
|
|
90
|
+
self._index = faiss.read_index(str(index_path))
|
|
91
|
+
with open(catalog_path, "rb") as f:
|
|
92
|
+
data = pickle.load(f)
|
|
93
|
+
self._chunks = data["chunks"]
|
|
94
|
+
self._dim = data["dim"]
|
|
95
|
+
|
|
96
|
+
def retrieve(self, query: str, top_k: int = 5) -> list[DocumentChunk]:
|
|
97
|
+
"""Return the top_k most relevant chunks for the given query."""
|
|
98
|
+
if self._index is None or self._index.ntotal == 0:
|
|
99
|
+
return []
|
|
100
|
+
vec = np.array([self._embed(query)], dtype="float32")
|
|
101
|
+
faiss.normalize_L2(vec)
|
|
102
|
+
_scores, indices = self._index.search(vec, min(top_k, self._index.ntotal))
|
|
103
|
+
return [self._chunks[i] for i in indices[0] if i >= 0]
|
|
104
|
+
|
|
105
|
+
# ------------------------------------------------------------------ #
|
|
106
|
+
# Private helpers #
|
|
107
|
+
# ------------------------------------------------------------------ #
|
|
108
|
+
|
|
109
|
+
def _parse_file(self, path: Path) -> str:
|
|
110
|
+
ext = path.suffix.lower()
|
|
111
|
+
if ext == ".pdf":
|
|
112
|
+
return self._parse_pdf(path)
|
|
113
|
+
if ext == ".json":
|
|
114
|
+
return self._parse_json(path)
|
|
115
|
+
return path.read_text(encoding="utf-8", errors="ignore")
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _parse_pdf(path: Path) -> str:
|
|
119
|
+
from pypdf import PdfReader
|
|
120
|
+
reader = PdfReader(str(path))
|
|
121
|
+
return "\n".join(page.extract_text() or "" for page in reader.pages)
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _parse_json(path: Path) -> str:
|
|
125
|
+
with open(path, encoding="utf-8") as f:
|
|
126
|
+
data = json.load(f)
|
|
127
|
+
return json.dumps(data, ensure_ascii=False, indent=2)
|
|
128
|
+
|
|
129
|
+
def _chunk_text(self, text: str) -> list[str]:
|
|
130
|
+
chunks = []
|
|
131
|
+
start = 0
|
|
132
|
+
while start < len(text):
|
|
133
|
+
end = start + self.chunk_size
|
|
134
|
+
chunks.append(text[start:end])
|
|
135
|
+
start = end - self.chunk_overlap
|
|
136
|
+
if start >= len(text):
|
|
137
|
+
break
|
|
138
|
+
return [c.strip() for c in chunks if c.strip()]
|
|
139
|
+
|
|
140
|
+
def _embed(self, text: str) -> list[float]:
|
|
141
|
+
response = self._client.embeddings.create(
|
|
142
|
+
model=self.embedding_model,
|
|
143
|
+
input=text,
|
|
144
|
+
)
|
|
145
|
+
return response.data[0].embedding
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from openai import OpenAI
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class LLMConnectionConfig:
|
|
10
|
+
model: str
|
|
11
|
+
base_url: str = "https://openrouter.ai/api/v1"
|
|
12
|
+
api_key: Optional[str] = None
|
|
13
|
+
embedding_model: str = "openai/text-embedding-3-small"
|
|
14
|
+
|
|
15
|
+
def get_api_key(self) -> str:
|
|
16
|
+
key = self.api_key or os.environ.get("OPENROUTER_API_KEY")
|
|
17
|
+
if not key:
|
|
18
|
+
raise ValueError("API key required: pass api_key or set OPENROUTER_API_KEY")
|
|
19
|
+
return key
|
|
20
|
+
|
|
21
|
+
def build_client(self) -> OpenAI:
|
|
22
|
+
return OpenAI(api_key=self.get_api_key(), base_url=self.base_url)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import json
|
|
3
|
+
from typing import Callable, Union, get_args, get_origin, get_type_hints
|
|
4
|
+
|
|
5
|
+
_JSON_TYPE_MAP = {
|
|
6
|
+
str: "string",
|
|
7
|
+
int: "integer",
|
|
8
|
+
float: "number",
|
|
9
|
+
bool: "boolean",
|
|
10
|
+
list: "array",
|
|
11
|
+
dict: "object",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _is_optional(tp) -> bool:
|
|
16
|
+
return get_origin(tp) is Union and type(None) in get_args(tp)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _unwrap_optional(tp):
|
|
20
|
+
args = [a for a in get_args(tp) if a is not type(None)]
|
|
21
|
+
return args[0] if args else str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _to_json_type(tp) -> str:
|
|
25
|
+
if _is_optional(tp):
|
|
26
|
+
tp = _unwrap_optional(tp)
|
|
27
|
+
return _JSON_TYPE_MAP.get(tp, "string")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_tool_schema(fn: Callable) -> dict:
|
|
31
|
+
hints = get_type_hints(fn)
|
|
32
|
+
hints.pop("return", None)
|
|
33
|
+
sig = inspect.signature(fn)
|
|
34
|
+
|
|
35
|
+
properties = {}
|
|
36
|
+
required = []
|
|
37
|
+
for name, param in sig.parameters.items():
|
|
38
|
+
tp = hints.get(name, str)
|
|
39
|
+
properties[name] = {"type": _to_json_type(tp)}
|
|
40
|
+
if not _is_optional(tp) and param.default is inspect.Parameter.empty:
|
|
41
|
+
required.append(name)
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
"type": "function",
|
|
45
|
+
"function": {
|
|
46
|
+
"name": fn.__name__,
|
|
47
|
+
"description": inspect.getdoc(fn) or "",
|
|
48
|
+
"parameters": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": properties,
|
|
51
|
+
"required": required,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def execute_tool_loop(
|
|
58
|
+
client,
|
|
59
|
+
model: str,
|
|
60
|
+
messages: list,
|
|
61
|
+
tools: dict[str, tuple[Callable, dict]],
|
|
62
|
+
debug: bool = False,
|
|
63
|
+
) -> str:
|
|
64
|
+
"""Run the agentic tool-calling loop and return the final text response."""
|
|
65
|
+
tool_schemas = [schema for _, schema in tools.values()]
|
|
66
|
+
|
|
67
|
+
while True:
|
|
68
|
+
kwargs = {"model": model, "messages": messages}
|
|
69
|
+
if tool_schemas:
|
|
70
|
+
kwargs["tools"] = tool_schemas
|
|
71
|
+
|
|
72
|
+
response = client.chat.completions.create(**kwargs)
|
|
73
|
+
choice = response.choices[0]
|
|
74
|
+
msg = choice.message
|
|
75
|
+
|
|
76
|
+
if choice.finish_reason != "tool_calls" or not msg.tool_calls:
|
|
77
|
+
return msg.content
|
|
78
|
+
|
|
79
|
+
messages.append(msg)
|
|
80
|
+
|
|
81
|
+
for tc in msg.tool_calls:
|
|
82
|
+
fn, _ = tools.get(tc.function.name, (None, None))
|
|
83
|
+
if fn is None:
|
|
84
|
+
result = f"Error: unknown tool '{tc.function.name}'"
|
|
85
|
+
if debug:
|
|
86
|
+
print(f"[debug] LLM called unknown tool '{tc.function.name}'")
|
|
87
|
+
else:
|
|
88
|
+
try:
|
|
89
|
+
args = json.loads(tc.function.arguments)
|
|
90
|
+
result = str(fn(**args))
|
|
91
|
+
if debug:
|
|
92
|
+
print(f"[debug] tool '{tc.function.name}' args={args} result={result}")
|
|
93
|
+
except Exception as e:
|
|
94
|
+
result = f"Error: {e}"
|
|
95
|
+
|
|
96
|
+
messages.append({
|
|
97
|
+
"role": "tool",
|
|
98
|
+
"tool_call_id": tc.id,
|
|
99
|
+
"content": result,
|
|
100
|
+
})
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llm-agent-base
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight Python framework for building LLM agents with tool calling and RAG
|
|
5
|
+
Author-email: Paweł Gryka <pawel.m.gryka@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mksochota16/llm-agent-base
|
|
8
|
+
Project-URL: Repository, https://github.com/mksochota16/llm-agent-base
|
|
9
|
+
Keywords: llm,agent,rag,openai,tool-calling
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: openai>=2.0.0
|
|
21
|
+
Requires-Dist: faiss-cpu>=1.7.0
|
|
22
|
+
Requires-Dist: pypdf>=4.0.0
|
|
23
|
+
Requires-Dist: numpy>=1.24.0
|
|
24
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
25
|
+
|
|
26
|
+
# llm-agent-base
|
|
27
|
+
|
|
28
|
+
A lightweight Python base for building LLM agents with tool calling and retrieval-augmented generation (RAG). It works with any OpenAI-compatible API (OpenRouter, OpenAI, local models via Ollama, etc.).
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- **Tool calling** — register plain Python functions as LLM-callable tools; schemas are built automatically from type hints and docstrings
|
|
33
|
+
- **RAG** — ingest a folder of documents (`.txt`, `.md`, `.json`, `.pdf`) into a FAISS vector index and inject relevant chunks into every prompt
|
|
34
|
+
- **Pipelines** — chain multiple agents so each agent's output becomes the next agent's input
|
|
35
|
+
- **Debug mode** — optional logging of tool calls and knowledge retrievals
|
|
36
|
+
|
|
37
|
+
## Project structure
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
agent_base.py # AgentBase class
|
|
41
|
+
agent_pipeline_base.py # AgentPipelineBase class
|
|
42
|
+
tool_calling.py # Schema building and tool-call execution loop
|
|
43
|
+
knowledge_base.py # Document ingestion, embedding, and FAISS retrieval
|
|
44
|
+
llm_connection_config.py# LLM client configuration
|
|
45
|
+
knowledge/ # Knowledge files (subdirectory per topic)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Setup
|
|
49
|
+
|
|
50
|
+
**1. Install dependencies**
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install -r requirements.txt
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**2. Configure environment**
|
|
57
|
+
|
|
58
|
+
Copy `.env` and fill in your credentials:
|
|
59
|
+
|
|
60
|
+
```env
|
|
61
|
+
OPENROUTER_API_KEY=your-api-key-here
|
|
62
|
+
MODEL=openai/gpt-4o-mini
|
|
63
|
+
BASE_URL=https://openrouter.ai/api/v1
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The default `BASE_URL` points to [OpenRouter](https://openrouter.ai), which gives access to many models through a single API key. You can swap it for the OpenAI base URL or any other compatible endpoint.
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
### Basic agent
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from agent_base import AgentBase
|
|
74
|
+
from llm_connection_config import LLMConnectionConfig
|
|
75
|
+
|
|
76
|
+
config = LLMConnectionConfig(model="openai/gpt-4o-mini", api_key="...")
|
|
77
|
+
|
|
78
|
+
agent = AgentBase(
|
|
79
|
+
system_prompt="You are a helpful assistant.",
|
|
80
|
+
llm_config=config,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
print(agent.run("What is the capital of France?"))
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Tool calling
|
|
87
|
+
|
|
88
|
+
Register any Python function as a tool. The function name becomes the tool name, the docstring becomes its description, and the type hints define the parameter schema.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
def get_weather(city: str) -> str:
|
|
92
|
+
"""Return the current weather for a given city."""
|
|
93
|
+
return f"The weather in {city} is sunny and 22°C."
|
|
94
|
+
|
|
95
|
+
def add(a: int, b: int) -> int:
|
|
96
|
+
"""Add two integers and return the result."""
|
|
97
|
+
return a + b
|
|
98
|
+
|
|
99
|
+
agent = AgentBase(
|
|
100
|
+
system_prompt="You are a helpful assistant. Use the available tools when needed.",
|
|
101
|
+
llm_config=config,
|
|
102
|
+
)
|
|
103
|
+
agent.register_tool(get_weather)
|
|
104
|
+
agent.register_tool(add)
|
|
105
|
+
|
|
106
|
+
print(agent.run("What is the weather in Tokyo and what is 10 + 20?"))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`register_tool` can also be used as a decorator:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
@agent.register_tool
|
|
113
|
+
def search_orders(order_id: str) -> str:
|
|
114
|
+
"""Look up an order by ID."""
|
|
115
|
+
...
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### RAG (knowledge base)
|
|
119
|
+
|
|
120
|
+
Place your documents in a folder (organised into subdirectories by topic). Call `ingest_knowledge` once to embed and index them, then `run` as normal — relevant chunks are automatically injected into the system prompt.
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
knowledge/
|
|
124
|
+
├── topic/
|
|
125
|
+
│ └── overview.txt
|
|
126
|
+
├── products/
|
|
127
|
+
│ ├── faq.md
|
|
128
|
+
│ └── pricing.json
|
|
129
|
+
└── support/
|
|
130
|
+
└── sla.md
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
agent = AgentBase(
|
|
135
|
+
system_prompt="You are a product assistant. Answer using only the provided context.",
|
|
136
|
+
llm_config=config,
|
|
137
|
+
knowledge_folder_path="knowledge",
|
|
138
|
+
knowledge_index_dir=".kb_index", # where the FAISS index is saved
|
|
139
|
+
knowledge_top_k=3, # number of chunks injected per prompt
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Build and persist the index (run once, or when documents change)
|
|
143
|
+
agent.ingest_knowledge(save=True)
|
|
144
|
+
|
|
145
|
+
# On subsequent runs, load from disk instead of re-embedding
|
|
146
|
+
# agent.load_knowledge()
|
|
147
|
+
|
|
148
|
+
print(agent.run("Who founded the company and when?"))
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Agent pipelines
|
|
152
|
+
|
|
153
|
+
Chain agents so the output of one becomes the input of the next:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from agent_pipeline_base import AgentPipelineBase
|
|
157
|
+
|
|
158
|
+
researcher = AgentBase(
|
|
159
|
+
system_prompt="Extract the key facts from the user's question.",
|
|
160
|
+
llm_config=config,
|
|
161
|
+
)
|
|
162
|
+
writer = AgentBase(
|
|
163
|
+
system_prompt="Turn the provided facts into a concise, friendly summary.",
|
|
164
|
+
llm_config=config,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
pipeline = AgentPipelineBase(agents=[researcher, writer])
|
|
168
|
+
print(pipeline.run("Tell me about the Acme Corp product lineup."))
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Debug mode
|
|
172
|
+
|
|
173
|
+
Pass `debug=True` to any agent to print tool invocations and knowledge retrievals to stdout:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
agent = AgentBase(..., debug=True)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
[debug] Retrieving knowledge
|
|
181
|
+
[debug] tool 'get_weather' args={'city': 'Tokyo'} result=The weather in Tokyo is sunny and 22°C.
|
|
182
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
llm_agent_base/__init__.py
|
|
4
|
+
llm_agent_base/agent_base.py
|
|
5
|
+
llm_agent_base/agent_pipeline_base.py
|
|
6
|
+
llm_agent_base/knowledge_base.py
|
|
7
|
+
llm_agent_base/llm_connection_config.py
|
|
8
|
+
llm_agent_base/tool_calling.py
|
|
9
|
+
llm_agent_base.egg-info/PKG-INFO
|
|
10
|
+
llm_agent_base.egg-info/SOURCES.txt
|
|
11
|
+
llm_agent_base.egg-info/dependency_links.txt
|
|
12
|
+
llm_agent_base.egg-info/requires.txt
|
|
13
|
+
llm_agent_base.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
llm_agent_base
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "llm-agent-base"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Lightweight Python framework for building LLM agents with tool calling and RAG"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "Paweł Gryka", email = "pawel.m.gryka@gmail.com"},
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
keywords = ["llm", "agent", "rag", "openai", "tool-calling"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"openai>=2.0.0",
|
|
28
|
+
"faiss-cpu>=1.7.0",
|
|
29
|
+
"pypdf>=4.0.0",
|
|
30
|
+
"numpy>=1.24.0",
|
|
31
|
+
"python-dotenv>=1.0.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/mksochota16/llm-agent-base"
|
|
36
|
+
Repository = "https://github.com/mksochota16/llm-agent-base"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
include = ["llm_agent_base*"]
|