Metadata-Version: 2.5
Name: determine
Version: 0.7.0
Summary: Simple context-aware AI inference for Python.
Author: Jack
License-Expression: LicenseRef-RAML-1.0
License-File: LICENSE
Keywords: ai,decision,generation,inference,llm,procedural,reasoning
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# Determine

Determine makes AI inference feel like a normal part of Python.

The main API has only two operations:

~~~python
determine.answer(...)
determine.choose(...)
~~~

## Install

~~~bash
pip install determine
~~~

## Quick start

~~~python
import determine

determine.configure(
    "http://127.0.0.1:8080"
)

preference = "Chinese"
budget = "cheap"

answer = determine.answer(
    "What should I eat?"
)

print(answer)
~~~

Determine can automatically see relevant runtime variables and surrounding
source code.

# answer()

Use `answer()` when the result can be open-ended.

~~~python
response = determine.answer(
    "Explain what is happening."
)
~~~

# choose()

Use `choose()` when the result must be one of several possibilities.

~~~python
health = 12
ammo = 0
enemy_distance = 3

action = determine.choose(
    "What should the player do?",
    [
        "fight",
        "run",
        "hide"
    ]
)

print(action)
~~~

# Explicit context

Automatic context is convenient for small programs:

~~~python
determine.answer(
    "What should happen?"
)
~~~

For larger programs, you can specify exactly what Determine should see.

By variable name:

~~~python
health = 12
ammo = 0
score = 400

action = determine.choose(
    "What should the player do?",
    [
        "fight",
        "run",
        "hide"
    ],
    context=[
        "health",
        "ammo"
    ]
)
~~~

Only `health` and `ammo` are sent as runtime context.

You can also provide an explicit dictionary:

~~~python
answer = determine.answer(
    "What should the player do?",
    context={
        "health": health,
        "ammo": ammo
    }
)
~~~

Or disable automatic context entirely:

~~~python
answer = determine.answer(
    "Say hello.",
    context=False
)
~~~

This makes it possible to start with Determine's automatic context while
still having precise control when an application becomes larger.

# Typed structured answers

`answer()` can return multiple typed values at once by using `schema=`.

~~~python
result = determine.answer(
    "Choose an action and estimate your confidence.",
    schema={
        "action": str,
        "confidence": float
    }
)

print(result)
~~~

The result is a normal Python dictionary:

~~~python
{
    "action": "run",
    "confidence": 0.93
}
~~~

Supported types include:

~~~python
str
int
float
bool
list
dict
~~~

Nested structures also work:

~~~python
result = determine.answer(
    "Evaluate the current situation.",
    schema={
        "action": str,
        "confidence": float,
        "details": {
            "danger": int,
            "safe": bool
        }
    }
)
~~~

Determine validates and converts the returned values to the requested types.

# max_tokens

Both operations support a generation limit:

~~~python
answer = determine.answer(
    "Think carefully about this.",
    max_tokens=2048
)
~~~

~~~python
choice = determine.choose(
    "What should happen?",
    [
        "continue",
        "stop",
        "retry"
    ],
    max_tokens=1024
)
~~~

This can be useful with reasoning models that need generation space before
producing their final answer.

# Multi-turn conversations

Use a normal Python list:

~~~python
history = []
~~~

Then reuse it:

~~~python
print(
    determine.answer(
        "My spaceship is called Juniper.",
        history=history
    )
)

print(
    determine.answer(
        "What is my spaceship called?",
        history=history
    )
)
~~~

The same history can be shared between `answer()` and `choose()`.

# Functions as choices

Functions can also be options.

~~~python
def backup():
    """Back up the connected device."""
    print("Backing up...")


def update(version="latest"):
    """Update the connected device."""
    print("Updating to", version)


request = input("> ")

determine.choose(
    request,
    [
        backup,
        update
    ]
)
~~~

Determine can choose the function that genuinely matches the request and
extract its arguments.

If none of the functions can perform the request, it returns `None`.

# OpenAI-compatible APIs

~~~python
determine.configure(
    "http://127.0.0.1:8080"
)
~~~

Determine attempts to discover the model automatically.

You can also specify it:

~~~python
determine.configure(
    "http://127.0.0.1:8080",
    model="my-model"
)
~~~

# Ollama

~~~python
determine.configure(
    "ollama:qwen3"
)
~~~

Or:

~~~python
determine.configure(
    "ollama"
)
~~~

# llama.cpp

~~~python
determine.configure(
    "llama.cpp:/home/me/model.gguf"
)
~~~

Extra arguments can be supplied:

~~~python
determine.configure(
    "llama.cpp:/home/me/model.gguf",
    args=[
        "-ngl", "all",
        "-c", "32768"
    ]
)
~~~

# API keys

~~~bash
export DETERMINE_API_KEY="your-key"
~~~

Determine also checks `OPENAI_API_KEY`.

# Advanced configuration

~~~python
determine.configure(
    endpoint="http://localhost:8080",
    model="my-model",
    temperature=0.2,
    timeout=120,
    reasoning_effort="high"
)
~~~

# Why Determine?

Some decisions have many interacting variables, thresholds, and possible
combinations.

Instead of maintaining a large decision tree:

~~~python
action = determine.choose(
    "What should the enemy do?",
    [
        "attack",
        "defend",
        "run",
        "heal"
    ]
)
~~~

Determine can reason over the current program state.

Useful examples include:

- game AI
- procedural generation
- adaptive software
- natural-language tools
- hardware-aware settings
- recommendation systems
- fuzzy classification
- decisions involving many variables and thresholds

# Security

AI output is nondeterministic.

Do not use Determine as the only protection for authentication, permissions,
financial actions, destructive operations, security boundaries, or other
safety-critical systems.

Only expose functions to `choose()` that the AI should actually be allowed
to execute.

# License

MIT

# License

Determine 0.6.0 and later are licensed under the:

**Redistribution and Modification License (RAML) 1.0**

RAML allows you to:

- use Determine in personal or commercial projects;
- use Determine as a dependency;
- study and modify the source code;
- create private modified versions;
- redistribute genuinely modified versions.

If you redistribute a modified version of Determine itself, it must contain
material functional changes and must clearly credit the original project and
author.

Superficial changes such as renaming, formatting changes, metadata changes,
version changes, comment changes, or minor cosmetic modifications do not by
themselves qualify as a material modification.

A simple acceptable attribution is:

    Based on Determine by Jack.

Modified versions must not represent themselves as the official Determine
project.

See the `LICENSE` file for the complete RAML 1.0 terms.

Earlier Determine releases remain available under the licenses that
accompanied those releases.


# Live token streaming

Streaming is optional and does not change normal Determine usage.

For a normal non-streaming call:

~~~python
answer = determine.answer(
    "Explain this."
)
~~~

Advanced users can receive generated text live with `on_token=`:

~~~python
import determine

determine.configure(
    "http://127.0.0.1:8080"
)


def show_token(token):
    print(
        token,
        end="",
        flush=True
    )


answer = determine.answer(
    "Tell me a short story.",
    on_token=show_token
)

print()
print()
print("Final answer:")
print(answer)
~~~

A short version is:

~~~python
answer = determine.answer(
    "Explain quantum computing.",
    on_token=lambda token: print(
        token,
        end="",
        flush=True
    )
)
~~~

The callback receives text as soon as the backend provides it.

`answer()` still returns the complete final response after generation finishes.

Streaming works with:

- OpenAI-compatible streaming APIs;
- Ollama;
- llama.cpp.

`choose()` also accepts `on_token=` for advanced debugging:

~~~python
choice = determine.choose(
    "What should the player do?",
    [
        "fight",
        "run",
        "hide"
    ],
    on_token=lambda token: print(
        token,
        end="",
        flush=True
    )
)
~~~

Because `choose()` uses constrained internal output, its streamed text may be
an option index or JSON rather than the final Python value.

When `schema=` is used with `answer()`, the stream contains the raw JSON as it
is generated, while the final returned value is still the validated Python
dictionary.
