mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-31 21:13:49 +08:00
Implement OpenAI chat completions in GO (#16177)
### What problem does this PR solve? Implement OpenAI chat completions in GO POST /api/v1/openai/<chat_id>/chat/completions OpenAI chat cli: internal/development.md ### Type of change - [x] Refactoring
This commit is contained in:
169
internal/common/timer.go
Normal file
169
internal/common/timer.go
Normal file
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Phase is a named timing bucket in the RAG pipeline
|
||||
type Phase string
|
||||
|
||||
const (
|
||||
PhaseCheckLLM Phase = "check_llm"
|
||||
PhaseCheckLangfuse Phase = "check_langfuse"
|
||||
PhaseBindModels Phase = "bind_models"
|
||||
PhaseQueryRefinement Phase = "query_refinement"
|
||||
PhaseRetrieval Phase = "retrieval"
|
||||
PhaseGenerateAnswer Phase = "generate_answer"
|
||||
)
|
||||
|
||||
// allPhases ordered for Markdown() display.
|
||||
var allPhases = []Phase{
|
||||
PhaseCheckLLM,
|
||||
PhaseCheckLangfuse,
|
||||
PhaseBindModels,
|
||||
PhaseQueryRefinement,
|
||||
PhaseRetrieval,
|
||||
PhaseGenerateAnswer,
|
||||
}
|
||||
|
||||
// Timer tracks elapsed wall-clock time per named Phase.
|
||||
// Supports reentrant Enter/Exit on the same phase (inner span's duration
|
||||
// adds to the outer span's accumulated total).
|
||||
type Timer struct {
|
||||
mu sync.Mutex
|
||||
start time.Time
|
||||
phases map[Phase]time.Duration
|
||||
entries map[Phase][]time.Time
|
||||
}
|
||||
|
||||
// NewTimer constructs a Timer.
|
||||
func NewTimer() *Timer {
|
||||
return &Timer{
|
||||
phases: make(map[Phase]time.Duration, len(allPhases)),
|
||||
entries: make(map[Phase][]time.Time, len(allPhases)),
|
||||
}
|
||||
}
|
||||
|
||||
// Start anchors the timer. Calling Start() twice resets all state.
|
||||
func (t *Timer) Start() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.start = time.Now()
|
||||
t.phases = make(map[Phase]time.Duration, len(allPhases))
|
||||
t.entries = make(map[Phase][]time.Time, len(allPhases))
|
||||
}
|
||||
|
||||
// Enter marks the start of phase p. Reentrant calls push a new anchor.
|
||||
func (t *Timer) Enter(p Phase) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.entries[p] = append(t.entries[p], time.Now())
|
||||
}
|
||||
|
||||
// Exit records the duration since the most recent Enter(p). No-op if no Enter.
|
||||
func (t *Timer) Exit(p Phase) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
stack := t.entries[p]
|
||||
if len(stack) == 0 {
|
||||
return
|
||||
}
|
||||
open := stack[len(stack)-1]
|
||||
t.entries[p] = stack[:len(stack)-1]
|
||||
t.phases[p] += time.Since(open)
|
||||
}
|
||||
|
||||
// Phase returns the accumulated duration for phase p.
|
||||
func (t *Timer) Phase(p Phase) time.Duration {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
return t.phases[p]
|
||||
}
|
||||
|
||||
// Total returns the elapsed time since Start().
|
||||
func (t *Timer) Total() time.Duration {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.start.IsZero() {
|
||||
return 0
|
||||
}
|
||||
return time.Since(t.start)
|
||||
}
|
||||
|
||||
// PhaseReport is the JSON-serializable view of a Timer's state.
|
||||
type PhaseReport struct {
|
||||
PhasesMs map[string]float64 `json:"phases_ms"`
|
||||
TotalMs float64 `json:"total_ms"`
|
||||
}
|
||||
|
||||
// Report returns a JSON-marshalable snapshot with microsecond precision.
|
||||
func (t *Timer) Report() *PhaseReport {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
phases := make(map[string]float64, len(allPhases))
|
||||
for _, p := range allPhases {
|
||||
phases[string(p)] = float64(t.phases[p].Microseconds()) / 1000.0
|
||||
}
|
||||
var totalMs float64
|
||||
if !t.start.IsZero() {
|
||||
totalMs = float64(time.Since(t.start).Microseconds()) / 1000.0
|
||||
}
|
||||
return &PhaseReport{PhasesMs: phases, TotalMs: totalMs}
|
||||
}
|
||||
|
||||
func (t *Timer) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(t.Report())
|
||||
}
|
||||
|
||||
// Markdown renders the Timer as a "## Time elapsed:" block matching
|
||||
func (t *Timer) Markdown() string {
|
||||
r := t.Report()
|
||||
var b strings.Builder
|
||||
b.WriteString("\n## Time elapsed:\n")
|
||||
b.WriteString(fmt.Sprintf(" - Total: %.1fms\n", r.TotalMs))
|
||||
for _, p := range allPhases {
|
||||
ms := r.PhasesMs[string(p)]
|
||||
b.WriteString(fmt.Sprintf(" - %s: %.1fms\n", displayName(p), ms))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func displayName(p Phase) string {
|
||||
switch p {
|
||||
case PhaseCheckLLM:
|
||||
return "Check LLM"
|
||||
case PhaseCheckLangfuse:
|
||||
return "Check Langfuse tracer"
|
||||
case PhaseBindModels:
|
||||
return "Bind models"
|
||||
case PhaseQueryRefinement:
|
||||
return "Query refinement(LLM)"
|
||||
case PhaseRetrieval:
|
||||
return "Retrieval"
|
||||
case PhaseGenerateAnswer:
|
||||
return "Generate answer"
|
||||
default:
|
||||
return string(p)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user