mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-06 02:36:49 +00:00
feat(parser): refactor BookingAgentParser to use OpenAI client and add parsing tests
- Update BookingAgentParser to utilize OpenAI's client for parsing booking data. - Remove base URL dependency and streamline the initialization process. - Introduce ResponseData struct to define expected JSON structure from the LLM. - Add unit tests for BookingAgentParser to validate parsing logic and expected output.
This commit is contained in:
parent
e298efabb4
commit
9e2cef07e1
3 changed files with 137 additions and 29 deletions
|
|
@ -1,58 +1,89 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/openai/openai-go"
|
||||
"github.com/openai/openai-go/responses"
|
||||
"github.com/rjNemo/rentease/internal/service/booking"
|
||||
)
|
||||
|
||||
type BookingAgentParser struct {
|
||||
baseUrl string
|
||||
systemPrompt string
|
||||
llmClient openai.Client
|
||||
}
|
||||
|
||||
func NewBookingAgentParser(baseUrl string) *BookingAgentParser {
|
||||
func NewBookingAgentParser() *BookingAgentParser {
|
||||
return &BookingAgentParser{
|
||||
baseUrl: baseUrl,
|
||||
llmClient: openai.NewClient(),
|
||||
systemPrompt: ` Extract the following fields from the booking content and return a JSON object with this exact structure (all fields required, use null or empty string if not available):
|
||||
|
||||
{
|
||||
"data": {
|
||||
"arrival_date": "YYYY-MM-DD",
|
||||
"booking_fees": "number as string",
|
||||
"booking_id": "string",
|
||||
"departure_date": "YYYY-MM-DD",
|
||||
"stay_length": int,
|
||||
"guest_email": "string",
|
||||
"guest_name": "string",
|
||||
"guest_number": int,
|
||||
"guest_phone": "string",
|
||||
"room_booked": "string",
|
||||
"standard_rate": "number as string",
|
||||
"special_requests": "string"
|
||||
}
|
||||
}
|
||||
|
||||
Respond ONLY with the JSON object above, wrapped in a top-level "data" field as shown, no code fences.
|
||||
|
||||
Booking content:`,
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseData is the expected structure returned by the LLM parser.
|
||||
type ResponseData struct {
|
||||
ArrivalDate string `json:"arrival_date"`
|
||||
BookingFees json.Number `json:"booking_fees"`
|
||||
BookingID string `json:"booking_id"`
|
||||
DepartureDate string `json:"departure_date"`
|
||||
StayLength int `json:"stay_length"`
|
||||
GuestEmail string `json:"guest_email"`
|
||||
GuestName string `json:"guest_name"`
|
||||
GuestNumber int `json:"guest_number"`
|
||||
GuestPhone string `json:"guest_phone"`
|
||||
RoomBooked string `json:"room_booked"`
|
||||
StandardRate json.Number `json:"standard_rate"`
|
||||
SpecialRequests string `json:"special_requests"`
|
||||
}
|
||||
|
||||
func (p *BookingAgentParser) Parse(rawContent string) (*booking.Booking, error) {
|
||||
log.Println("sending request to booking agent parser")
|
||||
log.Println("sending request to OpenAI LLM parser")
|
||||
|
||||
resp, err := http.Post(fmt.Sprintf("%s/sync?input=%s", p.baseUrl, url.QueryEscape(rawContent)), "application/json", nil)
|
||||
ctx := context.Background()
|
||||
|
||||
prompt := p.systemPrompt + "\n" + rawContent
|
||||
|
||||
resp, err := p.llmClient.Responses.New(ctx, responses.ResponseNewParams{
|
||||
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(prompt)},
|
||||
Model: "gpt-4o",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error sending request to booking agent parser: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
type ResponseData struct {
|
||||
ArrivalDate string `json:"arrival_date"`
|
||||
BookingFees json.Number `json:"booking_fees"`
|
||||
BookingID string `json:"booking_id"`
|
||||
DepartureDate string `json:"departure_date"`
|
||||
StayLength int `json:"stay_length"`
|
||||
GuestEmail string `json:"guest_email"`
|
||||
GuestName string `json:"guest_name"`
|
||||
GuestNumber int `json:"guest_number"`
|
||||
GuestPhone string `json:"guest_phone"`
|
||||
RoomBooked string `json:"room_booked"`
|
||||
StandardRate json.Number `json:"standard_rate"`
|
||||
SpecialRequests string `json:"special_requests"`
|
||||
return nil, fmt.Errorf("error sending request to OpenAI: %w", err)
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
var r struct {
|
||||
Data ResponseData `json:"data"`
|
||||
}
|
||||
|
||||
var r Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&r)
|
||||
// The LLM is instructed to return the JSON for ResponseData directly, so parse it
|
||||
err = json.Unmarshal([]byte(resp.OutputText()), &r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding response from booking agent parser: %w", err)
|
||||
return nil, fmt.Errorf("error decoding response from OpenAI: %w", err)
|
||||
}
|
||||
|
||||
var b booking.Booking
|
||||
|
|
|
|||
77
internal/driver/parser/parser_test.go
Normal file
77
internal/driver/parser/parser_test.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBookingAgentParser_Parse(t *testing.T) {
|
||||
input := ` Date d'arrivée jeu. 3 avr. 2025 Date de départ dim. 6 avr. 2025 Durée de séjour : 3 nuits Nombre de personnes :
|
||||
2
|
||||
Nombre d'hébergements
|
||||
1
|
||||
Montant total € 186 Nom du client :
|
||||
Olga Korovina
|
||||
|
||||
ru
|
||||
|
||||
okorov.905387@guest.booking.com
|
||||
Contactez vos clients ! Indiquez-leur l'heure à laquelle vous souhaitez les accueillir ou l'endroit où ils récupéreront leurs clés. Un simple appel suffit : Afficher le numéro de téléphone Vous pouvez également leur envoyer un e-mail ou un message. Langue préférée
|
||||
russe
|
||||
Canal : Booking.com Code IATA/TIDS :
|
||||
PC029090
|
||||
Numéro de réservation :
|
||||
4453602306
|
||||
Montant soumis à commission : € 177 Reçu jeu. 2 janv. 2025 Commission : € 31,86 Bloc-notes (usage interne) Ajoutez une note ici
|
||||
|
||||
Maison 1 Chambre T2 - VillaFleurie au bourg du Gosier)
|
||||
€ 186 jeu. 3 avr. 2025 dim. 6 avr. 2025 Nom du client
|
||||
Olga Korovina
|
||||
Occupation maximum 2 adultes, 2 enfants (3 personnes max.) Photo de l'hébergement Date Tarif Tarif par nuit
|
||||
03 - 04 avril
|
||||
|
||||
Standard Rate
|
||||
€ 59
|
||||
04 - 05 avril
|
||||
|
||||
Standard Rate
|
||||
€ 59
|
||||
05 - 06 avril
|
||||
|
||||
Standard Rate
|
||||
€ 59Sous-total € 177
|
||||
Taxe de séjour
|
||||
€ 1.50 par personne et par nuit € 9Tarif total de l'hébergement € 186 Le tarif comprend 8.9 % de TVA Conversation avec le client
|
||||
Aucun message
|
||||
|
||||
Les conversations avec vos clients apparaîtront ici.
|
||||
Booking.com reçoit tous les messages écrits ici et les traite selon sa Charte de confidentialité et informations sur les cookies Conditions `
|
||||
|
||||
parser := NewBookingAgentParser()
|
||||
booking, err := parser.Parse(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse failed: %v", err)
|
||||
}
|
||||
|
||||
if booking.Name != "Olga Korovina" {
|
||||
t.Errorf("expected Name 'Olga Korovina', got '%s'", booking.Name)
|
||||
}
|
||||
if booking.Email != "okorov.905387@guest.booking.com" {
|
||||
t.Errorf("expected Email 'okorov.905387@guest.booking.com', got '%s'", booking.Email)
|
||||
}
|
||||
if booking.Platform != "Booking" {
|
||||
t.Errorf("expected Platform 'Booking', got '%s'", booking.Platform)
|
||||
}
|
||||
if booking.ExternalId == nil || *booking.ExternalId != "4453602306" {
|
||||
t.Errorf("expected ExternalId '4453602306', got '%v'", booking.ExternalId)
|
||||
}
|
||||
if !booking.From.Equal(time.Date(2025, 4, 3, 0, 0, 0, 0, time.UTC)) {
|
||||
t.Errorf("expected From 2025-04-03, got %v", booking.From)
|
||||
}
|
||||
if !booking.To.Equal(time.Date(2025, 4, 6, 0, 0, 0, 0, time.UTC)) {
|
||||
t.Errorf("expected To 2025-04-06, got %v", booking.To)
|
||||
}
|
||||
if len(booking.Items) == 0 {
|
||||
t.Errorf("expected at least one item, got 0")
|
||||
}
|
||||
}
|
||||
2
main.go
2
main.go
|
|
@ -69,7 +69,7 @@ func run(c context.Context) error {
|
|||
return fmt.Errorf("error starting pdf client %w", err)
|
||||
}
|
||||
|
||||
parsingClient := parser.NewBookingAgentParser(appConfig.ParserBaseUrl)
|
||||
parsingClient := parser.NewBookingAgentParser()
|
||||
|
||||
bookingService, err := booking.NewService(bookingStore, parsingClient, pc)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in a new issue