@@ -1907,6 +1907,330 @@ Use pandas and summarize findings.`.split("\n"),
19071907 return [ ...messages ] . reverse ( ) . find ( ( message ) => message . role === "assistant" ) ;
19081908 } ;
19091909
1910+ it ( "should use temperature 0 by default when generating conversation titles" , async ( ) => {
1911+ const memory = new Memory ( {
1912+ storage : new InMemoryStorageAdapter ( ) ,
1913+ generateTitle : true ,
1914+ } ) ;
1915+ const agent = new Agent ( {
1916+ name : "TestAgent" ,
1917+ instructions : "Test" ,
1918+ model : mockModel as any ,
1919+ memory,
1920+ } ) ;
1921+ const span = {
1922+ end : vi . fn ( ) ,
1923+ setStatus : vi . fn ( ) ,
1924+ setAttribute : vi . fn ( ) ,
1925+ setAttributes : vi . fn ( ) ,
1926+ recordException : vi . fn ( ) ,
1927+ } ;
1928+ const context = {
1929+ operationId : "test-operation-id" ,
1930+ logger : {
1931+ debug : vi . fn ( ) ,
1932+ info : vi . fn ( ) ,
1933+ warn : vi . fn ( ) ,
1934+ error : vi . fn ( ) ,
1935+ trace : vi . fn ( ) ,
1936+ fatal : vi . fn ( ) ,
1937+ child : vi . fn ( ) . mockReturnThis ( ) ,
1938+ } ,
1939+ context : new Map ( ) ,
1940+ systemContext : new Map ( ) ,
1941+ isActive : true ,
1942+ traceContext : {
1943+ createChildSpan : vi . fn ( ) . mockReturnValue ( span ) ,
1944+ withSpan : vi . fn ( ) . mockImplementation ( async ( _span , fn ) => await fn ( ) ) ,
1945+ } ,
1946+ abortController : new AbortController ( ) ,
1947+ startTime : new Date ( ) ,
1948+ } ;
1949+
1950+ vi . mocked ( ai . generateText ) . mockResolvedValue ( {
1951+ text : "Rome Weekend Plan" ,
1952+ content : [ { type : "text" , text : "Rome Weekend Plan" } ] ,
1953+ reasoning : [ ] ,
1954+ files : [ ] ,
1955+ sources : [ ] ,
1956+ toolCalls : [ ] ,
1957+ toolResults : [ ] ,
1958+ finishReason : "stop" ,
1959+ usage : providerUsage ,
1960+ warnings : [
1961+ {
1962+ type : "unsupported" ,
1963+ feature : "temperature" ,
1964+ details : "temperature is not supported for reasoning models" ,
1965+ } ,
1966+ ] ,
1967+ request : { } ,
1968+ response : {
1969+ id : "test-response" ,
1970+ modelId : "test-model" ,
1971+ timestamp : new Date ( ) ,
1972+ messages : createAssistantResponseMessages ( "Rome Weekend Plan" ) ,
1973+ } ,
1974+ steps : [ ] ,
1975+ } as any ) ;
1976+
1977+ const titleGenerator = ( agent as any ) . createConversationTitleGenerator ( memory ) ;
1978+ const title = await titleGenerator ( {
1979+ input : "Plan a weekend trip to Rome." ,
1980+ context,
1981+ defaultTitle : "Conversation" ,
1982+ } ) ;
1983+
1984+ expect ( title ) . toBe ( "Rome Weekend Plan" ) ;
1985+ const generateTextCall = vi . mocked ( ai . generateText ) . mock . calls [ 0 ] [ 0 ] as Record <
1986+ string ,
1987+ unknown
1988+ > ;
1989+ expect ( generateTextCall . temperature ) . toBe ( 0 ) ;
1990+ expect ( generateTextCall . maxOutputTokens ) . toBe ( 32 ) ;
1991+ const createChildSpanCall = context . traceContext . createChildSpan . mock . calls [ 0 ] [ 2 ] as {
1992+ attributes : Record < string , unknown > ;
1993+ } ;
1994+ expect ( createChildSpanCall . attributes [ "llm.temperature" ] ) . toBe ( 0 ) ;
1995+ expect ( context . logger . warn ) . toHaveBeenCalledWith (
1996+ "[Memory] Conversation title generation model does not support temperature" ,
1997+ expect . objectContaining ( {
1998+ hint : expect . stringContaining ( "generateTitle.temperature" ) ,
1999+ warning : expect . stringContaining ( "temperature" ) ,
2000+ } ) ,
2001+ ) ;
2002+ } ) ;
2003+
2004+ it ( "should omit temperature when title generation temperature is null" , async ( ) => {
2005+ const memory = new Memory ( {
2006+ storage : new InMemoryStorageAdapter ( ) ,
2007+ generateTitle : { enabled : true , temperature : null } ,
2008+ } ) ;
2009+ const agent = new Agent ( {
2010+ name : "TestAgent" ,
2011+ instructions : "Test" ,
2012+ model : mockModel as any ,
2013+ memory,
2014+ } ) ;
2015+ const span = {
2016+ end : vi . fn ( ) ,
2017+ setStatus : vi . fn ( ) ,
2018+ setAttribute : vi . fn ( ) ,
2019+ setAttributes : vi . fn ( ) ,
2020+ recordException : vi . fn ( ) ,
2021+ } ;
2022+ const context = {
2023+ operationId : "test-operation-id" ,
2024+ logger : {
2025+ debug : vi . fn ( ) ,
2026+ info : vi . fn ( ) ,
2027+ warn : vi . fn ( ) ,
2028+ error : vi . fn ( ) ,
2029+ trace : vi . fn ( ) ,
2030+ fatal : vi . fn ( ) ,
2031+ child : vi . fn ( ) . mockReturnThis ( ) ,
2032+ } ,
2033+ context : new Map ( ) ,
2034+ systemContext : new Map ( ) ,
2035+ isActive : true ,
2036+ traceContext : {
2037+ createChildSpan : vi . fn ( ) . mockReturnValue ( span ) ,
2038+ withSpan : vi . fn ( ) . mockImplementation ( async ( _span , fn ) => await fn ( ) ) ,
2039+ } ,
2040+ abortController : new AbortController ( ) ,
2041+ startTime : new Date ( ) ,
2042+ } ;
2043+
2044+ vi . mocked ( ai . generateText ) . mockResolvedValue ( {
2045+ text : "Rome Weekend Plan" ,
2046+ content : [ { type : "text" , text : "Rome Weekend Plan" } ] ,
2047+ reasoning : [ ] ,
2048+ files : [ ] ,
2049+ sources : [ ] ,
2050+ toolCalls : [ ] ,
2051+ toolResults : [ ] ,
2052+ finishReason : "stop" ,
2053+ usage : providerUsage ,
2054+ warnings : [ ] ,
2055+ request : { } ,
2056+ response : {
2057+ id : "test-response" ,
2058+ modelId : "test-model" ,
2059+ timestamp : new Date ( ) ,
2060+ messages : createAssistantResponseMessages ( "Rome Weekend Plan" ) ,
2061+ } ,
2062+ steps : [ ] ,
2063+ } as any ) ;
2064+
2065+ const titleGenerator = ( agent as any ) . createConversationTitleGenerator ( memory ) ;
2066+ const title = await titleGenerator ( {
2067+ input : "Plan a weekend trip to Rome." ,
2068+ context,
2069+ defaultTitle : "Conversation" ,
2070+ } ) ;
2071+
2072+ expect ( title ) . toBe ( "Rome Weekend Plan" ) ;
2073+ const generateTextCall = vi . mocked ( ai . generateText ) . mock . calls [ 0 ] [ 0 ] as Record <
2074+ string ,
2075+ unknown
2076+ > ;
2077+ expect ( generateTextCall ) . not . toHaveProperty ( "temperature" ) ;
2078+ const createChildSpanCall = context . traceContext . createChildSpan . mock . calls [ 0 ] [ 2 ] as {
2079+ attributes : Record < string , unknown > ;
2080+ } ;
2081+ expect ( createChildSpanCall . attributes ) . not . toHaveProperty ( "llm.temperature" ) ;
2082+ } ) ;
2083+
2084+ it ( "should warn when conversation title generation returns an empty title" , async ( ) => {
2085+ const memory = new Memory ( {
2086+ storage : new InMemoryStorageAdapter ( ) ,
2087+ generateTitle : true ,
2088+ } ) ;
2089+ const agent = new Agent ( {
2090+ name : "TestAgent" ,
2091+ instructions : "Test" ,
2092+ model : mockModel as any ,
2093+ memory,
2094+ } ) ;
2095+ const span = {
2096+ end : vi . fn ( ) ,
2097+ setStatus : vi . fn ( ) ,
2098+ setAttribute : vi . fn ( ) ,
2099+ setAttributes : vi . fn ( ) ,
2100+ recordException : vi . fn ( ) ,
2101+ } ;
2102+ const context = {
2103+ operationId : "test-operation-id" ,
2104+ logger : {
2105+ debug : vi . fn ( ) ,
2106+ info : vi . fn ( ) ,
2107+ warn : vi . fn ( ) ,
2108+ error : vi . fn ( ) ,
2109+ trace : vi . fn ( ) ,
2110+ fatal : vi . fn ( ) ,
2111+ child : vi . fn ( ) . mockReturnThis ( ) ,
2112+ } ,
2113+ context : new Map ( ) ,
2114+ systemContext : new Map ( ) ,
2115+ isActive : true ,
2116+ traceContext : {
2117+ createChildSpan : vi . fn ( ) . mockReturnValue ( span ) ,
2118+ withSpan : vi . fn ( ) . mockImplementation ( async ( _span , fn ) => await fn ( ) ) ,
2119+ } ,
2120+ abortController : new AbortController ( ) ,
2121+ startTime : new Date ( ) ,
2122+ } ;
2123+
2124+ vi . mocked ( ai . generateText ) . mockResolvedValue ( {
2125+ text : " " ,
2126+ content : [ { type : "text" , text : " " } ] ,
2127+ reasoning : [ ] ,
2128+ files : [ ] ,
2129+ sources : [ ] ,
2130+ toolCalls : [ ] ,
2131+ toolResults : [ ] ,
2132+ finishReason : "stop" ,
2133+ usage : providerUsage ,
2134+ warnings : [ ] ,
2135+ request : { } ,
2136+ response : {
2137+ id : "test-response" ,
2138+ modelId : "test-model" ,
2139+ timestamp : new Date ( ) ,
2140+ messages : createAssistantResponseMessages ( " " ) ,
2141+ } ,
2142+ providerMetadata : { provider : { reason : "empty-output" } } ,
2143+ steps : [ ] ,
2144+ } as any ) ;
2145+
2146+ const titleGenerator = ( agent as any ) . createConversationTitleGenerator ( memory ) ;
2147+ const title = await titleGenerator ( {
2148+ input : "Plan a weekend trip to Rome." ,
2149+ context,
2150+ defaultTitle : "Conversation" ,
2151+ } ) ;
2152+
2153+ expect ( title ) . toBeNull ( ) ;
2154+ expect ( context . logger . warn ) . toHaveBeenCalledWith (
2155+ "[Memory] Conversation title generation returned an empty title" ,
2156+ expect . objectContaining ( {
2157+ text : " " ,
2158+ finishReason : "stop" ,
2159+ } ) ,
2160+ ) ;
2161+ } ) ;
2162+
2163+ it ( "should keep full conversation title generation errors at debug level" , async ( ) => {
2164+ const memory = new Memory ( {
2165+ storage : new InMemoryStorageAdapter ( ) ,
2166+ generateTitle : true ,
2167+ } ) ;
2168+ const agent = new Agent ( {
2169+ name : "TestAgent" ,
2170+ instructions : "Test" ,
2171+ model : mockModel as any ,
2172+ memory,
2173+ } ) ;
2174+ const span = {
2175+ end : vi . fn ( ) ,
2176+ setStatus : vi . fn ( ) ,
2177+ setAttribute : vi . fn ( ) ,
2178+ setAttributes : vi . fn ( ) ,
2179+ recordException : vi . fn ( ) ,
2180+ } ;
2181+ const context = {
2182+ operationId : "test-operation-id" ,
2183+ logger : {
2184+ debug : vi . fn ( ) ,
2185+ info : vi . fn ( ) ,
2186+ warn : vi . fn ( ) ,
2187+ error : vi . fn ( ) ,
2188+ trace : vi . fn ( ) ,
2189+ fatal : vi . fn ( ) ,
2190+ child : vi . fn ( ) . mockReturnThis ( ) ,
2191+ } ,
2192+ context : new Map ( ) ,
2193+ systemContext : new Map ( ) ,
2194+ isActive : true ,
2195+ traceContext : {
2196+ createChildSpan : vi . fn ( ) . mockReturnValue ( span ) ,
2197+ withSpan : vi . fn ( ) . mockImplementation ( async ( _span , fn ) => await fn ( ) ) ,
2198+ } ,
2199+ abortController : new AbortController ( ) ,
2200+ startTime : new Date ( ) ,
2201+ } ;
2202+
2203+ vi . mocked ( ai . generateText ) . mockRejectedValue ( new Error ( "Unsupported temperature" ) ) ;
2204+
2205+ const titleGenerator = ( agent as any ) . createConversationTitleGenerator ( memory ) ;
2206+ const title = await titleGenerator ( {
2207+ input : "Plan a weekend trip to Rome." ,
2208+ context,
2209+ defaultTitle : "Conversation" ,
2210+ } ) ;
2211+
2212+ expect ( title ) . toBeNull ( ) ;
2213+ expect ( context . logger . warn ) . toHaveBeenCalledWith (
2214+ "[Memory] Failed to generate conversation title" ,
2215+ expect . not . objectContaining ( {
2216+ error : expect . anything ( ) ,
2217+ } ) ,
2218+ ) ;
2219+ expect ( context . logger . warn ) . toHaveBeenCalledWith (
2220+ "[Memory] Failed to generate conversation title" ,
2221+ expect . objectContaining ( {
2222+ message : "Unsupported temperature" ,
2223+ hint : expect . stringContaining ( "generateTitle.temperature" ) ,
2224+ } ) ,
2225+ ) ;
2226+ expect ( context . logger . debug ) . toHaveBeenCalledWith (
2227+ "[Memory] Full error for title generation" ,
2228+ expect . objectContaining ( {
2229+ error : expect . any ( String ) ,
2230+ } ) ,
2231+ ) ;
2232+ } ) ;
2233+
19102234 it ( "should initialize with memory" , ( ) => {
19112235 const memory = new Memory ( {
19122236 storage : new InMemoryStorageAdapter ( ) ,
0 commit comments