picoflow.io

Lesson 2: Wire the API & FlowEngine


Flow plumbing

medical-appointment-bot/src/flow.module.ts keeps the runtime tiny:

@Module({
  imports: [ConfigModule.forRoot()],
  providers: [FlowEngine],
  exports: [FlowEngine, ConfigModule],
})
export class FlowModule {}

FlowEngine is the single dependency you pass around; it already knows how to load sessions, bind tools, and persist step state.


App module + controllers

src/app.module.ts imports FlowModule and exposes only two controllers:

@Module({
  imports: [FlowModule, ConfigModule],
  controllers: [ChatController, HealthController],
})
export class AppModule {}
  • HealthController answers /healthcheck for probes.
  • ChatController is the entry point for every chat turn.

Chat endpoints

src/controllers/chat-controller.ts shows the minimal integration surface:

@ApiTags('ai')
@Controller('ai')
export class ChatController {
  constructor(private flowEngine: FlowEngine) {
    flowEngine.registerFlows({ MedicalFlow });
    flowEngine.registerModel(ChatGoogleGenerativeAI, {
      model: 'gemini-2.5-pro',
      temperature: CoreConfig.llmTemperature,
      apiKey: CoreConfig.GeminiKey,
      maxRetries: CoreConfig.llmRetry,
    });
  }

  @Post('run')
  async run(@Res() res: FastifyReply,
            @Body(K.message) userMessage: string,
            @Body(K.flowName) flowName: string,
            @Body('config') config: object,
            @Headers(K.ChatSessionID) sessionId = '') {
    await this.flowEngine.run(res, flowName, userMessage, sessionId, config);
  }

  @Post('end')
  async endChat(@Res() res: FastifyReply,
                @Headers(K.ChatSessionID) sessionId = '') {
    const result = await this.flowEngine.endChat(sessionId);
    if (!result.success) res.status(HttpStatus.BAD_REQUEST);
    res.send(result);
  }
}

Key points:

  • Flow + model registration happens once in the constructor.
  • /ai/run is the only turn-by-turn endpoint; /ai/end closes sessions.
  • The engine handles retries, tool dispatch, and state writes under the hood.

main.ts

src/main.ts wires Fastify and Swagger:

const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
app.useStaticAssets({ root: join(__dirname, '..', 'public'), prefix: '/public/' });
const config = new DocumentBuilder().setTitle('Chat Flow API').setDescription('API documentation for Chat Flow').setVersion('1.0').build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(8000, '0.0.0.0');

Nothing else is needed to run the bot: register the flow, expose the endpoints, and start Fastify.