llmalchemy 2.0.0__tar.gz → 3.0.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.
- {llmalchemy-2.0.0 → llmalchemy-3.0.0}/PKG-INFO +4 -4
- {llmalchemy-2.0.0 → llmalchemy-3.0.0}/README.md +3 -3
- {llmalchemy-2.0.0 → llmalchemy-3.0.0}/pyproject.toml +1 -1
- {llmalchemy-2.0.0 → llmalchemy-3.0.0}/pyproject.toml.orig +1 -1
- {llmalchemy-2.0.0 → llmalchemy-3.0.0}/src/llmalchemy/agent.py +17 -33
- {llmalchemy-2.0.0 → llmalchemy-3.0.0}/src/llmalchemy/context.py +39 -28
- llmalchemy-3.0.0/src/llmalchemy/prompt.jinja +25 -0
- llmalchemy-2.0.0/src/llmalchemy/prompt.jinja +0 -27
- {llmalchemy-2.0.0 → llmalchemy-3.0.0}/src/llmalchemy/__init__.py +0 -0
- {llmalchemy-2.0.0 → llmalchemy-3.0.0}/src/llmalchemy/code.py +0 -0
- {llmalchemy-2.0.0 → llmalchemy-3.0.0}/src/llmalchemy/database.py +0 -0
- {llmalchemy-2.0.0 → llmalchemy-3.0.0}/src/llmalchemy/tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: llmalchemy
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: A full-access agent pattern.
|
|
5
5
|
Author: Ignacio Llorca
|
|
6
6
|
Author-email: Ignacio Llorca <nllorca@proton.me>
|
|
@@ -178,10 +178,10 @@ MessageEvent(message=UserMessage(content="Code rejected: Forbidden import: os"))
|
|
|
178
178
|
<details>
|
|
179
179
|
<summary>Custom system prompt</summary>
|
|
180
180
|
You can pass a Jinja template to override the default one (see `src/llmalchemy/prompt.jinja`).
|
|
181
|
-
It is recommended that the template contains vars {{ SCHEMA }}, {{
|
|
181
|
+
It is recommended that the template contains vars {{ SCHEMA }}, {{ TOOLS }} and {{ IMPORTS }}.
|
|
182
182
|
|
|
183
183
|
```python
|
|
184
|
-
prompt = """Write python code to answer user requests. You have access to {{ SCHEMA }}
|
|
184
|
+
prompt = """Write python code to answer user requests. You have access to {{ SCHEMA }} and {{ TOOLS }}."""
|
|
185
185
|
for event in run(
|
|
186
186
|
session=session,
|
|
187
187
|
messages=messages,
|
|
@@ -195,7 +195,7 @@ for event in run(
|
|
|
195
195
|
The emitted events are the same as in the minimal example; only the `SystemInstructionEvent` content changes to reflect your custom template:
|
|
196
196
|
|
|
197
197
|
```text
|
|
198
|
-
SystemInstructionEvent(content='Write python code to answer user requests. You have access to <schema
|
|
198
|
+
SystemInstructionEvent(content='Write python code to answer user requests. You have access to <schema...> and <tools...>')
|
|
199
199
|
```
|
|
200
200
|
|
|
201
201
|
</details>
|
|
@@ -164,10 +164,10 @@ MessageEvent(message=UserMessage(content="Code rejected: Forbidden import: os"))
|
|
|
164
164
|
<details>
|
|
165
165
|
<summary>Custom system prompt</summary>
|
|
166
166
|
You can pass a Jinja template to override the default one (see `src/llmalchemy/prompt.jinja`).
|
|
167
|
-
It is recommended that the template contains vars {{ SCHEMA }}, {{
|
|
167
|
+
It is recommended that the template contains vars {{ SCHEMA }}, {{ TOOLS }} and {{ IMPORTS }}.
|
|
168
168
|
|
|
169
169
|
```python
|
|
170
|
-
prompt = """Write python code to answer user requests. You have access to {{ SCHEMA }}
|
|
170
|
+
prompt = """Write python code to answer user requests. You have access to {{ SCHEMA }} and {{ TOOLS }}."""
|
|
171
171
|
for event in run(
|
|
172
172
|
session=session,
|
|
173
173
|
messages=messages,
|
|
@@ -181,7 +181,7 @@ for event in run(
|
|
|
181
181
|
The emitted events are the same as in the minimal example; only the `SystemInstructionEvent` content changes to reflect your custom template:
|
|
182
182
|
|
|
183
183
|
```text
|
|
184
|
-
SystemInstructionEvent(content='Write python code to answer user requests. You have access to <schema
|
|
184
|
+
SystemInstructionEvent(content='Write python code to answer user requests. You have access to <schema...> and <tools...>')
|
|
185
185
|
```
|
|
186
186
|
|
|
187
187
|
</details>
|
|
@@ -145,51 +145,28 @@ def _init_namespace(
|
|
|
145
145
|
session: Session,
|
|
146
146
|
base: type[DeclarativeBase],
|
|
147
147
|
tools: list[Tool],
|
|
148
|
-
) ->
|
|
148
|
+
) -> dict:
|
|
149
149
|
"""Build a fresh code execution namespace for one run.
|
|
150
150
|
|
|
151
151
|
Injects ``session``, ORM model classes, association tables, tool functions,
|
|
152
152
|
and ``disclose``.
|
|
153
|
-
|
|
154
|
-
Returns:
|
|
155
|
-
The namespace, and a ``{name: description}`` dict of every injected
|
|
156
|
-
**infrastructure** symbol. Tool symbols are excluded — their source of
|
|
157
|
-
truth is the ``Tool`` object itself, rendered separately by
|
|
158
|
-
``_render_tools_summary``.
|
|
159
153
|
"""
|
|
160
154
|
namespace: dict = {}
|
|
161
|
-
descriptions: dict[str, str] = {}
|
|
162
155
|
|
|
163
156
|
namespace["session"] = session
|
|
164
|
-
descriptions["session"] = "a `sqlalchemy.orm.Session` connected to the database."
|
|
165
157
|
|
|
166
|
-
|
|
167
|
-
for cls in orm_classes:
|
|
158
|
+
for cls in mapped_classes(base):
|
|
168
159
|
namespace[cls.__name__] = cls
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
descriptions[names] = "ORM model classes (see schema above)."
|
|
172
|
-
|
|
173
|
-
tables = association_tables(base)
|
|
174
|
-
namespace.update(tables)
|
|
175
|
-
if tables:
|
|
176
|
-
names = ", ".join(tables)
|
|
177
|
-
descriptions[names] = (
|
|
178
|
-
"`sqlalchemy.Table` association objects for many-to-many junctions,"
|
|
179
|
-
" usable directly (e.g. `session.execute(insert(table), rows)`)."
|
|
180
|
-
)
|
|
160
|
+
|
|
161
|
+
namespace.update(association_tables(base))
|
|
181
162
|
|
|
182
163
|
for t in tools:
|
|
183
164
|
namespace[t.name] = t.fn
|
|
184
165
|
|
|
185
166
|
if tools:
|
|
186
167
|
namespace["disclose"] = make_disclose_fn(tools)
|
|
187
|
-
descriptions["disclose"] = (
|
|
188
|
-
"`disclose(name: str) -> str` — prints the full signature"
|
|
189
|
-
" and docstring of a tool. Call it before using a tool you haven't seen yet."
|
|
190
|
-
)
|
|
191
168
|
|
|
192
|
-
return namespace
|
|
169
|
+
return namespace
|
|
193
170
|
|
|
194
171
|
|
|
195
172
|
def run(
|
|
@@ -202,6 +179,7 @@ def run(
|
|
|
202
179
|
allowed_imports: list[str] | None = None,
|
|
203
180
|
prompt_template: str | Path | None = None,
|
|
204
181
|
output_extensions: type[BaseModel] | None = None,
|
|
182
|
+
referenceable_tables: list[str] | None = None,
|
|
205
183
|
) -> Iterator[Event]:
|
|
206
184
|
"""Execute the agentic loop.
|
|
207
185
|
|
|
@@ -219,10 +197,12 @@ def run(
|
|
|
219
197
|
output_extensions: Optional Pydantic model to force in the LM structured output.
|
|
220
198
|
thinking_effort: Level of thinking for provider-native reasoning tokens.
|
|
221
199
|
prompt_template: Custom jinja system prompt. Should contain placeholders for:
|
|
222
|
-
- ``SCHEMA``: used to show agent the source code of
|
|
223
|
-
- ``
|
|
224
|
-
- ``TOOLS``: usedf to show the agent tool names + short descriptions.
|
|
200
|
+
- ``SCHEMA``: used to show the agent the source code of the schema module.
|
|
201
|
+
- ``TOOLS``: used to show the agent tool names + short descriptions.
|
|
225
202
|
- ``IMPORTS``: used to show the agent which modules it may import.
|
|
203
|
+
referenceable_tables: Schema table names the agent may cite inline in its
|
|
204
|
+
messages, e.g. ``[authors:1]``. Defaults to ``None`` (disabled);
|
|
205
|
+
unknown names raise ``ValueError``.
|
|
226
206
|
|
|
227
207
|
Yields:
|
|
228
208
|
``Event``: system instruction, loop signals, and conversation messages.
|
|
@@ -238,6 +218,7 @@ def run(
|
|
|
238
218
|
allowed_imports,
|
|
239
219
|
prompt_template,
|
|
240
220
|
output_extensions,
|
|
221
|
+
referenceable_tables,
|
|
241
222
|
)
|
|
242
223
|
|
|
243
224
|
|
|
@@ -251,14 +232,15 @@ def _run(
|
|
|
251
232
|
allowed_imports: list[str] | None,
|
|
252
233
|
prompt_template: str | Path | None,
|
|
253
234
|
output_extensions: type[BaseModel] | None,
|
|
235
|
+
referenceable_tables: list[str] | None = None,
|
|
254
236
|
) -> Iterator[Event]:
|
|
255
237
|
"""Agentic loop body (see :func:`run`)."""
|
|
256
238
|
# Initialize everything
|
|
257
239
|
tools = tools or []
|
|
258
240
|
allowed_imports = ["sqlalchemy"] if allowed_imports is None else allowed_imports
|
|
259
241
|
output_schema = _build_output_schema(output_extensions)
|
|
260
|
-
namespace
|
|
261
|
-
system_instruction = render(base, tools,
|
|
242
|
+
namespace = _init_namespace(session, base, tools)
|
|
243
|
+
system_instruction = render(base, tools, prompt_template, allowed_imports, referenceable_tables)
|
|
262
244
|
yield SystemInstructionEvent(system_instruction)
|
|
263
245
|
before = serialize(session, base)
|
|
264
246
|
|
|
@@ -268,6 +250,8 @@ def _run(
|
|
|
268
250
|
)
|
|
269
251
|
code = output.code
|
|
270
252
|
|
|
253
|
+
# Note: agent can inline cite tables, we should verify that they are valid (issue 30)
|
|
254
|
+
|
|
271
255
|
# Loop until model is over with the task
|
|
272
256
|
loops = 0
|
|
273
257
|
while code:
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
import re
|
|
5
|
+
import sys
|
|
5
6
|
import warnings
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
from lmdk import render_template
|
|
9
10
|
from sqlalchemy.orm import DeclarativeBase
|
|
10
11
|
|
|
11
|
-
from .database import mapped_classes
|
|
12
12
|
from .tools import Tool
|
|
13
13
|
|
|
14
14
|
_TEMPLATE_PATH = Path(__file__).parent / "prompt.jinja"
|
|
15
15
|
|
|
16
|
-
_REQUIRED_MARKERS = ("SCHEMA", "
|
|
16
|
+
_REQUIRED_MARKERS = ("SCHEMA", "TOOLS", "IMPORTS")
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class LLMAlchemyPromptWarning(UserWarning):
|
|
@@ -21,19 +21,12 @@ class LLMAlchemyPromptWarning(UserWarning):
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def _render_schema_source(base: type[DeclarativeBase]) -> str:
|
|
24
|
-
"""Return the source
|
|
24
|
+
"""Return the source of the module defining *base* for the LM prompt.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
blank lines.
|
|
26
|
+
The whole module is shown (enums, junction tables, mixins, constraints),
|
|
27
|
+
so keep implementation details such as ORM hooks in a separate module.
|
|
29
28
|
"""
|
|
30
|
-
|
|
31
|
-
return "\n\n".join(sources)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _render_symbols(descriptions: dict[str, str]) -> str:
|
|
35
|
-
"""Format the symbol descriptions dict as a Markdown bullet list."""
|
|
36
|
-
return "\n".join(f"- `{name}`: {desc}" for name, desc in descriptions.items())
|
|
29
|
+
return inspect.getsource(sys.modules[base.__module__])
|
|
37
30
|
|
|
38
31
|
|
|
39
32
|
def _render_tools_summary(tools: list[Tool]) -> str:
|
|
@@ -46,9 +39,34 @@ def _render_tools_summary(tools: list[Tool]) -> str:
|
|
|
46
39
|
def _render_imports(allowed_imports: list[str]) -> str:
|
|
47
40
|
"""Render the import policy as one sentence."""
|
|
48
41
|
if not allowed_imports:
|
|
49
|
-
return
|
|
42
|
+
return (
|
|
43
|
+
"`import` statements are forbidden. Use only the symbols pre-loaded in your namespace."
|
|
44
|
+
)
|
|
50
45
|
modules = ", ".join(f"`{m}`" for m in allowed_imports)
|
|
51
|
-
return f"
|
|
46
|
+
return f"Module imports are forbidden, with exception of: {modules}."
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _render_references(base: type[DeclarativeBase], referenceable_tables: list[str] | None) -> str:
|
|
50
|
+
"""Render the row-reference instruction for the allowed tables.
|
|
51
|
+
|
|
52
|
+
Table names are validated against the schema so a typo raises instead of
|
|
53
|
+
silently letting the agent cite a table that does not exist.
|
|
54
|
+
"""
|
|
55
|
+
if not referenceable_tables:
|
|
56
|
+
return ""
|
|
57
|
+
known = base.metadata.tables
|
|
58
|
+
unknown = [name for name in referenceable_tables if name not in known]
|
|
59
|
+
if unknown:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"Unknown referenceable table(s): {', '.join(unknown)}. "
|
|
62
|
+
f"Available tables: {', '.join(sorted(known))}."
|
|
63
|
+
)
|
|
64
|
+
names = ", ".join(f"`{name}`" for name in referenceable_tables)
|
|
65
|
+
return (
|
|
66
|
+
f"You can reference specific rows in {names} by inlining "
|
|
67
|
+
f"`[{referenceable_tables[0]}:row_pk]` in your message to the user, "
|
|
68
|
+
"where `row_pk` is the row's primary key value."
|
|
69
|
+
)
|
|
52
70
|
|
|
53
71
|
|
|
54
72
|
def _check_markers(source: str) -> None:
|
|
@@ -66,39 +84,32 @@ def _check_markers(source: str) -> None:
|
|
|
66
84
|
def render(
|
|
67
85
|
base: type[DeclarativeBase],
|
|
68
86
|
tools: list[Tool],
|
|
69
|
-
descriptions: dict[str, str],
|
|
70
87
|
template: str | Path | None = None,
|
|
71
88
|
allowed_imports: list[str] | None = None,
|
|
89
|
+
referenceable_tables: list[str] | None = None,
|
|
72
90
|
) -> str:
|
|
73
91
|
"""Build the system instruction for the LM with all context parts.
|
|
74
92
|
|
|
75
93
|
Args:
|
|
76
94
|
base: The declarative base describing the schema.
|
|
77
95
|
tools: User-provided tools registered for this run.
|
|
78
|
-
descriptions: ``{name: description}`` of every namespace symbol,
|
|
79
|
-
as returned by ``_init_namespace`` in ``agent.py``.
|
|
80
96
|
template: A Jinja template source string, a ``Path`` to a template
|
|
81
97
|
file, or ``None`` to use the shipped default.
|
|
82
98
|
allowed_imports: Modules the agent may import. ``None`` or an empty
|
|
83
99
|
list means imports are forbidden.
|
|
100
|
+
referenceable_tables: Schema table names the agent may cite inline,
|
|
101
|
+
e.g. ``[authors:1]``. ``None`` means citations are disabled.
|
|
84
102
|
"""
|
|
85
103
|
if template is None:
|
|
86
104
|
path: Path = _TEMPLATE_PATH
|
|
87
105
|
source = path.read_text()
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
template=source,
|
|
91
|
-
SCHEMA=_render_schema_source(base=base),
|
|
92
|
-
SYMBOLS=_render_symbols(descriptions),
|
|
93
|
-
TOOLS=_render_tools_summary(tools),
|
|
94
|
-
IMPORTS=_render_imports(allowed_imports or []),
|
|
95
|
-
)
|
|
96
|
-
source = template.read_text() if isinstance(template, Path) else template
|
|
106
|
+
else:
|
|
107
|
+
source = template.read_text() if isinstance(template, Path) else template
|
|
97
108
|
_check_markers(source)
|
|
98
109
|
return render_template(
|
|
99
110
|
template=source,
|
|
100
111
|
SCHEMA=_render_schema_source(base=base),
|
|
101
|
-
SYMBOLS=_render_symbols(descriptions),
|
|
102
112
|
TOOLS=_render_tools_summary(tools),
|
|
103
113
|
IMPORTS=_render_imports(allowed_imports or []),
|
|
114
|
+
REFERENCES=_render_references(base, referenceable_tables),
|
|
104
115
|
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
You are an expert assistant with access to a relational database containing the user's project.
|
|
2
|
+
You can interact with the database using Python code and SQLAlchemy.
|
|
3
|
+
Your task is to navigate and edit the project to fulfill the user's requests.
|
|
4
|
+
|
|
5
|
+
# Guidelines
|
|
6
|
+
- **Code Execution**: Run Python code to query or modify the database. Use `print()` to inspect results.
|
|
7
|
+
- **Iteration**: Use the execution output (sent as an automatic user message) to decide if you need to run more code or provide a final answer to end the turn.
|
|
8
|
+
- **Efficiency**: Keep `print()` statements concise, avoid redundant imports or unnecessary checks.
|
|
9
|
+
- **Persistence**: the Python namespace (variables, imports, ...) carry over between cells only within the same turn. A new user message means a newly initialized namespace.
|
|
10
|
+
- **Imports**: {{ IMPORTS }}
|
|
11
|
+
{% if REFERENCES %}- **References**: {{ REFERENCES }}{% endif %}
|
|
12
|
+
{% if TOOLS %}- **Tools**: Several tools (pre-built python functions) are available in your namespace. Use `print(disclose(name))` to discover a tool's full signature and docstring before calling it.{% endif %}
|
|
13
|
+
|
|
14
|
+
# Database Schema
|
|
15
|
+
```python
|
|
16
|
+
{{ SCHEMA }}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Your namespace is pre-loaded with `session` and every class and association table shown above — no need to import or redefine them.
|
|
20
|
+
|
|
21
|
+
{% if TOOLS %}
|
|
22
|
+
# Available Tools
|
|
23
|
+
The tools below are also pre-loaded in the namespace.
|
|
24
|
+
{{ TOOLS }}
|
|
25
|
+
{% endif %}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
You are an expert assistant with access to a relational database containing the user's project.
|
|
2
|
-
You can interact with the database using Python code and SQLAlchemy.
|
|
3
|
-
|
|
4
|
-
# Guidelines
|
|
5
|
-
- **Code Execution**: Run Python code to query or modify the database. Use `print()` to inspect results.
|
|
6
|
-
- **Imports**: {{ IMPORTS }}
|
|
7
|
-
- **Persistence**: Variables (and any allowed imports) carry over between cells, until the user sends a new message.
|
|
8
|
-
- **Printing**: Keep `print()` statements concise, but print anything you may need in a later turn (ids, key values, short findings).
|
|
9
|
-
- **Efficiency**: Avoid redundant imports or session initialization.
|
|
10
|
-
- **Iteration**: Use the execution output (sent as an automatic user message) to decide if you need to run more code or provide a final answer.
|
|
11
|
-
{% if TOOLS %}- **Tools**: Several tools are available in your namespace. Use `print(disclose(name))` to discover a tool's full signature and docstring before calling it.{% endif %}
|
|
12
|
-
- **Transactions**: the SQLAlchemy `session` is also shared by every cell, nothing is committed or rolled back for you.
|
|
13
|
-
- Uncommitted work piles up across cells.
|
|
14
|
-
- A code execution that raises halfway leaves the part that already ran pending.
|
|
15
|
-
|
|
16
|
-
# Database Schema
|
|
17
|
-
```python
|
|
18
|
-
{{ SCHEMA }}
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
# Available Symbols
|
|
22
|
-
The following symbols are pre-loaded in your namespace:
|
|
23
|
-
{{ SYMBOLS }}
|
|
24
|
-
{% if TOOLS %}
|
|
25
|
-
# Available Tools
|
|
26
|
-
{{ TOOLS }}
|
|
27
|
-
{% endif %}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|