Map Agent
Use a lightweight assistant panel inside the map to trigger fly_to commands with natural language.
Preview
MapAgent lives inside the map tree and talks to /api/map-agent. The current implementation focuses on a single map action: fly_to.
endpoint is required. Pass the API route that accepts map-agent requests, for example /api/map-agent. Provider credentials, model names, and base URLs should stay on the server.Props
| Prop | Type | Default | Description |
|---|---|---|---|
endpoint | string | — | Required API endpoint for assistant requests. This should point to your map-agent route, such as /api/map-agent. |
provider | "openai" | "anthropic" | "openai" | Selects which server-side provider profile should handle the request. |
defaultPrompt | string | "Fly to downtown Shanghai with a city-level zoom." | Initial prompt used for the first automatic assistant request. |
autoRun | boolean | false | Runs the initial prompt automatically after the map instance is ready. |
placeholder | string | "Try: Fly to West Lake in Hangzhou with a scenic city view" | Placeholder text shown in the assistant input field. |
position | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-left" | Controls where the assistant panel is rendered inside the map. |
className | string | — | Adds extra classes to the assistant container for custom layout or styling. |
Environment Template
Keep provider credentials and model configuration on the server. The template below shows the recommended shape for .env.local.
# Map agent server-side provider config
# Restart `npm run dev` after editing this file.
OPENAI_MAP_AGENT_TOKEN=your-openai-compatible-token
OPENAI_MAP_AGENT_BASE_URL=https://api.deepseek.com/v1
OPENAI_MAP_AGENT_MODEL=deepseek-chat
# Optional Anthropic-compatible provider config
# ANTHROPIC_MAP_AGENT_TOKEN=your-anthropic-token
# ANTHROPIC_MAP_AGENT_BASE_URL=https://your-anthropic-endpoint
# ANTHROPIC_MAP_AGENT_MODEL=claude-3-5-sonnet-latestServer Route
The example below shows the recommended API route shape. Keep provider model names, base URLs, and tokens on the server through PROVIDER_CONFIG and environment variables.
import { ChatAnthropic } from "@langchain/anthropic";
import { tool } from "@langchain/core/tools";
import { ChatOpenAI } from "@langchain/openai";
import { NextResponse } from "next/server";
import { z } from "zod";
const requestSchema = z.object({
prompt: z.string().min(1),
provider: z.enum(["openai", "anthropic"]).default("openai"),
});
const flyToCommandSchema = z.object({
type: z.literal("fly_to"),
center: z.tuple([z.number(), z.number()]),
zoom: z.number().optional(),
bearing: z.number().optional(),
pitch: z.number().optional(),
duration: z.number().optional(),
});
const flyToTool = tool(
async (input) => JSON.stringify({ type: "fly_to", ...input }),
{
name: "fly_to",
description: "Fly the map camera to a target location.",
schema: flyToCommandSchema.omit({ type: true }),
},
);
const PROVIDER_CONFIG = {
openai: {
model: process.env.OPENAI_MAP_AGENT_MODEL ?? "deepseek-chat",
baseUrl: process.env.OPENAI_MAP_AGENT_BASE_URL,
token: process.env.OPENAI_MAP_AGENT_TOKEN,
},
anthropic: {
model: process.env.ANTHROPIC_MAP_AGENT_MODEL ?? "claude-3-5-sonnet-latest",
baseUrl: process.env.ANTHROPIC_MAP_AGENT_BASE_URL,
token: process.env.ANTHROPIC_MAP_AGENT_TOKEN,
},
} as const;
export async function POST(request: Request) {
const { prompt, provider } = requestSchema.parse(await request.json());
const config = PROVIDER_CONFIG[provider];
const llm =
provider === "anthropic"
? new ChatAnthropic({
model: config.model,
apiKey: config.token,
anthropicApiUrl: config.baseUrl,
})
: new ChatOpenAI({
model: config.model,
apiKey: config.token,
configuration: { baseURL: config.baseUrl },
});
const agent = llm.bindTools([flyToTool], { tool_choice: "fly_to" });
const response = await agent.invoke(
[
'You are a map camera assistant.',
'Return exactly one "fly_to" action.',
'center must be [longitude, latitude].',
"",
`User request: ${prompt}`,
].join("\n"),
);
const toolCall = response.tool_calls?.find((call) => call.name === "fly_to");
if (!toolCall?.args) {
return NextResponse.json(
{ error: "Model did not return a fly_to tool call." },
{ status: 500 },
);
}
const command = flyToCommandSchema.parse({
type: "fly_to",
...toolCall.args,
});
return NextResponse.json({ command, provider, model: config.model });
}Extending The Agent
If you want the assistant to do more than fly_to, treat the change as a full contract update. The map UI, the client runtime, the server route, and the public docs all need to stay in sync.
- Extend the frontend command contract: update
src/lib/map-agent/types.ts,src/lib/map-agent/schema.ts, andsrc/lib/map-agent/execute.tsso the new command can be validated and executed on the client. - Update the assistant UI only if the public API changes: if a new feature needs extra props or different panel behavior, update
src/registry/map.tsxand keepMapAgentPropsaligned with the actual request shape. - Expand the server route tool set: update
src/app/api/map-agent/route.tsto add the new tool schema, bind the tool, and normalize the returned payload to the same command shape expected by the frontend runtime. - Sync public documentation: update this page,
API Reference, and any installation notes if the public surface or setup instructions changed. - Sync registry output when installable files change: if you add, remove, or rename files under
src/lib/map-agentor expose new public exports, updateregistry.jsonand rebuild the registry output withnpm run registry:build.
draw_polygon, you should expect to touch both sides of the contract at the same time. Do not change only the server tool or only the client executor.