---
title: クイックスタート
description: Package「anydoc」を使って、Agent が Word や PDF を読めるようにします。
---

誰かが Session に `q3-review.docx` をアップロードし、自分が何を決めるべきかを Agent に尋ねたとします。答えはファイルの中にあります。このページでは、その答えを取り出す方法と、それに Function が最適な理由を説明します。

## 課題: Agent はファイルを読めない

`.docx` はテキストではありません。複数の XML ファイルを ZIP でまとめたもので、段落はあるファイルに、スタイルは別のファイルに入り、表や番号付けは両方にまたがっています。バイト列をそのまま渡されても、モデルにはノイズにしか見えません。`.pdf`、`.xlsx`、`.pptx` も同様で、実際の仕事で扱うドキュメントはほとんどがバイナリ形式です。

すぐに思いつく回避策は、どれもモデルのコンテキストを経由します。

- **ファイルを会話に貼り付ける。** 2 MB のドキュメントを Base64 にしてメッセージに入れると、たいていの Context window に収まらないほどのトークンを消費します。しかも、モデルはそのバイト列をどのみち解析できません。
- **MCP サーバーに渡す。** MCP サーバーは別のサービスなので、Session のファイルを見られません。ドキュメントを届けるには、結局モデルを通して Tool 呼び出しの引数として渡すしかなく、コストは変わらないうえ、扱えるサイズは Context window で頭打ちになります。
- **ユーザーに先に変換してもらう。** Agent が手助けするはずだった相手に、作業を押し戻すことになります。

どれも望む形ではありません。そもそもドキュメントがモデルを通らないようにしたいのです。

## 発想: Tool の呼び出しをファイルへの操作にする

Session にはすでにファイルシステムがあります。[Session Volume](/ja/functions/runtime#session-volume) です。アップロードしたドキュメントは `/volume/data/uploads/q3-review.docx` に置かれています。それなら、Tool にそこで作業させればよいのです。

1. **モデルはファイルを指定する**

    モデルはドキュメントそのものではなく、パスを渡して Tool を呼び出します。かかるのは数十トークンです。

2. **Tool はファイルのそばで処理する**

    Tool は Volume からバイト列を読み、ZIP の展開、XML の解析、表の Markdown への整形といった、モデルにはできない処理を行います。

3. **結果はファイルか、小さな回答になる**

    Tool は副作用として、次の Tool がそのまま使える `q3-review.md` のような出力を Volume に書き戻せます。モデルに返すのは、モデルに必要なものだけです。短いドキュメントなら Markdown そのもの、長いドキュメントならパスとサイズです。

何をするかを決めるのはモデルで、計算とバイト列はファイルシステムの側にとどまります。これが Subako Function です。Subako がホストする環境で、Session の Volume のそばで動くコードであり、自分でサーバーを用意する必要はありません。

## Function としての anydoc

[anydoc](https://github.com/firecrawl/anydoc) は、まさに必要な変換を行うオープンソースの Rust ライブラリです。ドキュメントのバイト列を渡すと、形式 (Word、PowerPoint、Excel、OpenDocument、RTF、EPUB、CSV、テキストベースの PDF) を判別し、テキストを GitHub-Flavored Markdown にして返します。ただし、単体ではただのライブラリです。ファイルのそばでそれを実行する仕組みが要ります。

[anydoc のサンプル](https://github.com/subako-ai/subako-function-examples/tree/main/rust/anydoc) は、Subako の [Rust SDK](/ja/functions/rust) を使って、これを 1 つの関数として包んだものです。以下はその中心部分を、サンプルのソースから簡略化して示したものです。

```rust src/main.rs
#[derive(Deserialize, JsonSchema)]
struct ConvertInput {
    /// Path of the document on the session's Volume, under `/volume/tmp` or `/volume/data`.
    path: String,
    /// Where the Markdown goes: back in the reply (the default), or into a Volume file.
    #[serde(default)]
    output: MarkdownOutput,
}

/// Convert a document on the Volume to GitHub-Flavored Markdown. Reads Word
/// (doc, docx), PowerPoint (ppt, pptx), Excel (xls, xlsx, xlsb), OpenDocument
/// (odt, ods, odp), RTF, EPUB, CSV and text-based PDF. …
#[function]
fn convert_to_markdown(input: ConvertInput) -> Result<Converted, ToolError> {
    // Read the document from the session's volume…
    let bytes = std::fs::read(&input.path)?;
    // …let anydoc do the conversion…
    let format = Format::from_bytes(&bytes).ok_or("unrecognized file")?;
    let markdown = anydoc::to_markdown_bytes(&bytes, format)?;
    // …and answer with the text, or write it to a file and answer with where it is.
    let markdown = match input.output {
        MarkdownOutput::Inline => Markdown::Inline { text: markdown },
        MarkdownOutput::File { path } => {
            std::fs::write(&path, &markdown)?;
            Markdown::File { bytes: markdown.len(), path }
        }
    };
    Ok(Converted { format: format_name(format).into(), markdown })
}

package! {
    name: "anydoc",
    description: "Converts office documents, EPUB, CSV and text-based PDFs to Markdown with anydoc.",
    functions: [convert_to_markdown],
}
```

組み込みに必要なのはこれだけです。

- `#[function]` は普通の Rust の関数を Tool にします。doc コメントはモデルが読む説明に、`ConvertInput` はモデルが呼び出しに使う入力 Schema になります。
- ファイルへのアクセスは普通の `std::fs` で済みます。Volume が Function のファイルシステムにマウントされているからです。
- `package!` は crate を **Function package** にします。WebAssembly 向けにビルドすると 1 つの `module.wasm` になり、Subako はそれをどこでも、どの Session のためにも実行できます。デプロイするものは何もありません。

ここからは、この Package をビルドしてアップロードし、Agent に使わせるまでの手順です。

## anydoc をビルドする

Package のビルドには Rust が必要です。

```bash
git clone https://github.com/subako-ai/subako-function-examples
cd subako-function-examples/rust/anydoc
./package.sh        # dist/anydoc-function.tar.gz に書き出します
```

`package.sh` は Module を WASI preview 1 向けにビルドし (このターゲットは、サンプルの `rust-toolchain.toml` に従って rustup がインストールします)、`subako-package.json` と一緒に **Bundle** にまとめます。Bundle は、Subako が受け付ける形式の Archive です。

## アップロードする

コンソールで Workspace の **Function** ページを開き、**Package をアップロード** を選びます。**Archive** を選んで `dist/anydoc-function.tar.gz` を指定し、**アップロード** を押します。

![Function package のアップロード: anydoc の Archive を選択した状態](/blume-assets/content/docs/_images/ja/function-upload.webp)

Subako は Module を一度実行して提供される Tool を問い合わせ、Package のページを開きます。ページには、Tool とその説明、Parameter が、モデルから見えるとおりに表示されます。

![Package「anydoc」のページ: バージョン 1、そのビルド、Parameter 付きの Tool convert_to_markdown](/blume-assets/content/docs/_images/ja/function-package.webp)

## Agent に付与する

Agent を開き (まだなければ [クイックスタート](/ja/quickstart#agent-を公開する) のとおりに作成し)、**新しいバージョンを公開** を選びます。**Tool** › **Function package** で **Package を追加** を押し、**この Workspace** を選んで **anydoc** を指定します。

![Function package の Grant: Workspace の Package「anydoc」をバージョン 1 に固定](/blume-assets/content/docs/_images/ja/agent-editor-functions.webp)

anydoc は既定値のままで使えます。ネットワークは不要で、1 回の呼び出しは 60 秒よりずっと短く終わるからです。続いて **Session Volume** の **Function に Data (/volume/data) を用意する** をオンにします。これで、Session にアップロードしたファイルが、Session がある限り保持されます。

![Session Volume:「Function に Data (/volume/data) を用意する」をオンにした状態](/blume-assets/content/docs/_images/ja/agent-editor-volume.webp)

あわせて Platform package の [`files`](/ja/functions/platform-packages#files) も付与しておくと、Agent はパスを知らされていないドキュメントを探したり、長い変換結果を 1 ページずつ読んだりできます。設定が終わったら **バージョン N を公開** を押します。

## ドキュメントを渡す

その Agent で Session を開始し、**Session Volume** タブで **ファイルをアップロード** を選びます。ドキュメントは **Data (/volume/data)** の `uploads` などのフォルダーにアップロードします。

![ファイルのアップロード: q3-review.docx を Data (/volume/data) のフォルダー uploads にアップロード](/blume-assets/content/docs/_images/ja/session-volume-upload.webp)

Volume は、Agent の Function がファイルを読み書きする場所です。これでファイルは `/volume/data/uploads/q3-review.docx` に置かれました。

![Session Volume: uploads/q3-review.docx がある Data の領域](/blume-assets/content/docs/_images/ja/session-volume.webp)

自分のアプリから `PUT /v1/sessions/{session_id}/volume/data/{path}` でファイルを置くこともできます。詳しくは [API で Session を扱う](/ja/build/sessions-api) を参照してください。

## 質問する

**会話** に戻り、ドキュメントを読まなければ答えられないことを質問します。Agent はファイルを対象に `convert_to_markdown` を呼び出し、得られた Markdown を読んで回答します。

![会話: 質問、完了した Function「anydoc」の convert_to_markdown の呼び出し、3 つの決定事項を挙げた Agent の回答](/blume-assets/content/docs/_images/ja/session-function-calls.webp)

呼び出しを開くと、何が起きたかを正確に確認できます。モデルが送った引数と、anydoc が返した Markdown です。表もそのまま含まれています。

![convert_to_markdown の呼び出しを開いたところ: 引数 path と、Markdown になったドキュメント](/blume-assets/content/docs/_images/ja/session-function-result.webp)

短いドキュメントなら、この例のように Markdown がそのまま返ってきます。300 ページのマニュアルのような長いドキュメントでは、モデルは代わりに `"output": {"type": "file", "path": "/volume/data/uploads/manual.md"}` を指定します。anydoc は Markdown を Volume に書き込み、そのパスとサイズだけを返します。Agent は必要な部分だけを Platform package の [`files`](/ja/functions/platform-packages#files) で読みます。どちらの場合も、ドキュメントのバイト列がモデルを通ることはありません。

これで一連の流れは完了です。Package は一度アップロードして Agent に付与すれば、必要とするすべての Session から呼び出されます。自分で運用するサーバーは要りません。

## 次のステップ

**[独自の Package を書く](/ja/functions/write-a-function)**

Rust、TypeScript、Go のテンプレートから Package を始めます。

**[Function の付与](/ja/functions/granting)**

ネットワークアクセス、Credential、Timeout、バージョン、承認。

**[ランタイム](/ja/functions/runtime)**

Volume、Asset、HTTP、画像、そして呼び出しに適用される制限。

**[サンプル](/ja/functions/examples)**

Python、Bash、JavaScript、Typst、PowerPoint などを扱う Package。
