feat: add examples to toolconfigs and example-usage

pull/717/head
Travis Fischer 2025-07-01 04:12:21 -05:00
rodzic 59bda8c984
commit c7aa791a9f
8 zmienionych plików z 249 dodań i 33 usunięć

Wyświetl plik

@ -456,6 +456,46 @@
"minLength": 1
},
"description": "Allows you to override this tool's behavior or disable it entirely for different pricing plans. This is a map of PricingPlan slug to PricingPlanToolOverrides for that plan."
},
"examples": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The display name of the example."
},
"prompt": {
"type": "string",
"description": "The input prompt for agents to use when running this example."
},
"systemPrompt": {
"type": "string",
"description": "An optional system prompt for agents to use when running this example. Defaults to `You are a helpful assistant. Be as concise as possible.`"
},
"args": {
"type": "object",
"additionalProperties": {},
"description": "The arguments to pass to the tool for this example."
},
"featured": {
"type": "boolean",
"description": "Whether this example should be featured in the docs for the project."
},
"description": {
"type": "string",
"description": "A description of the example."
}
},
"required": [
"name",
"prompt",
"args"
],
"additionalProperties": false
},
"description": "Examples of how to use this tool. Used to generate example usage in the tool's docs."
}
},
"required": [

Wyświetl plik

@ -5,9 +5,10 @@ import { assert, omit, sanitizeSearchParams } from '@agentic/platform-core'
import { ExternalLinkIcon } from 'lucide-react'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useAgentic } from '@/components/agentic-provider'
import { ExampleUsage } from '@/components/example-usage'
import { LoadingIndicator } from '@/components/loading-indicator'
import { PageContainer } from '@/components/page-container'
import { ProjectPricingPlans } from '@/components/project-pricing-plans'
@ -144,6 +145,24 @@ export function MarketplaceProjectIndex({
hasInitializedCheckoutFromSearchParams
])
const featuredToolName = useMemo(() => {
const deployment = project?.lastPublishedDeployment
const toolConfigs = deployment?.toolConfigs?.filter(
(toolConfig) => toolConfig?.enabled !== false
)
return (
toolConfigs?.find((toolConfig) =>
toolConfig.examples?.find((example) => example.featured)
)?.name ??
toolConfigs?.find((toolConfig) => toolConfig.examples?.length)?.name ??
toolConfigs?.[0]?.name ??
deployment?.tools[0]?.name
)
}, [project])
const deployment = project?.lastPublishedDeployment
return (
<PageContainer>
<section>
@ -181,6 +200,31 @@ export function MarketplaceProjectIndex({
<h2 className='text-balance leading-snug md:leading-none text-xl font-semibold'>
Overview
</h2>
<div
className={`grid grid-cols grid-cols-1 gap-8 md:grid-cols-2`}
>
<div className='flex flex-col gap-4'>
{deployment ? (
<p>
{deployment?.description ||
'No description available'}
</p>
) : (
<p>
This project doesn't have any published deployments.
</p>
)}
</div>
<div className='flex flex-col gap-4'>
<ExampleUsage
projectIdentifier={projectIdentifier}
project={project}
tool={featuredToolName}
/>
</div>
</div>
</TabsContent>
<TabsContent value='tools' className='flex flex-col gap-4'>

Wyświetl plik

@ -18,7 +18,7 @@ import { globalAgenticApiClient } from '@/lib/global-api'
export default async function TheBestDamnLandingPageEver() {
const projectIdentifier = '@agentic/search'
const prompt = 'What is the latest news about AI?'
const tool = 'search'
const initialProject =
await globalAgenticApiClient.getPublicProjectByIdentifier({
@ -32,7 +32,7 @@ export default async function TheBestDamnLandingPageEver() {
project: initialProject,
deployment: initialProject.lastPublishedDeployment!,
identifier: projectIdentifier,
prompt
tool
})
const initialCodeBlock = await highlight(initialCodeSnippet)
@ -61,8 +61,8 @@ export default async function TheBestDamnLandingPageEver() {
<ExampleUsage
projectIdentifier={projectIdentifier}
prompt={prompt}
project={initialProject}
tool={tool}
initialCodeBlock={initialCodeBlock}
/>

Wyświetl plik

@ -33,13 +33,13 @@ import { LoadingIndicator } from './loading-indicator'
export function ExampleUsage({
projectIdentifier,
prompt,
project: initialProject,
tool,
initialCodeBlock
}: {
projectIdentifier: string
prompt: string
project?: Project
tool?: string
initialCodeBlock?: JSX.Element
}) {
const ctx = useAgentic()
@ -94,7 +94,7 @@ export function ExampleUsage({
<div className='w-full max-w-3xl flex flex-col items-center border rounded-lg shadow-sm p-2 md:p-4 bg-background'>
<ExampleUsageContent
projectIdentifier={projectIdentifier}
prompt={prompt}
tool={tool}
initialCodeBlock={initialCodeBlock}
isLoading={isLoading}
isError={isError}
@ -108,7 +108,7 @@ export function ExampleUsage({
function ExampleUsageContent({
projectIdentifier,
prompt,
tool,
initialCodeBlock,
isLoading,
isError,
@ -117,7 +117,7 @@ function ExampleUsageContent({
setConfig
}: {
projectIdentifier: string
prompt: string
tool?: string
initialCodeBlock?: JSX.Element
isLoading: boolean
isError: boolean
@ -146,7 +146,7 @@ function ExampleUsageContent({
project,
deployment,
identifier: projectIdentifier,
prompt
tool
})
return (
@ -163,7 +163,12 @@ function ExampleUsageContent({
>
<TabsList>
{targets.map((target) => (
<TabsTrigger key={target} value={target} className='cursor-pointer'>
<TabsTrigger
key={target}
value={target}
className='cursor-pointer'
disabled={target === 'http' && !tool}
>
{targetLabels[target]}
</TabsTrigger>
))}

Wyświetl plik

@ -1,5 +1,6 @@
import type { Deployment, Project } from '@agentic/platform-types'
import type { BundledLanguage } from 'shiki/bundle/web'
import type { Simplify } from 'type-fest'
import { assert } from '@agentic/platform-core'
import { gatewayBaseUrl } from './config'
@ -95,33 +96,68 @@ export type GetCodeForDeveloperConfigOpts = {
project: Project
deployment: Deployment
identifier: string
prompt: string
tool?: string
}
type GetCodeForDeveloperConfigInnerOpts = Simplify<
GetCodeForDeveloperConfigOpts & {
systemPrompt: string
prompt: string
args: Record<string, any>
}
>
export function getCodeForDeveloperConfig(
opts: GetCodeForDeveloperConfigOpts
): CodeSnippet {
const { config } = opts
const { config, tool } = opts
const toolConfig = tool
? opts.deployment.toolConfigs.find((toolConfig) => toolConfig.name === tool)
: undefined
const toolConfigExample =
toolConfig?.examples?.find((example) => example.featured) ??
toolConfig?.examples?.[0]
const innerOpts: GetCodeForDeveloperConfigInnerOpts = {
...opts,
// TODO: incorporate the system message into all of the example TS and
// Python code snippets
systemPrompt:
toolConfigExample?.systemPrompt ??
'You are a helpful assistant. Be as concise as possible.',
// TODO: generate this default on the backend based on the tool's name,
// description, inputSchema, and outputSchema
prompt: toolConfigExample?.prompt ?? 'What is the latest news about AI?',
// TODO: generate this default on the backend based on the tool's input
// schema
// TODO: if no `args` are provided, hide the `HTTP` tab?
args: toolConfigExample?.args ?? {
query: 'example search query'
}
}
switch (config.target) {
case 'mcp':
return getCodeForMCPClientConfig(opts)
return getCodeForMCPClientConfig(innerOpts)
case 'typescript':
return getCodeForTSFrameworkConfig(opts)
return getCodeForTSFrameworkConfig(innerOpts)
case 'python':
return getCodeForPythonFrameworkConfig(opts)
return getCodeForPythonFrameworkConfig(innerOpts)
case 'http':
return getCodeForHTTPConfig(opts)
return getCodeForHTTPConfig(innerOpts)
}
}
export function getCodeForMCPClientConfig({
identifier
}: GetCodeForDeveloperConfigOpts): CodeSnippet {
}: GetCodeForDeveloperConfigInnerOpts): CodeSnippet {
const mcpUrl = `${gatewayBaseUrl}/${identifier}/mcp`
return {
code: mcpUrl,
@ -132,8 +168,9 @@ export function getCodeForMCPClientConfig({
export function getCodeForTSFrameworkConfig({
config,
identifier,
prompt
}: GetCodeForDeveloperConfigOpts): CodeSnippet {
prompt,
systemPrompt
}: GetCodeForDeveloperConfigInnerOpts): CodeSnippet {
switch (config.tsFrameworkTarget) {
case 'ai':
return {
@ -288,7 +325,7 @@ const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
const exampleAgent = new Agent({
name: 'Example Agent',
model: openai('gpt-4o-mini') as any,
instructions: 'You are a helpful assistant. Be as concise as possible.',
instructions: '${systemPrompt}',
tools: createMastraTools(searchTool)
})
@ -357,7 +394,7 @@ export function getCodeForPythonFrameworkConfig({
config,
identifier,
prompt
}: GetCodeForDeveloperConfigOpts): CodeSnippet {
}: GetCodeForDeveloperConfigInnerOpts): CodeSnippet {
const mcpUrl = `${gatewayBaseUrl}/${identifier}/mcp`
switch (config.pyFrameworkTarget) {
@ -441,8 +478,9 @@ export function getCodeForHTTPConfig({
config,
identifier,
deployment,
tool
}: GetCodeForDeveloperConfigOpts): CodeSnippet {
tool,
args
}: GetCodeForDeveloperConfigInnerOpts): CodeSnippet {
tool ??= deployment.tools[0]?.name
assert(tool, 'tool is required')
// TODO: need a way of getting example tool args
@ -450,16 +488,25 @@ export function getCodeForHTTPConfig({
const url = `${gatewayBaseUrl}/${identifier}/${tool}`
switch (config.httpTarget) {
case 'curl':
return {
code: `curl -X POST -H "Content-Type: application/json" -d '{"query": "example google search"}' ${url}`,
lang: 'bash'
}
case 'curl': {
const formattedArgs = JSON.stringify(args).replace("'", "\\'")
case 'httpie':
// TODO: better formatting for the curl command
return {
code: `http ${url} query='example google search'`,
code: `curl -X POST -H "Content-Type: application/json" -d '${formattedArgs}' ${url}`,
lang: 'bash'
}
}
case 'httpie': {
const formattedArgs = Object.entries(args)
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
.join(' ')
return {
code: `http ${url} ${formattedArgs}`,
lang: 'bash'
}
}
}
}

Wyświetl plik

@ -685,6 +685,23 @@ export interface components {
pricingPlanOverridesMap?: {
[key: string]: components["schemas"]["PricingPlanToolOverride"];
};
/** @description Examples of how to use this tool. Used to generate example usage in the tool's docs. */
examples?: {
/** @description The display name of the example. */
name: string;
/** @description The input prompt for agents to use when running this example. */
prompt: string;
/** @description An optional system prompt for agents to use when running this example. Defaults to `You are a helpful assistant. Be as concise as possible.` */
systemPrompt?: string;
/** @description The arguments to pass to the tool for this example. */
args: {
[key: string]: unknown;
};
/** @description Whether this example should be featured in the docs for the project. */
featured?: boolean;
/** @description A description of the example. */
description?: string;
}[];
};
/**
* @description Display name for the pricing plan (eg, "Free", "Starter Monthly", "Pro Annual", etc)

Wyświetl plik

@ -85,6 +85,62 @@ export type PricingPlanToolOverride = z.infer<
typeof pricingPlanToolOverrideSchema
>
/**
* Example tool usage.
*/
export const toolConfigExampleSchema = z.object({
/**
* The display name of the example.
*/
name: z.string().describe('The display name of the example.'),
/**
* The input prompt for agents to use when running this example.
*/
prompt: z
.string()
.describe('The input prompt for agents to use when running this example.'),
/**
* An optional system prompt for agents to use when running this example.
*
* Defaults to `You are a helpful assistant. Be as concise as possible.`
*/
systemPrompt: z
.string()
.optional()
.describe(
'An optional system prompt for agents to use when running this example. Defaults to `You are a helpful assistant. Be as concise as possible.`'
),
/**
* The arguments to pass to the tool for this example.
*/
// TODO: validate example args against the tool's input schema during
// config validation
args: z
.record(z.string(), z.any())
.describe('The arguments to pass to the tool for this example.'),
/**
* Whether this example should be featured in the docs for the project.
*
* The first tool with a `featured` example will be the featured tool for the
* project.
*/
featured: z
.boolean()
.optional()
.describe(
'Whether this example should be featured in the docs for the project.'
),
/**
* A description of the example.
*/
description: z.string().optional().describe('A description of the example.')
})
/**
* Customizes a tool's default behavior across all pricing plans.
*/
@ -207,11 +263,17 @@ export const toolConfigSchema = z
.optional()
.describe(
"Allows you to override this tool's behavior or disable it entirely for different pricing plans. This is a map of PricingPlan slug to PricingPlanToolOverrides for that plan."
)
),
// TODO?
// examples
// headers
examples: z
.array(toolConfigExampleSchema)
.optional()
.describe(
"Examples of how to use this tool. Used to generate example usage in the tool's docs."
)
})
.openapi('ToolConfig')

Wyświetl plik

@ -121,3 +121,4 @@
- also add `@agentic/json-schema` to `createJsonSchema` parsing instead of current no-op
- add support for [`@google/genai`](https://github.com/googleapis/js-genai) tools adapter
- currently difficult due to their use of non-standard json schemas
- validate example args against the tool's input schema during config validation