Blogi

Asiakirjojen muuntaminen Markdowniksi Rustilla

17. elokuuta 2026 · 6 min lukuaika


Jos rakennat asiakirjankäsittelyputkea Rustilla, tunnet kaavan: hae data, muunna formaatit, tuota puhdas tulos — pitäen viive pienenä ja läpimeno suurena. AnyMD:n REST API sopii luontevasti Rust-ekosysteemiin, koska molemmat on rakennettu samoilla painotuksilla: suorituskyky, turvallisuus ja vähäinen overhead.

Tässä artikkelissa näytetään, miten asiakirjoja muunnetaan Markdowniksi Rustista käyttäen reqwest, tokio ja serde, ja benchmarkit osoittavat, miksi Rust-asiakas + Rust-palvelin -yhdistelmä tarjoaa erittäin pienen päästä päähän -viiveen. Katso myös oppaamme Node.js, Python ja CLI muille kielille.

Miksi Rust + AnyMD?

AnyMD:n muunnospalvelin on kirjoitettu Rustilla (Axum + Tokio). Kun myös asiakkaasi on Rustia, kielten välinen overhead poistuu kokonaan — ei Python GIL:iä, ei Node.js event loop -viivettä, ei Go GC -taukoja. Vain suora asynkroninen I/O kahden vahvasti optimoidun runtimen välillä.

Yhden asiakirjan muunnos

use reqwest::Client;
use std::path::Path;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    let api_key = "your-api-key";

    let file = Path::new("report.docx");
    let file_bytes = tokio::fs::read(file).await?;

    let part = reqwest::multipart::Part::bytes(file_bytes)
        .file_name("report.docx");

    let form = reqwest::multipart::Form::new()
        .part("file", part);

    let resp = client
        .post("https://anymd.net/api/convert")
        .header("Authorization", format!("Bearer {api_key}"))
        .multipart(form)
        .send()
        .await?;

    let markdown = resp.text().await?;
    println!("Got {} chars of Markdown", markdown.len());

    Ok(())
}

Erämuunnos tokio-taskeilla

Kun sinulla on hakemisto täynnä asiakirjoja, käynnistä rinnakkaisia taskeja ja hallitse samanaikaisuutta semaforilla.

use reqwest::Client;
use std::sync::Arc;
use std::path::PathBuf;
use tokio::sync::Semaphore;
use tokio::fs;

#[derive(serde::Serialize, serde::Deserialize)]
struct ConversionResult {
    file: String,
    ok: bool,
    chars: usize,
}

async fn convert_one(
    client: &Client,
    path: &Path,
    api_key: &str,
) -> ConversionResult {
    let bytes = match fs::read(path).await {
        Ok(b) => b,
        Err(e) => return ConversionResult {
            file: path.to_string_lossy().to_string(),
            ok: false,
            chars: 0,
        },
    };

    let part = reqwest::multipart::Part::bytes(bytes)
        .file_name(path.file_name().unwrap().to_string_lossy().to_string());
    let form = reqwest::multipart::Form::new().part("file", part);

    match client
        .post("https://anymd.net/api/convert")
        .header("Authorization", format!("Bearer {api_key}"))
        .multipart(form)
        .send()
        .await
    {
        Ok(resp) if resp.status().is_success() => {
            let text = resp.text().await.unwrap_or_default();
            ConversionResult {
                file: path.to_string_lossy().to_string(),
                ok: true,
                chars: text.len(),
            }
        }
        Ok(resp) => ConversionResult {
            file: path.to_string_lossy().to_string(),
            ok: false,
            chars: 0,
        },
        Err(e) => ConversionResult {
            file: path.to_string_lossy().to_string(),
            ok: false,
            chars: 0,
        },
    }
}

async fn batch_convert(dir: &str, api_key: &str, concurrency: usize) {
    let client = Client::new();
    let semaphore = Arc::new(Semaphore::new(concurrency));
    let mut entries = Vec::new();

    let mut read_dir = tokio::fs::read_dir(dir).await.unwrap();
    while let Some(entry) = read_dir.next_entry().await.unwrap() {
        entries.push(entry.path());
    }

    let mut handles = Vec::new();
    for path in entries {
        let permit = semaphore.clone().acquire_owned().await.unwrap();
        handles.push(tokio::spawn(async move {
            let result = convert_one(&client, &path, api_key).await;
            drop(permit);
            result
        }));
    }

    let mut ok = 0usize;
    for handle in handles {
        let result = handle.await.unwrap();
        if result.ok {
            ok += 1;
        }
        println!("{} — {}", result.file, if result.ok { "✓" } else { "✗" });
    }
    println!("{}/{} converted", ok, entries.len());
}

#[tokio::main]
async fn main() {
    batch_convert("./documents", "your-api-key", 10).await;
}

AnyMD:n käyttö RAG-ingestion-putkessa Rustilla

Tässä realistinen pipeline: tarkkaile hakemistoa uusien tiedostojen varalta, muunna ne Markdowniksi ja tee sitten chunking ja embedding vektorihakua varten.

Tuetut formaatit

LuokkaFormaatit
AsiakirjatPDF, DOCX, ODT, RTF, EPUB, CBR/CBZ
EsityksetPPTX, ODP
LaskentataulukotXLSX, ODS, CSV (renderöidään taulukoiksi)
WebHTML, Markdown (välitetään muuttamatta)
KuvatOCR-poiminta JPG-, PNG- ja TIFF-kuvista

Hinnoittelu

PakettiSivua/kuukausiTiedoston enimmäiskokoHinta
Ilmainen10010 Mt$0
Starter50025 Mt$19
Pro5,00050 Mt$99
EnterpriseMukautettuMukautettuMukautettu

Rust-muunnokset hyötyvät samasta nollasäilytyskäytännöstä ja alle sekunnin viiveestä kuin muutkin kielet. Katso API-dokumentaatio täydellistä määritystä varten ja tarkista hinnoittelu pakettien tiedoista.

Hanki ilmainen API-avaimesi — 100 sivua/kuukausi, luottokorttia ei tarvita.


← Lue lisää →