Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 131 additions & 4 deletions ai/ai-samples/src/features/function-calling/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,136 @@
import React from 'react';
import { useState } from 'react';
import { executeFunctionCalling, FunctionCallingResult } from './service';

export default function FunctionCallingView() {
const [prompt, setPrompt] = useState('What was the weather in Boston on October 17, 2024?');
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<FunctionCallingResult | null>(null);
const [error, setError] = useState<string | null>(null);

const handleRun = async () => {
const cleanedPrompt = prompt.trim();

if (!cleanedPrompt) {
setError('Please enter a prompt.');
return;
}

setLoading(true);
setError(null);
setResult(null);

try {
const res = await executeFunctionCalling(cleanedPrompt);
setResult(res);
} catch (err: unknown) {
console.error(err);
const message = err instanceof Error ? err.message : 'An error occurred during function calling.';
setError(message);
} finally {
setLoading(false);
}
};

export default function FunctionCallingFeature() {
return (
<div>
<h2>function-calling</h2>
<div style={{ padding: '20px', maxWidth: '700px', margin: '0 auto', fontFamily: 'sans-serif' }}>
<h2>Function Calling</h2>
<p style={{ color: '#666', marginBottom: '20px' }}>
Demonstrates the manual round-trip: Gemini extracts structured arguments,
your app executes a local function, and sends the result back for final text synthesis.
</p>

{/* Input Prompt */}
<div style={{ marginBottom: '15px' }}>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
rows={3}
style={{
width: '100%',
padding: '10px',
borderRadius: '6px',
border: '1px solid #ccc',
boxSizing: 'border-box',
fontSize: '14px',
}}
placeholder="Ask a question that triggers the tool..."
/>
</div>

<button
onClick={handleRun}
disabled={loading || !prompt.trim()}
style={{
padding: '10px 20px',
backgroundColor: loading ? '#ccc' : '#1a73e8',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: loading ? 'not-allowed' : 'pointer',
fontWeight: 'bold',
}}
>
{loading ? 'Executing Round-Trip...' : 'Run Function Calling'}
</button>

{/* Error Output */}
{error && (
<div style={{ marginTop: '20px', padding: '12px', backgroundColor: '#fde8e8', color: '#c5221f', borderRadius: '6px' }}>
<strong>Error:</strong> {error}
</div>
)}

{/* Visual Round-Trip Breakdown */}
{result && (
<div style={{ marginTop: '25px', display: 'flex', flexDirection: 'column', gap: '15px' }}>

{/* Step 1 & 2: Function Call Request */}
{result.functionCall ? (
<div style={{ padding: '15px', border: '1px solid #e0e0e0', borderRadius: '8px', backgroundColor: '#f8f9fa' }}>
<h4 style={{ margin: '0 0 8px 0', color: '#1a73e8' }}>
Step 1 & 2: Model Triggered Function Call
</h4>
<p style={{ margin: '0 0 8px 0', fontSize: '13px', color: '#555' }}>
Tool Requested: <code>{result.functionCall.name}</code>
</p>
<pre style={{ margin: 0, padding: '10px', backgroundColor: '#282c34', color: '#abb2bf', borderRadius: '4px', overflowX: 'auto', fontSize: '12px' }}>
{JSON.stringify(result.functionCall.args, null, 2)}
</pre>
</div>
) : (
<div style={{ padding: '15px', border: '1px solid #e0e0e0', borderRadius: '8px', backgroundColor: '#fff3cd' }}>
<p style={{ margin: 0, color: '#856404' }}>
</p>
</div>
)}

{/* Step 3: Local Function Output */}
{result.functionResult && (
<div style={{ padding: '15px', border: '1px solid #e0e0e0', borderRadius: '8px', backgroundColor: '#f8f9fa' }}>
<h4 style={{ margin: '0 0 8px 0', color: '#137333' }}>
Step 3: Local Function Execution Result
</h4>
<p style={{ margin: '0 0 8px 0', fontSize: '13px', color: '#555' }}>
Data returned from local <code>fetchWeather</code> API:
</p>
<pre style={{ margin: 0, padding: '10px', backgroundColor: '#282c34', color: '#abb2bf', borderRadius: '4px', overflowX: 'auto', fontSize: '12px' }}>
{JSON.stringify(result.functionResult, null, 2)}
</pre>
</div>
)}

{/* Step 4 & 5: Final Synthesized Text Response */}
<div style={{ padding: '15px', border: '1px solid #ceebe1', borderRadius: '8px', backgroundColor: '#e6f4ea' }}>
<h4 style={{ margin: '0 0 8px 0', color: '#137333' }}>
Step 4 & 5: Final Model Response
</h4>
<p style={{ margin: 0, fontSize: '15px', color: '#202124', lineHeight: '1.5' }}>
{result.finalText}
</p>
</div>

</div>
)}
</div>
);
}
113 changes: 112 additions & 1 deletion ai/ai-samples/src/features/function-calling/service.ts
Original file line number Diff line number Diff line change
@@ -1 +1,112 @@
// Service for function-calling
import { FunctionDeclarationsTool, Schema } from 'firebase/ai';
import { getAiModel } from '../../services/firebaseAIService';

export interface FunctionCallingResult {
functionCall?: {
name: string;
args: Record<string, any>;
};
functionResult?: Record<string, any>;
finalText: string;
}

/**
* Step 1: Write the local function.
* This interacts with your internal/external API or service.
*/

// This function calls a hypothetical external API that returns
// a collection of weather information for a given location on a given date.
// `location` is an object of the form { city: string, state: string }
async function fetchWeather({ location, date }: { location: { city: string; state: string }; date: string }) {
// TODO(developer): Write a standard function that would call to an external weather API.
console.log(`Fetching mock weather for ${location.city}, ${location.state} on ${date}`);
// For demo purposes, this hypothetical response is hardcoded here in the expected format.
return {
temperature: 38,
chancePrecipitation: '56%',
cloudConditions: 'partlyCloudy',
};
}

/**
* Step 2: Create a function declaration.
* This defines the schema and description that Gemini uses to decide when to call the tool.
*/
const fetchWeatherTool: FunctionDeclarationsTool = {
functionDeclarations: [
{
name: 'fetchWeather',
description: 'Get the weather conditions for a specific city on a specific date',
parameters: Schema.object({
properties: {
location: Schema.object({
description: 'The name of the city and its state for which to get the weather. Only cities in the USA are supported.',
properties: {
city: Schema.string({ description: 'The city of the location.' }),
state: Schema.string({ description: 'The US state of the location.' }),
},
}),
date: Schema.string({
description: 'The date for which to get the weather. Date must be in the format: YYYY-MM-DD.',
}),
},
}),
},
],
};

/**
* Steps 3-5: Execute the function calling round-trip.
*/
export async function executeFunctionCalling(prompt: string): Promise<FunctionCallingResult> {
// Step 3: Provide the function declaration during model initialization.
const model = getAiModel('gemini-3.6-flash', {
tools: [fetchWeatherTool],
});

const chat = model.startChat();

// Step 4: Call the function to invoke the external API.
// Send the user's question (the prompt) to the model using multi-turn chat.
let result = await chat.sendMessage(prompt);
const functionCalls = result.response.functionCalls() ?? [];

let functionCall: { name: string; args: Record<string, any> } | undefined;
let functionResult: Record<string, any> | undefined;
// When the model responds with one or more function calls, invoke the function(s).
if (functionCalls.length > 0) {
for (const call of functionCalls) {
if (call.name === 'fetchWeather') {
// Forward the structured input data prepared by the model
// to the hypothetical external API.
functionCall = { name: call.name, args: call.args as Record<string, any> };
functionResult = await fetchWeather(call.args as { location: { city: string; state: string }; date: string });
}
}
}

/**
* Step 5: Send the response from the function back to the model
* so that the model can use it to generate its final response.
*/

if (functionCall && functionResult) {
result = await chat.sendMessage([
{
functionResponse: {
name: functionCall.name, // "fetchWeather"
response: functionResult,
},
},
]);
}

const finalText = result.response.text();

return {
functionCall,
functionResult,
finalText,
};
}
Loading