diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84a2adf..6c384df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,8 @@ jobs: chmod 600 chatwoot.env cp evolution.env.example evolution.env chmod 600 evolution.env + cp tata.env.example tata.env + chmod 600 tata.env # ----------------------------------------------------------------------- # 2. Pull images @@ -61,7 +63,7 @@ jobs: # unchanged layers on subsequent runs. # ----------------------------------------------------------------------- - name: Pull images - run: docker compose pull postgres redis rails evolution-api + run: docker compose pull postgres redis rails evolution-api tata-agent # ----------------------------------------------------------------------- # 3. Start stack (CI subset) @@ -71,7 +73,7 @@ jobs: # with rails causes a UniqueViolation crash loop on pg_stat_statements. # ----------------------------------------------------------------------- - name: Start stack - run: docker compose up -d postgres redis rails evolution-api + run: docker compose up -d postgres redis rails evolution-api tata-agent # ----------------------------------------------------------------------- # 4. Wait for Chatwoot to be ready @@ -192,6 +194,91 @@ jobs: puts "✔ Evolution API instance 'testco' created successfully" RUBY + # ----------------------------------------------------------------------- + # 8. Wait for Tata agent to be ready + # + # TCP check on port 8000 inside the Docker network. + # ----------------------------------------------------------------------- + - name: Wait for Tata agent + timeout-minutes: 5 + run: | + until docker exec chatwoot_rails ruby -e \ + "require 'socket'; Socket.tcp('tata-agent', 8000, connect_timeout: 2) { }" \ + 2>/dev/null; do + echo "Waiting for Tata agent..." + sleep 5 + done + echo "✔ Tata agent is ready" + + # ----------------------------------------------------------------------- + # 9. Test Chatwoot agent-bot API – create bot and reset access token + # + # Uses the Chatwoot Account API to: + # 1. Create an agent bot pointing to the tata-agent webhook endpoint. + # 2. Reset the bot access token and verify the response contains + # an "access_token" field. + # This proves the Chatwoot ↔ tata-agent integration is wired correctly + # without requiring a real OpenAI key or triggering an actual LLM call. + # ----------------------------------------------------------------------- + - name: Test agent-bot API – create bot and reset token + run: | + docker exec -i chatwoot_rails bundle exec rails runner - <<'RUBY' + require 'net/http' + require 'json' + + # Retrieve the first account (created in step 5) + account = Account.first + account_id = account.id + + # Create a user token for account API access + user = account.users.first || begin + u = User.create!( + name: 'Bot Admin', + email: 'botadmin@example.org', + password: 'Password123!' + ) + AccountUser.create!(account: account, user: u, role: :administrator) + u + end + api_token = user.access_token.token + + # 1. Create an agent bot + uri = URI("http://localhost:3000/api/v1/accounts/#{account_id}/agent_bots") + req = Net::HTTP::Post.new(uri) + req['Content-Type'] = 'application/json' + req['api_access_token'] = api_token + req.body = { + name: 'Tata CI Bot', + description: 'CI test bot', + outgoing_url: 'http://tata-agent:8000/webhook', + bot_type: 'webhook', + bot_config: {} + }.to_json + + res = Net::HTTP.start(uri.host, uri.port) { |h| h.request(req) } + abort("Create bot failed: HTTP #{res.code} — #{res.body}") \ + unless res.code == '200' + bot = JSON.parse(res.body) + bot_id = bot['id'] + puts "✔ Agent bot created (id=#{bot_id}, name=#{bot['name']})" + + # 2. Reset access token + uri2 = URI("http://localhost:3000/api/v1/accounts/#{account_id}/agent_bots/#{bot_id}/reset_access_token") + req2 = Net::HTTP::Post.new(uri2) + req2['Content-Type'] = 'application/json' + req2['api_access_token'] = api_token + + res2 = Net::HTTP.start(uri2.host, uri2.port) { |h| h.request(req2) } + abort("Reset token failed: HTTP #{res2.code} — #{res2.body}") \ + unless res2.code == '200' + token_data = JSON.parse(res2.body) + abort('Response missing access_token field') \ + unless token_data.key?('access_token') && !token_data['access_token'].to_s.empty? + puts "✔ Agent bot access token reset (token=#{token_data['access_token'][0..7]}...)" + + puts "✔ All agent-bot API assertions passed" + RUBY + # ----------------------------------------------------------------------- # Debug: dump container logs on any failure # ----------------------------------------------------------------------- @@ -202,6 +289,7 @@ jobs: echo "=== chatwoot_postgres ===" && docker logs chatwoot_postgres 2>&1 | tail -20 || true echo "=== chatwoot_redis ===" && docker logs chatwoot_redis 2>&1 | tail -20 || true echo "=== evolution_api ===" && docker logs evolution_api 2>&1 | tail -50 || true + echo "=== tata_agent ===" && docker logs tata_agent 2>&1 | tail -50 || true # ----------------------------------------------------------------------- # Tear down diff --git a/.gitignore b/.gitignore index cd1f7ff..31bfc8a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ .env chatwoot.env evolution.env +tata.env acme.json # --------------------------------------------------------------------------- diff --git a/docker-compose.yml b/docker-compose.yml index 21c43ab..6f91a74 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ # • redis — cache and job queues # • rails — Chatwoot Rails web server (runs DB migrations on start) # • sidekiq — Chatwoot Sidekiq background worker +# • tata-agent — Tata AI agent bot (webhook receiver), exposed at bot. # • evolution-api — Evolution API (WhatsApp gateway), exposed at evo. # # Prerequisites / first-time setup: see README.md @@ -163,6 +164,42 @@ services: command: - "bundle exec rails db:chatwoot_prepare && exec bundle exec sidekiq -C config/sidekiq.yml" + # --------------------------------------------------------------------------- + # Tata Customer Agent – AI agent bot (webhook receiver for Chatwoot) + # + # Exposes the webhook endpoint at https://bot./webhook via Traefik. + # Shares the existing Chatwoot postgres for vector + conversation storage. + # Copy tata.env.example → tata.env and fill in all values, then: + # 1. Create an agent bot in Chatwoot (see README – Bot setup) + # 2. Reset the bot access token and copy it to CHATWOOT_API_TOKEN in tata.env + # 3. docker compose restart tata-agent + # --------------------------------------------------------------------------- + tata-agent: + image: ghcr.io/santzit/tata-customer-agent:v0.1.1 + container_name: tata_agent + restart: unless-stopped + networks: + - chatwoot-net + depends_on: + postgres: + condition: service_healthy + rails: + condition: service_started + env_file: tata.env + labels: + - "traefik.enable=true" + # HTTP → HTTPS redirect + - "traefik.http.routers.tata-http.rule=Host(`bot.${DOMAIN}`)" + - "traefik.http.routers.tata-http.entrypoints=web" + - "traefik.http.routers.tata-http.middlewares=redirect-https" + # HTTPS with Let's Encrypt TLS + - "traefik.http.routers.tata.rule=Host(`bot.${DOMAIN}`)" + - "traefik.http.routers.tata.entrypoints=websecure" + - "traefik.http.routers.tata.tls=true" + - "traefik.http.routers.tata.tls.certresolver=letsencrypt" + - "traefik.http.routers.tata.service=tata" + - "traefik.http.services.tata.loadbalancer.server.port=8000" + # --------------------------------------------------------------------------- # Evolution API – WhatsApp gateway / messaging integration # diff --git a/tata.env.example b/tata.env.example new file mode 100644 index 0000000..2759fa9 --- /dev/null +++ b/tata.env.example @@ -0,0 +1,79 @@ +# ============================================================================= +# tata.env.example – Tata Customer Agent configuration (v0.2x) +# +# Copy to tata.env and fill in every value. +# Do NOT commit tata.env to version control. +# +# Reference: https://github.com/santzit/tata-customer-agent +# +# Workflow: +# 1. Start the stack (docker compose up -d) +# 2. Create a Chatwoot account and an agent bot (see README – Bot setup) +# 3. Reset the bot access token via the Chatwoot API +# 4. Fill in CHATWOOT_API_TOKEN and CHATWOOT_ACCOUNT_ID below +# 5. Restart the tata-agent container (docker compose restart tata-agent) +# ============================================================================= + +# --------------------------------------------------------------------------- +# OpenAI credentials +# +# Replace with your real key before going to production. +# OPENAI_API_KEY: https://platform.openai.com/api-keys +# --------------------------------------------------------------------------- +OPENAI_API_KEY=sk-placeholder-replace-with-real-key + +# Chat model used for reply generation. +LLM_MODEL=gpt-4.1 + +# LLM provider: "openai" for openai.com, "azure" for Azure OpenAI. +LLM_PROVIDER=openai + +# Azure OpenAI endpoint — leave blank when LLM_PROVIDER=openai. +OPENAI_API_ENDPOINT= + +# Embedding model for the pgvector knowledge base. +EMBEDDING_MODEL_SMALL=text-embedding-3-small + +# --------------------------------------------------------------------------- +# Database (PostgreSQL with pgvector extension) +# +# Shares the existing Chatwoot postgres container. +# The tata tables (PG_VECTOR_TABLE, PG_MEMORY_TABLE) are created automatically +# in the chatwoot_production database and do not conflict with Chatwoot tables. +# +# The password must match POSTGRES_PASSWORD in .env. +# --------------------------------------------------------------------------- +POSTGRES_DSN=postgresql://chatwoot:testpass@postgres:5432/chatwoot_production + +# Table names — change only if they conflict with existing tables. +PG_VECTOR_TABLE=tata_knowledge +PG_MEMORY_TABLE=tata_conversations + +# --------------------------------------------------------------------------- +# Chatwoot integration +# +# CHATWOOT_BASE_URL: Internal Docker service URL for the Rails container. +# Keep as "http://rails:3000" — do not use the public URL. +# CHATWOOT_API_TOKEN: Agent-bot access token obtained by resetting the bot +# token via POST /api/v1/accounts/:id/agent_bots/:id/reset_access_token +# CHATWOOT_ACCOUNT_ID: Numeric Chatwoot account ID (visible in the Chatwoot URL). +# --------------------------------------------------------------------------- +CHATWOOT_BASE_URL=http://rails:3000 +CHATWOOT_API_TOKEN=changeme-agent-bot-access-token +CHATWOOT_ACCOUNT_ID=1 + +# --------------------------------------------------------------------------- +# Webhook security (optional) +# +# Set to the same value configured in the Chatwoot agent-bot outgoing URL +# to validate incoming webhook signatures. Leave blank to disable. +# --------------------------------------------------------------------------- +WEBHOOK_TOKEN= + +# --------------------------------------------------------------------------- +# Bot behaviour +# +# RESPONSE_DELAY_SECONDS: seconds of conversation silence before the agent +# sends its reply (message-buffer debounce window). Default: 120. +# --------------------------------------------------------------------------- +RESPONSE_DELAY_SECONDS=120