mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
context tools
This commit is contained in:
parent
18b278577d
commit
00da05f24f
5 changed files with 240 additions and 115 deletions
193
src/vs/platform/void/common/toolsService.ts
Normal file
193
src/vs/platform/void/common/toolsService.ts
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
import { URI } from '../../../base/common/uri'
|
||||
import { IModelService } from '../../../editor/common/services/model'
|
||||
import { VSReadFile } from '../../../workbench/contrib/void/browser/helpers/readFile'
|
||||
import { IFileService, IFileStat } from '../../files/common/files'
|
||||
import { registerSingleton, InstantiationType } from '../../instantiation/common/extensions'
|
||||
import { createDecorator } from '../../instantiation/common/instantiation'
|
||||
|
||||
|
||||
const pagination = {
|
||||
desc: `Very large results may be paginated (indicated in the result). Pagination fails gracefully if out of bounds or invalid page number.`,
|
||||
param: { pageNumber: { type: 'number', description: 'The page number (optional, defaults to 1).' }, }
|
||||
} as const
|
||||
|
||||
|
||||
|
||||
// we do this using Anthropic's style and convert to OpenAI style later
|
||||
export type InternalToolInfo = {
|
||||
description: string,
|
||||
params: {
|
||||
[paramName: string]: { type: string, description: string | undefined } // name -> type
|
||||
},
|
||||
required: string[], // required paramNames
|
||||
}
|
||||
const contextTools = {
|
||||
read_file: {
|
||||
description: 'Returns file contents of a given URI.',
|
||||
params: {
|
||||
uri: { type: 'string', description: undefined },
|
||||
},
|
||||
required: ['uri'],
|
||||
},
|
||||
|
||||
list_dir: {
|
||||
description: `Returns all file names and folder names in a given URI. ${pagination.desc}`,
|
||||
params: {
|
||||
uri: { type: 'string', description: undefined },
|
||||
...pagination.param
|
||||
},
|
||||
required: ['uri'],
|
||||
},
|
||||
|
||||
pathname_search: {
|
||||
description: `Returns all pathnames that match a given grep query. You should use this when looking for a file with a specific name or path. This does NOT search file content. ${pagination.desc}`,
|
||||
params: {
|
||||
query: { type: 'string', description: undefined },
|
||||
...pagination.param,
|
||||
},
|
||||
required: ['query']
|
||||
},
|
||||
|
||||
grep_search: {
|
||||
description: `Returns all code excerpts containing the given string or grep query. Does not search filename. As a follow-up, you may want to use read_file to view the full file contents of the results. ${pagination.desc}`,
|
||||
params: {
|
||||
query: { type: 'string', description: undefined },
|
||||
...pagination.param,
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
|
||||
// semantic_search: {
|
||||
// description: 'Searches files semantically for the given string query.',
|
||||
// // RAG
|
||||
// },
|
||||
|
||||
} as const satisfies { [name: string]: InternalToolInfo }
|
||||
|
||||
type ContextToolName = keyof typeof contextTools
|
||||
type ContextParamNames<T extends ContextToolName> = keyof typeof contextTools[T]['params']
|
||||
type ContextParams<T extends ContextToolName> = { [paramName in ContextParamNames<T>]: string }
|
||||
|
||||
type ContextToolCallFns = {
|
||||
[ToolName in ContextToolName]: ((p: (ContextParams<ToolName>)) => Promise<string>)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Generates something that looks like this:
|
||||
|
||||
+ folder1
|
||||
│ ├── file1.py
|
||||
│ ├── subfolder1
|
||||
│ │ ├── file1.json
|
||||
│ │ └── file2.py
|
||||
│ └── another_file.txt
|
||||
└── folder2
|
||||
├── script.js
|
||||
└── styles.css
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Generates a Markdown tree starting at the given URI.
|
||||
* The root folder is printed as a header (without a bullet).
|
||||
*/
|
||||
export async function generateMarkdownTree(fileService: IFileService, uri: URI): Promise<string> {
|
||||
|
||||
let output = ''
|
||||
|
||||
function traverseChildren(children: IFileStat[], depth: number) {
|
||||
const indentation = ' '.repeat(depth);
|
||||
for (const child of children) {
|
||||
output += `${indentation}- ${child.name}\n`;
|
||||
output += traverseChildren(child.children ?? [], depth + 1);
|
||||
}
|
||||
}
|
||||
const stat = await fileService.resolve(uri, { resolveMetadata: false });
|
||||
|
||||
// kickstart recursion
|
||||
output += `${stat.name}\n`;
|
||||
traverseChildren(stat.children ?? [], 1);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
const validateURI = (uriStr: unknown) => {
|
||||
if (typeof uriStr !== 'string') throw new Error('(uri was not a string)')
|
||||
console.log('uriStr!!!!', uriStr)
|
||||
const uri = URI.file(uriStr)
|
||||
console.log('uri!!!!', uri)
|
||||
return uri
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export interface IToolService {
|
||||
readonly _serviceBrand: undefined;
|
||||
callContextTool: <T extends ContextToolName>(toolName: T, params: ContextParams<T>) => Promise<string>
|
||||
}
|
||||
|
||||
export const IToolService = createDecorator<IToolService>('ToolService');
|
||||
|
||||
|
||||
// implemented by calling channel
|
||||
export class ToolService implements IToolService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
contextToolCallFns: ContextToolCallFns
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@IModelService modelService: IModelService,
|
||||
) {
|
||||
this.contextToolCallFns = {
|
||||
read_file: async ({ uri: uriStr }) => {
|
||||
const uri = validateURI(uriStr)
|
||||
const fileContents = await VSReadFile(modelService, uri)
|
||||
return fileContents ?? '(could not read file)'
|
||||
},
|
||||
list_dir: async ({ uri: uriStr }) => {
|
||||
const uri = validateURI(uriStr)
|
||||
const treeStr = await generateMarkdownTree(fileService, uri)
|
||||
return treeStr
|
||||
},
|
||||
pathname_search: async ({ query }) => {
|
||||
return ''
|
||||
},
|
||||
grep_search: async ({ query }) => {
|
||||
return ''
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
callContextTool: IToolService['callContextTool'] = (toolName, params) => {
|
||||
return this.contextToolCallFns[toolName](params)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IToolService, ToolService, InstantiationType.Eager);
|
||||
|
||||
|
|
@ -6,6 +6,27 @@
|
|||
import Anthropic from '@anthropic-ai/sdk';
|
||||
import { _InternalSendLLMChatMessageFnType } from '../../common/llmMessageTypes.js';
|
||||
import { anthropicMaxPossibleTokens } from '../../common/voidSettingsTypes.js';
|
||||
import { InternalToolInfo } from '../../common/toolsService.js';
|
||||
|
||||
|
||||
|
||||
|
||||
export const toAnthropicTool = (toolName: string, toolInfo: InternalToolInfo) => {
|
||||
const { description, params, required } = toolInfo
|
||||
return {
|
||||
name: toolName,
|
||||
description: description,
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: params,
|
||||
required: required,
|
||||
}
|
||||
} satisfies Anthropic.Messages.Tool
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const sendAnthropicChat: _InternalSendLLMChatMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,33 @@
|
|||
import OpenAI from 'openai';
|
||||
import { _InternalModelListFnType, _InternalSendLLMFIMMessageFnType, _InternalSendLLMChatMessageFnType } from '../../common/llmMessageTypes.js';
|
||||
import { Model } from 'openai/resources/models.js';
|
||||
import { InternalToolInfo } from '../../common/toolsService.js';
|
||||
// import { parseMaxTokensStr } from './util.js';
|
||||
|
||||
|
||||
// https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command
|
||||
// https://platform.openai.com/docs/guides/reasoning#advice-on-prompting
|
||||
// developer command - https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command
|
||||
// prompting - https://platform.openai.com/docs/guides/reasoning#advice-on-prompting
|
||||
|
||||
|
||||
export const toOpenAITool = (toolName: string, toolInfo: InternalToolInfo) => {
|
||||
const { description, params, required } = toolInfo
|
||||
return {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: toolName,
|
||||
description: description,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: params,
|
||||
required: required,
|
||||
}
|
||||
}
|
||||
} satisfies OpenAI.Chat.Completions.ChatCompletionTool
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// might not currently be used in the code
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import OpenAI from 'openai'
|
||||
|
||||
const pagination = {
|
||||
desc: `Very large results may be paginated (indicated in the result). Pagination fails gracefully if out of bounds or invalid page number.`,
|
||||
param: { pageNumber: { type: 'number', description: 'The page number (optional, defaults to 1).' }, }
|
||||
} as const
|
||||
|
||||
// we do this using Anthropic's style and convert to OpenAI style later
|
||||
type InternalToolInfo = {
|
||||
description: string,
|
||||
params: {
|
||||
[paramName: string]: { type: string, description: string | undefined } // name -> type
|
||||
},
|
||||
required: string[], // required paramNames
|
||||
}
|
||||
const contextTools = {
|
||||
read_file: {
|
||||
description: 'Returns file contents of a given URI.',
|
||||
params: {
|
||||
uri: { type: 'string', description: undefined },
|
||||
},
|
||||
required: ['uri'],
|
||||
},
|
||||
|
||||
list_dir: {
|
||||
description: `Returns all file names and folder names in a given URI. ${pagination.desc}`,
|
||||
params: {
|
||||
uri: { type: 'string', description: undefined },
|
||||
...pagination.param
|
||||
},
|
||||
required: ['uri'],
|
||||
},
|
||||
|
||||
pathname_search: {
|
||||
description: `Returns all pathnames that match a given grep query. You should use this when looking for a file with a specific name or path. This does NOT search file content. ${pagination.desc}`,
|
||||
params: {
|
||||
query: { type: 'string', description: undefined },
|
||||
...pagination.param,
|
||||
},
|
||||
required: ['query']
|
||||
},
|
||||
|
||||
grep_search: {
|
||||
description: `Returns all code excerpts containing the given string or grep query. Does not search filename. As a follow-up, you may want to use read_file to view the full file contents of the results. ${pagination.desc}`,
|
||||
params: {
|
||||
query: { type: 'string', description: undefined },
|
||||
...pagination.param,
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
|
||||
// semantic_search: {
|
||||
// description: 'Searches files semantically for the given string query.',
|
||||
// // RAG
|
||||
// },
|
||||
|
||||
} as const satisfies { [name: string]: InternalToolInfo }
|
||||
|
||||
type ContextToolName = keyof typeof contextTools
|
||||
type ContextParams<T extends ContextToolName> = keyof typeof contextTools[T]['params']
|
||||
|
||||
const contextFunctions: { [ToolName in ContextToolName]: (p: ({ [paramName in ContextParams<ToolName>]: string })) => string } = {
|
||||
read_file: ({ uri }) => {
|
||||
return ''
|
||||
},
|
||||
list_dir: ({ }) => {
|
||||
return ''
|
||||
},
|
||||
pathname_search: ({ }) => {
|
||||
return ''
|
||||
},
|
||||
grep_search: ({ }) => {
|
||||
return ''
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const toOpenAITool = (toolName: string, toolInfo: InternalToolInfo) => {
|
||||
const { description, params, required } = toolInfo
|
||||
return {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: toolName,
|
||||
description: description,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: params,
|
||||
required: required,
|
||||
}
|
||||
}
|
||||
} satisfies OpenAI.Chat.Completions.ChatCompletionTool
|
||||
}
|
||||
|
||||
|
||||
|
||||
const toAnthropicTool = (toolName: string, toolInfo: InternalToolInfo) => {
|
||||
const { description, params, required } = toolInfo
|
||||
return {
|
||||
name: toolName,
|
||||
description: description,
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: params,
|
||||
required: required,
|
||||
}
|
||||
} satisfies Anthropic.Messages.Tool
|
||||
}
|
||||
|
|
@ -133,8 +133,8 @@ Store Result: After computing fib(n), the result is stored in memo for future re
|
|||
`
|
||||
|
||||
|
||||
type FileSelnLocal = FileSelection & { content: string }
|
||||
const stringifyFileSelection = ({ fileURI, selectionStr, range, content }: FileSelnLocal) => {
|
||||
type FileSelnLocal = { fileURI: URI, content: string }
|
||||
const stringifyFileSelection = ({ fileURI, content }: FileSelnLocal) => {
|
||||
return `\
|
||||
${fileURI.fsPath}
|
||||
\`\`\`${filenameToVscodeLanguage(fileURI.fsPath) ?? ''}
|
||||
|
|
|
|||
Loading…
Reference in a new issue