@@ -15,19 +15,43 @@ export interface ModelMap {
15
15
16
16
const defaultContentType = 'application/json'
17
17
18
+ function getPath ( url : string ) : { path : string , base : string , apiKey ?: string } {
19
+ try {
20
+ const urlParts = url . split ( '|' )
21
+ const apiKey = urlParts . length > 1 ? urlParts [ 1 ] : undefined
22
+ const urlObject = new URL ( url )
23
+ return {
24
+ path : urlObject . pathname || '/v1' ,
25
+ base : urlObject . origin ,
26
+ apiKey
27
+ }
28
+ } catch ( error ) {
29
+ // Return the input if it's already a path starting with '/'
30
+ if ( url . startsWith ( '/' ) ) return { path : url , base : 'http://localhost' }
31
+ // Return '/v1' for invalid URLs
32
+ return { path : '/v1' , base : 'http://localhost' }
33
+ }
34
+ }
35
+
18
36
async function fetchModels ( targetUrls : string [ ] ) : Promise < ModelMap > {
19
37
const tmp : ModelMap = { }
20
38
for ( const urlAndToken of targetUrls ) {
21
39
const [ url , apiKey ] = urlAndToken . split ( '|' ) . map ( s => s . trim ( ) )
22
- const reqHeaders : { [ key : string ] : string } = {
40
+ const { path, base } = getPath ( url )
41
+ const headers : { [ key : string ] : string } = {
23
42
accept : defaultContentType ,
24
43
'Content-Type' : defaultContentType
25
44
}
26
45
if ( apiKey != null && apiKey !== '' ) {
27
- reqHeaders [ 'Authorization' ] = `Bearer ${ apiKey } `
46
+ headers [ 'Authorization' ] = `Bearer ${ apiKey } `
47
+ }
48
+ const params = {
49
+ method : 'GET' ,
50
+ url : `${ base } /${ path } /models` ,
51
+ headers
28
52
}
29
53
try {
30
- const response = await axios . get ( ` ${ url } /v1/models` )
54
+ const response = await axios ( params )
31
55
const models = response . data . data || [ ]
32
56
const hostId = extractDomainName ( url )
33
57
models . forEach ( ( model : Model ) => {
@@ -85,26 +109,36 @@ export class LLMController {
85
109
public async forwardPostRequest ( req : Request , res : Response , next : NextFunction ) {
86
110
if (
87
111
req . method === 'POST' &&
88
- ( req . path . startsWith ( 'v1/ ' ) || req . path . startsWith ( '/v1/ ' ) ) &&
112
+ ( req . path . startsWith ( 'v1' ) || req . path . startsWith ( '/v1' ) ) &&
89
113
req . body != null &&
90
114
req . body . model != null &&
91
115
this . targetUrls . length > 0
92
116
) {
93
117
const { model : modelId } = req . body
94
- let targetUrl = this . targetUrls [ 0 ] // Default to first URL if no matching model found
118
+ const { base : firstBaseUrl , path : firstPath , apiKey : firstApiKey } = getPath ( this . targetUrls [ 0 ] )
119
+ let targetUrl = firstBaseUrl // Default to first URL if no matching model found
120
+ let targetPath = firstPath
121
+ let targetApiKey = firstApiKey
95
122
96
123
const hash = md5 ( modelId )
97
124
if ( modelId && this . modelCache [ hash ] ) {
98
- targetUrl = this . modelCache [ hash ] . url
125
+ const { path, base, apiKey } = getPath ( this . modelCache [ hash ] . url )
126
+ targetUrl = base
127
+ targetPath = path
128
+ targetApiKey = apiKey
99
129
}
100
- const fullUrl = new URL ( req . path , targetUrl ) . toString ( )
130
+ const reqPath = req . path . startsWith ( '/v1/' ) ? req . path . replace ( '/v1' , targetPath ) : `${ targetPath } ${ req . path } `
131
+ const fullUrl = new URL ( reqPath , targetUrl ) . toString ( )
101
132
log ( 'info' , `Forwarding request to: ${ fullUrl } -> ${ modelId } ` )
102
-
133
+ const headers = { ...req . headers }
134
+ if ( targetApiKey ) {
135
+ headers [ 'Authorization' ] = `Bearer ${ targetApiKey } `
136
+ }
103
137
try {
104
138
const axiosConfig : AxiosRequestConfig = {
105
139
method : req . method ,
106
140
url : fullUrl ,
107
- headers : { ... req . headers } ,
141
+ headers,
108
142
data : req . body ,
109
143
responseType : 'stream'
110
144
}
0 commit comments