diff --git a/typescript/examples/output_parsing.ts b/typescript/examples/output_parsing.ts index b9de64809..f580db943 100644 --- a/typescript/examples/output_parsing.ts +++ b/typescript/examples/output_parsing.ts @@ -12,7 +12,6 @@ const MovieReview = z.object({ const generateMovieReview = ell.complex({ model: "gpt-4o-mini", response_format: MovieReview , - exempt_from_tracking: true }, async (movie: string) => { return [ ell.system("You are a movie review generator. Given the name of a movie, you need to return a structured review."), @@ -24,8 +23,8 @@ const generateMovieReview = ell.complex({ ell.init({ store: './logdir', autocommit: true, verbose: true }) const reviewMessage = await generateMovieReview("The Matrix") + console.log(reviewMessage) const review = reviewMessage.parsed - review.rating.toExponential() console.log(`Movie: ${review.title}, Rating: ${review.rating}/10`) console.log(`Summary: ${review.summary}`) diff --git a/typescript/src/lmp/_track.ts b/typescript/src/lmp/_track.ts index 82e00b272..8ef6b50d0 100644 --- a/typescript/src/lmp/_track.ts +++ b/typescript/src/lmp/_track.ts @@ -89,7 +89,7 @@ const lmpTypeFromDefinitionType = (definitionType: LMPDefinitionType) => { /** * Invokes the LMP with tracking. - * @param lmp I + * @param lmp * @param args * @param f * @param a @@ -152,7 +152,7 @@ export const invokeWithTracking = async (lmp: LMPDefinition & { lmpId: string }, // // We get strange behavior if a breakpoint is set to a line that isn't one of the "blessed" possible breakpoint lines // (the program exits with code 0 unexpectedly) - // So we try to find the closest one + // So we try to find the closest one to the ending return statement of the LMP let bestBreakpoint = await getBestClosureInspectionBreakpoint(session, location.scriptId, { line: generatedPositionStart.line, endLine: generatedPositionEnd.line, @@ -188,6 +188,12 @@ export const invokeWithTracking = async (lmp: LMPDefinition & { lmpId: string }, }) const handleBreakpointHit = async ({ params }: { params: inspector.Debugger.PausedEventDataType }) => { + // TODO. For performance we should aggressively filter the breakpoints we set. + // This function runs whenever the debugger pauses, including a user's "step-into/step-over" etc. + // When using "step into/out/over", hitBreakpoints is empty + if (!params.hitBreakpoints || params.hitBreakpoints.length === 0) { + return + } logger.debug('Paused on breakpoint', { breakpoints: params.hitBreakpoints, // params: JSON.stringify(params, null, 2) @@ -195,7 +201,13 @@ export const invokeWithTracking = async (lmp: LMPDefinition & { lmpId: string }, const { callFrames } = params const scopes = callFrames[0].scopeChain - // Get the variables you're interested in + // Get the variables we're interested in + // + // TODO. Once we have clarified what is needed for free vars vs global vars + // we should be able to get everything we need here, potentially from scope.type === 'global' in addition to these + // There is additional line information on the scope that is available for us to use + // for checking we're in the right spot. + // For now we expect to do this work when we set breakpoints in the first place for (const scope of scopes) { if (scope.type === 'closure') { const result = (await session @@ -244,8 +256,9 @@ export const invokeWithTracking = async (lmp: LMPDefinition & { lmpId: string }, } } + // By this time we assume we captured all variables of interest session.off('Debugger.paused', handleBreakpointHit) - + resolve(undefined) } @@ -279,7 +292,7 @@ export const invokeWithTracking = async (lmp: LMPDefinition & { lmpId: string }, // todo. find what these refer to exactly initial_free_vars: {}, initial_global_vars: {}, - // todo. requires static analysis of direct children of this lmp definition + // todo. requires static analysis of direct children of this lmp definition? uses: [], }) } catch (e) { @@ -352,7 +365,7 @@ export const invokeWithTracking = async (lmp: LMPDefinition & { lmpId: string }, state_cache_key: '', consumes: [], }) - return result + return result.length === 1 ? result[0] : result } ) } diff --git a/typescript/src/serialize/sql.ts b/typescript/src/serialize/sql.ts index 0d5799cfa..1f5206cda 100644 --- a/typescript/src/serialize/sql.ts +++ b/typescript/src/serialize/sql.ts @@ -6,7 +6,7 @@ import * as sqlite3 from 'sqlite3' import { open, Database } from 'sqlite' import { LMPType } from '../lmp/types' import * as logging from '../util/_logging' -import { ISODateString } from './types' +import { ISODateString, WriteInvocationInput, WriteLMPInput } from './types' const logger = logging.getLogger('sql') const gzip = promisify(zlib.gzip) diff --git a/typescript/src/util/closure.ts b/typescript/src/util/closure.ts index d4b067045..41805b2b0 100644 --- a/typescript/src/util/closure.ts +++ b/typescript/src/util/closure.ts @@ -104,10 +104,10 @@ export async function getBestClosureInspectionBreakpoint( }, end: { scriptId, - // this helps single line functions - lineNumber: location.endLine + 1, + lineNumber: location.line === location.endLine ? location.line + 1 : location.endLine, }, }) + if (!possibleBreakpoints.locations || possibleBreakpoints.locations.length === 0) { return null } @@ -119,6 +119,21 @@ export async function getBestClosureInspectionBreakpoint( logger.debug('Using return breakpoint', { lastBreakpoint }) return lastBreakpoint } + // This is the best heuristic we have atm. Alternatives that may be more reliable: + // - retrive the return position from typescript ast, get the source mapped position of that, then find the closest available breakpoint to that location here. + // Note: When we always add 1 to the range above, v8 gives us + // a breakpoint that is column 0 and of `type: undefined` and a line past function's return statement. + // This causes some silent crash and should probably be reported to nodejs. + // We may want to avoid that situation in all cases by only using a return breakpoint. + const secondLastBreakpoint = possibleBreakpoints.locations[possibleBreakpoints.locations.length - 2] + if (secondLastBreakpoint.type === 'return') { + logger.debug('Last breakpoint not a return. Using second last breakpoint (return)', { + secondLastBreakpoint, + lastBreakpoint, + }) + return secondLastBreakpoint + } + logger.debug('No return breakpoint seemed safe. Using last breakpoint', { lastBreakpoint }) // use the last one regardless until we have a better idea return lastBreakpoint }