Skip to content

Commit 97fbb44

Browse files
ivictborCopilot
andcommitted
docs: enhance agent plugin tutorial with detailed usage instructions and custom skill definitions
Co-authored-by: Copilot <copilot@github.com>
1 parent 271f3e4 commit 97fbb44

1 file changed

Lines changed: 179 additions & 18 deletions

File tree

  • adminforth/documentation/docs/tutorial/08-Plugins

adminforth/documentation/docs/tutorial/08-Plugins/01-agent.md

Lines changed: 179 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -279,22 +279,197 @@ plugins: [
279279
]
280280
```
281281

282-
When using OpenAI-compatible providers for agent modes, you can force Chat Completions API mode with `useComplitionApi: true`:
282+
Each item in `modes` defines a user-selectable preset in the chat UI. The selected mode is sent to the backend and the plugin uses that mode's `completionAdapter` for the response.
283+
284+
The plugin adds a chat surface to the admin UI, keeps session history per admin user, and shows a mode picker when `modes` are configured.
285+
286+
# Using with self-hosted models
287+
288+
`CompletionAdapterOpenAIResponses` when works with agent plugin, under the hood uses the LangChain internal proxy called `OpenAIChat` (in LangChain they call it "provider"). This proxy is capable with a fresh versions of OpenAI-compatible Responses APIs, for example [self-hosted latest versions of vLLM installations](https://devforth.io/insights/self-hosted-gpt-real-response-time-token-throughput-and-cost-on-l4-l40s-and-h100-for-gpt-oss-20b/)
289+
To use them, just point the adapter to your local vLLM server:
290+
291+
292+
```ts
293+
completionAdapter: new CompletionAdapterOpenAIResponses({
294+
openAiApiKey: process.env.MY_API_KEY as string, // if you use authorization
295+
baseUrl: 'http://my_local_vllm_server:8000/v1',
296+
model: 'gpt-oss-120b',
297+
})
298+
```
299+
300+
However some of 3rd party providers might serve outdated vLLM and still don't fully support the Responses API needed for langchain internal implmentation, for example [OVH AI Endpoints](https://www.ovhcloud.com/en/public-cloud/ai-endpoints/) in Responses mode still don't play well with langchain proxy (25 Apr 2026)
301+
302+
In that case you can try to use the OpenAI Complition API mode of the plugin, which is less efficient but more compatible with older APIs, you can force Chat Completions API mode with `useComplitionApi: true`:
283303

284304
```ts
285305
completionAdapter: new CompletionAdapterOpenAIResponses({
286306
openAiApiKey: process.env.OVH_AI_ENDPOINTS_ACCESS_TOKEN as string,
287307
baseUrl: 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1',
288-
model: 'gpt-oss-20b',
308+
model: 'gpt-oss-120b',
289309
useComplitionApi: true,
290310
})
291311
```
292312

293313
OVH AI Endpoints still does not fully support the OpenAI `responses` API, so `useComplitionApi: false` may work unstably there.
294314

295-
Each item in `modes` defines a user-selectable preset in the chat UI. The selected mode is sent to the backend and the plugin uses that mode's `completionAdapter` for the response.
296315

297-
The plugin adds a chat surface to the admin UI, keeps session history per admin user, and shows a mode picker when `modes` are configured.
316+
317+
318+
## Writing own skills
319+
320+
You can write own skills by following [SKILL.md Agennt Skills](https://agentskills.io/)
321+
322+
For example we will write a skill which can summarizes backoffice data stats like record counts by resources and send it to user email.
323+
324+
It may handle prompts like
325+
326+
```
327+
Please send record counts for all resources to my email
328+
```
329+
330+
Or:
331+
332+
```
333+
Please send record counts to all admin users
334+
```
335+
336+
### Writing custom tools
337+
338+
To define a custom tool you should simply define an express API route in your app using `admin.express.withSchema` wrapper which makes the route available for the agent (clear and predictable schema is a crucial part of making the tool work well).
339+
340+
If you are using `admin.express.authorize` wrapper for authorization, adminuser will be injected atomatically from user which sits on the surface and controls the agent. In other words all permissions and access rights of the agent are defined by the admin user which is controlling this agent. At the same time all actions done by agent are automatically attributed in the audit log to the admin user which is controlling the agent.
341+
342+
This example uses the same email adapter pattern shown in the Email Invite and Email Password Reset plugins. The transport below uses Mailgun only to keep the snippet short; you can replace it with SES or any other adapter from [List of adapters](/docs/tutorial/ListOfAdapters/).
343+
344+
```ts title="./api.ts"
345+
import type { Express, Response } from 'express';
346+
import { Filters, type IAdminForth, type IAdminUserExpressRequest } from 'adminforth';
347+
import * as z from 'zod';
348+
import EmailAdapterMailgun from '@adminforth/email-adapter-mailgun';
349+
350+
const agentEmailAdapter = new EmailAdapterMailgun({
351+
apiKey: process.env.MAILGUN_API_KEY as string,
352+
domain: process.env.MAILGUN_DOMAIN as string,
353+
baseUrl: process.env.MAILGUN_REGION_URL || 'api.mailgun.net',
354+
});
355+
const agentSendFrom = process.env.AGENT_SEND_FROM_EMAIL as string;
356+
357+
function escapeHtml(value: string) {
358+
return value
359+
.replace(/&/g, '&amp;')
360+
.replace(/</g, '&lt;')
361+
.replace(/>/g, '&gt;');
362+
}
363+
364+
function renderEmailHtml(body: string) {
365+
return `<html><body><pre style="font-family: sans-serif; white-space: pre-wrap;">${escapeHtml(body)}</pre></body></html>`;
366+
}
367+
368+
export function initApi(app: Express, admin: IAdminForth) {
369+
app.post('/send_email_to_user',
370+
admin.express.withSchema(
371+
{
372+
description: 'Send an email to one AdminForth user by id. Use this after the user row is resolved.',
373+
request: z.object({
374+
userId: z.string(),
375+
subject: z.string().min(1),
376+
body: z.string().min(1),
377+
}),
378+
response: z.object({
379+
ok: z.boolean(),
380+
email: z.string().email().optional(),
381+
error: z.string().optional(),
382+
}),
383+
},
384+
admin.express.authorize(
385+
async (req: IAdminUserExpressRequest, res: Response) => {
386+
const { userId, subject, body } = req.body as {
387+
userId: string;
388+
subject: string;
389+
body: string;
390+
};
391+
392+
await agentEmailAdapter.validate();
393+
394+
const user = await admin.resource('adminuser').get(
395+
Filters.EQ('id', userId),
396+
);
397+
398+
if (!user || typeof user.email !== 'string' || !user.email) {
399+
res.status(404).json({ ok: false, error: 'User not found' });
400+
return;
401+
}
402+
403+
const result = await agentEmailAdapter.sendEmail(
404+
agentSendFrom,
405+
user.email,
406+
body,
407+
renderEmailHtml(body),
408+
subject,
409+
);
410+
411+
if (!result.ok) {
412+
res.status(500).json({ ok: false, error: result.error ?? 'Failed to send email' });
413+
return;
414+
}
415+
416+
res.json({ ok: true, email: user.email });
417+
}
418+
)
419+
)
420+
);
421+
}
422+
```
423+
424+
## Define skills instructions
425+
426+
Custom skills live in your app's custom directory. The agent loads project skills from:
427+
428+
- `custom/skills/<skill_name>/SKILL.md`
429+
430+
Also it can load skills from plugins, it allows other plugins to expose their own skills and tools:
431+
432+
- `custom/plugins/adminforth-agent/skills/<skill_name>/SKILL.md`
433+
434+
Each skill needs YAML frontmatter with `name` and `description`. The `description` is the discovery surface, so include the phrases the admin is likely to type in chat. Skills are not loaded automcatically, agent loads them
435+
on demand once understands that `description` of the skill matches the user intent, so keep the description clear and concise.
436+
437+
Tools are also not loaded automatically, the agent loads them only if they are mentioned in the skill instructions. Then agent calls `fetch_tool_schema` meta tool to load actual schema of your custom tool.
438+
439+
Tool names are derived from route paths. If you want the tool name to be exactly `send_email_to_user`, register the route as `/send_email_to_user`.
440+
441+
The example below creates a minimal user skill which:
442+
443+
- resolves a user row
444+
- reads the `total` count for each resource with `get_resource_data`
445+
- sends the final report by email with `send_email_to_user`
446+
447+
Create the skill in your app custom folder:
448+
449+
```md title="./custom/skills/email_resource_counts/SKILL.md"
450+
---
451+
name: email_resource_counts
452+
description: Email record counts for each AdminForth resource to a user. Use when the user asks to send resource counts or all-record statistics by email.
453+
---
454+
455+
# Involved tools
456+
457+
Use `send_email_to_user` to send the final report after you have one exact target user row.
458+
459+
# Instructions
460+
461+
- For each resource in system use fetch data default skill to collect total count of records in each resource.
462+
- Create html report in format `Resource Label (resourceId): count` for each resource on a new line, sort resources by count in descending order.
463+
- Use modern, stylish but compatible html formatting in the email.
464+
- Call `send_email_to_user` with the resolved user primary key, the final subject, and the final plain text body.
465+
- After the tool succeeds, tell the user the email was sent and include a short summary in chat.
466+
```
467+
468+
This way allows to extend your agent with literally any custom instructions and tools and make it do complex tasks related to your backoffice data and operations.
469+
470+
471+
## Standard skills
472+
298473

299474
## Persistent checkpointer
300475

@@ -559,17 +734,3 @@ services:
559734
![alt text](image-6.png)
560735

561736

562-
## Custom skills and tools
563-
564-
565-
Place you skills in `custom/skills/<skill_name>/SKILL.md` file. The plugin will pick them up automatically and make available in agent's toolbox.
566-
567-
568-
To define custom tools, create api endpoints, prefer `admin.express.withSchema(...)` from [Custom Pages / API docs](/docs/tutorial/Customization/customPages/). That exposes machine-readable request and response schemas the agent can use.
569-
570-
In skills markdown file, merge which tool exactlu agent should load.
571-
572-
573-
Skill example:
574-
575-
// TODO

0 commit comments

Comments
 (0)