Metadata-Version: 2.4
Name: pop-python
Version: 1.1.5
Summary: Prompt Oriented Programming (POP): reusable, composable prompt functions for LLMs.
Home-page: https://github.com/sgt1796/POP
Author: Guotai Shen
Author-email: sgt1796@gmail.com
License: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openai>=1.0.0
Requires-Dist: requests>=2.25.0
Requires-Dist: python-dotenv
Requires-Dist: pydantic>=1.10
Requires-Dist: numpy>=1.21
Requires-Dist: backoff
Requires-Dist: Pillow>=9.0
Provides-Extra: local-embeddings
Requires-Dist: torch; extra == "local-embeddings"
Requires-Dist: transformers>=4.30.0; extra == "local-embeddings"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# Prompt Oriented Programming (POP)

```python
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.

Version 1.1.5 fixes the Gemini tool-normalization regression from 1.1.4 and converts empty Gemini tool-enabled stream replies into explicit errors instead of silent failures.

PyPI:
[https://pypi.org/project/pop-python/](https://pypi.org/project/pop-python/)

GitHub:
[https://github.com/sgt1796/POP](https://github.com/sgt1796/POP)

---

## Table of Contents

1. [Overview](#1-overview)
2. [Update Note](#2-update-note)
3. [Major Updates](#3-major-updates)
4. [Features](#4-features)
5. [Installation](#5-installation)
6. [Setup](#6-setup)
7. [PromptFunction](#7-promptfunction)
8. [Provider Registry](#8-provider-registry)
9. [Tool Calling](#9-tool-calling)
10. [Function Schema Generation](#10-function-schema-generation)
11. [Embeddings](#11-embeddings)
12. [Web Snapshot Utility](#12-web-snapshot-utility)
13. [Examples](#13-examples)
14. [Contributing](#14-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.5 (March 23, 2026)**

* **Gemini tool compatibility**: restores support for POP's flat legacy tool schema, including `type: "custom"` agent tools.
* **Gemini failure visibility**: empty Gemini replies on tool-enabled requests now surface as explicit stream errors instead of silent `done` events.
* **Request diagnostics**: Gemini requests now warn on partially dropped tools and fail fast when no supplied tool can be normalized.

---

# 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, Claude, 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, Claude, 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:

```bash
pip install pop-python
```

Or install in development mode from GitHub:

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

---

# 6. Setup

Create a `.env` file in your project root:

```ini
OPENAI_API_KEY=your_openai_key
ANTHROPIC_API_KEY=your_anthropic_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.

```python
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:

```python
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

```python
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:

```python
better = pf.improve_prompt()
print(better)
```

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

---

## 7.5. Token Usage Tracking

POP tracks usage per call at the framework level.

After each `execute()` call:

* `pf.last_usage` stores the normalized usage record for the latest request.
* `pf.usage_history` stores a bounded history (`maxlen=200`) of usage records.
* `pf.get_usage_summary()` returns cumulative totals for that `PromptFunction` instance.

```python
from POP import PromptFunction

pf = PromptFunction(prompt="Give me 3 names for a <<<thing>>>.", client="openai")
result = pf.execute(thing="robot")

print(result)
print(pf.last_usage["source"])        # provider | estimate | hybrid | none
print(pf.last_usage["total_tokens"])  # canonical total used by POP
print(pf.get_usage_summary())         # cumulative counters
```

Usage is provider-first:

* Provider-reported usage is used when available.
* POP estimates tokens when provider usage is missing.
* POP marks anomaly metadata when provider and estimate differ significantly.

---

# 8. Provider Registry

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

```python
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")
claude_client = get_client("claude")
```

Non-default model example:

```python
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:

```python
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

```python
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)
```

If you do not want tool calling for a request, either omit `tools` entirely or pass `tools=[]`.
POP treats both as plain text generation and omits tool-calling fields from the provider request.
This is especially important for Gemini's OpenAI-compatible endpoint, where tool-free requests should not send `tools` or `tool_choice`.

When using `client="claude"`, POP talks to Anthropic through the OpenAI Python SDK compatibility endpoint.
POP warns and degrades for documented compatibility gaps instead of pretending OpenAI-only features are enforced:

* `fmt` / `response_format` is omitted because Anthropic ignores it on the compatibility layer
* strict tool schemas are removed because Anthropic ignores `strict`
* system and developer prompts are hoisted into one system prompt before the request is sent

Claude defaults to `claude-haiku-4-5`. You can override it per call, for example `model="claude-sonnet-4-6"`.

---

# 10. Function Schema Generation

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

```python
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/`

---

# 11. Embeddings

POP includes a unified embedding interface:

```python
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.

---

# 12. Web Snapshot Utility

```python
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

---

# 13. Examples

```python
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):

```python
from POP import PromptFunction

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

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

---

# 14. 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
