Metadata-Version: 2.4
Name: axonx
Version: 0.0.1
Summary: An extensible, Pydantic-based framework for defining, discovering, and running ordered flows.
Author-email: FlowLLM-AI <jinli.yl@alibaba-inc.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/FlowLLM-AI/Axon
Project-URL: Issues, https://github.com/FlowLLM-AI/Axon/issues
Project-URL: Repository, https://github.com/FlowLLM-AI/Axon
Keywords: workflow,pipeline,automation,cli,pydantic,plugins
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.28.1
Requires-Dist: loguru>=0.7.3
Requires-Dist: pydantic>=2.13.4
Provides-Extra: dev
Requires-Dist: build>=1.3.0; extra == "dev"
Requires-Dist: coverage>=7.10.0; extra == "dev"
Requires-Dist: pre-commit>=4.6.0; extra == "dev"
Requires-Dist: pytest>=8.4.0; extra == "dev"
Dynamic: license-file

<p align="center">
  <img src="https://raw.githubusercontent.com/FlowLLM-AI/assets/main/axon/axon_logo.png" alt="Axon Logo" width="52%">
</p>

<p align="center">
  <a href="https://pypi.org/project/axonx/"><img src="https://img.shields.io/pypi/v/axonx" alt="PyPI"></a>
  <a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.11%2B-blue" alt="Python 3.11+"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green" alt="Apache-2.0 License"></a>
  <a href="README_ZH.md"><img src="https://img.shields.io/badge/README-中文-orange" alt="Chinese README"></a>
</p>

Axon is a small, extensible framework for defining, discovering, and running ordered flows. Each flow combines a
strict Pydantic configuration, Python callables executed in order, a shared runtime context, and declared outputs.
Applications install their flows as providers and expose them through the same `axon` command.

The distribution is named `axonx`; the Python package and command are both named `axon`.

## Framework at a glance

| Layer | Contract | Responsibility |
|---|---|---|
| Configuration | `BaseConfig` | Validates typed inputs with unknown fields forbidden. |
| Flow | `BaseFlow` | Owns configuration, logger, shared context, steps, and outputs. |
| Step | `Callable[[], None]` | Performs one unit of work and reads or updates the flow context. |
| Registry | `@register("name")` | Maps a normalized action name to one flow class. |
| Discovery | `axon.flows` entry points | Imports installed provider modules so registration runs. |
| Runtime | `axon --action --field value` | Parses inputs, validates configuration, executes steps, and emits JSON. |

Axon deliberately keeps orchestration close to Python. A concrete list builds a fixed sequence; a generator can use
normal `if`, `for`, and `yield from` control flow to decide later steps after earlier steps update the context. There is
no separate workflow DSL or scheduler.

## Installation

Axon requires Python 3.11 or newer.

```bash
python -m pip install axonx
```

For framework development:

```bash
git clone https://github.com/FlowLLM-AI/Axon.git
cd Axon/packages
python -m pip install -e ".[dev]"
```

## Quick start

The framework includes a small `demo` flow:

```bash
axon --list
axon --demo --x 1 --y 2
```

The successful result is written to stdout as JSON:

```json
{"result": 3}
```

Every configuration argument is a `--field value` pair, including booleans. Hyphens in action and field names are
normalized to underscores. Unknown fields, duplicate fields, missing values, malformed values, and unknown actions
fail before flow execution.

## Define a flow

Subclass `BaseConfig` for inputs and `BaseFlow` for orchestration. Annotating `config` lets Axon infer the configuration
class. `build_steps()` returns or yields zero-argument callables, while `output_keys` declares which context values
must exist after execution.

```python
from collections.abc import Iterable

from axon.cli import BaseConfig, BaseFlow, Step, register


class GreetConfig(BaseConfig):
    name: str
    times: int = 1


@register("greet")
class GreetFlow(BaseFlow):
    config: GreetConfig
    output_keys = ("message",)

    def build_steps(self) -> Iterable[Step]:
        yield self.build_message

    def build_message(self) -> None:
        self.context["message"] = " ".join([f"hello {self.config.name}"] * self.config.times)
```

Import the module containing the class, then run:

```bash
axon --greet --name Axon --times 2
```

```json
{"message": "hello Axon hello Axon"}
```

The execution contract is intentionally small:

| Element | Rule |
|---|---|
| `config` annotation | Must resolve to a `BaseConfig` subclass. |
| `build_steps()` | Returns an iterable of zero-argument callables in execution order. |
| `self.context` | Starts with constructor keyword arguments and carries state between steps. |
| `output_keys` | Names required context keys and preserves their declared order in the result. |
| `execute()` | Runs every yielded step, then raises if any declared output is missing. |

Dynamic orchestration uses ordinary Python:

| Pattern | Expression |
|---|---|
| Task | `yield self.step` |
| Sequence | `yield from self.build_subsequence()` |
| Conditional | Use `if` while lazily yielding steps. |
| For each | Loop and yield a zero-argument callable; use `functools.partial` to bind arguments. |

Because a generator resumes between steps, conditions placed after a `yield` can observe context written by the
previous step.

## Publish a flow provider

An external package exposes its flows through the `axon.flows` entry-point group. For example, this repository's
[`pyproject.toml`](../pyproject.toml) connects `axon-core` to the framework with:

```toml
[project.entry-points."axon.flows"]
default = "core.default"
```

Axon imports `core.default`, whose package initialization imports the modules containing `@register(...)`. After the
provider is installed, its flows appear automatically:

```bash
axon --list
```

## Built-in flows

This table describes flows shipped by `axonx`. Provider packages should document their own flows in the same format,
making the catalog easy to extend as new flows are added.

| Flow | Config | Inputs | Output | Purpose |
|---|---|---|---|---|
| `demo` | `DemoConfig` | `x: int`, `y: int` | `result` | Demonstrates task, sequence, conditional, and loop-based step generation by adding two integers. |

## Utilities

The supported helpers are exported from `axon.utils`. Add one row when a new public utility is introduced so this
table remains the compact public catalog.

| Utility | Signature | Configuration | Behavior |
|---|---|---|---|
| `load_env` | `load_env(path=None, *, override=True) -> dict[str, str]` | Optional file path | Loads an explicit file or the nearest `.env` in the working directory or first five parents. Returns only values written to the environment. |
| `get_logger` | `get_logger()` | `AXON_LOG_DIR` | Lazily creates the shared Loguru INFO logger with stderr and daily rotating file sinks; file retention is seven days. |
| `send_dingtalk_message` | `send_dingtalk_message(title, text, msgtype="markdown", timeout=10.0) -> str` | `DINGTALK_CLIENT_ID`, `DINGTALK_CLIENT_SECRET`, `DINGTALK_CONVERSATIONS` | Sends Markdown or text through a DingTalk application robot to every configured, deduplicated conversation. |

`DINGTALK_CONVERSATIONS` must be a JSON object whose non-empty names map to non-empty conversation IDs, for example
`{"research":"cidxxx","operations":"cidyyy"}`. Credentials and group IDs are omitted from transport errors. Network
calls occur only when `send_dingtalk_message()` is invoked.

## Environment variables

Axon calls `load_env()` before CLI provider discovery, allowing provider imports to resolve environment-based defaults.

| Variable | Used by | Default | Purpose |
|---|---|---|---|
| `AXON_LOG_DIR` | Shared logger | `logs` | Directory for timestamped process log files. |
| `DINGTALK_CLIENT_ID` | DingTalk helper | Required on use | Application key and robot code. |
| `DINGTALK_CLIENT_SECRET` | DingTalk helper | Required on use | Application secret used to obtain an access token. |
| `DINGTALK_CONVERSATIONS` | DingTalk helper | Required on use | JSON object mapping names to group conversation IDs. |

Provider-specific variables belong in the provider's documentation rather than the framework package.

## Package layout

| Path | Contents |
|---|---|
| `axon/cli.py` | Configuration and flow bases, registry, provider discovery, argument parsing, execution, and CLI. |
| `axon/flows/` | Built-in flows; importing the package registers them. |
| `axon/utils/` | Environment, logging, and notification helpers. |
| `tests/` | Fast tests using temporary files and mocked network boundaries. |

## Development

```bash
python -m pytest -v --tb=long
pre-commit run --all-files
python -m build
```

Keep provider-specific business logic outside the framework. A provider may depend on `axonx`; `axonx` must not
depend on that provider.

## License

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.
