Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. Input - A question, claim, or statement to analyze
  2. Process - Structured reasoning steps
  3. 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

  1. Clear purpose - Each tool should do one thing well
  2. Structured output - Use typed structs, not free text
  3. Configurable - Allow customization via config
  4. Tested - Include unit and integration tests
  5. Documented - Explain what it does and when to use it