Calling Prolog from Python

Published

In a previous article, I used Prolog to generate a procedural storyline. The next problem was then to create text for it, with the recent release of ChatGPT by Open.ai it seems this problem can be solved and I can use the previously procedural story points to generate a full story with this model.

Still I’m more familiar with Python and there are some utilities which allow to directly interact with ChatGPT in Python, such as PyChatGPT.

Now I just need to be able to run my Prolog queries through Python and get the results. So, how do we do that?

There are several ways to do this, but the best way seems to be swiplserver:

  • It has official support and can be found in the official SWI-Prolog repository on Github
  • Its usage is simple and feels like using any other Python library
  • There is support for multithreading (not Python threads)
  • It returns all potential solutions, not just the first one

Under the hood, it actually calls your local install of swipl. This makes the setup quite straightforward. All data is returned as a JSON object.

For this demo, I’m using the code from my Completionist repository. This takes a list of objects, locations and constraints on location access to generate a new adventure every time, similar to what’s done in randomizers.

Here’s what using swiplserver looks like:

# Import the library, it uses a message queue interface to communicate with Swipl
from swiplserver import PrologMQI

# Create an execution thread. You can create several of these 
prolog_thread = mqi.create_thread()

# Now we use the query method to do what we want
# This supports pretty much everything 

# let's start by importing the knowledge base 
prolog_thread.query("[generator]")

# now we generate a "world" 
# I'm using assertz and dynamics in my code, so this is supported as well
prolog_thread.query("generate()")

# Now let's make an actual query 
# You can notice here that it indeeds returns all solutions not only the first match 
rslt = prolog_thread.query("location(Location)")
print(rslt)
>> [{'Location': 'mido_house'}, {'Location': 'sword_chest'}, {'Location': 'deku_tree'}, {'Location': 'hyrule_castle'}, {'Location': 'temple_of_time'}, {'Location': 'ganon'}] 

# Very nice, but we can also obviously use built-in predicates  
# Let's get all locations with a findall  
rslt = prolog_thread.query("findall(Location, location(Location), Locs)")
print(rslt)
>> [{'Location': '_', 'Locs': ['mido_house', 'sword_chest', 'deku_tree', 'hyrule_castle', 'temple_of_time', 'ganon']}]  

# Finally, we'll get the items locations which have set by the procedure 
# Once again, no issues we can get access to data that has been set at runtime 
rslt = prolog_thread.query("contains(Location, Item)")
print(rslt)
>> [{'Location': 'mido_house', 'Item': 'sword'}, {'Location': 'sword_chest', 'Item': 'bow'}, {'Location': 'hyrule_castle', 'Item': 'light_arrows'}, {'Location': 'temple_of_time', 'Item': 'ocarina'}]

# You can also set a timeout for your queries  
prolog_thread.query("contains(Location, Item)", query_timeout_seconds=3.0)

# Or perform an async query  
prolog_thread.query_async("contains(Location, Item)", find_all=True, query_timeout_seconds=3.0)

# Don't forget to stop the message queue! 
mqi.stop()

I’m quite happy about the ease of use swiplserver! It does require a local install of swipl but apart from that it’s quite nice and allows the use of all Prolog features.

I’d also like to try out Prolog in different environments, there’s support to compile SWI-Prolog in the browser using Wasm compilation so I definitely want to test it out.

Next time, we’ll be using the output from our Prolog algorithm to serve as input for ChatGPT in a fully automated way. Stay tuned!