diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7dfe042 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ +## Summary + + + +## Type of change + +- [ ] Bug fix +- [ ] New provider / API surface +- [ ] Refactor +- [ ] Documentation +- [ ] Tests / CI +- [ ] Other + +## Checklist + +- [ ] Builds with the package’s supported Xcode/Swift versions +- [ ] Tests added or updated when behavior changes +- [ ] No secrets or API keys committed +- [ ] Docs updated if products, targets, public API, or dependencies changed: + - [ ] `README.md` + - [ ] `docs/ARCHITECTURE.md` (graph / tables) + - [ ] `CHANGELOG.md` (`[Unreleased]`) +- [ ] Layering respected (providers → `LargeLanguageModels` → `CoreMI`; avoid new provider→provider deps) + +## Test plan + + + +## Related issues + + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2896a4f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [Unreleased] + +### Added + +- Added architecture documentation in `docs/ARCHITECTURE.md`. +- Added contribution guidelines in `CONTRIBUTING.md`. +- Improved `README.md` with architecture, dependency graph, and documentation links. +- Added repository research and agent workflow guidance to `CONTRIBUTING.md`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0e562b8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,78 @@ +# Contributing + +Thanks for contributing to AI. + +## Prerequisites + +- Xcode 15.4+ (Swift 5.10 toolchain) +- Apple platform SDKs supported by this package + +## Getting Started + +1. Fork the repository and create a feature branch. +2. Make focused changes with clear commit messages. +3. Run tests locally: + +```bash +swift test +``` + +4. Update documentation when behavior, APIs, or architecture changes. +5. Open a pull request with a clear summary and test notes. + +## Pull Request Checklist + +- [ ] Changes are scoped to one topic +- [ ] Public API changes are documented +- [ ] Tests pass locally (`swift test`) +- [ ] README/docs/changelog updated when needed + +## Repository Research Plan + +When working in this repository, start by identifying the layer and capability you are changing before touching code. + +1. Map the affected API surface: + - `Sources/CoreMI` + - `Sources/LargeLanguageModels` + - `Sources/AI` + - the specific provider target under `Sources/` +2. Inventory shared abstractions and extension points involved in the change: + - request handling + - model identifiers + - chat/completions + - embeddings + - audio, file, structured output, and tool-calling support +3. Compare provider parity for that capability to avoid introducing one-off patterns. +4. Audit the relevant tests under `Tests/` by capability as well as by provider. +5. Update docs when package products, imports, architecture, or behavior change. +6. Run `swift test` for code changes and record any notable validation steps in the pull request. + +## Agent / Workflow Roles + +Use focused ownership when splitting work across contributors or automation: + +- **Architecture**: owns module boundaries, `Package.swift`, and `docs/ARCHITECTURE.md`. +- **Core abstractions**: owns `CoreMI` and `LargeLanguageModels` protocols, shared request/response behavior, and compatibility across providers. +- **Provider**: owns one provider target at a time and aligns provider-specific APIs with shared abstractions. +- **Test**: expands coverage in `Tests/`, adds reusable fixtures/helpers, and verifies `swift test`. +- **Docs**: keeps `README.md`, `CONTRIBUTING.md`, `docs/ARCHITECTURE.md`, and `CHANGELOG.md` aligned with code changes. +- **Release / governance**: checks change scope, public API impact, dependency layering, and pull request hygiene. + +Recommended workflow: + +1. Scope work to one layer or one provider. +2. Review architecture or shared abstraction impact first. +3. Implement the provider or core change. +4. Update tests for behavior changes. +5. Update docs for API, product, or architecture changes. +6. Run `swift test`. +7. Review for consistency with neighboring providers before merging. + +## Reporting Issues + +When filing a bug, include: + +- Steps to reproduce +- Expected vs actual behavior +- Platform and Xcode/Swift versions +- Relevant logs or sample payloads diff --git a/Package.resolved b/Package.resolved index 1aaa850..07dab8b 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "24eb1da3856f86e40bb6b87db2d4707a7cf68fd47d732dcbb9ec4d0aa614626d", + "originHash" : "2572cb6f07a6ae0395beb608bb9286d4ef1f1dee043e4886367aa794452e4a55", "pins" : [ { "identity" : "corepersistence", @@ -42,7 +42,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { - "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", + "revision" : "ff27e3678ddb895ebf917ce513f3105395078dae", "version" : "1.1.4" } }, @@ -51,7 +51,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-syntax.git", "state" : { - "revision" : "0687f71944021d616d34d922343dcef086855920", + "revision" : "60e8eb850721b5a6eebbd973b39f450a16553bd9", "version" : "600.0.1" } }, diff --git a/README.md b/README.md index cf435ef..abb9f56 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,100 @@ -> [!IMPORTANT] +> 🕴️[!IMPORTANT] > This package is presently in its alpha stage of development (2026-03-04). ---- +#### Supported Platforms - +SPM platforms from [`Package.swift`](Package.swift): **iOS 16+**, **macOS 13+**, **tvOS 16+**, **visionOS 1+**, **watchOS 9+**. -#### Supported Platforms

- macos + macOS   - macos + iOS   - macos + iPadOS   - macos + tvOS   - macos + watchOS

+Also targets **visionOS** (badge art not yet in `Images/`). + # AI The definitive, open-source Swift framework for interfacing with generative AI. -[Installation](#installation)\ -[Usage](#usage) - -* [Import the framework](#import-the-framework) -* [Initialize an AI Client](#initialize-an-ai-client) -* [LLM Clients Abstraction](#llm-clients-abstraction) -* [Supported Models](#supported-models) -* [Completions](#completions) - * [Basic Completions](#basic-completions) - * [Vision: Image-to-Text](#vision-image-to-text) - * [Function Calling](#function-calling) -* [DALLE-3 Image Generation](#dalle-3-image-generation) -* [Audio](#audio) - * [Audio Transcription: Whisper](#audio-transcription-whisper) - * [Audio Generation: OpenAI](#audio-generation-openai) - * [Audio Generation: ElevenLabs](#audio-generation-elevenlabs) -* [Text Embeddings](#text-embeddings) - -[Roadmap](#roadmap) \ -[Acknowledgements](#acknowledgements) \ -[License](#license) +## Documentation + +- [Architecture](docs/ARCHITECTURE.md) +- [Contributing](CONTRIBUTING.md) +- [Changelog](CHANGELOG.md) + +## Table of Contents + +- [Architecture Overview](#architecture-overview) +- [Dependencies](#dependencies) +- [Installation](#installation) +- [Usage](#usage) + - [Import the framework](#import-the-framework) + - [Initialize an AI Client](#initialize-an-ai-client) + - [LLM Clients Abstraction](#llm-clients-abstraction) + - [Supported Models](#supported-models) + - [Completions](#completions) + - [Basic Completions](#basic-completions) + - [Vision: Image-to-Text](#vision-image-to-text) + - [Function Calling](#function-calling) + - [DALLE-3 Image Generation](#dalle-3-image-generation) + - [Audio](#audio) + - [Audio Transcription: Whisper](#audio-transcription-whisper) + - [Audio Generation: OpenAI](#audio-generation-openai) + - [Audio Generation: ElevenLabs](#audio-generation-elevenlabs) + - [Text Embeddings](#text-embeddings) +- [Roadmap](#roadmap) +- [Acknowledgements](#acknowledgements) +- [License](#license) + +## Architecture Overview + +This package is organized as a Swift Package Manager workspace with layered modules: + +- `CoreMI` provides shared core abstractions used across providers. +- `LargeLanguageModels` builds common LLM request/response functionality on top of `CoreMI`. +- Provider modules (`OpenAI`, `Anthropic`, `Mistral`, `Groq`, `ElevenLabs`, etc.) implement vendor-specific APIs. +- `AI` is the umbrella product that re-exports the main modules for one-stop integration. + +### Component Dependency Graph + +A full dependency graph generated from `Package.swift` target dependencies is available in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#component-dependency-graph). + +## Dependencies + +External package dependencies declared in `Package.swift`: + +- [`CorePersistence`](https://github.com/vmanot/CorePersistence) +- [`Merge`](https://github.com/vmanot/Merge) +- [`NetworkKit`](https://github.com/vmanot/NetworkKit) +- [`Swallow`](https://github.com/vmanot/Swallow) +- [`SwiftUIX`](https://github.com/SwiftUIX/SwiftUIX) # Installation @@ -74,6 +106,179 @@ The definitive, open-source Swift framework for interfacing with generative AI. 4. Choose the version you'd like to install. 5. Click `Add Package`. +Or add the dependency in `Package.swift`: + +```swift +dependencies: [ + .package(url: "https://github.com/PreternaturalAI/AI.git", branch: "main") +] +``` + +Then link the `AI` product (umbrella) or a standalone product such as `OpenAI`, `Anthropic`, or `Perplexity` (see [Architecture](#architecture)). + +# Architecture + +The package is organized in layers. Provider SDKs implement shared protocols (`LLMRequestHandling`, embeddings, TTS, and so on) defined in the core modules. + +| Layer | Modules | Responsibility | +|-------|---------|----------------| +| Umbrella module | `AI` | `@_exported` re-exports **CoreMI**, **LargeLanguageModels**, **OpenAI** (and Swallow macros client). Other bundled modules still need their own `import`. | +| Umbrella product | `AI` library | SPM product that **links** core + Anthropic, Cohere, ElevenLabs, Groq, HuggingFace, Jina, Mistral, Ollama, OpenAI, and the `AI` module | +| Providers | `OpenAI`, `Anthropic`, `Mistral`, `Groq`, `Ollama`, `Perplexity`, `Cohere`, `Jina`, `VoyageAI`, `TogetherAI`, `ElevenLabs`, `PlayHT`, `Rime`, `HumeAI`, `NeetsAI`, `_Gemini`, `HuggingFace`, … | API clients and model types | +| LLM abstractions | `LargeLanguageModels` | `AbstractLLM`, `PromptLiteral`, chat/function-calling, embeddings protocols | +| Foundations | `CoreMI` | Request handling, model identifiers, service credentials, ASR/TTS bases | +| External | Swallow, Merge, NetworkKit, CorePersistence, SwiftUIX | Shared infrastructure (see [Dependencies](#dependencies)) | + +`import AI` is enough for OpenAI + shared LLM types. For other clients linked by the product, add e.g. `import Anthropic` or `import Groq`. Providers shipped only as standalone products (`Perplexity`, `_Gemini`, `VoyageAI`, …) require linking that product. + +### Component dependency graph + +Graph of **package targets** as declared in [`Package.swift`](Package.swift) (external packages shown once at the bottom). For products, tests, and design notes, see **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)**. + +```mermaid +flowchart TB + subgraph umbrella [Umbrella] + AI + end + + subgraph providers [Provider targets] + OpenAI + Anthropic + Mistral + Groq + Ollama + Perplexity + Cohere + Jina + VoyageAI + TogetherAI + ElevenLabs + PlayHT + Rime + HumeAI + NeetsAI + Gemini["_Gemini"] + HuggingFace + end + + subgraph core [Core targets] + LargeLanguageModels + CoreMI + end + + subgraph external [External packages] + Swallow + Merge + NetworkKit + CorePersistence + SwiftUIX + end + + AI --> CoreMI + AI --> LargeLanguageModels + AI --> OpenAI + AI --> Anthropic + AI --> Mistral + AI --> Groq + AI --> Ollama + AI --> Cohere + AI --> ElevenLabs + AI --> HuggingFace + AI --> Jina + + OpenAI --> LargeLanguageModels + Anthropic --> LargeLanguageModels + Mistral --> LargeLanguageModels + Groq --> LargeLanguageModels + Ollama --> LargeLanguageModels + Perplexity --> LargeLanguageModels + Perplexity --> OpenAI + Cohere --> LargeLanguageModels + Jina --> LargeLanguageModels + VoyageAI --> LargeLanguageModels + TogetherAI --> LargeLanguageModels + ElevenLabs --> LargeLanguageModels + PlayHT --> LargeLanguageModels + Rime --> LargeLanguageModels + HumeAI --> LargeLanguageModels + NeetsAI --> LargeLanguageModels + Gemini --> LargeLanguageModels + HuggingFace --> CoreMI + + LargeLanguageModels --> CoreMI + LargeLanguageModels --> CorePersistence + LargeLanguageModels --> Merge + LargeLanguageModels --> NetworkKit + LargeLanguageModels --> Swallow + LargeLanguageModels --> SwiftUIX + + CoreMI --> CorePersistence + CoreMI --> Merge + CoreMI --> Swallow +``` + +**Notes** + +- Most providers also depend on `CoreMI`, `CorePersistence`, `Merge`, `NetworkKit`, and `Swallow` directly (edges omitted above for readability where they already flow through `LargeLanguageModels`). +- **OpenAI** and **Anthropic** depend on `LargeLanguageModels` + networking stack, not on `CoreMI` directly in `Package.swift`. +- **Perplexity** is the only provider that depends on another provider target (`OpenAI`). +- **HuggingFace** depends on `CoreMI` + `Swallow` only (hub/tokenizer helpers, not the full LLM stack). +- The `AI` **product** does **not** link every target (for example `_Gemini`, `Perplexity`, `PlayHT`, `Rime`, `TogetherAI`, `VoyageAI`, `HumeAI`, `NeetsAI` are separate products). +- The `AI` **module** re-exports only CoreMI, LargeLanguageModels, and OpenAI among first-party modules (`Sources/AI/module.swift`). + +### Protocol capabilities (from source) + +Which shared protocols have an explicit client conformance today: + +| Target | `LLMRequestHandling` | `TextEmbeddingsRequestHandling` | Notes | +|--------|:--------------------:|:-------------------------------:|-------| +| OpenAI | yes | yes | Also images, Whisper, speech, assistants | +| Anthropic | yes | | | +| Mistral | yes | yes | | +| Groq | yes | | | +| Ollama | yes | | | +| Perplexity | yes | | Depends on OpenAI target | +| Cohere | | yes | | +| Jina | | yes | | +| VoyageAI | | yes | | +| TogetherAI, ElevenLabs, PlayHT, Rime, HumeAI, NeetsAI, _Gemini, HuggingFace | | | Provider-specific client APIs / hub helpers (see `Sources/`) | + +Full layering detail: **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)**. Docs index: **[docs/README.md](docs/README.md)**. + +# Dependencies + +### External Swift packages + +| Package | Repository | Role | +|---------|------------|------| +| Swallow | [vmanot/Swallow](https://github.com/vmanot/Swallow) | Extensions, macros client, diagnostics | +| Merge | [vmanot/Merge](https://github.com/vmanot/Merge) | Concurrency / async utilities | +| NetworkKit | [vmanot/NetworkKit](https://github.com/vmanot/NetworkKit) | HTTP client & API specifications | +| CorePersistence | [vmanot/CorePersistence](https://github.com/vmanot/CorePersistence) | Persistence & schema helpers (e.g. JSONSchema) | +| SwiftUIX | [SwiftUIX/SwiftUIX](https://github.com/SwiftUIX/SwiftUIX) | SwiftUI extensions | + +Transitive pins (see [`Package.resolved`](Package.resolved)) include **SwiftAPI**, **swift-collections**, and **swift-syntax**. + +### SPM products you can link + +Exact product names from `Package.swift`: + +| Product | Targets exposed | Use when | +|---------|-----------------|----------| +| `AI` | CoreMI, LargeLanguageModels, Anthropic, Cohere, ElevenLabs, Groq, HuggingFace, Jina, Mistral, Ollama, OpenAI, AI | Default multi-provider app dependency | +| `OpenAI` | OpenAI | OpenAI-only | +| `Anthropic` | Anthropic | Anthropic-only | +| `Perplexity` | Perplexity | Perplexity (pulls OpenAI transitively) | +| `TogetherAI` | TogetherAI | Together AI | +| `VoyageAI` | VoyageAI | Voyage embeddings | +| `PlayHT` | PlayHT | PlayHT voice | +| `Rime` | Rime | Rime voice | +| `HumeAI` | HumeAI | Hume | +| `NeetsAI` | NeetsAI | Neets | +| `_Gemini` | _Gemini | Google Gemini client | + +There is **no** standalone SPM product today for `CoreMI`, `LargeLanguageModels`, `Cohere`, `Groq`, `HuggingFace`, `Jina`, `Mistral`, `Ollama`, or `ElevenLabs` — consume them via the `AI` product (or add a product in `Package.swift` if you need a leaner slice). + # Usage ## Import the framework @@ -551,14 +756,76 @@ let embeddings = try await LLMManager.client.textEmbeddings( return embeddings.data.first?.embedding.description ``` +# Documentation + +| Document | Contents | +|----------|----------| +| **[docs/README.md](docs/README.md)** | Documentation index | +| **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** | Full architecture, products vs targets, Mermaid graph, protocol matrix, tests | +| **[CONTRIBUTING.md](CONTRIBUTING.md)** | Dev setup, coding guidelines, how to add a provider | +| **[CHANGELOG.md](CHANGELOG.md)** | Release notes and unreleased changes | +| **[LICENSE](LICENSE)** | MIT License text | + +API behavior is illustrated in the [Usage](#usage) sections above; protocol details live in `Sources/LargeLanguageModels` and `Sources/CoreMI`. + # Roadmap -- [x] OpenAI +Status reflects modules present in this repository (`Sources/` + `Package.swift`). Checked items ship as package targets; depth of feature coverage varies by provider. + +### Core + +- [x] `CoreMI` — request handling, model identifiers, services, ASR/TTS foundations +- [x] `LargeLanguageModels` — `AbstractLLM`, prompts, function calling, embeddings protocols +- [x] `AI` umbrella product + +### LLM & multimodal providers + +- [x] OpenAI (chat, vision, tools, images, Whisper, speech) - [x] Anthropic - [x] Mistral +- [x] Groq - [x] Ollama -- [ ] Perplexity -- [ ] Groq +- [x] Perplexity +- [x] TogetherAI +- [x] `_Gemini` (Google Gemini client; standalone product) +- [ ] Broader parity matrix (streaming, tools, vision) across every LLM provider + +### Embeddings & hub + +- [x] OpenAI embeddings +- [x] Cohere +- [x] Jina +- [x] VoyageAI +- [x] Hugging Face hub helpers / tokenizer resources + +### Voice & speech + +- [x] ElevenLabs +- [x] PlayHT +- [x] Rime +- [x] HumeAI +- [x] NeetsAI +- [x] OpenAI TTS / Whisper + +### Project hygiene + +- [x] Architecture & dependency documentation +- [x] Contributing guide, docs index, PR template & changelog +- [ ] Register missing test targets (Ollama, Rime, TogetherAI folder) +- [ ] visionOS badge artwork under `Images/` +- [ ] Tagged semantic releases with automated changelog sections + +# Contributing + +Contributions are welcome. Please read **[CONTRIBUTING.md](CONTRIBUTING.md)** for: + +- Environment and build requirements +- PR expectations and testing notes +- Steps to add or extend a provider without breaking layering rules + +# Changelog + +See **[CHANGELOG.md](CHANGELOG.md)** for user-visible changes. The package is in **alpha**; expect API evolution between releases. # Acknowledgements @@ -567,4 +834,4 @@ return embeddings.data.first?.embedding.description # License -This package is licensed under the MIT License. +This package is licensed under the [MIT License](LICENSE). diff --git a/Sources/LargeLanguageModels/Intramodular/LLMs/Agents/LLMAgentPipeline.swift b/Sources/LargeLanguageModels/Intramodular/LLMs/Agents/LLMAgentPipeline.swift new file mode 100644 index 0000000..b83e9a1 --- /dev/null +++ b/Sources/LargeLanguageModels/Intramodular/LLMs/Agents/LLMAgentPipeline.swift @@ -0,0 +1,148 @@ +// +// Copyright (c) Vatsal Manot +// + +import Foundation +import Swallow + +// MARK: - Stage protocol + +/// A single stage in an LLM-driven agent pipeline. +/// +/// Each stage receives a plain-text input, processes it with an LLM using +/// its ``systemPrompt``, and returns a plain-text output that feeds the next stage. +public protocol LLMAgentStage: Sendable { + /// A human-readable label for this stage (e.g. `"Observer"`). + var stageName: String { get } + + /// The system prompt that instructs the LLM on how to behave in this stage. + var systemPrompt: String { get } + + /// Process `input` using `llm` and return the stage's output. + func process(input: String, using llm: any LLMRequestHandling) async throws -> String +} + +extension LLMAgentStage { + public func process(input: String, using llm: any LLMRequestHandling) async throws -> String { + let messages: [AbstractLLM.ChatMessage] = [ + .system(systemPrompt), + .user(input), + ] + let completion = try await llm.complete(messages) + return try completion.toString() + } +} + +// MARK: - Built-in stages + +extension AbstractLLM { + /// Observes raw input (events, signals, changes) and describes what is happening. + public struct ObserverStage: LLMAgentStage { + public let stageName: String = "Observer" + public let systemPrompt: String + + public init( + systemPrompt: String = """ + You are an observer agent. Given raw input data, events, or signals, \ + clearly describe what is happening. Be concise and factual. + """ + ) { + self.systemPrompt = systemPrompt + } + } + + /// Analyses observations to discover meaning, patterns, and predictions. + public struct ResearchStage: LLMAgentStage { + public let stageName: String = "Research" + public let systemPrompt: String + + public init( + systemPrompt: String = """ + You are a research agent. Given an observation, analyse its meaning, \ + identify patterns, and make predictions. Provide a concise analytical summary. + """ + ) { + self.systemPrompt = systemPrompt + } + } + + /// Takes research results and produces a concrete decision or action recommendation. + public struct DecisionStage: LLMAgentStage { + public let stageName: String = "Decision" + public let systemPrompt: String + + public init( + systemPrompt: String = """ + You are a decision agent. Given research findings, produce a clear, \ + actionable decision or recommendation. Be direct and specific. + """ + ) { + self.systemPrompt = systemPrompt + } + } +} + +// MARK: - Pipeline result + +extension AbstractLLM { + /// The output produced by each stage of an ``AgentPipeline`` run. + public struct AgentPipelineResult: Sendable { + /// Raw input passed into the pipeline. + public let input: String + /// Output from the ``ObserverStage``: what is happening. + public let observation: String + /// Output from the ``ResearchStage``: what it means. + public let research: String + /// Output from the ``DecisionStage``: what to do. + public let decision: String + } +} + +// MARK: - Pipeline + +extension AbstractLLM { + /// A sequential Observer → Research → Decision agent pipeline. + /// + /// Each stage's output becomes the next stage's input, mirroring the + /// Reality → Observation → Intelligence → Decision flow. + /// + /// ```swift + /// let pipeline = AbstractLLM.AgentPipeline(llm: myLLM) + /// let result = try await pipeline.run(input: "Server CPU spiked to 98% at 03:00 UTC") + /// print(result.decision) // "Scale out the web tier by two instances …" + /// ``` + public struct AgentPipeline: Sendable { + public let observer: any LLMAgentStage + public let researcher: any LLMAgentStage + public let decisionMaker: any LLMAgentStage + public let llm: any LLMRequestHandling + + public init( + observer: any LLMAgentStage = ObserverStage(), + researcher: any LLMAgentStage = ResearchStage(), + decisionMaker: any LLMAgentStage = DecisionStage(), + llm: any LLMRequestHandling + ) { + self.observer = observer + self.researcher = researcher + self.decisionMaker = decisionMaker + self.llm = llm + } + + /// Run the full Observer → Research → Decision pipeline on `input`. + /// + /// - Parameter input: The raw reality input (events, signals, data). + /// - Returns: An ``AgentPipelineResult`` containing each stage's output. + public func run(input: String) async throws -> AgentPipelineResult { + let observation = try await observer.process(input: input, using: llm) + let research = try await researcher.process(input: observation, using: llm) + let decision = try await decisionMaker.process(input: research, using: llm) + return AgentPipelineResult( + input: input, + observation: observation, + research: research, + decision: decision + ) + } + } +} diff --git a/Sources/LargeLanguageModels/Intramodular/LLMs/Logging/FailedLLMResponseStore.swift b/Sources/LargeLanguageModels/Intramodular/LLMs/Logging/FailedLLMResponseStore.swift new file mode 100644 index 0000000..8ddad34 --- /dev/null +++ b/Sources/LargeLanguageModels/Intramodular/LLMs/Logging/FailedLLMResponseStore.swift @@ -0,0 +1,108 @@ +// +// Copyright (c) Vatsal Manot +// + +import Foundation +import Swallow + +extension AbstractLLM { + /// A record of a chat completion request that resulted in an error. + /// + /// These records can be exported and used to improve prompts or to build a + /// fine-tuning dataset that corrects known failure modes. + public struct FailedChatResponse: Codable, Hashable, Identifiable, Sendable { + public let id: UUID + public let timestamp: Date + public let modelIdentifier: ModelIdentifier? + public let prompt: [AbstractLLM.ChatMessage] + public let errorDescription: String + + public init( + id: UUID = UUID(), + timestamp: Date = Date(), + modelIdentifier: ModelIdentifier? = nil, + prompt: [AbstractLLM.ChatMessage], + errorDescription: String + ) { + self.id = id + self.timestamp = timestamp + self.modelIdentifier = modelIdentifier + self.prompt = prompt + self.errorDescription = errorDescription + } + } +} + +/// A thread-safe store for chat completion requests that resulted in errors. +/// +/// Accumulated entries can be exported as JSONL for use in fine-tuning +/// pipelines that teach models to handle or avoid past failure patterns. +public actor FailedLLMResponseStore { + /// The shared, process-wide failed-response store. + public static let shared = FailedLLMResponseStore() + + private var _entries: [AbstractLLM.FailedChatResponse] = [] + + public init() {} + + /// All recorded failed responses, in chronological order. + public var entries: [AbstractLLM.FailedChatResponse] { + _entries + } + + /// Append a new failed-response record. + public func record(_ entry: AbstractLLM.FailedChatResponse) { + _entries.append(entry) + } + + /// Remove all recorded entries. + public func clear() { + _entries.removeAll() + } + + /// Export all entries as a JSONL string. + /// + /// Each line is a JSON object representing one ``AbstractLLM/FailedChatResponse``. + /// The output is suitable for ingestion by fine-tuning pipelines that use + /// the JSONL format (e.g. OpenAI fine-tuning, Axolotl, Unsloth). + public func exportAsJSONL() throws -> String { + let encoder = JSONEncoder() + encoder.outputFormatting = [] + encoder.dateEncodingStrategy = .iso8601 + return try _entries + .map { try String(decoding: encoder.encode($0), as: UTF8.self) } + .joined(separator: "\n") + } +} + +// MARK: - LLMRequestHandling integration + +extension LLMRequestHandling { + /// Complete a chat prompt, automatically recording any failure in `store`. + /// + /// On failure the record is appended to `store` and then the error is + /// rethrown so normal error-handling still applies. + /// + /// - Parameters: + /// - prompt: The chat prompt to complete. + /// - parameters: Completion parameters. + /// - store: The ``FailedLLMResponseStore`` to append failures to. + public func complete( + prompt: AbstractLLM.ChatPrompt, + parameters: AbstractLLM.ChatCompletionParameters, + recordingFailuresIn store: FailedLLMResponseStore + ) async throws -> AbstractLLM.ChatCompletion { + do { + return try await complete(prompt: prompt, parameters: parameters) + } catch { + let modelID = try? prompt.context.modelIdentifier?._oneValue + let entry = AbstractLLM.FailedChatResponse( + modelIdentifier: modelID, + prompt: prompt.messages, + errorDescription: error.localizedDescription + ) + await store.record(entry) + throw error + } + } +} diff --git a/Sources/LargeLanguageModels/Intramodular/LLMs/Logging/LLMErrorLogger.swift b/Sources/LargeLanguageModels/Intramodular/LLMs/Logging/LLMErrorLogger.swift new file mode 100644 index 0000000..34c7f14 --- /dev/null +++ b/Sources/LargeLanguageModels/Intramodular/LLMs/Logging/LLMErrorLogger.swift @@ -0,0 +1,94 @@ +// +// Copyright (c) Vatsal Manot +// + +import Foundation +import Swallow + +extension AbstractLLM { + /// A record of an error that occurred during an LLM request. + public struct ErrorLogEntry: Codable, Hashable, Identifiable, Sendable { + public let id: UUID + public let timestamp: Date + public let modelIdentifier: ModelIdentifier? + public let promptDescription: String + public let errorDescription: String + + public init( + id: UUID = UUID(), + timestamp: Date = Date(), + modelIdentifier: ModelIdentifier? = nil, + promptDescription: String, + errorDescription: String + ) { + self.id = id + self.timestamp = timestamp + self.modelIdentifier = modelIdentifier + self.promptDescription = promptDescription + self.errorDescription = errorDescription + } + } +} + +/// A thread-safe logger for errors that occur during LLM requests. +/// +/// Errors accumulated here can be reviewed to improve prompt design or to +/// identify patterns in failures. Use +/// ``LLMRequestHandling/complete(_:parameters:loggingErrorsTo:)`` to +/// automatically capture failures from any `LLMRequestHandling` call. +public actor LLMErrorLogger { + /// The shared, process-wide error logger. + public static let shared = LLMErrorLogger() + + private var _entries: [AbstractLLM.ErrorLogEntry] = [] + + public init() {} + + /// All recorded error entries, in chronological order. + public var entries: [AbstractLLM.ErrorLogEntry] { + _entries + } + + /// Append a new entry to the log. + public func log(_ entry: AbstractLLM.ErrorLogEntry) { + _entries.append(entry) + } + + /// Remove all recorded entries. + public func clear() { + _entries.removeAll() + } +} + +// MARK: - LLMRequestHandling integration + +extension LLMRequestHandling { + /// Complete a prompt, automatically logging any thrown error to `logger`. + /// + /// On failure the entry is logged and then the error is rethrown so normal + /// error-handling still applies. + /// + /// - Parameters: + /// - prompt: The prompt to complete. + /// - parameters: Completion parameters. + /// - logger: The ``LLMErrorLogger`` to append failures to. + /// Defaults to ``LLMErrorLogger/shared``. + public func complete( + _ prompt: Prompt, + parameters: Prompt.CompletionParameters, + loggingErrorsTo logger: LLMErrorLogger + ) async throws -> Prompt.Completion { + do { + return try await complete(prompt: prompt, parameters: parameters) + } catch { + let modelID = try? prompt.context.modelIdentifier?._oneValue + let entry = AbstractLLM.ErrorLogEntry( + modelIdentifier: modelID, + promptDescription: String(describing: prompt), + errorDescription: error.localizedDescription + ) + await logger.log(entry) + throw error + } + } +} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..913a99c --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,100 @@ +# Architecture + +This document summarizes the package architecture as defined in `Package.swift`. + +## Module Layers + +1. **Core layer** + - `CoreMI` +2. **LLM abstraction layer** + - `LargeLanguageModels` +3. **Provider layer** + - `Anthropic`, `Cohere`, `ElevenLabs`, `Groq`, `HuggingFace`, `HumeAI`, `Jina`, `Mistral`, `NeetsAI`, `Ollama`, `OpenAI`, `Perplexity`, `PlayHT`, `Rime`, `TogetherAI`, `VoyageAI`, `_Gemini` +4. **Umbrella layer** + - `AI` + +## Component Dependency Graph + +In this graph, `CorePersistence`, `Merge`, `NetworkKit`, `Swallow`, and `SwiftUIX` are external package dependencies (not targets in this repository). + +```mermaid +graph TD + CoreMI --> CorePersistence + CoreMI --> Merge + CoreMI --> Swallow + + LargeLanguageModels --> CoreMI + LargeLanguageModels --> CorePersistence + LargeLanguageModels --> Merge + LargeLanguageModels --> NetworkKit + LargeLanguageModels --> Swallow + LargeLanguageModels --> SwiftUIX + + Anthropic --> LargeLanguageModels + OpenAI --> LargeLanguageModels + HuggingFace --> CoreMI + HuggingFace --> Swallow + + Cohere --> CoreMI + Cohere --> LargeLanguageModels + ElevenLabs --> CoreMI + ElevenLabs --> LargeLanguageModels + Groq --> CoreMI + Groq --> LargeLanguageModels + HumeAI --> CoreMI + HumeAI --> LargeLanguageModels + Jina --> CoreMI + Jina --> LargeLanguageModels + Mistral --> CoreMI + Mistral --> LargeLanguageModels + NeetsAI --> CoreMI + NeetsAI --> LargeLanguageModels + Ollama --> CoreMI + Ollama --> LargeLanguageModels + PlayHT --> CoreMI + PlayHT --> LargeLanguageModels + Rime --> CoreMI + Rime --> LargeLanguageModels + TogetherAI --> CoreMI + TogetherAI --> LargeLanguageModels + VoyageAI --> CoreMI + VoyageAI --> LargeLanguageModels + _Gemini --> CoreMI + _Gemini --> LargeLanguageModels + + Perplexity --> OpenAI + Perplexity --> CoreMI + Perplexity --> LargeLanguageModels + + AI --> CoreMI + AI --> LargeLanguageModels + AI --> Anthropic + AI --> Cohere + AI --> ElevenLabs + AI --> Groq + AI --> HuggingFace + AI --> Jina + AI --> Mistral + AI --> Ollama + AI --> OpenAI + + AI -. standalone product .-> HumeAI + AI -. standalone product .-> NeetsAI + AI -. standalone product .-> Perplexity + AI -. standalone product .-> PlayHT + AI -. standalone product .-> Rime + AI -. standalone product .-> TogetherAI + AI -. standalone product .-> VoyageAI + AI -. standalone product .-> _Gemini +``` +> Note: The `AI` umbrella target currently depends on `CoreMI`, `LargeLanguageModels`, `Anthropic`, `Cohere`, `ElevenLabs`, `Groq`, `HuggingFace`, `Jina`, `Mistral`, `Ollama`, and `OpenAI`. Other provider modules in this repository are distributed as standalone products and are not re-exported by `AI` in `Package.swift`. + +> Note: `HuggingFace` intentionally differs from most provider modules: in `Package.swift` it depends on `CoreMI` and `Swallow`, and does not depend on `LargeLanguageModels`. + +## External Dependencies + +- `CorePersistence` +- `Merge` +- `NetworkKit` +- `Swallow` +- `SwiftUIX` diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..5e24f4c --- /dev/null +++ b/docs/README.md @@ -0,0 +1,31 @@ +# Documentation + +Guides for the **AI** Swift package ([PreternaturalAI/AI](https://github.com/PreternaturalAI/AI)). + +| Document | Audience | Description | +|----------|----------|-------------| +| [../README.md](../README.md) | Everyone | Install, usage examples, architecture summary, dependency graph, roadmap | +| [ARCHITECTURE.md](ARCHITECTURE.md) | Contributors & integrators | Layers, products vs targets, Mermaid graphs, protocol matrix, tests | +| [../CONTRIBUTING.md](../CONTRIBUTING.md) | Contributors | Setup, PR process, adding providers | +| [../CHANGELOG.md](../CHANGELOG.md) | Everyone | Notable changes | +| [../LICENSE](../LICENSE) | Everyone | MIT | + +## Quick orientation + +```text +Package.swift products, targets, external deps +Sources/CoreMI foundations (requests, model IDs, services) +Sources/LargeLanguageModels AbstractLLM, prompts, embeddings protocols +Sources/ vendor clients +Sources/AI umbrella module (selective @_exported imports) +Tests/ parallel test targets (see ARCHITECTURE.md for gaps) +docs/ long-form documentation (this folder) +``` + +## Keeping docs honest + +When you change `Package.swift` or add a provider: + +1. Update the dependency graph and tables in [ARCHITECTURE.md](ARCHITECTURE.md). +2. Mirror user-facing bits in [../README.md](../README.md) (products, roadmap, import notes). +3. Note the change under `[Unreleased]` in [../CHANGELOG.md](../CHANGELOG.md).