Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions packages/server/src/services/prompts-lists/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import axios from 'axios'
import promptsListsService from '.'

jest.mock('axios')

const mockedAxios = axios as jest.Mocked<typeof axios>

describe('promptsListsService.createPromptsList', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('fetches LangChain Hub prompts with a bounded timeout', async () => {
const repos = [{ id: 'owner/prompt', num_likes: 42 }]
mockedAxios.get.mockResolvedValue({ data: { repos } })

const result = await promptsListsService.createPromptsList({})

expect(mockedAxios.get).toHaveBeenCalledWith(
'https://api.hub.langchain.com/repos/?limit=100&has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false',
{ timeout: 10000 }
)
expect(result).toEqual({
status: 'OK',
repos
})
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add a test case to verify that the tags parameter is correctly appended to the URL query parameters. This will help prevent regressions and verify the fix for the URL query parameter formatting bug.

    })

    it('fetches LangChain Hub prompts with tags', async () => {
        const repos = [{ id: 'owner/prompt', num_likes: 42 }]
        mockedAxios.get.mockResolvedValue({ data: { repos } })

        const result = await promptsListsService.createPromptsList({ tags: 'agent' })

        expect(mockedAxios.get).toHaveBeenCalledWith(
            'https://api.hub.langchain.com/repos/?limit=100&tags=agent&has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false',
            { timeout: 10000 }
        )
        expect(result).toEqual({
            status: 'OK',
            repos
        })
    }


it('returns the existing fallback when LangChain Hub request fails', async () => {
mockedAxios.get.mockRejectedValue(new Error('timeout'))

await expect(promptsListsService.createPromptsList({})).resolves.toEqual({
status: 'ERROR',
repos: []
})
})
})
4 changes: 3 additions & 1 deletion packages/server/src/services/prompts-lists/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import axios from 'axios'

const LANGCHAIN_HUB_PROMPTS_TIMEOUT_MS = 10000

const createPromptsList = async (requestBody: any) => {
try {
const tags = requestBody.tags ? `tags=${requestBody.tags}` : ''
// Default to 100, TODO: add pagination and use offset & limit
const url = `https://api.hub.langchain.com/repos/?limit=100&${tags}has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false`
const resp = await axios.get(url)
const resp = await axios.get(url, { timeout: LANGCHAIN_HUB_PROMPTS_TIMEOUT_MS })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There is a bug in the URL construction on line 9 when tags are provided. If requestBody.tags is present, tags is set to 'tags=some-tag'. When interpolated into the URL:
...&${tags}has_commits=true...
It results in:
...&tags=some-taghas_commits=true...
This merges the tags and has_commits parameters, making the request malformed and breaking the tag filtering.

Additionally, if resp.data.repos is falsy, the function currently reaches the end of the try block without returning anything, resulting in undefined being returned instead of a consistent { status: 'ERROR', repos: [] } or { status: 'OK', repos: [] } structure.

To fix both issues, consider refactoring the URL construction using URLSearchParams and ensuring a consistent return value:

const createPromptsList = async (requestBody: any) => {
    try {
        const params = new URLSearchParams({
            limit: '100',
            has_commits: 'true',
            sort_field: 'num_likes',
            sort_direction: 'desc',
            is_archived: 'false'
        })
        if (requestBody.tags) {
            params.append('tags', requestBody.tags)
        }
        const url = `https://api.hub.langchain.com/repos/?${params.toString()}`
        const resp = await axios.get(url, { timeout: LANGCHAIN_HUB_PROMPTS_TIMEOUT_MS })
        
        if (resp.data?.repos) {
            return {
                status: 'OK',
                repos: resp.data.repos
            }
        }
        return { status: 'ERROR', repos: [] }
    } catch (error) {
        return { status: 'ERROR', repos: [] }
    }
}

if (resp.data.repos) {
return {
status: 'OK',
Expand Down