AdaptBot is a self-extending AI agent system that dynamically expands its capabilities at runtime. It routes user queries through a permanently compiled LangGraph workflow. If a requested capability or tool does not exist, AdaptBot generates a new Python tool file, registers it, and runs it on the fly. Performance and reliability metrics are captured thread-safely into a CSV logger.
Tip
Key Design Principles & Advantages:
- ✓ Graph compiled once: The LangGraph is initialized only once on startup.
- ✓ No graph rewiring: Adding capabilities never changes the graph architecture.
- ✓ No server restart: Changes apply live without server downtime.
- ✓ Runtime tool discovery: Instantly reads available tools from the registry.
- ✓ Dynamic module loading: Uses on-demand Python imports/reloads.
graph TD
User([User Client]) <-->|HTTP POST /chat| FastAPI[FastAPI Server: app.py]
FastAPI -->|1. Classify Intent| Router{Intent Router}
Router -->|BUILD| Builder[Tool Generator Graph: tool_generator.py]
Router -->|CHAT| Orchestrator[Static Orchestrator Graph: graph.py]
subgraph Tool Lifecycle Management
Builder -->|Write File| ToolsDir[tools/ directory]
Builder -->|Update Registry| Registry[tools_registry.json]
end
subgraph Execution & Extraction Loop
Orchestrator -->|Read Registry| Registry
Orchestrator -->|Import Module| ToolsDir
Orchestrator -->|Structured Extraction| Extractor[LLM Parameter Extractor]
Extractor -->|Run Parameters| Execution[Tool Entrypoint execute_tool]
end
FastAPI -->|Log Performance| Logger[Benchmark Logger: benchmark_logger.py]
Logger -->|Write Entry| CSV[benchmarks.csv]
- FastAPI & Jinja2 Templates: Serves a lightweight, clean, light-mode chat interface (
templates/index.html) using standard fetch calls. - Intent Router: Uses the primary LLM (
openai/gpt-oss-20bvia Groq) to inspect raw user input and classify it asBUILD(wants a tool created/modified/deleted) orCHAT(wants an answer/execution). - Latency Measurement Wrapper: Starts a timer on request entry and completes it on response return. Measures overall response latencies and logs them.
This is a LangGraph workflow compiled exactly once on startup. It has four stable nodes that never require recompilation:
classifier_node: Inspects the query againsttools_registry.jsonand classifies it intogeneral,need_tool, or a specific tool name (e.g.weather_forecast).general_node: Responds directly to general knowledge questions without tools.execute_tool_node: Dynamically resolves and imports the module from thetools/directory.- Universal Parameter Extractor: If the tool script declares a Pydantic
ToolInputSchemawith parameters, the node uses LLM structured output to extract precise arguments from raw user conversational history. - Safeguarded Invocation: Wraps parameter extraction and execution in independent try-except blocks, recording success states.
- Universal Parameter Extractor: If the tool script declares a Pydantic
synthesizer_node: Rewrites raw tool outputs into friendly, conversational responses.
A secondary LangGraph workflow tasked with tool lifecycle changes:
decision_tool: Determines if the build request is aCREATE,MODIFY, orDELETEoperation.create_tool/modify_tool: Prompts the LLM to write isolated Python files to thetools/directory. The LLM must output clean code conforming to the universal input/execution schema.delete_tool: Removes the file and updates the registry.
Provides thread-safe file handling to write logs to benchmarks.csv. Captured fields include:
timestamp: When the transaction occurred.query: The user's prompt.intent:BUILDorCHAT.category: Path/Tool executed.tool_generation_time_sec: Time taken to compile a tool.parameter_extraction_success:True/False/N/Aindicating if the extraction succeeded.tool_execution_success:True/False/N/Aindicating if the tool completed without crash.end_to_end_latency_sec: Complete request processing duration.error_message: Stack trace or error string if any node errored.
- User enters: "What is the weather in New York?"
app.pyreceives request -> starts timer.app.pyclassifies intent asCHAT.graph.pyclassifier_noderuns. It readstools_registry.json(containsweather_forecast) and matches the query to theweather_forecasttool.execute_tool_nodedynamically importstools/weather_forecast.py.- It looks at the
ToolInputSchema(requirescity: str), calls the LLM with structured output to extractcity="New York", and setsparameter_extraction_success="True". - Runs
execute_tool(city="New York"), receives response, and setstool_execution_success="True". synthesizer_nodeformats the raw weather data into a friendly reply.app.pylogs the successful execution tobenchmarks.csvand returns the reply.
- User enters: "Convert 100 USD to EUR"
- If the registry does not contain a currency tool:
graph.pyclassifier_noderoutes it asneed_tool.app.pyreturnsneed_tool: Truewith a prompt asking the user if they'd like to build the tool.
- User clicks "Create Tool" in the browser.
- Browser posts a request to build the tool.
app.pyclassifies the request asBUILDand pipes it totool_generator.py.create_toolwritestools/currency_converter.pyand registers it intools_registry.json.app.pylogstool_generation_time_sec.- The UI automatically resubmits the original query ("Convert 100 USD to EUR"), which now successfully executes using Scenario A.