artificial 0.7.0

Typed, provider-agnostic prompt-engineering SDK for Rust
Documentation
# Artificial ‒ typed, composable prompt engineering in Rust

Artificial is a **batteries-included toy framework** that demonstrates how to build
strongly-typed, provider-agnostic prompt pipelines in Rust.

The code base is intentionally small—less than 3 k lines spread over multiple
crates—yet it already shows how to:

* Compose reusable prompt *fragments* into high-level templates.
* Target multiple model providers via pluggable back-ends
  (currently OpenAI, adding others is trivial).
* Describe the LLM’s **JSON response format in Rust** and have it checked
  at compile time with [`schemars`]https://docs.rs/schemars.
* Bubble up provider errors through a single, unified error type.
* Keep all heavy dependencies optional behind feature flags.

If you are curious how the new crop of “AI SDKs” work under the hood—or you
need a lean starting point for your own experiments—this repo is for you.

---

## Table of contents

1. [Crate layout]#crate-layout
2. [Installation]#installation
3. [Quick start]#quick-start
4. [Audio transcription]#audio-transcription
5. [Library tour]#library-tour
6. [Design goals]#design-goals
7. [FAQ]#faq
8. [License]#license

---

## Crate layout

| Crate                        | Purpose                                                            |
|------------------------------|--------------------------------------------------------------------|
| **`artificial-core`**        | Provider-agnostic traits (`ChatCompletionProvider`, `PromptTemplate`), client, error types |
| **`artificial-prompt`**      | String-building helpers (`PromptBuilder`, `PromptChain`)           |
| **`artificial-types`**       | Shared fragments (`CurrentDateFragment`, `StaticFragment`) and output helpers |
| **`artificial-openai`**      | Thin wrapper around *OpenAI /v1* with JSON-Schema function calling |
| **`artificial`**             | Glue crate that re-exports everything above for convenience        |

Each crate lives under `crates/*` and can be used independently, but most
people will depend on the umbrella crate:

```toml
[dependencies]
artificial = { version = "*", features = ["openai"] }
```

---

## Installation

Artificial is published as a **workspace example**, so you typically work
against the Git repository directly:

```bash
git clone https://github.com/mrcrgl/artificial-rs.git
cd artificial-rs
cargo run -p artificial --example openai_hello_world
```

Requirements:

* **Rust 1.77** or newer (edition 2024)
* An OpenAI API key exported as `OPENAI_API_KEY`
* Internet access for the example back-end

---

## Quick start

Below is a minimal “Hello, JSON” example taken from
[`examples/openai_hello_world.rs`](crates/artificial/examples/openai_hello_world.rs):

```rust
use artificial::{
    ArtificialClient,
    generic::{GenericMessage, GenericRole},
    model::{Model, OpenAiModel},
    template::{IntoPrompt, PromptTemplate},
};
use artificial_openai::{HttpTimeoutConfig, OpenAiAdapterBuilder};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::time::Duration;

#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
struct HelloResponse { greeting: String }

struct HelloPrompt;

impl IntoPrompt for HelloPrompt {
    type Message = GenericMessage;
    fn into_prompt(self) -> Vec<Self::Message> {
        vec![
            GenericMessage::new("You are R2-D2.".into(), GenericRole::System),
            GenericMessage::new("Say hello!".into(),  GenericRole::User),
        ]
    }
}

impl PromptTemplate for HelloPrompt {
    type Output = HelloResponse;
    const MODEL: Model = Model::OpenAi(OpenAiModel::Gpt4oMini);
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let backend  = OpenAiAdapterBuilder::new_from_env()
        .with_http_timeouts(HttpTimeoutConfig {
            connect_timeout: Some(Duration::from_secs(10)),
            request_timeout: Some(Duration::from_secs(30)),
            stream_timeout: None, // keep streaming responses open
        })
        .build()?;
    let client   = ArtificialClient::new(backend);
    let response = client.chat_complete(HelloPrompt).await?;
    println!("The droid says: {:?}", response);
    Ok(())
}
```

Run it:

```bash
cargo run -p artificial --example openai_hello_world
```

The program sends a request with an **inline JSON-Schema** and prints the
deserialised reply.

---

## Audio transcription

`artificial-core` now exposes a provider-agnostic transcription capability via:

- `TranscriptionRequest`
- `TranscriptionResult`
- `TranscriptionProvider`

The OpenAI adapter implements this against `/audio/transcriptions`.

Working example:

```bash
OPENAI_API_KEY=... cargo run -p artificial --example openai_audio_transcription -- ./sample.wav
```

Example source:
[`crates/artificial/examples/openai_audio_transcription.rs`](crates/artificial/examples/openai_audio_transcription.rs)

---

## Library tour

### Prompt fragments
`artificial-types::fragments` contains small building blocks that turn into
`GenericMessage`s—think “Current date”, “Static system instruction”, “Last user
message”. You can combine them via `PromptChain`:

```rust
PromptChain::new()
    .with(CurrentDateFragment::new())
    .with(StaticFragment::new("You are a helpful bot.", GenericRole::System))
    .with(last_user_message)
    .build();
```

### Strongly-typed outputs
Define a `struct` (must implement `JsonSchema` + `Deserialize`) and declare it
as `PromptTemplate::Output`. The OpenAI back-end automatically injects the
schema as `response_format = json_schema`.

### Provider back-ends
A back-end only has to implement the single-method trait

```rust
trait ChatCompletionProvider {
    type Message;
    fn chat_complete<P>(&self, prompt: P) -> Pin<Box<dyn Future<Output = Result<P::Output>> + Send>>
}
```

Because the `PromptTemplate` carries the desired model, the back-end can map it
to the provider’s naming scheme (`gpt-4o-mini`, `gpt-4o`, …).

---

## Design goals

* **Minimal surface area** – the framework should fit in one screenful of
  source code, making it easy to fork or copy-paste.
* **Compile-time checks everywhere** – wrong model name? missing `OPENAI_API_KEY`?
  Incompatible JSON schema? You find out early.
* **Pluggable transports** – swap out `artificial-openai` for an
  Ollama/Anthropic adapter without touching user code.
* **No macros** – generic traits and `impl`s keep the magic transparent.

---

## FAQ

**Why another AI SDK?**
Because many existing SDKs are either *too* heavyweight or hide the inner
workings behind procedural macros. Artificial aims to be the smallest possible
blueprint you can still use in production.

**Does it support streaming completions?**
Not yet. The `ChatCompletionProvider` trait is purposely tiny; adding another method for
streaming is straightforward.

**Is the OpenAI back-end production ready?**
It handles basic JSON-Schema function calling, retries and streaming are
missing. Treat it as a reference implementation.

**How do I add Anthropic or Ollama?**

1. Create `artificial-anthropic` (or similar) crate.
2. Wrap the provider’s HTTP API in a small client struct.
3. Implement `ChatCompletionProvider` for the adapter.
4. Done—`ArtificialClient` works instantly with the new provider.

---

## License

Licensed under **MIT**. See [`LICENSE`](LICENSE) for the full text.