Skip to content

Commit 9b6fb36

Browse files
ChuKhaLidaniel-lxs
andauthored
fix(litellm): handle baseurl with paths correctly (#5697)
Co-authored-by: Daniel Riccio <[email protected]>
1 parent d0452d0 commit 9b6fb36

File tree

2 files changed

+131
-1
lines changed

2 files changed

+131
-1
lines changed

src/api/providers/fetchers/__tests__/litellm.spec.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,132 @@ describe("getLiteLLMModels", () => {
3939
})
4040
})
4141

42+
it("handles base URLs with a path correctly", async () => {
43+
const mockResponse = {
44+
data: {
45+
data: [],
46+
},
47+
}
48+
49+
mockedAxios.get.mockResolvedValue(mockResponse)
50+
51+
await getLiteLLMModels("test-api-key", "http://localhost:4000/litellm")
52+
53+
expect(mockedAxios.get).toHaveBeenCalledWith("http://localhost:4000/litellm/v1/model/info", {
54+
headers: {
55+
Authorization: "Bearer test-api-key",
56+
"Content-Type": "application/json",
57+
...DEFAULT_HEADERS,
58+
},
59+
timeout: 5000,
60+
})
61+
})
62+
63+
it("handles base URLs with a path and trailing slash correctly", async () => {
64+
const mockResponse = {
65+
data: {
66+
data: [],
67+
},
68+
}
69+
70+
mockedAxios.get.mockResolvedValue(mockResponse)
71+
72+
await getLiteLLMModels("test-api-key", "http://localhost:4000/litellm/")
73+
74+
expect(mockedAxios.get).toHaveBeenCalledWith("http://localhost:4000/litellm/v1/model/info", {
75+
headers: {
76+
Authorization: "Bearer test-api-key",
77+
"Content-Type": "application/json",
78+
...DEFAULT_HEADERS,
79+
},
80+
timeout: 5000,
81+
})
82+
})
83+
84+
it("handles base URLs with double slashes correctly", async () => {
85+
const mockResponse = {
86+
data: {
87+
data: [],
88+
},
89+
}
90+
91+
mockedAxios.get.mockResolvedValue(mockResponse)
92+
93+
await getLiteLLMModels("test-api-key", "http://localhost:4000/litellm//")
94+
95+
expect(mockedAxios.get).toHaveBeenCalledWith("http://localhost:4000/litellm/v1/model/info", {
96+
headers: {
97+
Authorization: "Bearer test-api-key",
98+
"Content-Type": "application/json",
99+
...DEFAULT_HEADERS,
100+
},
101+
timeout: 5000,
102+
})
103+
})
104+
105+
it("handles base URLs with query parameters correctly", async () => {
106+
const mockResponse = {
107+
data: {
108+
data: [],
109+
},
110+
}
111+
112+
mockedAxios.get.mockResolvedValue(mockResponse)
113+
114+
await getLiteLLMModels("test-api-key", "http://localhost:4000/litellm?key=value")
115+
116+
expect(mockedAxios.get).toHaveBeenCalledWith("http://localhost:4000/litellm/v1/model/info?key=value", {
117+
headers: {
118+
Authorization: "Bearer test-api-key",
119+
"Content-Type": "application/json",
120+
...DEFAULT_HEADERS,
121+
},
122+
timeout: 5000,
123+
})
124+
})
125+
126+
it("handles base URLs with fragments correctly", async () => {
127+
const mockResponse = {
128+
data: {
129+
data: [],
130+
},
131+
}
132+
133+
mockedAxios.get.mockResolvedValue(mockResponse)
134+
135+
await getLiteLLMModels("test-api-key", "http://localhost:4000/litellm#section")
136+
137+
expect(mockedAxios.get).toHaveBeenCalledWith("http://localhost:4000/litellm/v1/model/info#section", {
138+
headers: {
139+
Authorization: "Bearer test-api-key",
140+
"Content-Type": "application/json",
141+
...DEFAULT_HEADERS,
142+
},
143+
timeout: 5000,
144+
})
145+
})
146+
147+
it("handles base URLs with port and no path correctly", async () => {
148+
const mockResponse = {
149+
data: {
150+
data: [],
151+
},
152+
}
153+
154+
mockedAxios.get.mockResolvedValue(mockResponse)
155+
156+
await getLiteLLMModels("test-api-key", "http://localhost:4000")
157+
158+
expect(mockedAxios.get).toHaveBeenCalledWith("http://localhost:4000/v1/model/info", {
159+
headers: {
160+
Authorization: "Bearer test-api-key",
161+
"Content-Type": "application/json",
162+
...DEFAULT_HEADERS,
163+
},
164+
timeout: 5000,
165+
})
166+
})
167+
42168
it("successfully fetches and formats LiteLLM models", async () => {
43169
const mockResponse = {
44170
data: {

src/api/providers/fetchers/litellm.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ export async function getLiteLLMModels(apiKey: string, baseUrl: string): Promise
2424
headers["Authorization"] = `Bearer ${apiKey}`
2525
}
2626
// Use URL constructor to properly join base URL and path
27-
const url = new URL("/v1/model/info", baseUrl).href
27+
// This approach handles all edge cases including paths, query params, and fragments
28+
const urlObj = new URL(baseUrl)
29+
// Normalize the pathname by removing trailing slashes and multiple slashes
30+
urlObj.pathname = urlObj.pathname.replace(/\/+$/, "").replace(/\/+/g, "/") + "/v1/model/info"
31+
const url = urlObj.href
2832
// Added timeout to prevent indefinite hanging
2933
const response = await axios.get(url, { headers, timeout: 5000 })
3034
const models: ModelRecord = {}

0 commit comments

Comments
 (0)