Files
gochat/docs/comparison/M10-captain-chatwoot-vs-gochat.md
T
2026-06-04 15:44:48 +08:00

14 KiB

P10 (M10) Captain AI + Copilot — Chatwoot vs GoChat Comparison

This document maps every Chatwoot Enterprise Captain/Copilot feature to its GoChat port, highlighting structural differences, naming changes, and intentional deviations.


1. Architecture Overview

Aspect Chatwoot (Ruby/Rails) GoChat (Go/Gin)
Language Ruby on Rails Go + Gin framework
ORM ActiveRecord GORM
Vector DB pgvector via ActiveRecord pgvector via pgvector-go
HTTP client Net::HTTP / HTTParty resty/v2
DI pattern Rails autoloading Manual constructor injection (repos→services→handlers)
Routing config/routes.rb (327+ routes) internal/router/router.go (group-based)
Config ENV vars + features.yml CaptainConfig in config.yaml (viper)

2. Models

Captain::Assistant → CaptainAssistant

Chatwoot Field GoChat Field Notes
id (bigint) ID (uint, GORM auto) Same semantics
account_id AccountID (uint) FK to accounts
name (string) Name (string) Identical
status (enum: active/draft/archived) Status (AssistantStatus string enum) Same values, Go uses typed string
config (jsonb) Config (json.RawMessage) GoChat uses json.RawMessage for lazy parsing; Chatwoot stores as native jsonb
created_at/updated_at CreatedAt/UpdatedAt GORM auto-managed
— DeletedAt (gorm.DeletedAt) GoChat uses soft delete; Chatwoot has no soft delete on this model

Captain::Document → CaptainDocument

Chatwoot Field GoChat Field Notes
id ID (uint) Same
assistant_id AssistantID (uint) FK to captain_assistant
account_id AccountID (uint) Account scope
url (string) URL (string) External URL
external_url ExternalURL (string) Chatwoot distinguishes url vs external_url
status (enum: active/processing/failed) Status (DocumentStatus string enum) Same values
content (text) Content (string) Raw document text
embedding (vector(1536)) Embedding (pgvector.Vector) Both use pgvector 1536-dim cosine
— DeletedAt GoChat soft delete

Captain::Scenario → CaptainScenario

Chatwoot Field GoChat Field Notes
id ID (uint) Same
assistant_id AssistantID (uint) FK
account_id AccountID (uint) Account scope
name Name (string) Identical
description Description (string) Identical
scenario_type (enum: greeting/farewell/faq/custom) ScenarioType (ScenarioType string enum) Same values
trigger_keywords (jsonb) TriggerKeywords (json.RawMessage) GoChat uses json.RawMessage
response_template (text) ResponseTemplate (string) Identical
— DeletedAt Soft delete

Captain::InboxAssistant → CaptainInbox

Chatwoot Field GoChat Field Notes
id ID (uint) Same
inbox_id InboxID (uint) FK to inbox
assistant_id AssistantID (uint) FK to captain_assistant
account_id AccountID (uint) Account scope

Captain::Response → CaptainResponse

Chatwoot model is a join/pivot; GoChat keeps it as a standalone model for audit trail.

Chatwoot Field GoChat Field Notes
id ID (uint) Same
assistant_id AssistantID (uint) FK
conversation_id ConversationID (uint) FK
account_id AccountID (uint) Account scope
content (text) Content (string) The AI-generated response
source (enum) Source (string) "captain" / "copilot"

3. Copilot Models

Captain::CopilotThread → CopilotThread

Chatwoot Field GoChat Field Notes
id ID (uint) Same
account_id AccountID (uint) Account scope
user_id UserID (uint) Who owns this thread
title Title (string) Thread display name
status Status (string) active/archived
metadata (jsonb) Metadata (json.RawMessage) Flexible metadata
— DeletedAt Soft delete

Captain::CopilotMessage → CopilotMessage

Chatwoot Field GoChat Field Notes
id ID (uint) Same
thread_id ThreadID (uint) FK to copilot_thread
role (enum: user/assistant/system) Role (string) Same values
content (text) Content (string) Message body

4. LLM Provider Abstraction

Chatwoot GoChat Notes
Captain::BaseAiService llm.LLMProvider (interface) GoChat uses Go interface; Chatwoot uses Ruby class hierarchy
Captain::OpenAiService llm.OpenAIProvider (struct) Both implement OpenAI chat+embeddings
— llm.ProviderConfig Config struct mapped from CaptainConfig in config.yaml
— llm.ChatRequest/ChatResponse Unified request/response types
— llm.EmbeddingRequest/EmbeddingResponse Embedding types
— llm.StreamChunk Streaming support (Chatwoot uses SSE via ActionController::Live)
— llm.ToolCall/ToolResult Structured tool call interface

Key Differences

  • Chatwoot: Uses Net::HTTP with custom streaming; GoChat uses resty.Client
  • Chatwoot: Environment variables for API keys; GoChat uses CaptainConfig struct via viper
  • Chatwoot: Provider selection via feature flags; GoChat uses captain.llm_provider config field

5. Tool Registry

Chatwoot GoChat Notes
Captain::ToolRegistry (Ruby module) llm.ToolRegistry (Go struct) Both register named tools
Captain::SearchDocumentationService llm.SearchDocumentationService Vector similarity search as a tool
Captain::CustomHttpTool llm.CustomHttpTool HTTP call tool with headers/body config
— llm.BaseTool (interface) Go interface for extensible tools; Chatwoot uses module mixins

6. Services

Chatwoot Service GoChat Service Notes
Captain::AssistantService CaptainAssistantService CRUD + config update + list by account
Captain::DocumentService CaptainDocumentService CRUD + search similar (vector) + list by assistant
Captain::ScenarioService CaptainScenarioService CRUD
Captain::InboxAssistantService (embedded in CaptainAssistantService.BindInbox/UnbindInbox) GoChat folds inbox binding into assistant service
Captain::CopilotService CopilotService Thread CRUD + message CRUD + LLM calls
— ReplySuggestionService Extracted as sub-service within CopilotService
— ConversationSummaryService Extracted as sub-service within CopilotService
— TranslationService Extracted as sub-service within CopilotService

Key Differences

  • Chatwoot: Inbox binding is a separate service; GoChat folds it into CaptainAssistantService
  • Chatwoot: Copilot features are separate controller actions; GoChat uses a single CopilotService with sub-methods
  • GoChat: Each service takes its repo(s) via constructor injection (newer pattern)

7. Repositories

Chatwoot GoChat Notes
ActiveRecord scopes (.where, .order) GORM .Where, .Order, .Limit Equivalent query patterns
Captain::Assistant.where(account_id: id) CaptainAssistantRepo.ListByAccount(accountID) Named methods
Vector search: raw SQL ORDER BY embedding <=> $1 CaptainDocumentRepo.SearchSimilar(embedding, limit) using pgvector-go cosine distance Both use pgvector cosine distance operator
— BaseRepository[T] generic GoChat has a generic base repo pattern

8. Handlers / Controllers

Chatwoot Controller GoChat Handler Routes
Captain::AssistantsController CaptainAssistantHandler /accounts/:id/captain/assistants
Captain::DocumentsController CaptainDocumentHandler /accounts/:id/captain/assistants/:aid/documents
Captain::ScenariosController CaptainScenarioHandler /accounts/:id/captain/assistants/:aid/scenarios
Captain::CustomToolsController CaptainCustomToolHandler /accounts/:id/captain/custom_tools
Captain::CopilotThreadsController CopilotHandler.ListThreads/CreateThread/GetThread/DeleteThread /accounts/:id/captain/copilot_threads
Captain::CopilotMessagesController CopilotHandler.SendMessage /accounts/:id/captain/copilot_threads/:id/messages
— CopilotHandler.SuggestReplies /accounts/:id/captain/copilot/suggest_replies
— CopilotHandler.SummarizeConversation /accounts/:id/captain/copilot/summarize
— CopilotHandler.TranslateMessage /accounts/:id/captain/copilot/translate

Key Differences

  • Chatwoot: Separate controllers per resource; GoChat uses grouped handlers with method-per-action
  • Chatwoot: Copilot threads/messages are separate controllers; GoChat unifies under single CopilotHandler
  • GoChat: Uses response.Success/Error/JSON helpers; Chatwoot uses Rails render json:
  • GoChat: Uses parseUintParam/getAccountID/getUserID helpers for param extraction

9. Route Structure

Chatwoot Route Pattern GoChat Route Pattern Notes
namespace :api, scope :v1 → namespace :accounts → namespace :captain /api/v1/accounts/:account_id/captain/... Same nesting structure
resources :assistants captain.Group("/assistants") Identical CRUD
assistants/:id/inboxes (POST/DELETE) assistants.POST("/:id/inboxes") Inbox binding routes
assistants/:assistant_id/documents assistants.Group("/:assistant_id/documents") Nested documents
assistants/:assistant_id/scenarios assistants.Group("/:assistant_id/scenarios") Nested scenarios
resources :custom_tools captain.Group("/custom_tools") Account-level tools
resources :copilot_threads captain.Group("/copilot_threads") Thread CRUD
copilot/suggest_replies captain.POST("/copilot/suggest_replies") AI action endpoints
copilot/summarize captain.POST("/copilot/summarize") Summary endpoint
copilot/translate captain.POST("/copilot/translate") Translation endpoint

10. Bootstrap / Dependency Wiring

Chatwoot GoChat Notes
Rails autoloading (no explicit DI) Manual constructor chain in bootstrap.go Explicit wiring: repos→services→handlers
config/initializers/ auto-run Bootstrap() function step-by-step Ordered dependency creation
Feature flags (CAPTAIN_ENABLED) captain.enabled in config.yaml Toggle feature availability
ENV: OPENAI_API_KEY captain.llm_api_key Same concept, different naming

11. File Inventory

GoChat Files Created (P10 M10)

File Lines Purpose
internal/model/captain_models.go 237 6 Captain models + enums
internal/model/copilot_models.go ~70 CopilotThread + CopilotMessage
internal/repository/captain_repo.go 295 6 repos with vector search
internal/repository/copilot_repo.go 84 2 repos
internal/service/captain_service.go 225 4 Captain services
internal/service/copilot_service.go 238 CopilotService (CRUD + AI actions)
internal/service/llm/provider.go 322 LLMProvider interface + OpenAIProvider
internal/service/llm/prompts.go 159 System prompt builders
internal/service/llm/tools.go ~300 ToolRegistry + tools
internal/handler/api/v1/captain_handler.go 624 4 Captain handlers
internal/handler/api/v1/copilot_handler.go 218 CopilotHandler
internal/handler/api/v1/helpers.go 69 parseUintParam, getUserID, getPaginationParams
internal/config/config.go +17 CaptainConfig struct
internal/router/router.go +63 Captain route group
internal/app/bootstrap.go +30 Captain repos, services, LLM, handlers wiring

Modified Existing Files

File Change
internal/config/config.go Added Captain CaptainConfig to Config struct
internal/router/router.go Added 5 handler fields to Handlers struct; Captain route group
internal/app/bootstrap.go Added llm import; 8 Captain repos; 4 services + LLM provider + copilot; 5 handler wires

12. Intentional Deviations from Chatwoot

  1. Soft delete on all models — GoChat uses gorm.DeletedAt universally; Chatwoot Captain models don't soft-delete
  2. json.RawMessage instead of native jsonb — GoChat avoids the GORM datatypes.JSON dependency; lazy parsing in Go
  3. Inbox binding folded into assistant service — GoChat merges inbox binding/unbinding into CaptainAssistantService rather than a separate service
  4. Single CopilotHandler for all copilot actions — GoChat unifies threads, messages, suggest/summarize/translate under one handler
  5. LLMProvider as Go interface — Chatwoot uses Ruby class hierarchy; GoChat uses interface for polymorphic provider support
  6. Config struct for Captain settings — Chatwoot uses ENV vars; GoChat uses typed CaptainConfig in YAML
  7. Separate CopilotService — GoChat extracts Copilot into its own service rather than nesting under Captain::AssistantService
  8. No Captain::ResponseService — GoChat has the model but no dedicated response service (responses are embedded in assistant service flow)

13. Missing / Deferred Items

Feature Status Notes
Document processing (ingestion, chunking) Deferred Needs document processor service
Embedding generation pipeline Deferred Needs background job to call OpenAI embeddings API
Streaming responses (SSE) Stub only Chatwoot uses ActionController::Live; GoChat needs Gin SSE implementation
Captain webhook for inbox events Deferred Needs P7 channel integration
Response quality scoring Deferred Chatwoot has response rating; GoChat model has fields but no scoring service
Multiple LLM providers (Azure, Anthropic, Ollama) Interface ready OpenAIProvider implemented; others stubs
Admin UI for Captain config Deferred Front-end work
Document upload endpoint Missing Chatwoot has file upload; GoChat needs multipart handler