12
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NuqsでLLMとUIのフィルター状態を共有する

Last updated at Posted at 2025-12-13

この記事はBrainpad Advent Calendar 2025の13日目になります。

株式会社ブレインパッド プロダクトユニットの角田です。

弊社は「データ活用の促進を通じて持続可能な未来をつくる」をミッションに、
データ分析支援やSaaSプロダクトの提供を通じて、企業の「データ活用の日常化」を推進しております。

業務では、LLMエージェント等の技術を使った新規事業開発を担当しております。

はじめに

最近、クエリパラメータの状態管理ライブラリのNuqsが話題になっています。
今回はデータテーブルとLLM検索を組み合わせたUIで使えるか試してみました。

Nuqsを使うと「LLMが返したフィルターJSONをそのままUIに適用する」実装がシンプルに書けます。フィルター状態をNuqsのURL stateで一元管理することで、UIとLLMが同じデータを見るようになります。

Nuqsとは

Next.js / React向けのクエリパラメータの状態管理ライブラリです。URLのクエリ文字列をReactのstateのように扱えます。
今回は基本的な使い方のみを簡単に説明します。

基本的な使い方

import { useQueryState, parseAsString } from 'nuqs';

const [name, setName] = useQueryState('name', parseAsString.withDefault(''));

useStateと同じインターフェースで、値の変更が自動的にURLに反映されます。setName('alice')を呼ぶと、URLが?name=aliceに変わります。

複数のパラメータをまとめて管理する

複数のクエリパラメータを扱う場合はuseQueryStatesを使います。

import { useQueryStates, parseAsString, parseAsInteger } from 'nuqs';

const [filters, setFilters] = useQueryStates({
  q: parseAsString.withDefault(''),
  page: parseAsInteger.withDefault(1),
});

// filters.q, filters.page でアクセス
// setFilters({ q: 'test', page: 2 }) でまとめて更新

型安全なパーサー

Nuqsは様々な型のパーサーを提供しています。

  • parseAsString - 文字列
  • parseAsInteger - 整数
  • parseAsBoolean - 真偽値
  • parseAsArrayOf - 配列
  • parseAsJson - JSONオブジェクト

URLの文字列から適切な型への変換と、その逆変換が自動で行われます。

今回作ったもの

1211.gif

検証用に作ったのは、データテーブルとLLM検索を組み合わせたUIのデモです。

ユーザーが「エンタープライズ顧客に絞って」と入力すると、LLMがフィルター条件を返し、それをUIに反映します。
フィルター済みの状態から条件を追加することもできます。

Nuqsなしで実装すると何が辛いか

ローカルstate + 手書きURL管理だと、フィルター状態が3箇所に分散します。
また、UI state更新後に、URLも手動で更新する必要があります。

管理対象 形式 更新タイミング
UI state Reactのstate ユーザー操作時
URLクエリ 文字列 手動で同期
LLM入出力 JSONオブジェクト API呼び出し時に変換

別々に管理していると、

  • UI state更新後に、URLも手動で更新する必要がある
  • LLMに渡すfiltersの形と、URLのクエリの形と、UIのstateがズレやすい
  • フィルターを増やすたびに URL ↔ state ↔ LLM入力の変換コードが増える

のような問題が起こりがちです。

Nuqsでフィルターを一元管理する

Nuqsを使うと、フィルター状態を一元管理できるようになります。
まず、フィルターの型を定義します。

type TableFilters = {
  q: string;
  status: Status[];
  segment: Segment[];
  owner: string;
  from: string;
  to: string;
  sort: "arr-desc" | "arr-asc" | "date-desc" | "date-asc";
  page: number;
};

この型をNuqsで使います。

import { useQueryStates, parseAsString, parseAsArrayOf, parseAsInteger } from 'nuqs';

const [filters, setFilters] = useQueryStates({
  q: parseAsString.withDefault(""),
  status: parseAsArrayOf<Status>(parseAsString).withDefault([]),
  segment: parseAsArrayOf<Segment>(parseAsString).withDefault([]),
  owner: parseAsString.withDefault(""),
  from: parseAsString.withDefault(""),
  to: parseAsString.withDefault(""),
  sort: parseAsString.withDefault("arr-desc"),
  page: parseAsInteger.withDefault(1),
});

LLMのレスポンス型も同じTableFiltersを参照します。

type LlmResponse = {
  filters: Partial<TableFilters>;
  explanation: string;
};

UIもLLMも同じTableFiltersを見るので、ズレが起きません。

Before After
UIは複数のstateを見る UIはfiltersを見るだけ
URL同期は手動管理 setFilters()を呼ぶだけでURLは自動更新
LLM用に別途変換が必要 LLMに渡すfiltersの形 = Nuqsのスキーマ

LLMが返したフィルターをそのまま適用する

LLMから返ってくるフィルター条件を、setFiltersでマージします。

const result = await simulateLlm(prompt, filters);

setFilters((prev) => ({
  ...prev,
  ...result.filters,
  page: 1,
}));

これだけで、

  • URLがLLM提案どおりの条件に変わる
  • テーブルも同じ条件でフィルタされる
  • 次にLLMに聞くときのfiltersも揃う

「LLM → UI反映」がNuqsのstate更新1ステップで済みます。

動作イメージ

ユーザー: 「先月の未対応案件を見せて」
     ↓
LLM: { status: ["pending"], from: "2024-11-01", to: "2024-11-30" }
     ↓
setFilters(result.filters)
     ↓
URL: ?status=pending&from=2024-11-01&to=2024-11-30
     ↓
テーブル: フィルタ適用済みで表示

コード比較

Nuqsなし

const llmFilters = result.filters;

// 1. UI stateに反映
setStatusState(llmFilters.status);
setFromState(llmFilters.from);
setToState(llmFilters.to);

// 2. URLにも反映
const params = new URLSearchParams();
params.set('status', llmFilters.status.join(','));
params.set('from', llmFilters.from);
params.set('to', llmFilters.to);
router.push(`?${params.toString()}`);

// 3. 次回LLM呼び出し用のオブジェクトも更新
setLlmInputFilters({
  status: llmFilters.status,
  from: llmFilters.from,
  to: llmFilters.to,
});

変換レイヤーが増え、バグの温床になります。

Nuqsあり

setFilters(result.filters);

人間の操作も、LLMの提案も、setFiltersに流すだけです。

まとめ

今回、Nuqsを使って人間操作とLLM提案が同じインターフェースで簡単に書けることを紹介しました。

Nuqsには複雑なクエリパラメータを型安全に扱う機能が他にもあります。Zodスキーマとの統合や、配列・JSONのパースなど、今回紹介した以外にも便利な機能があるので、試してみてください。

参考

12
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?