{"slug": "tool-vs-talent-in-solon-ai-when-a-function-is-not-enough", "title": "Tool vs Talent in Solon AI: When a Function Is Not Enough", "summary": "Solon AI v4.0.3 introduces Talent, a product unit that packages tools with standard operating procedures and activation rules, addressing the limitations of bare function tools in agent workflows. Talent enables domain-aware tool selection, context injection, and token reduction by filtering inactive domains, as demonstrated in the framework's official comparison.", "body_md": "Most agent tutorials stop at tools: give the model a function schema, hope it calls the right one. That works for `get_time`\n\nand `hash_string`\n\n. It falls apart when the model skips a knowledge search and opens a ticket, or when eighty APIs all land in one context window.\n\nSolon AI keeps tools as the execution unit, then adds **Talent** as the product unit: tools plus SOP plus activation rules. Think of it this way:\n\nThis post is a practical map of when to stay on tools, when to wrap them in a talent, and how registration actually works in Solon v4.0.3.\n\nBare tools only answer two questions for the model:\n\nThey do **not** answer:\n\nThat gap shows up as:\n\nTalent is Solon’s answer: a reusable package of **awareness + instruction + tools**, with automatic **coloring** so tools keep their domain identity.\n\nFrom the official comparison:\n\n| Dimension | Tool (`FunctionTool` ) |\nTalent (`Talent` ) |\n|---|---|---|\n| Unit | Single function / method | Instruction + tool set + state |\n| Abstraction | Physical: how\n|\nLogical: when and under which SOP\n|\n| Context awareness | Passive |\n`isSupported(Prompt)` can activate or hide |\n| Injected content | Tool schema (JSON) | System prompt fragment + tool list |\n| Constraint strength | Weak — model freestyles from description | Strong — SOP via `getInstruction`\n|\n| Registration |\n`defaultToolAdd` / `toolAdd`\n|\n`defaultTalentAdd` / `talentAdd`\n|\n\nThey are not rivals. A talent **contains** tools. Registering a talent also registers its tools; you do not need a second `defaultToolAdd`\n\nfor the same set.\n\nWhen a request starts, Solon walks registered talents:\n\n`isSupported(prompt)`\n\nfilters inactive ones`onAttach(prompt)`\n\nfor warm-up / audit / context prep`getInstruction`\n\nis merged into the system message; tools get talent metadata (coloring)That is why talents can cut tokens: inactive domains never enter the tool table.\n\nUse a plain tool when the capability is:\n\n```\npublic class ClockTools extends AbsToolProvider {\n    @ToolMapping(description = \"Return the current server time in ISO-8601\")\n    public String now() {\n        return Instant.now().toString();\n    }\n}\n\nChatModel chatModel = ChatModel.of(apiUrl)\n        .apiKey(apiKey)\n        .defaultModel(model)\n        .defaultToolAdd(new ClockTools())\n        .build();\n```\n\nAlso fine for request-scoped options when the branching is tiny:\n\n``` php\nchatModel.prompt(\"Weather in Hangzhou?\")\n        .options(o -> {\n            o.systemPrompt(\"You are a weather assistant.\");\n            if (\"admin\".equals(role)) {\n                o.toolAdd(new UserTool());\n                o.toolAdd(new AdminTool());\n            } else {\n                o.toolAdd(new UserTool());\n            }\n        })\n        .call();\n```\n\nGood for spikes. Painful when the same role rules repeat across controllers.\n\nWrap tools in a talent when you need any of:\n\n`TalentDesc`\n\n```\nTalentDesc orderTalent = new TalentDesc(\"order_expert\")\n        .description(\"Order assistant\")\n        .isSupported(prompt -> prompt.getUserContent().contains(\"order\"))\n        .instruction(prompt -> {\n            if (\"VIP\".equals(prompt.attr(\"user_level\"))) {\n                return \"VIP customer: prefer fast_track_tool when eligible.\";\n            }\n            return \"Follow the standard order lookup flow.\";\n        })\n        .toolAdd(new OrderTools());\n```\n\nFast for local, lambda-friendly definitions.\n\n`AbsTalent`\n\n+ `@ToolMapping`\n\n```\npublic class TechSupportTalent extends AbsTalent {\n    @Override\n    public String name() {\n        return \"tech_support\";\n    }\n\n    @Override\n    public String description() {\n        return \"Technical support: diagnose before opening tickets\";\n    }\n\n    @Override\n    public boolean isSupported(Prompt prompt) {\n        String content = prompt.getUserContent();\n        return content != null && (\n                content.contains(\"error\")\n                        || content.contains(\"故障\")\n                        || content.contains(\"报错\"));\n    }\n\n    @Override\n    public String getInstruction(Prompt prompt) {\n        return \"\"\"\n                You are a tech support specialist. Follow this SOP:\n                1. Search the knowledge base first (search_kb).\n                2. Confirm the runtime version before any fix.\n                3. Only open a ticket after diagnosis fails.\n                \"\"\";\n    }\n\n    @ToolMapping(name = \"search_kb\", description = \"Search the tech knowledge base\")\n    public String searchKb(@Param(\"query\") String query) {\n        return kbService.search(query);\n    }\n\n    @ToolMapping(name = \"open_ticket\", description = \"Open a support ticket after diagnosis\")\n    public String openTicket(@Param(\"summary\") String summary) {\n        return ticketService.create(summary);\n    }\n}\n```\n\n`AbsTalent`\n\nscans `@ToolMapping`\n\nmethods via `MethodToolProvider`\n\n, same family of tool registration you already use for agents.\n\n```\npublic class AuthControlTalent extends AbsTalent {\n    private final UserTool userTool = new UserTool();\n    private final AdminTool adminTool = new AdminTool();\n\n    @Override\n    public String getInstruction(Prompt prompt) {\n        return \"You are a weather assistant. Respect the caller's role.\";\n    }\n\n    @Override\n    public boolean isSupported(Prompt prompt) {\n        return prompt.getUserContent() != null\n                && prompt.getUserContent().contains(\"weather\");\n    }\n\n    @Override\n    public Collection<FunctionTool> getTools(Prompt prompt) {\n        String role = prompt.attrAs(\"role\");\n        if (\"admin\".equals(role)) {\n            return Arrays.asList(userTool, adminTool);\n        }\n        return Collections.singletonList(userTool);\n    }\n}\n```\n\nCall site stays thin:\n\n```\nChatModel chatModel = ChatModel.of(apiUrl)\n        .apiKey(apiKey)\n        .defaultModel(model)\n        .defaultTalentAdd(new AuthControlTalent())\n        .build();\n\nchatModel.prompt(Prompt.of(\"Weather in Hangzhou today?\")\n                .attrPut(\"role\", role))\n        .call();\n```\n\nOr per request:\n\n``` php\nchatModel.prompt(Prompt.of(\"...\").attrPut(\"role\", role))\n        .options(o -> o.talentAdd(new AuthControlTalent()))\n        .call();\n```\n\n| Scope | API |\n|---|---|\n| Every request on a model | `ChatModel.of(...).defaultTalentAdd(talent)` |\n| One request | `prompt(...).options(o -> o.talentAdd(talent))` |\n| Ordered injection |\n`defaultTalentAdd(index, talent)` / `talentAdd(index, talent)`\n|\n\nMultiple talents inject instructions in registration order. Their tools are colored with talent metadata so the model can align SOP text with the right tool group.\n\nSame pattern works on **ChatModel**, **SimpleAgent**, **ReActAgent**, and **TeamAgent**.\n\n| Signal | Prefer |\n|---|---|\n| Single pure function, no policy | Tool |\n| Same branching copy-pasted at call sites | Talent |\n| Must force order: search → confirm → mutate |\nTalent (`getInstruction` ) |\n| Hide whole domains by intent / tenant |\nTalent (`isSupported` + dynamic `getTools` ) |\n| Huge OpenAPI / MCP surface |\nGateway Talent (staged discovery) — still a talent, not a flat tool dump |\n| Prototype only | Tool first; promote when the model mis-orders or over-calls |\n\nOfficial guidance in one line: **start with tools; wrap in a talent when the model needs a playbook or a gate.**\n\nIf your agent keeps “knowing the tools” but still fails the business path, you usually do not need more tools. You need a talent that owns the path.", "url": "https://wpnews.pro/news/tool-vs-talent-in-solon-ai-when-a-function-is-not-enough", "canonical_source": "https://dev.to/solonjava/tool-vs-talent-in-solon-ai-when-a-function-is-not-enough-47bh", "published_at": "2026-07-22 00:02:20+00:00", "updated_at": "2026-07-22 00:29:09.777324+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "artificial-intelligence"], "entities": ["Solon AI", "FunctionTool", "Talent", "AbsToolProvider", "ChatModel", "TalentDesc", "AbsTalent", "TechSupportTalent"], "alternates": {"html": "https://wpnews.pro/news/tool-vs-talent-in-solon-ai-when-a-function-is-not-enough", "markdown": "https://wpnews.pro/news/tool-vs-talent-in-solon-ai-when-a-function-is-not-enough.md", "text": "https://wpnews.pro/news/tool-vs-talent-in-solon-ai-when-a-function-is-not-enough.txt", "jsonld": "https://wpnews.pro/news/tool-vs-talent-in-solon-ai-when-a-function-is-not-enough.jsonld"}}