Skip to content

Commit 4718deb

Browse files
authored
Merge pull request #69 from BohuTANG/dev-llmchain
feat: Upgrade to llmchain.rs crate for enhanced performance and power
2 parents c18f880 + bd00ce1 commit 4718deb

File tree

25 files changed

+1742
-1158
lines changed

25 files changed

+1742
-1158
lines changed

Cargo.lock

Lines changed: 1577 additions & 241 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[workspace]
22
members = [
33
"app",
4-
]
4+
]
5+

README.md

Lines changed: 8 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,16 @@
1-
# AskBend: SQL-based Knowledge Base Search and Completion using Databend
2-
## Demo https://ask.databend.rs/
3-
4-
AskBend is a Rust project that utilizes the power of Databend and OpenAI to create a SQL-based knowledge base from Markdown files.
5-
6-
Databend is a cloud-native data warehouse adept at storing and performing vector computations, making it suitable for this use case.
7-
8-
[Databend Cloud](https://databend.com) seamlessly integrates with OpenAI's capabilities, such as embedding generation, cosine distance calculation, and text completion. This integration means you don't need to interact with OpenAI directly; Databend Cloud manages everything.
9-
10-
The project automatically generates document embeddings from the content, enabling users to search and retrieve the most relevant information to their queries using SQL.
11-
12-
SQL-Based means you don't need any OpenAI API knowledge. With the Databend Cloud platform, you can perform these tasks using SQL. Some SQL AI functions of Databend Cloud include:
13-
14-
- [ai_embedding_vector](https://databend.rs/doc/sql-functions/ai-functions/ai-embedding-vector): Get the vector from OpenAI API
15-
- [ai_text_completion](https://databend.rs/doc/sql-functions/ai-functions/ai-text-completion): Get the completion of a text
16-
- [cosine_distance](https://databend.rs/doc/sql-functions/ai-functions/ai-cosine-distance): Calculate the distance between embedding vectors
1+
# AskBend: Empower developers to explore & implement your knowledge base
172

18-
## Overview
19-
20-
The project follows this general process:
3+
## Demo https://ask.databend.rs/
214

22-
1. Read and parse Markdown files from a directory.
23-
2. Extract the content and store it in the askbend.doc table.
24-
3. Compute embeddings for the content using Databend Cloud's built-in AI capabilities, including OpenAI's embedding generation, all through SQL.
25-
4. When a user queries, generate the query embedding using Databend Cloud's SQL-based `ai_embedding_vector` function.
26-
5. Perform a vector calculation to find the most relevant doc.content using Databend Cloud's SQL-based `cosine_distance` function.
27-
6. Concatenate the retrieved content and use OpenAI's completion capabilities with Databend Cloud's SQL-based `ai_text_completion` function.
28-
7. Output the completion result in Markdown format.
5+
AskBend is a project built in Rust that leverages the [llmchain.rs](https://github.com/shafishlabs/llmchain.rs) library to create a SQL-based knowledge base from Markdown files.
296

307
## Setup
318

329
### 1. Download
3310

3411
https://github.com/datafuselabs/askbend/releases
3512

36-
### 2. Create a table in your Databend Cloud
37-
38-
[table](schema/table.sql):
39-
```
40-
CREATE DATABASE askbend;
41-
USE askbend;
42-
43-
CREATE TABLE doc (path VARCHAR, content VARCHAR, embedding ARRAY(FLOAT32));
44-
```
45-
46-
### 3. Modify the configuration file [conf/askbend.toml](conf/askbend.toml)
13+
### 2. Modify the configuration file [conf/askbend.toml](conf/askbend.toml)
4714

4815
```
4916
# Usage:
@@ -66,20 +33,11 @@ port = 8081
6633
6734
[query]
6835
top = 3
69-
product = "YourProductName"
70-
prompt = '''
71-
<your prompt including {{product}}> ...
72-
Documentation sections:
73-
{{context}}
74-
75-
Question:
76-
{{query}}
77-
'''
7836
```
7937

80-
### 4. Prepare your Markdown files by copying them to the `data/` directory
38+
### 3. Prepare your Markdown files by copying them to the `data/` directory
8139

82-
### 5. Parse the Markdown files and build embeddings
40+
### 4. Parse the Markdown files and build embeddings
8341

8442
```
8543
./target/release/askbend -c conf/askbend.toml --rebuild
@@ -96,13 +54,13 @@ Question:
9654
The `--rebuild` flag rebuilds all the embeddings for the data directory. This process may take a few minutes, depending on the number of Markdown files.
9755

9856

99-
### 6. Start the API server
57+
### 5. Start the API server
10058

10159
```
10260
./target/release/askbend -c conf/askbend.toml
10361
```
10462

105-
### 7. Query your Markdown knowledge base using the API
63+
### 6. Query your Markdown knowledge base using the API
10664
```
10765
curl -X POST -H "Content-Type: application/json" -d '{"query": "tell me how to do copy"}' http://localhost:8081/query
10866
```

app/Cargo.toml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,27 @@ doctest = false
1414
test = false
1515

1616
[dependencies]
17+
actix-cors = "0.6.4"
18+
actix-web = "4.3.1"
1719
clap = { version = "4.1.7", features = ["derive", "env"] }
1820
chrono = "0.4.24"
19-
comrak = "0.18.0"
20-
databend-driver = "0.1.4"
21+
databend-driver = "0.2.23"
2122
log = "0.4.0"
2223
env_logger = "0.9.0"
2324
regex = "1.7.3"
2425
serde = { version = "1.0.159", features = ["derive"] }
2526
serfig = "0.0.3"
26-
actix-web = "4.3.1"
27-
actix-cors = "0.6.4"
2827
anyhow = "1.0.58"
2928
syn = "1.0"
3029
proc-macro2 = "1.0"
3130
quote = "1.0"
3231
markdown = "0.3.0"
33-
tokio = { version = "1.19.2", features = ["full"] }
32+
tokio = { version = "1.28", features = ["full"] }
3433
tokio-stream = "0.1.12"
3534
walkdir = "2.3.3"
3635

36+
llmchain = { git = "https://github.com/shafishlabs/llmchain.rs" , rev = "f204230"}
37+
3738
[dev-dependencies]
39+
40+

app/bin/ask.rs

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,21 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
use std::sync::Arc;
16+
1517
use anyhow::Result;
1618
use askbend::APIHandler;
1719
use askbend::Config;
18-
use askbend::DatabendDriver;
19-
use askbend::FileOperator;
20-
use askbend::Markdown;
21-
use askbend::Parse;
22-
use askbend::RustCode;
2320
use env_logger::Builder;
2421
use env_logger::Env;
22+
use llmchain::DatabendEmbedding;
23+
use llmchain::DatabendVectorStore;
24+
use llmchain::DocumentLoader;
25+
use llmchain::DocumentPath;
26+
use llmchain::DocumentSplitter;
27+
use llmchain::MarkdownLoader;
28+
use llmchain::MarkdownSplitter;
29+
use llmchain::VectorStore;
2530
use log::info;
2631
use tokio::time::Instant;
2732

@@ -46,58 +51,44 @@ async fn main() -> Result<()> {
4651

4752
/// Rebuild all embeddings.
4853
async fn rebuild_embedding(conf: &Config) -> Result<()> {
49-
let ext = conf.data.file_ext.clone();
50-
let ignore_dirs = conf.data.ignore_dirs.clone();
51-
info!("Step-1: begin parser all {} files", ext);
52-
let file_opt = FileOperator::create(&conf.data.path, &conf.data.file_ext, &ignore_dirs);
53-
let files = file_opt.list()?;
54-
55-
let snippet_files = match conf.data.file_ext.as_str() {
56-
"md" => Markdown::parse_multiple(
57-
&files
58-
.iter()
59-
.map(|v| v.full_path.clone())
60-
.collect::<Vec<String>>(),
61-
),
54+
let local_disk = llmchain::LocalDisk::create()?;
55+
let markdown_loader = MarkdownLoader::create(local_disk.clone());
56+
let directory_loader =
57+
llmchain::DirectoryLoader::create(local_disk).with_loader("**/*.md", markdown_loader);
58+
let documents = directory_loader
59+
.load(DocumentPath::Str(conf.data.path.clone()))
60+
.await?;
61+
info!("Step-1: parser all files:{}", documents.len());
6262

63-
"rs" => RustCode::parse_multiple(
64-
&files
65-
.iter()
66-
.map(|v| v.full_path.clone())
67-
.collect::<Vec<String>>(),
68-
),
69-
_ => Err(anyhow::Error::msg(format!(
70-
"Only support file ext: md or rs, got:{}",
71-
conf.data.file_ext
72-
))),
73-
}?;
63+
let documents = MarkdownSplitter::create().split_documents(&documents)?;
64+
info!("Step-2: split all files to:{}", documents.len());
7465

66+
let now = Instant::now();
7567
info!(
76-
"Step-1: finish parser all {} files:{}, sections:{}, tokens:{}",
77-
ext,
78-
files.len(),
79-
snippet_files.all_snippets(),
80-
snippet_files.all_tokens()
68+
"Step-3: begin embedding to table:{}.{}",
69+
conf.database.database, conf.database.table
8170
);
71+
let dsn = conf.database.dsn.clone();
72+
let databend_embedding = Arc::new(DatabendEmbedding::create(&dsn));
73+
let databend_vector_store = DatabendVectorStore::create(&dsn, databend_embedding)
74+
.with_database(&conf.database.database)
75+
.with_table(&conf.database.table);
76+
databend_vector_store.init().await?;
8277

83-
let dal = DatabendDriver::connect(conf)?;
84-
85-
info!("Step-2: begin insert to table");
86-
dal.insert(&snippet_files).await?;
87-
info!("Step-2: finish insert to table");
88-
89-
info!("Step-3: begin generate embedding, may take some minutes");
90-
dal.embedding().await?;
91-
info!("Step-3: finish generate embedding");
92-
78+
let _ = databend_vector_store.add_documents(documents).await?;
79+
info!(
80+
"Step-3: finish embedding to table:{}.{}, cost {}",
81+
conf.database.database,
82+
conf.database.table,
83+
now.elapsed().as_secs()
84+
);
9385
Ok(())
9486
}
9587

9688
/// Start the api server.
9789
async fn start_api_server(conf: &Config) -> Result<()> {
9890
info!("Start api server {}:{}", conf.server.host, conf.server.port);
99-
let dal = DatabendDriver::connect(conf)?;
100-
let handler = APIHandler::create(&conf.server, dal.clone());
91+
let handler = APIHandler::create(conf);
10192
handler.start().await?;
10293
Ok(())
10394
}

app/src/api/http.rs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,21 @@ use anyhow::Result;
2121

2222
use crate::api::query::query_handler;
2323
use crate::api::status::status_handler;
24-
use crate::configs::ServerConfig;
25-
use crate::DatabendDriver;
24+
use crate::Config;
2625

2726
pub struct APIHandler {
28-
pub conf: ServerConfig,
29-
pub db: DatabendDriver,
27+
pub conf: Config,
3028
}
3129

3230
impl APIHandler {
33-
pub fn create(conf: &ServerConfig, db: DatabendDriver) -> Self {
34-
APIHandler {
35-
conf: conf.clone(),
36-
db,
37-
}
31+
pub fn create(conf: &Config) -> Self {
32+
APIHandler { conf: conf.clone() }
3833
}
3934

4035
pub async fn start(self) -> Result<()> {
4136
let conf = self.conf.clone();
42-
let host = conf.host.clone();
43-
let port = conf.port;
44-
let data = self.db.clone();
37+
let host = conf.server.host.clone();
38+
let port = conf.server.port;
4539

4640
HttpServer::new(move || {
4741
let mut cors = Cors::default()
@@ -52,12 +46,12 @@ impl APIHandler {
5246
http::header::CONTENT_TYPE,
5347
])
5448
.max_age(3600);
55-
for origin in &conf.cors {
49+
for origin in &conf.server.cors {
5650
cors = cors.allowed_origin(origin);
5751
}
5852
App::new()
5953
.wrap(cors)
60-
.app_data(web::Data::new(data.clone()))
54+
.app_data(web::Data::new(conf.clone()))
6155
.route("/status", web::get().to(status_handler))
6256
.route("/query", web::post().to(query_handler))
6357
})

app/src/api/query.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ use actix_web::HttpResponse;
1818
use actix_web::Responder;
1919
use log::error;
2020

21-
use crate::DatabendDriver;
21+
use crate::llm::BendLLM;
22+
use crate::Config;
2223

2324
#[derive(serde::Deserialize)]
2425
pub struct Query {
@@ -31,17 +32,13 @@ struct Response {
3132
}
3233

3334
/// curl -X POST -H "Content-Type: application/json" -d '{"query": "whats the fast way to load data to databend"}' http://localhost:8081/query
34-
pub async fn query_handler(
35-
query: web::Json<Query>,
36-
db: web::Data<DatabendDriver>,
37-
) -> impl Responder {
38-
let result = db.query(&query.query).await;
35+
pub async fn query_handler(query: web::Json<Query>, conf: web::Data<Config>) -> impl Responder {
36+
let llm = BendLLM::create(&conf);
37+
let result = llm.query(&query.query).await;
3938
match result {
4039
Ok(result) => {
4140
let response = if !result.is_empty() {
42-
Response {
43-
result: result[0].to_string(),
44-
}
41+
Response { result }
4542
} else {
4643
Response {
4744
result: "Sorry, I dont know how to help with that.".to_string(),

app/src/base/string.rs

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,10 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use regex::Regex;
16-
1715
pub fn escape_sql_string(input: &str) -> String {
1816
input
1917
.replace('\\', "\\\\")
2018
.replace('\'', "''")
2119
.replace('\n', " ")
2220
.replace('\r', "\\r")
2321
}
24-
25-
pub fn remove_markdown_links(input: &str) -> String {
26-
let link_regex = Regex::new(r"\[(?P<text>[^\]]+)\]\((?P<url>[^\)]+)\)").unwrap();
27-
let result = link_regex.replace_all(input, "$text");
28-
result.to_string()
29-
}
30-
31-
pub fn replace_multiple_spaces(input: &str) -> String {
32-
let re = Regex::new(r" +").unwrap();
33-
re.replace_all(input, " ").to_string()
34-
}
35-
36-
pub trait LengthWithoutSymbols {
37-
fn length_without_symbols(&self) -> usize;
38-
}
39-
40-
impl LengthWithoutSymbols for String {
41-
fn length_without_symbols(&self) -> usize {
42-
self.chars()
43-
.filter(|c| c.is_alphanumeric() || c.is_whitespace())
44-
.count()
45-
}
46-
}

0 commit comments

Comments
 (0)