Custom ThinkTools
Build your own reasoning modules.
Overview
ReasonKit’s architecture allows you to create custom ThinkTools that integrate seamlessly with the framework.
ThinkTool Anatomy
Every ThinkTool has:
- Input - A question, claim, or statement to analyze
- Process - Structured reasoning steps
- Output - Formatted analysis results
#![allow(unused)]
fn main() {
pub trait ThinkTool {
type Output;
fn name(&self) -> &str;
fn description(&self) -> &str;
async fn analyze(&self, input: &str) -> Result<Self::Output>;
}
}
Creating a Custom Tool
1. Define the Output Structure
#![allow(unused)]
fn main() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakeholderAnalysis {
pub stakeholders: Vec<Stakeholder>,
pub conflicts: Vec<Conflict>,
pub recommendations: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Stakeholder {
pub name: String,
pub interests: Vec<String>,
pub power_level: PowerLevel,
pub stance: Stance,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PowerLevel {
High,
Medium,
Low,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Stance {
Supportive,
Neutral,
Opposed,
}
}
2. Implement the Tool
#![allow(unused)]
fn main() {
use reasonkit::prelude::*;
pub struct StakeholderMap {
min_stakeholders: usize,
include_conflicts: bool,
}
impl StakeholderMap {
pub fn new() -> Self {
Self {
min_stakeholders: 5,
include_conflicts: true,
}
}
pub fn min_stakeholders(mut self, n: usize) -> Self {
self.min_stakeholders = n;
self
}
}
impl ThinkTool for StakeholderMap {
type Output = StakeholderAnalysis;
fn name(&self) -> &str {
"StakeholderMap"
}
fn description(&self) -> &str {
"Identifies and analyzes stakeholders affected by a decision"
}
async fn analyze(&self, input: &str) -> Result<Self::Output> {
let prompt = format!(
r#"Analyze the stakeholders for this decision: "{}"
Identify at least {} stakeholders. For each:
1. Name/category
2. Their interests
3. Power level (High/Medium/Low)
4. Likely stance (Supportive/Neutral/Opposed)
Also identify conflicts between stakeholders.
Format as JSON."#,
input, self.min_stakeholders
);
let response = self.llm().complete(&prompt).await?;
let analysis: StakeholderAnalysis = serde_json::from_str(&response)?;
Ok(analysis)
}
}
}
3. Create the Prompt Template
#![allow(unused)]
fn main() {
impl StakeholderMap {
fn build_prompt(&self, input: &str) -> String {
format!(r#"
STAKEHOLDER ANALYSIS
# Input Decision
{input}
# Your Task
Identify all parties affected by this decision.
# Required Analysis
## 1. Stakeholder Identification
List at least {min} stakeholders, considering:
- Direct participants
- Indirect affected parties
- Decision makers
- Influencers
- Silent stakeholders (often forgotten)
## 2. For Each Stakeholder
- **Name/Category**: Who they are
- **Interests**: What they want/need
- **Power Level**: High (can block/enable), Medium (can influence), Low (affected but limited voice)
- **Likely Stance**: Supportive, Neutral, or Opposed
## 3. Conflict Analysis
Identify where stakeholder interests conflict.
## 4. Recommendations
How to navigate the stakeholder landscape.
# Output Format
Respond in JSON matching this structure:
```json
{{
"stakeholders": [...],
"conflicts": [...],
"recommendations": [...]
}}
}
“#, input = input, min = self.min_stakeholders ) } }
## Configuration
Make your tool configurable:
```toml
# In config.toml
[thinktools.stakeholdermap]
min_stakeholders = 5
include_conflicts = true
power_analysis = true
#![allow(unused)]
fn main() {
impl StakeholderMap {
pub fn from_config(config: &Config) -> Self {
Self {
min_stakeholders: config.get("min_stakeholders").unwrap_or(5),
include_conflicts: config.get("include_conflicts").unwrap_or(true),
}
}
}
}
Adding CLI Support
#![allow(unused)]
fn main() {
// In main.rs or cli module
use clap::Parser;
#[derive(Parser)]
pub struct StakeholderMapArgs {
/// Input decision to analyze
input: String,
/// Minimum stakeholders to identify
#[arg(long, default_value = "5")]
min_stakeholders: usize,
/// Include conflict analysis
#[arg(long, default_value = "true")]
conflicts: bool,
}
pub async fn run_stakeholder_map(args: StakeholderMapArgs) -> Result<()> {
let tool = StakeholderMap::new()
.min_stakeholders(args.min_stakeholders);
let result = tool.analyze(&args.input).await?;
println!("{}", result.format(Format::Pretty));
Ok(())
}
}
Example Custom Tools
Devil’s Advocate
Argues against the proposed idea:
#![allow(unused)]
fn main() {
pub struct DevilsAdvocate {
aggression_level: u8, // 1-10
}
impl ThinkTool for DevilsAdvocate {
type Output = CounterArguments;
async fn analyze(&self, input: &str) -> Result<Self::Output> {
// Generate strongest possible arguments against
}
}
}
Timeline Analyst
Evaluates time-based factors:
#![allow(unused)]
fn main() {
pub struct TimelineAnalyst {
horizon_years: u32,
}
impl ThinkTool for TimelineAnalyst {
type Output = TimelineAnalysis;
async fn analyze(&self, input: &str) -> Result<Self::Output> {
// Analyze short/medium/long term implications
}
}
}
Reversibility Checker
Assesses how reversible a decision is:
#![allow(unused)]
fn main() {
pub struct ReversibilityChecker;
impl ThinkTool for ReversibilityChecker {
type Output = ReversibilityAnalysis;
async fn analyze(&self, input: &str) -> Result<Self::Output> {
// Analyze cost and feasibility of reversal
}
}
}
Testing Custom Tools
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_stakeholder_map() {
let tool = StakeholderMap::new().min_stakeholders(3);
let result = tool
.analyze("Should we open source our codebase?")
.await
.unwrap();
assert!(result.stakeholders.len() >= 3);
assert!(!result.recommendations.is_empty());
}
}
}
Publishing Custom Tools
Share your tools with the community:
# Package as crate
cargo publish --crate reasonkit-stakeholdermap
# Or contribute to main repo
git clone https://github.com/reasonkit/reasonkit-core
# Add tool in src/thinktools/contrib/
Best Practices
- Clear purpose - Each tool should do one thing well
- Structured output - Use typed structs, not free text
- Configurable - Allow customization via config
- Tested - Include unit and integration tests
- Documented - Explain what it does and when to use it