Run this notebook

Use Livebook to open this notebook and explore new ideas.

It is easy to get started, on your machine or the cloud.

Click below to open and run it in your Livebook at .

(or change your Livebook location)

# PTC-Lisp Playground ```elixir # For local dev: run `mix deps.get` in the project root first repo_root = Path.expand("..", __DIR__) deps = if File.exists?(Path.join(repo_root, "mix.exs")) do [{:ptc_runner, path: repo_root}] else [{:ptc_runner, "~> 0.9.0"}] end Mix.install(deps, consolidate_protocols: false) ``` ## Introduction PTC-Lisp is a small, safe subset of Clojure designed for Programmatic Tool Calling. Programs run in sandboxed BEAM processes with resource limits (1s timeout, 10MB memory). **Key concepts:** * `->>` threads data through a pipeline (like Elixir's `|>`) * `:keyword` accesses map fields (converted to string keys internally) * `where` builds predicates for filtering * `tool/tool-name` calls external tools ## Basic Example Filter expenses and sum amounts: ```elixir tools = %{ "get-expenses" => fn _args -> [ %{"category" => "travel", "amount" => 500}, %{"category" => "food", "amount" => 50}, %{"category" => "travel", "amount" => 200} ] end } program = ~S|(->> (tool/get-expenses) (filter (where :category = "travel")) (sum-by :amount))| {:ok, step} = PtcRunner.Lisp.run(program, tools: tools) IO.puts("Travel expenses: #{step.return}") ``` ### Step-by-step breakdown 1. `(tool/get-expenses)` - calls the tool, returns list of expense maps 2. `(filter (where :category = "travel"))` - keeps only travel expenses 3. `(sum-by :amount)` - sums the amount field ## PTC-Lisp Extensions The example above uses `where` and `sum-by`, which are PTC-Lisp extensions not found in standard Clojure. These make common filtering and aggregation patterns more concise. In standard Clojure, the same logic would be: ```elixir program = ~S|(->> (tool/get-expenses) (filter #(= (:category %) "travel")) (map :amount) (reduce +))| {:ok, step} = PtcRunner.Lisp.run(program, tools: tools) IO.puts("Travel expenses (Clojure style): #{step.return}") ``` **Key PTC-Lisp extensions:** | Extension | Clojure Equivalent | Purpose | | ------------------------ | ------------------------------ | ------------------------------ | | `(where :field = value)` | `#(= (:field %) value)` | Build predicates for filtering | | `(sum-by :field coll)` | `(reduce + (map :field coll))` | Sum a field across collection | | `(avg-by :field coll)` | Manual calculation | Average a field | | `(all-of pred1 pred2)` | `#(and (pred1 %) (pred2 %))` | Combine predicates with AND | | `(any-of pred1 pred2)` | `#(or (pred1 %) (pred2 %))` | Combine predicates with OR | These extensions are designed for LLM code generation - they reduce syntax errors and make intent clearer. ## Key Convention Tools receive and return **string-keyed maps** (like JSON): ```elixir # Tool data uses string keys %{"category" => "travel", "amount" => 500} ``` PTC-Lisp's FlexAccess makes `:keyword` access work transparently with string-keyed data: ```elixir # These all work on string-keyed maps: (:category item) # => "travel" (where :category = "travel") # matches %{"category" => "travel"} (sum-by :amount expenses) # sums the "amount" field ``` **Why string keys?** * Prevents atom table exhaustion from LLM-generated code * Matches JSON conventions (external APIs, Phoenix params) * Tools pattern match on string keys: `fn %{"id" => id} -> ... end` ## Working with Variables Use `let` to bind intermediate results: ```elixir program = ~S""" (let [expenses (tool/get-expenses) travel (filter (where :category = "travel") expenses)] {:count (count travel) :total (sum-by :amount travel) :avg (avg-by :amount travel)}) """ {:ok, step} = PtcRunner.Lisp.run(program, tools: tools) step.return ``` ## Error Handling PTC-Lisp provides helpful error messages with hints: ```elixir # Missing operator in where clause bad_program = ~S|(filter (where :status "active") items)| case PtcRunner.Lisp.run(bad_program, tools: %{}) do {:error, error} -> IO.puts("Error: #{inspect(error)}") {:ok, step} -> step.return end ``` ```elixir # Type error - sum-by needs a collection bad_program = ~S|(sum-by :amount "not a list")| case PtcRunner.Lisp.run(bad_program, tools: %{}) do {:error, error} -> IO.puts("Error: #{inspect(error)}") {:ok, step} -> step.return end ``` ## Data Transformation Transform and join data from multiple sources: ```elixir tools = %{ "get-users" => fn _args -> [ %{"id" => 1, "name" => "Alice", "email" => "alice@example.com"}, %{"id" => 2, "name" => "Bob", "email" => "bob@example.com"} ] end, "get-orders" => fn _args -> [ %{"user-id" => 1, "product" => "Laptop", "total" => 1200}, %{"user-id" => 2, "product" => "Mouse", "total" => 25}, %{"user-id" => 1, "product" => "Keyboard", "total" => 150} ] end } program = ~S""" (let [users (tool/get-users) orders (tool/get-orders) high-value (filter (where :total > 100) orders)] (->> high-value (mapv (fn [order] (let [user (find (where :id = (:user-id order)) users)] {:customer (:name user) :product (:product order) :total (:total order)}))))) """ {:ok, step} = PtcRunner.Lisp.run(program, tools: tools) step.return ``` ## Advanced: Grouping and Aggregation Group expenses by category and compute totals: ```elixir expenses_tools = %{ "get-expenses" => fn _args -> [ %{"category" => "travel", "amount" => 500}, %{"category" => "food", "amount" => 50}, %{"category" => "travel", "amount" => 200}, %{"category" => "food", "amount" => 75} ] end } program = ~S""" (let [expenses (tool/get-expenses) by-category (group-by :category expenses)] (->> (keys by-category) (mapv (fn [cat] {:category cat :total (sum-by :amount (get by-category cat)) :count (count (get by-category cat))})))) """ {:ok, step} = PtcRunner.Lisp.run(program, tools: expenses_tools) step.return ``` ## Learn More * [PTC-Lisp Specification](https://hexdocs.pm/ptc_runner/ptc-lisp-specification.html) - Complete language reference * [SubAgent Getting Started](https://hexdocs.pm/ptc_runner/subagent-getting-started.html) - Build LLM-powered agents * [LLM Agent Livebook](ptc_runner_llm_agent.livemd) - Interactive agent example
See source

Have you already installed Livebook?

If you already installed Livebook, you can configure the default Livebook location where you want to open notebooks.
Livebook up Checking status We can't reach this Livebook (but we saved your preference anyway)
Run notebook

Not yet? Install Livebook in just a minute

Livebook is open source, free, and ready to run anywhere.

Run on your machine

with Livebook Desktop

Run in the cloud

on select platforms

To run on Linux, Docker, embedded devices, or Elixir’s Mix, check our README.

PLATINUM SPONSORS
SPONSORS
Code navigation with go to definition of modules and functions Read More