GitHubスター
83
ユーザー評価
未評価
フォーク
2
イシュー
0
閲覧数
1
お気に入り
0
README
React Agent Hooks
Agentic Counter Demo | Agentic Todo Demo |
---|---|
Turn React Hooks into LLM Tools
- 🪝 Familiar: same semantics as React hooks
- 🤝 Symbiotic: human interface and Agent interface derived from the same state.
- 🛡️ Safe: developer controls the schema for Agentic state change.
- ➕ Incremental adoption: use as much or as little as you want.
- 📦 Composable: fully interoperable with classic React hooks.
- 🔮 Future-ready: forward-compatible with MCP and llms.txt.
Before
import { useCallback, useState } from "react";
function MyComponent() {
const [name, setName] = useState("John Doe");
const [age, setAge] = useState(30);
const adjust = useCallback((delta) => setAge((prev) => prev + delta), []);
return (
<div>
<h1>{name}</h1>
<p>{age}</p>
<button onClick={() => adjust(-1)}>Be younger</button>
<button onClick={() => adjust(1)}>Be older</button>
</div>
);
}
After
import { useAgent, useAgentState, useAgentTool } from "react-agent-hooks";
export function MyComponent() {
const agent = useAgent({ apiKey: "******" });
const [name, setName] = useAgentState("Name", "John Doe");
const [age, setAge] = useAgentState("Age", 30);
const adjust = useCallback((delta) => setAge((prev) => prev + delta), []);
useAgentTool("Change age", z.number().describe("the delta of age change"), adjust);
return (
<div>
<h1>{name}</h1>
<p>{age}</p>
<button onClick={() => agent.run("be younger")}>Be younger</button>
<button onClick={() => agent.run("be older")}>Be older</button>
</div>
);
}
Get Started
npm install react-agent-hooks
Usage
Give Agent "Eyes" 👀
import { useAgentMemo } from "react-agent-hooks";
function MyComponent() {
const [name, setName] = useState("John Doe");
const [age, setAge] = useState(30);
// Describe a readable state to the Agent
useAgentMemo("User's profile", () => ({ name, age }), [name, age]);
return (
<div>
<h1>{name}</h1>
<p>{age}</p>
</div>
);
}
Give Agent "Hands" 👏
import {z} from "zod";
import { useAgentState, useAgentTool } from "react-agent-hooks";
function MyComponent() {
// Describe a readable state to the Agent while exposing a setter function to developer
const [foodPreferences, setFoodPreferences] = useAgentState("food preference", ["Pizza", "Sushi"]);
// Wrap the setter as a tool and describe it to the Agent
const addFoodPreference = useAgentTool("add-food-preference", z.object(foodItems: z.array(z.string())), (foodItems) => {
setFoodPreferences((prev) => [...prev, ...foodItems]);
});
const removeFoodPreference = useAgentTool("remove-food-preference", z.object(foodItems: z.array(z.string())), (foodItems) => {
setFoodPreferences((prev) => prev.filter((item) => !foodItems.includes(item)));
});
return <ul>
{foodPreferences.map(item => <li key={item}>{item}</li>)}
</ul>
}
Run the Agent
import { useAgent } from "react-agent-hooks";
function MyApp() {
// Run the Agent with a prompt
// Agent always sees the latest states from `useAgentState`, `useAgentMemo`, and can uses the latest tools from `useAgentTool`
const agent = useAgent({ apiKey: "******" });
// Call the Agent
const handleFormSubmit = (e) => {
e.preventDefault();
const input = e.target.elements[0].value;
agent.run(input);
};
return (
<form onSubmit={handleFormSubmit}>
<input type="text" placeholder="Your request" />
<button onClick={handleRunAgent}>Ask Agent</button>
</form>
);
}
Compose Agentic Application
Inside a component, use the enabled
option to dynamically show/hide states and tools to the Agent.
const shouldShowFeature = true; // You can dynamically decide this value
useAgentMemo("User's profile", () => ({ name, age }), [name, age], { enabled: shouldShowFeature });
useAgentState("some state", { name: "Some state" }, { enabled: shouldShowFeature });
useAgentTool(
"update state",
z.object({ name: z.string() }),
(newState) => {
setSomeState(newState);
},
{ enabled: shouldShowFeature },
);
In a component tree, use JSX to dynamically show/hide states and tools to the Agent.
function ParentComponent() {
// A higher level component can dynamically decide what lower level states/tools are available
const = [shouldShowFeature, setShouldShowFeature] = useAgentState("toggle feature", z.boolean(), true);
useAgentTool("toggle feature", z.object({}), () => setShouldShowFeature(prev) => !prev);
return <AppRoot>{shouldShowFeatureB ? <ChildComponent /> : null}</AppRoot>;
}
function ChildComponent() {
// The state and tool will be available to the Agent only if the child component is rendered
useAgentState("some state", { name: "Some state" });
useAgentTool("update state", z.object({ name: z.string() }), (newState) => {
setSomeState(newState);
});
return <div>...</div>;
}
Build a custom Agent
Access currently active states and tools with useAgentContext
hook. Here is an example of building your own agent
export function useMyAgent() {
const openai = new OpenAI({ dangerouslyAllowBrowser: true, apiKey: "******" });
const agentContext = useAgentContext();
const run = async (prompt: string) => {
const task = openai.chat.completions.runTools({
stream: true,
model: "gpt-4.1",
messages: [
{
role: "system",
content: `
User is interacting with a web app in the following state:
\`\`\`yaml
${agentContext.stringifyStates()}
\`\`\`
Based on user's instruction or goals, either answer user's question based on app state, or use one of the provided tools to update the state.
Short verbal answer/confirmation in the end.
`.trim(),
},
{
role: "user",
content: prompt,
},
],
tools: agentContext.getTools(),
});
return task;
};
return {
run,
};
}
Scale-up with Context
The AgentContext
is an optional React Context to help you hierarchically organizing states and tools.
This prevents naming collisions and reduces agent confusion from too many similar states and tools.
import { AgentContext } from "react-agent-hooks";
function MyApp() {
return (
<AgentContext name="app root">
<AgentContext name="nav">
<Nav />
</AgentContext>
<AgentContext name="content">
<header>
<AgentContext name="header">
<HeaderContent />
</AgentContext>
</header>
<main>
<AgentContext name="main">
<MainContent />
</AgentContext>
</main>
</AgentContext>
</AgentContext>
);
}
function HeaderContent() {
// The Agent will see this state appear within the "app root > nav > header" context
const [someState, setSomeState] = useAgentState("some state", { name: "Some state" });
return <div>...</div>;
}
Future Work
Render to MCP Server
import { renderToMCPServer } from "react-agent-hooks";
function main() {
// Spin up an MCP server at port 3000.
// React Agent state -> MCP resource and prompts
// React Agent tools -> MCP tools
const server = renderToMCPServer(<App />).listen(3000);
}
Render to llms.txt
import { renderToLlmsTxt } from "react-agent-hooks";
function main() {
server.get("/llms.txt", (req, res) => {
const userContext = req.query.userContext;
const llmsTxtContent = renderToLlmsTxt(<App context={userContext} />);
res.send(llmsTxtContent);
});
}
Reference
Blog article: React (hooks) is All You Need
作者情報
76
フォロワー
96
リポジトリ
7
Gist
15
貢献数
トップ貢献者
スレッド