openai service and basic tables with migrations
This commit is contained in:
12
db/migrations/20260102021529_wa_identity_map.sql
Normal file
12
db/migrations/20260102021529_wa_identity_map.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- migrate:up
|
||||
create table wa_identity_map (
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
wa_chat_id text not null,
|
||||
woo_customer_id bigint not null,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
primary key (tenant_id, wa_chat_id)
|
||||
);
|
||||
|
||||
-- migrate:down
|
||||
drop table if exists wa_identity_map;
|
||||
13
db/migrations/20260102021640_tenants.sql
Normal file
13
db/migrations/20260102021640_tenants.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- migrate:up
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
CREATE TABLE tenants (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
key text NOT NULL UNIQUE,
|
||||
name text NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- migrate:down
|
||||
DROP TABLE IF EXISTS tenants;
|
||||
DROP EXTENSION IF EXISTS pgcrypto;
|
||||
23
db/migrations/20260102021749_wa_conversation_state.sql
Normal file
23
db/migrations/20260102021749_wa_conversation_state.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
-- migrate:up
|
||||
create table wa_conversation_state (
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
wa_chat_id text not null,
|
||||
|
||||
state text not null, -- IDLE / BUILDING_ORDER / WAITING_PAYMENT
|
||||
last_intent text null,
|
||||
last_order_id bigint null,
|
||||
|
||||
context jsonb not null default '{}'::jsonb,
|
||||
|
||||
state_updated_at timestamptz not null default now(),
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
|
||||
primary key (tenant_id, wa_chat_id)
|
||||
);
|
||||
|
||||
create index idx_state_tenant_updated
|
||||
on wa_conversation_state (tenant_id, updated_at desc);
|
||||
|
||||
-- migrate:down
|
||||
drop table if exists wa_conversation_state;
|
||||
24
db/migrations/20260102021829_wa_messages.sql
Normal file
24
db/migrations/20260102021829_wa_messages.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
-- migrate:up
|
||||
create table wa_messages (
|
||||
id bigserial primary key,
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
wa_chat_id text not null,
|
||||
|
||||
provider text not null, -- sim / evolution / twilio
|
||||
message_id text not null, -- idempotencia por provider
|
||||
direction text not null, -- in / out
|
||||
|
||||
ts timestamptz not null default now(),
|
||||
text text null,
|
||||
payload jsonb not null default '{}'::jsonb,
|
||||
|
||||
run_id uuid null,
|
||||
|
||||
unique (tenant_id, provider, message_id)
|
||||
);
|
||||
|
||||
create index idx_msgs_tenant_chat_ts
|
||||
on wa_messages (tenant_id, wa_chat_id, ts desc);
|
||||
|
||||
-- migrate:down
|
||||
drop table if exists wa_messages;
|
||||
32
db/migrations/20260102021907_conversation_runs.sql
Normal file
32
db/migrations/20260102021907_conversation_runs.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- migrate:up
|
||||
create table conversation_runs (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
wa_chat_id text not null,
|
||||
message_id text not null,
|
||||
|
||||
ts timestamptz not null default now(),
|
||||
prev_state text null,
|
||||
user_text text null,
|
||||
|
||||
llm_output jsonb null,
|
||||
tools jsonb not null default '[]'::jsonb,
|
||||
invariants jsonb not null default '{}'::jsonb,
|
||||
|
||||
final_reply text null,
|
||||
order_id bigint null,
|
||||
payment_link text null,
|
||||
|
||||
status text not null default 'ok', -- ok | warn | error
|
||||
error_code text null,
|
||||
error_detail text null,
|
||||
latency_ms int null,
|
||||
|
||||
unique (tenant_id, message_id)
|
||||
);
|
||||
|
||||
create index idx_runs_tenant_chat_ts
|
||||
on conversation_runs (tenant_id, wa_chat_id, ts desc);
|
||||
|
||||
-- migrate:down
|
||||
drop table if exists conversation_runs;
|
||||
@@ -1,3 +1,5 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:20-alpine
|
||||
@@ -5,7 +7,10 @@ services:
|
||||
command: sh -c "npm install && npm run dev"
|
||||
ports:
|
||||
- "3000:3000"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- PORT=3000
|
||||
- DATABASE_URL=postgres://${POSTGRES_USER:-botino}:${POSTGRES_PASSWORD:-botino}@db:5432/${POSTGRES_DB:-botino}
|
||||
- REDIS_URL=redis://redis:6379
|
||||
@@ -13,12 +18,16 @@ services:
|
||||
- .:/usr/src/app
|
||||
- /usr/src/app/node_modules
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- POSTGRES_DB=${POSTGRES_DB:-botino}
|
||||
- POSTGRES_USER=${POSTGRES_USER:-botino}
|
||||
|
||||
34
package-lock.json
generated
34
package-lock.json
generated
@@ -10,7 +10,9 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2"
|
||||
"express": "^4.19.2",
|
||||
"openai": "^6.15.0",
|
||||
"zod": "^4.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dbmate": "^2.0.0",
|
||||
@@ -965,6 +967,27 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/openai": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-6.15.0.tgz",
|
||||
"integrity": "sha512-F1Lvs5BoVvmZtzkUEVyh8mDQPPFolq4F+xdsx/DO8Hee8YF3IGAlZqUIsF+DVGhqf4aU0a3bTghsxB6OIsRy1g==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"openai": "bin/cli"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.25 || ^4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ws": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
@@ -1340,6 +1363,15 @@
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.4.tgz",
|
||||
"integrity": "sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2"
|
||||
"express": "^4.19.2",
|
||||
"openai": "^6.15.0",
|
||||
"zod": "^4.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dbmate": "^2.0.0",
|
||||
|
||||
21
src/services/openai.js
Normal file
21
src/services/openai.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// src/services/openai.js (o directo en main.js por ahora)
|
||||
import OpenAI from "openai";
|
||||
|
||||
export const openai = new OpenAI({ apiKey: process.env.OPENAI_APIKEY });
|
||||
|
||||
// promptSystem = tu prompt (no lo tocamos mucho)
|
||||
// input = { last_user_message, conversation_history, current_conversation_state, ... }
|
||||
export async function llmPlan({ promptSystem, input }) {
|
||||
const resp = await openai.responses.create({
|
||||
model: "gpt-5-mini", // o gpt-5 (más caro/mejor) / el que estés usando
|
||||
input: [
|
||||
{ role: "system", content: promptSystem },
|
||||
{ role: "user", content: JSON.stringify(input) }
|
||||
],
|
||||
// Si estás usando "Structured Outputs" nativo, acá va tu schema.
|
||||
// En caso de que tu SDK no lo soporte directo, lo hacemos con zod/JSON parse robusto.
|
||||
});
|
||||
|
||||
const text = resp.output_text; // ojo: depende del SDK/model; es el agregado de outputs
|
||||
return text;
|
||||
}
|
||||
Reference in New Issue
Block a user