mirror of
https://github.com/FJurmanovic/sugarr.git
synced 2026-11-11 15:51:09 +00:00
Add MovieRecommenderTool and MovieSearchTool
This commit is contained in:
38
src/app/tools/MovieRecommenderTool.ts
Normal file
38
src/app/tools/MovieRecommenderTool.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { SchemaLlmTool } from '../../core/llm/SchemaLlmTool';
|
||||||
|
import type { RunContext } from '../../core/llm/RunContext';
|
||||||
|
import type { Movie, MovieSuggestion } from '../models';
|
||||||
|
|
||||||
|
export interface RecommendInput {
|
||||||
|
favorites: Movie[];
|
||||||
|
watched: Movie[];
|
||||||
|
library: Movie[];
|
||||||
|
previouslySuggested: Movie[];
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const movieSuggestionSchema = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
year: z.number(),
|
||||||
|
reason: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export class MovieRecommenderTool extends SchemaLlmTool<RecommendInput, MovieSuggestion[]> {
|
||||||
|
protected readonly promptName = 'movie-recommender';
|
||||||
|
protected readonly outputSchema = z.array(movieSuggestionSchema);
|
||||||
|
|
||||||
|
protected buildVariables(input: RecommendInput, _ctx: RunContext): Record<string, unknown> {
|
||||||
|
return {
|
||||||
|
count: input.count,
|
||||||
|
favorites: formatMovieList(input.favorites),
|
||||||
|
watched: formatMovieList(input.watched),
|
||||||
|
library: formatMovieList(input.library),
|
||||||
|
previouslySuggested: formatMovieList(input.previouslySuggested),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMovieList(movies: Movie[]): string {
|
||||||
|
if (movies.length === 0) return '(none)';
|
||||||
|
return movies.map((m) => `- ${m.name} (${m.year})`).join('\n');
|
||||||
|
}
|
||||||
24
src/app/tools/MovieSearchTool.ts
Normal file
24
src/app/tools/MovieSearchTool.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { SchemaLlmTool } from '../../core/llm/SchemaLlmTool';
|
||||||
|
import type { RunContext } from '../../core/llm/RunContext';
|
||||||
|
import type { MovieSuggestion } from '../models';
|
||||||
|
|
||||||
|
export interface SearchInput {
|
||||||
|
query: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const movieSuggestionSchema = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
year: z.number(),
|
||||||
|
reason: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export class MovieSearchTool extends SchemaLlmTool<SearchInput, MovieSuggestion[]> {
|
||||||
|
protected readonly promptName = 'movie-search';
|
||||||
|
protected readonly outputSchema = z.array(movieSuggestionSchema);
|
||||||
|
|
||||||
|
protected buildVariables(input: SearchInput, _ctx: RunContext): Record<string, unknown> {
|
||||||
|
return { query: input.query, count: input.count };
|
||||||
|
}
|
||||||
|
}
|
||||||
60
test/app/tools/MovieRecommenderTool.test.ts
Normal file
60
test/app/tools/MovieRecommenderTool.test.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { MovieRecommenderTool } from '../../../src/app/tools/MovieRecommenderTool';
|
||||||
|
import type { RunContext } from '../../../src/core/llm/RunContext';
|
||||||
|
import type { Movie } from '../../../src/app/models';
|
||||||
|
|
||||||
|
function makeTool() {
|
||||||
|
return new MovieRecommenderTool(
|
||||||
|
{} as unknown as ConstructorParameters<typeof MovieRecommenderTool>[0],
|
||||||
|
{} as unknown as ConstructorParameters<typeof MovieRecommenderTool>[1]
|
||||||
|
) as unknown as {
|
||||||
|
buildVariables(input: unknown, ctx: RunContext): Record<string, unknown>;
|
||||||
|
parseOutput(raw: string, ctx: RunContext): unknown;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const movie = (name: string, year: number): Movie => ({
|
||||||
|
name,
|
||||||
|
year,
|
||||||
|
imdbId: '',
|
||||||
|
tmdbId: '',
|
||||||
|
genres: [],
|
||||||
|
communityRating: 0,
|
||||||
|
isFavorite: false,
|
||||||
|
playCount: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('MovieRecommenderTool.buildVariables', () => {
|
||||||
|
it('formats favorites/watched/library/previouslySuggested as readable lists', () => {
|
||||||
|
const tool = makeTool();
|
||||||
|
const variables = tool.buildVariables(
|
||||||
|
{
|
||||||
|
favorites: [movie('Dune', 2021)],
|
||||||
|
watched: [],
|
||||||
|
library: [movie('Arrival', 2016)],
|
||||||
|
previouslySuggested: [],
|
||||||
|
count: 5,
|
||||||
|
},
|
||||||
|
{} as RunContext
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(variables).toEqual({
|
||||||
|
count: 5,
|
||||||
|
favorites: '- Dune (2021)',
|
||||||
|
watched: '(none)',
|
||||||
|
library: '- Arrival (2016)',
|
||||||
|
previouslySuggested: '(none)',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('MovieRecommenderTool.parseOutput', () => {
|
||||||
|
it('validates suggestions against the movie suggestion schema', () => {
|
||||||
|
const tool = makeTool();
|
||||||
|
const result = tool.parseOutput(
|
||||||
|
'[{"title":"Dune","year":2021,"reason":"sci-fi epic"}]',
|
||||||
|
{} as RunContext
|
||||||
|
);
|
||||||
|
expect(result).toEqual([{ title: 'Dune', year: 2021, reason: 'sci-fi epic' }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
15
test/app/tools/MovieSearchTool.test.ts
Normal file
15
test/app/tools/MovieSearchTool.test.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { MovieSearchTool } from '../../../src/app/tools/MovieSearchTool';
|
||||||
|
import type { RunContext } from '../../../src/core/llm/RunContext';
|
||||||
|
|
||||||
|
describe('MovieSearchTool.buildVariables', () => {
|
||||||
|
it('passes the query and requested count through as prompt variables', () => {
|
||||||
|
const tool = new MovieSearchTool(
|
||||||
|
{} as unknown as ConstructorParameters<typeof MovieSearchTool>[0],
|
||||||
|
{} as unknown as ConstructorParameters<typeof MovieSearchTool>[1]
|
||||||
|
) as unknown as { buildVariables(input: unknown, ctx: RunContext): Record<string, unknown> };
|
||||||
|
|
||||||
|
const variables = tool.buildVariables({ query: 'sci-fi like Blade Runner', count: 5 }, {} as RunContext);
|
||||||
|
expect(variables).toEqual({ query: 'sci-fi like Blade Runner', count: 5 });
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user