langchain-prolog 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.
- langchain_prolog-0.1.0/LICENSE +21 -0
- langchain_prolog-0.1.0/PKG-INFO +274 -0
- langchain_prolog-0.1.0/README.md +246 -0
- langchain_prolog-0.1.0/pyproject.toml +94 -0
- langchain_prolog-0.1.0/src/langchain_prolog/__init__.py +59 -0
- langchain_prolog-0.1.0/src/langchain_prolog/__version__.py +3 -0
- langchain_prolog-0.1.0/src/langchain_prolog/_prolog_init.py +205 -0
- langchain_prolog-0.1.0/src/langchain_prolog/consult_ex.pl +47 -0
- langchain_prolog-0.1.0/src/langchain_prolog/exceptions.py +61 -0
- langchain_prolog-0.1.0/src/langchain_prolog/logger.py +102 -0
- langchain_prolog-0.1.0/src/langchain_prolog/runnable.py +721 -0
- langchain_prolog-0.1.0/src/langchain_prolog/tool.py +151 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Antonio Pisani.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: langchain-prolog
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An integration package connecting Prolog and LangChain
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: langchain,prolog,swi-prolog,llm,agent
|
|
7
|
+
Author: Antonio Pisani
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Prolog
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Dist: janus-swi (>=1.5.0,<2.0.0)
|
|
21
|
+
Requires-Dist: langchain-core (>=0.3.0,<0.4.0)
|
|
22
|
+
Requires-Dist: pydantic (>=2.0.0,<3.0.0)
|
|
23
|
+
Project-URL: Repository, https://github.com/apisani1/langchain-prolog
|
|
24
|
+
Project-URL: Release Notes, https://github.com/langchain-ai/langchain/releases?q=tag%3A%22prolog%3D%3D0%22&expanded=true
|
|
25
|
+
Project-URL: Source Code, https://github.com/langchain-ai/langchain/tree/master/libs/partners/prolog
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
[](https://opensource.org/licenses/MIT)
|
|
29
|
+
[](https://badge.fury.io/py/langchain-prolog)
|
|
30
|
+
[](https://www.python.org/downloads/release/python-390/)
|
|
31
|
+
[](https://langchain-prolog.readthedocs.io/en/latest/?badge=latest)
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
# LangChain-Prolog
|
|
35
|
+
|
|
36
|
+
A Python library that integrates SWI-Prolog with LangChain, allowing seamless
|
|
37
|
+
blending of Prolog's logic programming capabilities into LangChain applications.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- Seamless integration between LangChain and SWI-Prolog
|
|
43
|
+
- Use Prolog queries as LangChain runnables and tools
|
|
44
|
+
- Invoke Prolog predicates from LangChain LLM models, chains and agents
|
|
45
|
+
- Support for both synchronous and asynchronous operations
|
|
46
|
+
- Comprehensive error handling and logging
|
|
47
|
+
- Cross-platform support (macOS, Linux, Windows)
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
#### Prerequisites
|
|
52
|
+
|
|
53
|
+
- Python 3.10 or later
|
|
54
|
+
- SWI-Prolog installed on your system
|
|
55
|
+
- The following python libraries installed:
|
|
56
|
+
- langchain 0.3.0 or later
|
|
57
|
+
- janus-swi 1.5.0 or later
|
|
58
|
+
- pydantic 0.2.0 or later
|
|
59
|
+
|
|
60
|
+
langchain-prolog can be installed using pip:
|
|
61
|
+
```bash
|
|
62
|
+
pip install langchain-prolog
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Runnable Interface
|
|
66
|
+
|
|
67
|
+
The PrologRunnable class allows the generation of langchain runnables that use Prolog rules to generate answers.
|
|
68
|
+
|
|
69
|
+
Let's assume that we have the following set of Prolog rules in the file family.pl:
|
|
70
|
+
|
|
71
|
+
```prolog
|
|
72
|
+
parent(john, bianca, mary).
|
|
73
|
+
parent(john, bianca, michael).
|
|
74
|
+
parent(peter, patricia, jennifer).
|
|
75
|
+
partner(X, Y) :- parent(X, Y, _).
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
There are three diferent ways to use a PrologRunnable to query Prolog:
|
|
79
|
+
|
|
80
|
+
#### 1) Using a Prolog runnable with a full predicate string
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from langchain_prolog import PrologConfig, PrologRunnable
|
|
84
|
+
|
|
85
|
+
config = PrologConfig(rules_file="family.pl")
|
|
86
|
+
prolog = PrologRunnable(prolog_config=config)
|
|
87
|
+
result = prolog.invoke("partner(X, Y)")
|
|
88
|
+
print(result)
|
|
89
|
+
```
|
|
90
|
+
We can pass a string representing a single predicate query. The invoke method will return `True`, `False` or a list of dictionaries with all the solutions to the query:
|
|
91
|
+
```python
|
|
92
|
+
[{'X': 'john', 'Y': 'bianca'},
|
|
93
|
+
{'X': 'john', 'Y': 'bianca'},
|
|
94
|
+
{'X': 'peter', 'Y': 'patricia'}]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### 2) Using a Prolog runnable with a default predicate
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from langchain_prolog import PrologConfig, PrologRunnable
|
|
101
|
+
|
|
102
|
+
config = PrologConfig(rules_file="family.pl", default_predicate="partner")
|
|
103
|
+
prolog = PrologRunnable(prolog_config=config)
|
|
104
|
+
result = prolog.invoke("peter, X")
|
|
105
|
+
print(result)
|
|
106
|
+
```
|
|
107
|
+
When using a default predicate, only the arguments for the predicate are passed to the Prolog runable, as a single string. Following Prolog conventions, uppercase identifiers are variables and lowercase identifiers are values (atoms or strings):
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
[{'X': 'patricia'}]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 3) Using a Prolog runnable with a dictionary and schema validation
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from langchain_prolog import PrologConfig, PrologRunnable
|
|
117
|
+
|
|
118
|
+
schema = PrologRunnable.create_schema("partner", ["man", "woman"])
|
|
119
|
+
config = PrologConfig(rules_file="family.pl", query_schema=schema)
|
|
120
|
+
prolog = PrologRunnable(prolog_config=config)
|
|
121
|
+
result = prolog.invoke({"man": None, "woman": "bianca"})
|
|
122
|
+
print(result)
|
|
123
|
+
```
|
|
124
|
+
If a schema is defined, we can pass a dictionary using the names of the parameters in the schema as the keys in the dictionary. The values can represent Prolog variables (uppercase first letter) or strings (lower case first letter). A `None` value is interpreted as a variable and replaced with the key capitalized:
|
|
125
|
+
```python
|
|
126
|
+
[{'Man': 'john'}, {'Man': 'john'}]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
You can also pass a Pydantic object generated with the schema to the invoke method:
|
|
130
|
+
```python
|
|
131
|
+
args = schema(man='M', woman='W')
|
|
132
|
+
result = prolog.invoke(args)
|
|
133
|
+
print(result)
|
|
134
|
+
```
|
|
135
|
+
Uppercase values are treated as variables:
|
|
136
|
+
```python
|
|
137
|
+
[{'M': 'john', 'W': 'bianca'},
|
|
138
|
+
{'M': 'john', 'W': 'bianca'},
|
|
139
|
+
{'M': 'peter', 'W': 'patricia'}]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Tool Interface
|
|
143
|
+
|
|
144
|
+
The PrologTool class allows the generation of langchain tools that use Prolog rules to generate answers.
|
|
145
|
+
|
|
146
|
+
See the Runnable Interface section for the conventions on hown to pass variables and values to the Prolog interpreter.
|
|
147
|
+
|
|
148
|
+
Let's assume that we have the following set of Prolog rules in the file family.pl:
|
|
149
|
+
|
|
150
|
+
```prolog
|
|
151
|
+
parent(john, bianca, mary).
|
|
152
|
+
parent(john, bianca, michael).
|
|
153
|
+
parent(peter, patricia, jennifer).
|
|
154
|
+
partner(X, Y) :- parent(X, Y, _).
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
There are two ways to use a Prolog tool:
|
|
158
|
+
|
|
159
|
+
### 1) Using a Prolog tool with an LLM and function calling
|
|
160
|
+
|
|
161
|
+
First create the Prolog tool:
|
|
162
|
+
```python
|
|
163
|
+
from langchain_prolog import PrologConfig, PrologTool
|
|
164
|
+
|
|
165
|
+
schema = PrologRunnable.create_schema('parent', ['men', 'women', 'child'])
|
|
166
|
+
config = PrologConfig(
|
|
167
|
+
rules_file="family.pl",
|
|
168
|
+
query_schema=schema,
|
|
169
|
+
)
|
|
170
|
+
prolog_tool = PrologTool(
|
|
171
|
+
prolog_config=config,
|
|
172
|
+
name="family_query",
|
|
173
|
+
description="""
|
|
174
|
+
Query family relationships using Prolog.
|
|
175
|
+
parent(X, Y, Z) implies only that Z is a child of X and Y.
|
|
176
|
+
Input can be a query string like 'parent(john, X, Y)'
|
|
177
|
+
or 'john, X, Y'"
|
|
178
|
+
You have to specify 3 parameters: men, woman, child.
|
|
179
|
+
Do not use quotes.
|
|
180
|
+
"""
|
|
181
|
+
)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Then bind it to the LLM model and query the model:
|
|
185
|
+
```python
|
|
186
|
+
from langchain_openai import ChatOpenAI
|
|
187
|
+
from langchain_core.messages import HumanMessage
|
|
188
|
+
|
|
189
|
+
llm = ChatOpenAI(model="gpt-4o-mini")
|
|
190
|
+
llm_with_tools = llm.bind_tools([prolog_tool])
|
|
191
|
+
messages = [HumanMessage("Who are John's children?")]
|
|
192
|
+
response = llm_with_tools.invoke(messages)
|
|
193
|
+
messages.append(response)
|
|
194
|
+
print(response.tool_calls[0])
|
|
195
|
+
```
|
|
196
|
+
The LLM will respond with a tool call request:
|
|
197
|
+
```python
|
|
198
|
+
{'name': 'family_query',
|
|
199
|
+
'args': {'men': 'john', 'women': None, 'child': None},
|
|
200
|
+
'id': 'call_V6NUsJwhF41G9G7q6TBmghR0',
|
|
201
|
+
'type': 'tool_call'}
|
|
202
|
+
```
|
|
203
|
+
The tool takes this request and queries the Prolog database:
|
|
204
|
+
```python
|
|
205
|
+
tool_msg = prolog_tool.invoke(response.tool_calls[0])
|
|
206
|
+
messages.append(tool_msg)
|
|
207
|
+
print(tool_msg)
|
|
208
|
+
```
|
|
209
|
+
The tool returns a list with all the solutions for the query:
|
|
210
|
+
```python
|
|
211
|
+
content='[{"Women": "bianca", "Child": "mary"}, {"Women": "bianca", "Child": "michael"}]'
|
|
212
|
+
name='family_query'
|
|
213
|
+
tool_call_id='call_V6NUsJwhF41G9G7q6TBmghR0'
|
|
214
|
+
```
|
|
215
|
+
That we then pass to the LLM:
|
|
216
|
+
```python
|
|
217
|
+
answer = llm_with_tools.invoke(messages)
|
|
218
|
+
print(answer.content)
|
|
219
|
+
```
|
|
220
|
+
And the LLM answers the original query using the tool response:
|
|
221
|
+
```python
|
|
222
|
+
John has two children: Mary and Michael. Their mother is Bianca.
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### 2) Using a Prolog tool with an agent
|
|
226
|
+
|
|
227
|
+
First create the Prolog tool:
|
|
228
|
+
```python
|
|
229
|
+
from langchain_prolog import PrologConfig, PrologTool
|
|
230
|
+
|
|
231
|
+
schema = PrologRunnable.create_schema('parent', ['men', 'women', 'child'])
|
|
232
|
+
config = PrologConfig(
|
|
233
|
+
rules_file="family.pl",
|
|
234
|
+
query_schema=schema,
|
|
235
|
+
)
|
|
236
|
+
prolog_tool = PrologTool(
|
|
237
|
+
prolog_config=config,
|
|
238
|
+
name="family_query",
|
|
239
|
+
description="""
|
|
240
|
+
Query family relationships using Prolog.
|
|
241
|
+
parent(X, Y, Z) implies only that Z is a child of X and Y.
|
|
242
|
+
Input can be a query string like 'parent(john, X, Y)'
|
|
243
|
+
or 'john, X, Y'"
|
|
244
|
+
You have to specify 3 parameters: men, woman, child.
|
|
245
|
+
Do not use quotes.
|
|
246
|
+
"""
|
|
247
|
+
)
|
|
248
|
+
```
|
|
249
|
+
Then pass it to the agent's constructor:
|
|
250
|
+
```python
|
|
251
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
252
|
+
from langchain.agents import create_tool_calling_agent, AgentExecutor
|
|
253
|
+
|
|
254
|
+
llm = ChatOpenAI(model="gpt-4o-mini")
|
|
255
|
+
prompt = ChatPromptTemplate.from_messages(
|
|
256
|
+
[
|
|
257
|
+
("system", "You are a helpful assistant"),
|
|
258
|
+
("human", "{input}"),
|
|
259
|
+
("placeholder", "{agent_scratchpad}"),
|
|
260
|
+
]
|
|
261
|
+
)
|
|
262
|
+
tools = [prolog_tool]
|
|
263
|
+
agent = create_tool_calling_agent(llm, tools, prompt)
|
|
264
|
+
agent_executor = AgentExecutor(agent=agent, tools=tools)
|
|
265
|
+
```
|
|
266
|
+
The agent takes the query and use the Prolog tool if needed:
|
|
267
|
+
```python
|
|
268
|
+
answer = agent_executor.invoke({"input": "Who are John's children?"})
|
|
269
|
+
print(answer)
|
|
270
|
+
```
|
|
271
|
+
Then the agent recieves the tool response as part of the {agent_scratchpad} placeholder and generates the answer:
|
|
272
|
+
```python
|
|
273
|
+
{'input': "Who are John's children?", 'output': 'John has two children: Mary and Michael, with Bianca as their mother.'}
|
|
274
|
+
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
[](https://opensource.org/licenses/MIT)
|
|
2
|
+
[](https://badge.fury.io/py/langchain-prolog)
|
|
3
|
+
[](https://www.python.org/downloads/release/python-390/)
|
|
4
|
+
[](https://langchain-prolog.readthedocs.io/en/latest/?badge=latest)
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
# LangChain-Prolog
|
|
8
|
+
|
|
9
|
+
A Python library that integrates SWI-Prolog with LangChain, allowing seamless
|
|
10
|
+
blending of Prolog's logic programming capabilities into LangChain applications.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Seamless integration between LangChain and SWI-Prolog
|
|
16
|
+
- Use Prolog queries as LangChain runnables and tools
|
|
17
|
+
- Invoke Prolog predicates from LangChain LLM models, chains and agents
|
|
18
|
+
- Support for both synchronous and asynchronous operations
|
|
19
|
+
- Comprehensive error handling and logging
|
|
20
|
+
- Cross-platform support (macOS, Linux, Windows)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
#### Prerequisites
|
|
25
|
+
|
|
26
|
+
- Python 3.10 or later
|
|
27
|
+
- SWI-Prolog installed on your system
|
|
28
|
+
- The following python libraries installed:
|
|
29
|
+
- langchain 0.3.0 or later
|
|
30
|
+
- janus-swi 1.5.0 or later
|
|
31
|
+
- pydantic 0.2.0 or later
|
|
32
|
+
|
|
33
|
+
langchain-prolog can be installed using pip:
|
|
34
|
+
```bash
|
|
35
|
+
pip install langchain-prolog
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Runnable Interface
|
|
39
|
+
|
|
40
|
+
The PrologRunnable class allows the generation of langchain runnables that use Prolog rules to generate answers.
|
|
41
|
+
|
|
42
|
+
Let's assume that we have the following set of Prolog rules in the file family.pl:
|
|
43
|
+
|
|
44
|
+
```prolog
|
|
45
|
+
parent(john, bianca, mary).
|
|
46
|
+
parent(john, bianca, michael).
|
|
47
|
+
parent(peter, patricia, jennifer).
|
|
48
|
+
partner(X, Y) :- parent(X, Y, _).
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
There are three diferent ways to use a PrologRunnable to query Prolog:
|
|
52
|
+
|
|
53
|
+
#### 1) Using a Prolog runnable with a full predicate string
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from langchain_prolog import PrologConfig, PrologRunnable
|
|
57
|
+
|
|
58
|
+
config = PrologConfig(rules_file="family.pl")
|
|
59
|
+
prolog = PrologRunnable(prolog_config=config)
|
|
60
|
+
result = prolog.invoke("partner(X, Y)")
|
|
61
|
+
print(result)
|
|
62
|
+
```
|
|
63
|
+
We can pass a string representing a single predicate query. The invoke method will return `True`, `False` or a list of dictionaries with all the solutions to the query:
|
|
64
|
+
```python
|
|
65
|
+
[{'X': 'john', 'Y': 'bianca'},
|
|
66
|
+
{'X': 'john', 'Y': 'bianca'},
|
|
67
|
+
{'X': 'peter', 'Y': 'patricia'}]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### 2) Using a Prolog runnable with a default predicate
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from langchain_prolog import PrologConfig, PrologRunnable
|
|
74
|
+
|
|
75
|
+
config = PrologConfig(rules_file="family.pl", default_predicate="partner")
|
|
76
|
+
prolog = PrologRunnable(prolog_config=config)
|
|
77
|
+
result = prolog.invoke("peter, X")
|
|
78
|
+
print(result)
|
|
79
|
+
```
|
|
80
|
+
When using a default predicate, only the arguments for the predicate are passed to the Prolog runable, as a single string. Following Prolog conventions, uppercase identifiers are variables and lowercase identifiers are values (atoms or strings):
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
[{'X': 'patricia'}]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3) Using a Prolog runnable with a dictionary and schema validation
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from langchain_prolog import PrologConfig, PrologRunnable
|
|
90
|
+
|
|
91
|
+
schema = PrologRunnable.create_schema("partner", ["man", "woman"])
|
|
92
|
+
config = PrologConfig(rules_file="family.pl", query_schema=schema)
|
|
93
|
+
prolog = PrologRunnable(prolog_config=config)
|
|
94
|
+
result = prolog.invoke({"man": None, "woman": "bianca"})
|
|
95
|
+
print(result)
|
|
96
|
+
```
|
|
97
|
+
If a schema is defined, we can pass a dictionary using the names of the parameters in the schema as the keys in the dictionary. The values can represent Prolog variables (uppercase first letter) or strings (lower case first letter). A `None` value is interpreted as a variable and replaced with the key capitalized:
|
|
98
|
+
```python
|
|
99
|
+
[{'Man': 'john'}, {'Man': 'john'}]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
You can also pass a Pydantic object generated with the schema to the invoke method:
|
|
103
|
+
```python
|
|
104
|
+
args = schema(man='M', woman='W')
|
|
105
|
+
result = prolog.invoke(args)
|
|
106
|
+
print(result)
|
|
107
|
+
```
|
|
108
|
+
Uppercase values are treated as variables:
|
|
109
|
+
```python
|
|
110
|
+
[{'M': 'john', 'W': 'bianca'},
|
|
111
|
+
{'M': 'john', 'W': 'bianca'},
|
|
112
|
+
{'M': 'peter', 'W': 'patricia'}]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Tool Interface
|
|
116
|
+
|
|
117
|
+
The PrologTool class allows the generation of langchain tools that use Prolog rules to generate answers.
|
|
118
|
+
|
|
119
|
+
See the Runnable Interface section for the conventions on hown to pass variables and values to the Prolog interpreter.
|
|
120
|
+
|
|
121
|
+
Let's assume that we have the following set of Prolog rules in the file family.pl:
|
|
122
|
+
|
|
123
|
+
```prolog
|
|
124
|
+
parent(john, bianca, mary).
|
|
125
|
+
parent(john, bianca, michael).
|
|
126
|
+
parent(peter, patricia, jennifer).
|
|
127
|
+
partner(X, Y) :- parent(X, Y, _).
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
There are two ways to use a Prolog tool:
|
|
131
|
+
|
|
132
|
+
### 1) Using a Prolog tool with an LLM and function calling
|
|
133
|
+
|
|
134
|
+
First create the Prolog tool:
|
|
135
|
+
```python
|
|
136
|
+
from langchain_prolog import PrologConfig, PrologTool
|
|
137
|
+
|
|
138
|
+
schema = PrologRunnable.create_schema('parent', ['men', 'women', 'child'])
|
|
139
|
+
config = PrologConfig(
|
|
140
|
+
rules_file="family.pl",
|
|
141
|
+
query_schema=schema,
|
|
142
|
+
)
|
|
143
|
+
prolog_tool = PrologTool(
|
|
144
|
+
prolog_config=config,
|
|
145
|
+
name="family_query",
|
|
146
|
+
description="""
|
|
147
|
+
Query family relationships using Prolog.
|
|
148
|
+
parent(X, Y, Z) implies only that Z is a child of X and Y.
|
|
149
|
+
Input can be a query string like 'parent(john, X, Y)'
|
|
150
|
+
or 'john, X, Y'"
|
|
151
|
+
You have to specify 3 parameters: men, woman, child.
|
|
152
|
+
Do not use quotes.
|
|
153
|
+
"""
|
|
154
|
+
)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Then bind it to the LLM model and query the model:
|
|
158
|
+
```python
|
|
159
|
+
from langchain_openai import ChatOpenAI
|
|
160
|
+
from langchain_core.messages import HumanMessage
|
|
161
|
+
|
|
162
|
+
llm = ChatOpenAI(model="gpt-4o-mini")
|
|
163
|
+
llm_with_tools = llm.bind_tools([prolog_tool])
|
|
164
|
+
messages = [HumanMessage("Who are John's children?")]
|
|
165
|
+
response = llm_with_tools.invoke(messages)
|
|
166
|
+
messages.append(response)
|
|
167
|
+
print(response.tool_calls[0])
|
|
168
|
+
```
|
|
169
|
+
The LLM will respond with a tool call request:
|
|
170
|
+
```python
|
|
171
|
+
{'name': 'family_query',
|
|
172
|
+
'args': {'men': 'john', 'women': None, 'child': None},
|
|
173
|
+
'id': 'call_V6NUsJwhF41G9G7q6TBmghR0',
|
|
174
|
+
'type': 'tool_call'}
|
|
175
|
+
```
|
|
176
|
+
The tool takes this request and queries the Prolog database:
|
|
177
|
+
```python
|
|
178
|
+
tool_msg = prolog_tool.invoke(response.tool_calls[0])
|
|
179
|
+
messages.append(tool_msg)
|
|
180
|
+
print(tool_msg)
|
|
181
|
+
```
|
|
182
|
+
The tool returns a list with all the solutions for the query:
|
|
183
|
+
```python
|
|
184
|
+
content='[{"Women": "bianca", "Child": "mary"}, {"Women": "bianca", "Child": "michael"}]'
|
|
185
|
+
name='family_query'
|
|
186
|
+
tool_call_id='call_V6NUsJwhF41G9G7q6TBmghR0'
|
|
187
|
+
```
|
|
188
|
+
That we then pass to the LLM:
|
|
189
|
+
```python
|
|
190
|
+
answer = llm_with_tools.invoke(messages)
|
|
191
|
+
print(answer.content)
|
|
192
|
+
```
|
|
193
|
+
And the LLM answers the original query using the tool response:
|
|
194
|
+
```python
|
|
195
|
+
John has two children: Mary and Michael. Their mother is Bianca.
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 2) Using a Prolog tool with an agent
|
|
199
|
+
|
|
200
|
+
First create the Prolog tool:
|
|
201
|
+
```python
|
|
202
|
+
from langchain_prolog import PrologConfig, PrologTool
|
|
203
|
+
|
|
204
|
+
schema = PrologRunnable.create_schema('parent', ['men', 'women', 'child'])
|
|
205
|
+
config = PrologConfig(
|
|
206
|
+
rules_file="family.pl",
|
|
207
|
+
query_schema=schema,
|
|
208
|
+
)
|
|
209
|
+
prolog_tool = PrologTool(
|
|
210
|
+
prolog_config=config,
|
|
211
|
+
name="family_query",
|
|
212
|
+
description="""
|
|
213
|
+
Query family relationships using Prolog.
|
|
214
|
+
parent(X, Y, Z) implies only that Z is a child of X and Y.
|
|
215
|
+
Input can be a query string like 'parent(john, X, Y)'
|
|
216
|
+
or 'john, X, Y'"
|
|
217
|
+
You have to specify 3 parameters: men, woman, child.
|
|
218
|
+
Do not use quotes.
|
|
219
|
+
"""
|
|
220
|
+
)
|
|
221
|
+
```
|
|
222
|
+
Then pass it to the agent's constructor:
|
|
223
|
+
```python
|
|
224
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
225
|
+
from langchain.agents import create_tool_calling_agent, AgentExecutor
|
|
226
|
+
|
|
227
|
+
llm = ChatOpenAI(model="gpt-4o-mini")
|
|
228
|
+
prompt = ChatPromptTemplate.from_messages(
|
|
229
|
+
[
|
|
230
|
+
("system", "You are a helpful assistant"),
|
|
231
|
+
("human", "{input}"),
|
|
232
|
+
("placeholder", "{agent_scratchpad}"),
|
|
233
|
+
]
|
|
234
|
+
)
|
|
235
|
+
tools = [prolog_tool]
|
|
236
|
+
agent = create_tool_calling_agent(llm, tools, prompt)
|
|
237
|
+
agent_executor = AgentExecutor(agent=agent, tools=tools)
|
|
238
|
+
```
|
|
239
|
+
The agent takes the query and use the Prolog tool if needed:
|
|
240
|
+
```python
|
|
241
|
+
answer = agent_executor.invoke({"input": "Who are John's children?"})
|
|
242
|
+
print(answer)
|
|
243
|
+
```
|
|
244
|
+
Then the agent recieves the tool response as part of the {agent_scratchpad} placeholder and generates the answer:
|
|
245
|
+
```python
|
|
246
|
+
{'input': "Who are John's children?", 'output': 'John has two children: Mary and Michael, with Bianca as their mother.'}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["poetry-core>=1.0.0"]
|
|
3
|
+
build-backend = "poetry.core.masonry.api"
|
|
4
|
+
|
|
5
|
+
[tool.poetry]
|
|
6
|
+
name = "langchain-prolog"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "An integration package connecting Prolog and LangChain"
|
|
9
|
+
authors = ["Antonio Pisani"]
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
repository = "https://github.com/apisani1/langchain-prolog"
|
|
12
|
+
license = "MIT"
|
|
13
|
+
packages = [{ include = "langchain_prolog", from = "src" }]
|
|
14
|
+
keywords = ["langchain", "prolog", "swi-prolog", "llm", "agent"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Prolog",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
24
|
+
]
|
|
25
|
+
include = [
|
|
26
|
+
{ path = "src/langchain_prolog/*.pl", format = "sdist" },
|
|
27
|
+
{ path = "src/langchain_prolog/*.pl", format = "wheel" }
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[tool.poetry.urls]
|
|
31
|
+
"Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/prolog"
|
|
32
|
+
"Release Notes" = "https://github.com/langchain-ai/langchain/releases?q=tag%3A%22prolog%3D%3D0%22&expanded=true"
|
|
33
|
+
|
|
34
|
+
[tool.poetry.dependencies]
|
|
35
|
+
python = ">=3.10,<4.0"
|
|
36
|
+
langchain-core = "^0.3.0"
|
|
37
|
+
janus-swi = "^1.5.0"
|
|
38
|
+
pydantic = "^2.0.0"
|
|
39
|
+
|
|
40
|
+
[tool.coverage.run]
|
|
41
|
+
omit = ["tests/*"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
addopts = "--strict-markers --strict-config --durations=5"
|
|
45
|
+
markers = [
|
|
46
|
+
"compile: mark placeholder test used to compile integration tests without running them",
|
|
47
|
+
"integration: mark integration tests",
|
|
48
|
+
"requires_prolog: mark tests that require SWI-Prolog",]
|
|
49
|
+
asyncio_mode = "auto"
|
|
50
|
+
|
|
51
|
+
[tool.poetry.group.test]
|
|
52
|
+
optional = true
|
|
53
|
+
|
|
54
|
+
[tool.poetry.group.lint]
|
|
55
|
+
optional = true
|
|
56
|
+
|
|
57
|
+
[tool.poetry.group.dev]
|
|
58
|
+
optional = true
|
|
59
|
+
|
|
60
|
+
[tool.poetry.group.dev.dependencies]
|
|
61
|
+
black = "^25.1.0"
|
|
62
|
+
isort = "^6.0.0"
|
|
63
|
+
mypy = "^1.15.0"
|
|
64
|
+
|
|
65
|
+
[tool.poetry.group.test.dependencies]
|
|
66
|
+
pytest = "^7.0.0"
|
|
67
|
+
pytest-asyncio = "^0.21.0"
|
|
68
|
+
|
|
69
|
+
[tool.poetry.group.lint.dependencies]
|
|
70
|
+
flake8 = "^7.1.0"
|
|
71
|
+
|
|
72
|
+
[tool.poetry.group.typing.dependencies]
|
|
73
|
+
mypy = "^1.10"
|
|
74
|
+
|
|
75
|
+
[tool.poetry.group.docs.dependencies]
|
|
76
|
+
sphinx = "^8.1.3"
|
|
77
|
+
sphinx-rtd-theme = "^3.0.2"
|
|
78
|
+
myst-parser = "^4.0.0"
|
|
79
|
+
sphinx-copybutton = "^0.5.2"
|
|
80
|
+
|
|
81
|
+
[tool.black]
|
|
82
|
+
line-length = 100
|
|
83
|
+
target-version = ['py39']
|
|
84
|
+
|
|
85
|
+
[tool.isort]
|
|
86
|
+
profile = "black"
|
|
87
|
+
line_length = 100
|
|
88
|
+
|
|
89
|
+
[tool.mypy]
|
|
90
|
+
python_version = "3.9"
|
|
91
|
+
disallow_untyped_defs = true
|
|
92
|
+
warn_return_any = true
|
|
93
|
+
warn_unused_configs = true
|
|
94
|
+
check_untyped_defs = true
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
langchain_prolog - A LangChain integration for SWI-Prolog
|
|
3
|
+
|
|
4
|
+
This module provides a bridge between LangChain and SWI-Prolog, allowing seamless
|
|
5
|
+
integration of Prolog's logic programming capabilities into LangChain pipelines.
|
|
6
|
+
|
|
7
|
+
Key Components:
|
|
8
|
+
- PrologConfig: Configuration class for Prolog settings
|
|
9
|
+
- PrologRunnable: Main class for executing Prolog queries
|
|
10
|
+
- PrologTool: Utility class for integrating Prolog queries into LangChain tools
|
|
11
|
+
- PrologResult: Type representing possible Prolog query results
|
|
12
|
+
- PrologInput: Type representing valid input formats
|
|
13
|
+
- PrologRuntimeError: Exception class for Prolog execution errors
|
|
14
|
+
|
|
15
|
+
Requirements:
|
|
16
|
+
- Python 3.9 or higher
|
|
17
|
+
- LangChain 0.3.0 or higher
|
|
18
|
+
- Pydantic 2.0 or higher
|
|
19
|
+
- SWI-Prolog must be installed and accessible in the system path
|
|
20
|
+
- On macOS, requires Homebrew installation of SWI-Prolog
|
|
21
|
+
- The janus_swipl package must be installed
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from pydantic import ValidationError
|
|
25
|
+
|
|
26
|
+
from ._prolog_init import initialize_prolog
|
|
27
|
+
from .exceptions import (
|
|
28
|
+
PrologFileNotFoundError,
|
|
29
|
+
PrologInitializationError,
|
|
30
|
+
PrologRuntimeError,
|
|
31
|
+
PrologToolException,
|
|
32
|
+
PrologValueError,
|
|
33
|
+
)
|
|
34
|
+
from .runnable import (
|
|
35
|
+
PrologConfig,
|
|
36
|
+
PrologInput,
|
|
37
|
+
PrologResult,
|
|
38
|
+
PrologRunnable,
|
|
39
|
+
)
|
|
40
|
+
from .tool import PrologTool
|
|
41
|
+
from .__version__ import __version__
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"PrologConfig",
|
|
46
|
+
"PrologInput",
|
|
47
|
+
"PrologRunnable",
|
|
48
|
+
"PrologRuntimeError",
|
|
49
|
+
"PrologFileNotFoundError",
|
|
50
|
+
"PrologInitializationError",
|
|
51
|
+
"PrologToolException",
|
|
52
|
+
"PrologValueError",
|
|
53
|
+
"PrologResult",
|
|
54
|
+
"PrologTool",
|
|
55
|
+
"ValidationError",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# Initialize Prolog immediately when this module is imported
|
|
59
|
+
initialize_prolog()
|