Skip to content

Commit

Permalink
feat(agents): **EXPERIMENTAL** Agents in Swiftide (#463)
Browse files Browse the repository at this point in the history
Agents are coming to Swiftide! We are still ironing out all the kinks,
while we make it ready for a proper release. You can already experiment
with agents, see the rustdocs for documentation, and an example in
`/examples`, and feel free to contact us via github or discord. Better
documentation, examples, and tutorials are coming soon.

Run completions in a loop, define tools with two handy macros, customize
the agent by hooking in on lifecycle events, and much more.

Besides documentation, expect a big release for what we build this for
soon! 🎉
  • Loading branch information
timonv authored Dec 11, 2024
1 parent e154b8b commit 7211559
Show file tree
Hide file tree
Showing 63 changed files with 5,016 additions and 269 deletions.
464 changes: 284 additions & 180 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
[workspace]
members = ["swiftide", "swiftide-*", "examples", "benchmarks"]
default-members = ["swiftide", "swiftide-*"]

resolver = "2"

[workspace.package]
Expand All @@ -21,6 +20,7 @@ similar.opt-level = 3

[workspace.dependencies]
anyhow = { version = "1.0" }
thiserror = "2.0.3"
async-trait = { version = "0.1" }
derive_builder = { version = "0.20" }
futures-util = { version = "0.3" }
Expand All @@ -40,6 +40,7 @@ indoc = { version = "2.0" }
regex = { version = "1.11.1" }
uuid = { version = "1.10", features = ["v3", "v4", "serde"] }
dyn-clone = { version = "1.0" }
convert_case = "0.6.0"

# Integrations
spider = { version = "2.13" }
Expand Down Expand Up @@ -92,6 +93,7 @@ mockall = "0.13.1"
temp-dir = "0.1.13"
wiremock = "0.6.0"
test-case = "3.3.1"
pretty_assertions = "1.4"
insta = { version = "1.41.1", features = ["yaml"] }


Expand Down
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
<h3 align="center">Swiftide</h3>

<p align="center">
Fast, streaming indexing and query library for AI applications, written in Rust
Fast, streaming, indexing, query, and agent library for building LLM applications in Rust.
<br />
<a href="https://swiftide.rs"><strong>Read more on swiftide.rs »</strong></a>
<br />
Expand All @@ -76,6 +76,8 @@ Swiftide is a Rust native library for building LLM applications. Large language
to solve real problems. Swiftide allows you to ingest, transform and index large amounts of data fast, and then query that data so it it can be injected into prompts.
This process is called Retrieval Augmented Generation.

With Swiftide Agents, you have the building blocks to model and build a large variety of agents. The goal is to provide flexible building blocks, so that we can focus on experimenting and finding a model that works best, without having to constantly re-invent the underlying plumbing.

With Swiftide, you can build your AI application from idea to production in a few lines of code.

<div align="center">
Expand Down Expand Up @@ -155,20 +157,32 @@ query::Pipeline::default()
.await?;
```

_You can find more examples in [/examples](https://github.com/bosun-ai/swiftide/tree/master/examples)_
Running an agent that can search code:

```rust
agents::Agent::builder()
.llm(&openai)
.tools(vec![search_code()])
.build()?
.query("In what file can I find an example of a swiftide agent?")
.await?;
```

_You can find more detailed examples in [/examples](https://github.com/bosun-ai/swiftide/tree/master/examples)_

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Vision

Our goal is to create a fast, extendable platform for Retrieval Augmented Generation to further the development of automated AI applications, with an easy-to-use and easy-to-extend api.
Our goal is to create a fast, extendable platform for building LLLM applications in Rust, to further the development of automated AI applications, with an easy-to-use and easy-to-extend api.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Features

- Fast, modular streaming indexing pipeline with async, parallel processing
- Experimental query pipeline
- Experimental agent framework
- A variety of loaders, transformers, semantic chunkers, embedders, and more
- Bring your own transformers by extending straightforward traits or use a closure
- Splitting and merging pipelines
Expand Down
1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ ignore = [
{ id = "RUSTSEC-2023-0086", reason = "Ignore a security adivisory on lexical-core" },
{ id = "RUSTSEC-2021-0141", reason = "Dotenv is used by spider" },
{ id = "RUSTSEC-2024-0384", reason = "Instant is unmaintained" },
{ id = "RUSTSEC-2024-0421", reason = "Older version of idna used by reqwest" },
]

[bans]
Expand Down
12 changes: 11 additions & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,24 @@ swiftide = { path = "../swiftide/", features = [
"ollama",
"fluvio",
"lancedb",
"pgvector",
"pgvector",
"swiftide-agents",
] }
swiftide-macros = { path = "../swiftide-macros", version = "*" }
tracing-subscriber = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
spider = { workspace = true }
qdrant-client = { workspace = true }
fluvio = { workspace = true }
temp-dir = { workspace = true }
anyhow = { workspace = true }
indoc = { workspace = true }
sqlx = { workspace = true }
swiftide-test-utils = { path = "../swiftide-test-utils" }
tracing = { workspace = true }


[[example]]
doc-scrape-examples = true
name = "index-codebase"
Expand Down Expand Up @@ -96,6 +102,10 @@ path = "fluvio.rs"
name = "lancedb"
path = "lancedb.rs"

[[example]]
name = "hello-agents"
path = "hello_agents.rs"

[[example]]
name = "index-md-pgvector"
path = "index_md_into_pgvector.rs"
84 changes: 84 additions & 0 deletions examples/hello_agents.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! This is an example of how to build a Swiftide agent
//!
//! A swiftide agent runs completions in a loop, optionally with tools, to complete a task
//! autonomously. Agents stop when either the LLM calls the always included `stop` tool, or
//! (configurable) if the last message in the completion chain was from the assistant.
//!
//! Tools can be created by using the `tool` attribute macro as shown here. For more control (i.e.
//! internal state), there
//! is also a `Tool` derive macro for convenience. Anything that implements the `Tool` trait can
//! act as a tool.
//!
//! Agents operate on an `AgentContext`, which is responsible for managaging the completion history
//! and providing access to the outside world. For the latter, the context is expected to have a
//! `ToolExecutor`, which by default runs locally.
//!
//! When building the agent, hooks are available to influence the state, completions, and general
//! behaviour of the agent. Hooks are also traits.
//!
//! Refer to the api documentation for more detailed information.
use anyhow::Result;
use swiftide::{
agents,
chat_completion::{errors::ToolError, ToolOutput},
traits::{AgentContext, Command},
};

#[swiftide_macros::tool(
description = "Searches code",
param(name = "code_query", description = "The code query")
)]
async fn search_code(
context: &dyn AgentContext,
code_query: &str,
) -> Result<ToolOutput, ToolError> {
let command_output = context
.exec_cmd(&Command::shell(format!("rg '{code_query}'")))
.await?;

Ok(command_output.into())
}

#[tokio::main]
async fn main() -> Result<()> {
println!("Hello, agents!");

let openai = swiftide::integrations::openai::OpenAI::builder()
.default_embed_model("text-embeddings-3-small")
.default_prompt_model("gpt-4o-mini")
.build()?;

let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<String>();

tokio::spawn(async move {
while let Some(msg) = rx.recv().await {
println!("{}", msg);
}
});

agents::Agent::builder()
.llm(&openai)
.tools(vec![search_code()])
.before_all(move |_context| {
// This is a hook that runs before any command is executed
// No native async closures in Rust yet, so we have to use Box::pin
Box::pin(async move {
println!("Hello hook!");
Ok(())
})
})
// Every message added by the agent will be printed to stdout
.on_new_message(move |_, msg| {
let msg = msg.to_string();
let tx = tx.clone();
Box::pin(async move {
tx.send(msg).unwrap();
Ok(())
})
})
.build()?
.query("In what file can I find an example of a swiftide agent?")
.await?;

Ok(())
}
42 changes: 42 additions & 0 deletions swiftide-agents/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[package]
name = "swiftide-agents"
version.workspace = true
edition.workspace = true
license.workspace = true
readme.workspace = true
keywords.workspace = true
description.workspace = true
categories.workspace = true
repository.workspace = true
homepage.workspace = true

[dependencies]
swiftide-core = { path = "../swiftide-core", version = "0.14" }
swiftide-macros = { path = "../swiftide-macros", version = "0.14" }
anyhow.workspace = true
async-trait.workspace = true
dyn-clone.workspace = true
derive_builder.workspace = true
tokio.workspace = true
indoc.workspace = true
tracing.workspace = true
pretty_assertions.workspace = true
strum.workspace = true
strum_macros.workspace = true
serde.workspace = true
serde_json.workspace = true

[dev-dependencies]
swiftide-core = { path = "../swiftide-core", version = "0.14", features = [
"test-utils",
] }
swiftide-integrations = { path = "../swiftide-integrations", version = "0.14", features = [
"openai",
] }
mockall.workspace = true
test-log.workspace = true
temp-dir.workspace = true
insta.workspace = true

[lints]
workspace = true
Loading

0 comments on commit 7211559

Please sign in to comment.