Skip to main content

Prompt Oriented Programming (POP): reusable, composable prompt functions for LLMs.

Project description

Prompt Oriented Programming (POP)

from POP import PromptFunction

pf = PromptFunction(
    prompt="Draw a simple ASCII art of <<<object>>>.",
    client="openai",
)

print(pf.execute(object="a cat"))
print(pf.execute(object="a rocket"))
 /\_/\  
( o.o )
 > ^ <  

   /\
  /  \
 /    \
 |    |
 |    |

Reusable, composable prompt functions for LLM workflows.

This 1.1.0 dev update restructures POP into small, focused modules and adds a provider registry inspired by pi-mono's ai package.

PyPI: https://pypi.org/project/pop-python/

GitHub: https://github.com/sgt1796/POP


Table of Contents

  1. Overview
  2. Update Note
  3. Major Updates
  4. Features
  5. Installation
  6. Setup
  7. PromptFunction
  8. Provider Registry
  9. Tool Calling
  10. Agent Loop (POP.agent)
  11. Function Schema Generation
  12. Embeddings
  13. Web Snapshot Utility
  14. Examples
  15. Contributing

1. Overview

Prompt Oriented Programming (POP) is a lightweight framework for building reusable, parameterized prompt functions. Instead of scattering prompt strings across your codebase, POP lets you:

  • encapsulate prompts as objects
  • pass parameters cleanly via placeholders
  • select a backend LLM client dynamically
  • improve prompts using meta-prompting
  • generate OpenAI-compatible function schemas
  • use unified embedding tools
  • work with multiple LLM providers through a centralized registry

POP is designed to be simple, extensible, and production-friendly.


2. Update Note

1.1.0-dev (February 5, 2026)

  • Breaking import path: use POP (uppercase) for imports. Example: from POP import PromptFunction.
  • Provider registry: clients live under POP/providers/ and are instantiated via POP.api_registry.
  • LLMClient base class: now in POP.providers.llm_client (kept as an abstract base class).

3. Major Updates

3.1. Modularized architecture

The project has been decomposed into small, focused modules:

  • POP/prompt_function.py
  • POP/embedder.py
  • POP/context.py
  • POP/api_registry.py
  • POP/providers/ (one provider per file)
  • POP/utils/

This mirrors the structure in the pi-mono ai package for clarity and maintainability.

3.2. Provider registry + per-provider clients

Each provider has its own adaptor (OpenAI, Gemini, DeepSeek, Doubao, Local, Ollama). The registry gives you:

  • list_providers()
  • list_default_model()
  • list_models()
  • get_client()

4. Features

  • Reusable Prompt Functions Use <<<placeholder>>> syntax to inject dynamic content.

  • Multi-LLM Backend Choose between OpenAI, Gemini, DeepSeek, Doubao, Local, or Ollama.

  • Tool Calling Pass a tool schema list to execute() and receive tool-call arguments.

  • Multimodal (Text + Image) Pass images=[...] (URLs or base64) when the provider supports it.

  • Prompt Improvement Improve or rewrite prompts using Fabric-style meta-prompts.

  • Function Schema Generation Convert natural language descriptions into OpenAI-function schemas.

  • Unified Embedding Interface Supports OpenAI, Jina AI embeddings, and local HuggingFace models.

  • Webpage Snapshot Utility Convert any URL into structured text using r.jina.ai with optional image captioning.


5. Installation

Install from PyPI:

pip install pop-python

Or install in development mode from GitHub:

git clone https://github.com/sgt1796/POP.git
cd POP
pip install -e .

6. Setup

Create a .env file in your project root:

OPENAI_API_KEY=your_openai_key
GEMINI_API_KEY=your_gcp_gemini_key
DEEPSEEK_API_KEY=your_deepseek_key
DOUBAO_API_KEY=your_volcengine_key
JINAAI_API_KEY=your_jina_key

All clients automatically read keys from environment variables.


7. PromptFunction

The core abstraction of POP is the PromptFunction class.

from POP import PromptFunction

pf = PromptFunction(
    sys_prompt="You are a helpful AI.",
    prompt="Give me a summary about <<<topic>>>.",
)

print(pf.execute(topic="quantum biology"))

7.1. Placeholder Syntax

Use angle-triple-brackets inside your prompt:

<<<placeholder>>>

These are replaced at execution time.

Example:

prompt = "Translate <<<sentence>>> to French."

7.2. Reserved Keywords

Within .execute(), the following keyword arguments are reserved and should not be used as placeholder names:

  • model
  • sys
  • fmt
  • tools
  • tool_choice
  • temp
  • images
  • ADD_BEFORE
  • ADD_AFTER

Most keywords are used for parameters. ADD_BEFORE and ADD_AFTER will attach input string to head/tail of the prompt.


7.3. Executing prompts

result = pf.execute(
    topic="photosynthesis",
    model="gpt-5-mini",
    temp=0.3,
)

7.4. Improving Prompts

You can ask POP to rewrite or enhance your system prompt:

better = pf.improve_prompt()
print(better)

This uses a Fabric-inspired meta-prompt bundled in the POP/prompts/ directory.


8. Provider Registry

Use the registry to list providers/models or instantiate clients.

from POP import list_providers, list_models, list_default_model, get_client

print(list_providers())
print(list_default_model())
print(list_models())

client = get_client("openai")

Non-default model example:

from POP import PromptFunction, get_client

client = get_client("gemini", "gemini-2.5-pro")

pf = PromptFunction(prompt="Draw a rocket.", client=client)
print(pf.execute())

Direct provider class example:

from POP import PromptFunction
from POP.providers.gemini_client import GeminiClient

pf = PromptFunction(prompt="Draw a rocket.", client=GeminiClient(model="gemini-2.5-pro"))
print(pf.execute())

9. Tool Calling

from POP import PromptFunction

tools = [
    {
        "type": "function",
        "function": {
            "name": "create_reminder",
            "description": "Create a reminder.",
            "parameters": {
                "type": "object",
                "properties": {
                    "description": {"type": "string"},
                    "when": {"type": "string"},
                },
                "required": ["description"],
            },
        },
    }
]

pf = PromptFunction(
    sys_prompt="You are a helpful assistant.",
    prompt="<<<input>>>",
    client="openai",
)

result = pf.execute(input="Remind me to walk at 9am.", tools=tools)
print(result)

10. Agent Loop (POP.agent)

POP includes a lightweight, event-driven agent loop in the agent/ package for tool calling, steering, and follow-ups. It is designed to sit on top of the POP provider registry via POP.stream.stream, but you can supply any stream_fn that matches the (model, context, options) signature and returns an async event stream with a result() method.

In this repo the import path is agent (for example, from agent import Agent).

Key concepts

  • Agent – stateful controller for model, messages, tools, and queues.
  • AgentTool – async tool interface; implement execute and return AgentToolResult.
  • Steering – inject user messages mid-run with agent.steer(...).
  • Follow-ups – queue messages after tool execution with agent.follow_up(...).
  • Events – subscribe to lifecycle events for streaming UIs.

Minimal example

import asyncio
from agent import Agent
from POP.stream import stream

async def main():
    agent = Agent({"stream_fn": stream})
    agent.set_model({"provider": "gemini", "id": "gemini-3-flash-preview", "api": None})
    await agent.prompt("Say hi")
    await agent.wait_for_idle()

asyncio.run(main())

Tools + steering + follow-up (condensed from agent/test.py)

import asyncio, time
from agent import Agent
from agent.agent_types import AgentMessage, TextContent, AgentToolResult, AgentTool
from POP.stream import stream

class SlowTool(AgentTool):
    name = "slow"
    description = "Sleep a bit"
    parameters = {"type": "object", "properties": {"seconds": {"type": "number"}}}
    label = "Slow"

    async def execute(self, tool_call_id, params, signal=None, on_update=None):
        seconds = float(params.get("seconds", 1.0))
        await asyncio.sleep(seconds)
        return AgentToolResult(
            content=[TextContent(type="text", text=f"slow done {seconds}s")],
            details={},
        )

class FastTool(AgentTool):
    name = "fast"
    description = "Return quickly"
    parameters = {"type": "object", "properties": {}}
    label = "Fast"

    async def execute(self, tool_call_id, params, signal=None, on_update=None):
        return AgentToolResult(
            content=[TextContent(type="text", text="fast done")],
            details={},
        )

async def main():
    agent = Agent({"stream_fn": stream})
    agent.set_model({"provider": "gemini", "id": "gemini-3-flash-preview", "api": None})
    agent.set_tools([SlowTool(), FastTool()])

    agent.follow_up(AgentMessage(
        role="user",
        content=[TextContent(type="text", text="follow up: summarize")],
        timestamp=time.time(),
    ))

    task = asyncio.create_task(
        agent.prompt("Call tool slow with seconds=1.2, then call tool fast")
    )
    agent.steer(AgentMessage(
        role="user",
        content=[TextContent(type="text", text="steer: actually, call slow 4 times, and in between of each call add a fast call, but keep the total time unchanged as 1.2s. then fast")],
        timestamp=time.time(),
    ))
    await task
    await agent.wait_for_idle()

asyncio.run(main())

Notes:

  • If POP is not importable, pass stream_fn explicitly; the agent does not ship a provider.
  • agent/test.py is the most complete end-to-end example right now.
  • Run the demo with python -m agent.test.

11. Function Schema Generation

POP supports generating OpenAI function-calling schemas from natural language descriptions.

schema = pf.generate_schema(
    description="Return the square and cube of a given integer."
)

print(schema)

What this does:

  • Applies a standard meta-prompt
  • Uses the selected LLM backend
  • Produces a valid JSON Schema for OpenAI function calling
  • Optionally saves it under schemas/

12. Embeddings

POP includes a unified embedding interface:

from POP import Embedder

embedder = Embedder(use_api="openai")
vecs = embedder.get_embedding(["hello world"])

Supported modes:

  • OpenAI embeddings
  • Gemini embeddings (via OpenAI-compatible Gemini endpoint)
  • JinaAI embeddings
  • Local HuggingFace model embeddings (cpu/gpu)

Large inputs are chunked automatically when needed.


13. Web Snapshot Utility

from POP.utils.web_snapshot import get_text_snapshot

text = get_text_snapshot("https://example.com", image_caption=True)
print(text[:500])

Supports:

  • optional image removal
  • optional image captioning
  • DOM selector filtering
  • returning JSON or plain text

14. Examples

from POP import PromptFunction

pf = PromptFunction(prompt="Give me 3 creative names for a <<<thing>>>.")

print(pf.execute(thing="robot"))
print(pf.execute(thing="new language"))

Multimodal example (provider must support images):

from POP import PromptFunction

image_b64 = "..."  # base64-encoded image

pf = PromptFunction(prompt="Describe the image.", client="openai")
print(pf.execute(images=[image_b64]))

15. Contributing

Steps:

  1. Fork the GitHub repo
  2. Create a feature branch
  3. Add tests or examples
  4. Submit a PR with a clear explanation

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pop_python-1.1.2.tar.gz (110.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pop_python-1.1.2-py3-none-any.whl (123.8 kB view details)

Uploaded Python 3

File details

Details for the file pop_python-1.1.2.tar.gz.

File metadata

  • Download URL: pop_python-1.1.2.tar.gz
  • Upload date:
  • Size: 110.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pop_python-1.1.2.tar.gz
Algorithm Hash digest
SHA256 4f0f44be06e1841c6f925a89ce6c68a80fa6f885ed95afa60621fce4050b0379
MD5 84d9f4a45d087dcaec8c46a89ce327f3
BLAKE2b-256 11b1868a3ce991fee545b51692b24445ddc9412d463f7156c58b06b11ff25081

See more details on using hashes here.

Provenance

The following attestation bundles were made for pop_python-1.1.2.tar.gz:

Publisher: publish-pypi.yaml on sgt1796/POP

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pop_python-1.1.2-py3-none-any.whl.

File metadata

  • Download URL: pop_python-1.1.2-py3-none-any.whl
  • Upload date:
  • Size: 123.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pop_python-1.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 10ab42cd33729bc1317a3162cff25f49a957d2826550304b51f10f25d0353fac
MD5 627f29fa1127689f0be08f5ac38117dd
BLAKE2b-256 aab3cd1b49dd7653a0a51e385c75d0b7c42b069571f3ecab94fe930f672c5aa4

See more details on using hashes here.

Provenance

The following attestation bundles were made for pop_python-1.1.2-py3-none-any.whl:

Publisher: publish-pypi.yaml on sgt1796/POP

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page