diff --git a/e2e/runtime_test.go b/e2e/runtime_test.go index 3e7f9367..dd94b41b 100644 --- a/e2e/runtime_test.go +++ b/e2e/runtime_test.go @@ -31,8 +31,8 @@ func TestRuntime_OpenAI_Basic(t *testing.T) { require.NoError(t, err) response := sess.GetLastAssistantMessageContent() - assert.Equal(t, "2 + 2 is equal to 4.", response) - assert.Equal(t, "Simple Math Calculation", sess.Title) + assert.Equal(t, "2 + 2 equals 4.", response) + assert.Equal(t, "Basic Math Question", sess.Title) } func TestRuntime_Mistral_Basic(t *testing.T) { @@ -55,5 +55,5 @@ func TestRuntime_Mistral_Basic(t *testing.T) { response := sess.GetLastAssistantMessageContent() assert.Equal(t, "The sum of 2 + 2 is 4.", response) - assert.Equal(t, "Basic Arithmetic: Sum of 2 and 2", sess.Title) + assert.Equal(t, "Math Basics: Simple Addition", sess.Title) } diff --git a/e2e/testdata/cassettes/TestRuntime_Mistral_Basic.yaml b/e2e/testdata/cassettes/TestRuntime_Mistral_Basic.yaml index a6ed3489..e2e4183e 100644 --- a/e2e/testdata/cassettes/TestRuntime_Mistral_Basic.yaml +++ b/e2e/testdata/cassettes/TestRuntime_Mistral_Basic.yaml @@ -1,83 +1,91 @@ --- version: 2 interactions: -- id: 0 - request: - proto: HTTP/1.1 - proto_major: 1 - proto_minor: 1 - content_length: 0 - host: api.mistral.ai - body: "{\"messages\":[{\"content\":\"You are a knowledgeable assistant that helps users with various tasks.\\nBe helpful, accurate, and concise in your responses.\\n\",\"role\":\"system\"},{\"content\":\"What's 2+2?\",\"role\":\"user\"}],\"model\":\"mistral-small\",\"stream_options\":{\"include_usage\":true},\"stream\":true}" - url: https://api.mistral.ai/v1/chat/completions - method: POST - response: - proto: HTTP/2.0 - proto_major: 2 - proto_minor: 0 - content_length: 144 - body: "{\"object\":\"error\",\"message\":\"Service tier capacity exceeded for this model.\",\"type\":\"service_tier_capacity_exceeded\",\"param\":null,\"code\":\"3505\"}" - headers: {} - status: 429 Too Many Requests - code: 429 - duration: 150.027666ms -- id: 1 - request: - proto: HTTP/1.1 - proto_major: 1 - proto_minor: 1 - content_length: 0 - host: api.mistral.ai - body: "{\"messages\":[{\"content\":\"You are a helpful AI assistant that generates concise, descriptive titles for conversations. You will be given a conversation history and asked to create a title that captures the main topic.\",\"role\":\"system\"},{\"content\":\"Based on the following message a user sent to an AI assistant, generate a short, descriptive title (maximum 50 characters) that captures the main topic or purpose of the conversation. Return ONLY the title text, nothing else.\\n\\nUser message: What's 2+2?\\n\\n\",\"role\":\"user\"}],\"model\":\"mistral-small\",\"max_tokens\":100,\"stream_options\":{\"include_usage\":true},\"stream\":true}" - url: https://api.mistral.ai/v1/chat/completions - method: POST - response: - proto: HTTP/2.0 - proto_major: 2 - proto_minor: 0 - content_length: -1 - body: "data: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"Basic\"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstu\"}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" Ar\"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstuvwxyz01\"}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ithmetic\"},\"finish_reason\":null}],\"p\":\"abcdefghij\"}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\":\"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnop\"}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" Sum\"},\"finish_reason\":null}],\"p\":\"a\"}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" of\"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstuvwx\"}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" \"},\"finish_reason\":null}],\"p\":\"abcde\"}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"2\"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstuvwxyz01234\"}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" and\"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstuvwxyz012345\"}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" \"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstuv\"}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"2\"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstuvwxyz01234567\"}\n\ndata: {\"id\":\"22d3e79db32e469e983e04c9d630461a\",\"object\":\"chat.completion.chunk\",\"created\":1763739255,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\"},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":95,\"total_tokens\":107,\"completion_tokens\":12},\"p\":\"abcdefghijklmnopqrstuvwxy\"}\n\ndata: [DONE]\n\n" - headers: {} - status: 200 OK - code: 200 - duration: 178.825584ms -- id: 2 - request: - proto: HTTP/1.1 - proto_major: 1 - proto_minor: 1 - content_length: 0 - host: api.mistral.ai - body: "{\"messages\":[{\"content\":\"You are a knowledgeable assistant that helps users with various tasks.\\nBe helpful, accurate, and concise in your responses.\\n\",\"role\":\"system\"},{\"content\":\"What's 2+2?\",\"role\":\"user\"}],\"model\":\"mistral-small\",\"stream_options\":{\"include_usage\":true},\"stream\":true}" - url: https://api.mistral.ai/v1/chat/completions - method: POST - response: - proto: HTTP/2.0 - proto_major: 2 - proto_minor: 0 - content_length: 144 - body: "{\"object\":\"error\",\"message\":\"Service tier capacity exceeded for this model.\",\"type\":\"service_tier_capacity_exceeded\",\"param\":null,\"code\":\"3505\"}" - headers: {} - status: 429 Too Many Requests - code: 429 - duration: 106.131458ms -- id: 3 - request: - proto: HTTP/1.1 - proto_major: 1 - proto_minor: 1 - content_length: 0 - host: api.mistral.ai - body: "{\"messages\":[{\"content\":\"You are a knowledgeable assistant that helps users with various tasks.\\nBe helpful, accurate, and concise in your responses.\\n\",\"role\":\"system\"},{\"content\":\"What's 2+2?\",\"role\":\"user\"}],\"model\":\"mistral-small\",\"stream_options\":{\"include_usage\":true},\"stream\":true}" - url: https://api.mistral.ai/v1/chat/completions - method: POST - response: - proto: HTTP/2.0 - proto_major: 2 - proto_minor: 0 - content_length: -1 - body: "data: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"The\"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrs\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" sum\"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstuvwxy\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" of\"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstuvwxyz01234567\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" \"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstuvwx\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"2\"},\"finish_reason\":null}],\"p\":\"abcdefghij\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" +\"},\"finish_reason\":null}],\"p\":\"abcdef\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" \"},\"finish_reason\":null}],\"p\":\"abcd\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"2\"},\"finish_reason\":null}],\"p\":\"abcdefghij\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" is\"},\"finish_reason\":null}],\"p\":\"abcdefghi\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" \"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstuvwxyz\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"4\"},\"finish_reason\":null}],\"p\":\"abcdefghijklm\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\".\"},\"finish_reason\":null}],\"p\":\"abcdefghijklmnopqrstuvwxyz01\"}\n\ndata: {\"id\":\"83ed4dcd54424114974dc875355245e5\",\"object\":\"chat.completion.chunk\",\"created\":1763739257,\"model\":\"mistral-small\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\"},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":35,\"total_tokens\":48,\"completion_tokens\":13},\"p\":\"abcdefghijklmnopqrstuvwxyz012\"}\n\ndata: [DONE]\n\n" - headers: {} - status: 200 OK - code: 200 - duration: 153.283583ms + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + host: api.mistral.ai + body: '{"messages":[{"content":"You are a helpful AI assistant that generates concise, descriptive titles for conversations. You will be given a conversation history and asked to create a title that captures the main topic.","role":"system"},{"content":"Based on the following message a user sent to an AI assistant, generate a short, descriptive title (maximum 50 characters) that captures the main topic or purpose of the conversation. Return ONLY the title text, nothing else.\n\nUser message: What''s 2+2?\n\n","role":"user"}],"model":"mistral-small","max_tokens":20,"stream_options":{"include_usage":true},"stream":true}' + url: https://api.mistral.ai/v1/chat/completions + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: |+ + data: {"id":"64e7339a44a2476eb3de21fd5370b67e","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} + + data: {"id":"64e7339a44a2476eb3de21fd5370b67e","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":"Math"},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvw"} + + data: {"id":"64e7339a44a2476eb3de21fd5370b67e","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":" Bas"},"finish_reason":null}],"p":"abcdefghijklmnopqrst"} + + data: {"id":"64e7339a44a2476eb3de21fd5370b67e","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":"ics"},"finish_reason":null}],"p":"abcdefghijklmnop"} + + data: {"id":"64e7339a44a2476eb3de21fd5370b67e","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":":"},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvwxyz0"} + + data: {"id":"64e7339a44a2476eb3de21fd5370b67e","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":" Simple"},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvwx"} + + data: {"id":"64e7339a44a2476eb3de21fd5370b67e","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":" Addition"},"finish_reason":null}],"p":"abcdefghijk"} + + data: {"id":"64e7339a44a2476eb3de21fd5370b67e","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop"}],"usage":{"prompt_tokens":95,"total_tokens":102,"completion_tokens":7},"p":"abcdefghijklmnopqrstuvwxyz01234567"} + + data: [DONE] + + headers: {} + status: 200 OK + code: 200 + duration: 170.585708ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + host: api.mistral.ai + body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"What''s 2+2?","role":"user"}],"model":"mistral-small","stream_options":{"include_usage":true},"stream":true}' + url: https://api.mistral.ai/v1/chat/completions + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: |+ + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":"The"},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvwxyz"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":" sum"},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvwx"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":" of"},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvwxyz0123456"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":" "},"finish_reason":null}],"p":"abcdefghi"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":"2"},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvwxyz01"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":" +"},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvwxyz01234"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":" "},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvwxyz0123456789"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":"2"},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvwxyz01"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":" is"},"finish_reason":null}],"p":"abcdefghijklmnopqrstuv"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":" "},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvwxyz0"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":"4"},"finish_reason":null}],"p":"abcdefghijklmnopqrstuvwxyz012345"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":"."},"finish_reason":null}],"p":"abcdefghijklmnopqrstu"} + + data: {"id":"7d923d77d213429980bd9cef9ea3c507","object":"chat.completion.chunk","created":1764948624,"model":"mistral-small","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop"}],"usage":{"prompt_tokens":35,"total_tokens":48,"completion_tokens":13},"p":"a"} + + data: [DONE] + + headers: {} + status: 200 OK + code: 200 + duration: 395.9145ms diff --git a/e2e/testdata/cassettes/TestRuntime_OpenAI_Basic.yaml b/e2e/testdata/cassettes/TestRuntime_OpenAI_Basic.yaml index 8b1355c0..1edb59a9 100644 --- a/e2e/testdata/cassettes/TestRuntime_OpenAI_Basic.yaml +++ b/e2e/testdata/cassettes/TestRuntime_OpenAI_Basic.yaml @@ -1,43 +1,81 @@ --- version: 2 interactions: -- id: 0 - request: - proto: HTTP/1.1 - proto_major: 1 - proto_minor: 1 - content_length: 0 - host: api.openai.com - body: "{\"messages\":[{\"content\":\"You are a helpful AI assistant that generates concise, descriptive titles for conversations. You will be given a conversation history and asked to create a title that captures the main topic.\",\"role\":\"system\"},{\"content\":\"Based on the following message a user sent to an AI assistant, generate a short, descriptive title (maximum 50 characters) that captures the main topic or purpose of the conversation. Return ONLY the title text, nothing else.\\n\\nUser message: What's 2+2?\\n\\n\",\"role\":\"user\"}],\"model\":\"gpt-3.5-turbo\",\"max_tokens\":100,\"stream_options\":{\"include_usage\":true},\"stream\":true}" - url: https://api.openai.com/v1/chat/completions - method: POST - response: - proto: HTTP/2.0 - proto_major: 2 - proto_minor: 0 - content_length: -1 - body: "data: {\"id\":\"chatcmpl-CeNLtVCFNU3lBc6gInKVXnHuI6ehW\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"N896aZOS\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtVCFNU3lBc6gInKVXnHuI6ehW\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\"Simple\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"X2QH\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtVCFNU3lBc6gInKVXnHuI6ehW\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\" Math\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"3Vsm4\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtVCFNU3lBc6gInKVXnHuI6ehW\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\" Calculation\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"6Du58SsRk5FWED\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtVCFNU3lBc6gInKVXnHuI6ehW\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":null,\"obfuscation\":\"9GgV\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtVCFNU3lBc6gInKVXnHuI6ehW\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[],\"usage\":{\"prompt_tokens\":100,\"completion_tokens\":3,\"total_tokens\":103,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}},\"obfuscation\":\"lwKM2Tep\"}\n\ndata: [DONE]\n\n" - headers: {} - status: 200 OK - code: 200 - duration: 679.727208ms -- id: 1 - request: - proto: HTTP/1.1 - proto_major: 1 - proto_minor: 1 - content_length: 0 - host: api.openai.com - body: "{\"messages\":[{\"content\":\"You are a knowledgeable assistant that helps users with various tasks.\\nBe helpful, accurate, and concise in your responses.\\n\",\"role\":\"system\"},{\"content\":\"What's 2+2?\",\"role\":\"user\"}],\"model\":\"gpt-3.5-turbo\",\"stream_options\":{\"include_usage\":true},\"stream\":true}" - url: https://api.openai.com/v1/chat/completions - method: POST - response: - proto: HTTP/2.0 - proto_major: 2 - proto_minor: 0 - content_length: -1 - body: "data: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"B3pHPFXe\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\"2\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"SrGwGZGh8\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\" +\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"vmpMm7vm\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\" \"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"93ZI2bCLW\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\"2\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"xpZzu22D3\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\" is\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"umyd0qS\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\" equal\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"1RQ1\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\" to\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"ctoYIye\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\" \"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"aSTWAgLB2\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\"4\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"DJl00XEnR\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{\"content\":\".\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null,\"obfuscation\":\"pLR6OOk1T\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":null,\"obfuscation\":\"xQOL\"}\n\ndata: {\"id\":\"chatcmpl-CeNLtbeny7kGGVGbPxRiHjb9AXSjI\",\"object\":\"chat.completion.chunk\",\"created\":1763738921,\"model\":\"gpt-3.5-turbo-0125\",\"service_tier\":\"default\",\"system_fingerprint\":null,\"choices\":[],\"usage\":{\"prompt_tokens\":41,\"completion_tokens\":10,\"total_tokens\":51,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}},\"obfuscation\":\"F1NfkDIir\"}\n\ndata: [DONE]\n\n" - headers: {} - status: 200 OK - code: 200 - duration: 1.184495709s + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + host: api.openai.com + body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"What''s 2+2?","role":"user"}],"model":"gpt-3.5-turbo","stream_options":{"include_usage":true},"stream":true}' + url: https://api.openai.com/v1/chat/completions + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: |+ + data: {"id":"chatcmpl-CjS3FGKDoLn6D7C8N7al2izQSygLt","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"XT00DLhE"} + + data: {"id":"chatcmpl-CjS3FGKDoLn6D7C8N7al2izQSygLt","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"2"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"x7a4nz8rQ"} + + data: {"id":"chatcmpl-CjS3FGKDoLn6D7C8N7al2izQSygLt","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" +"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"D6S7pgk0"} + + data: {"id":"chatcmpl-CjS3FGKDoLn6D7C8N7al2izQSygLt","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"jYidOIPQM"} + + data: {"id":"chatcmpl-CjS3FGKDoLn6D7C8N7al2izQSygLt","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"2"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"65iEUD2XA"} + + data: {"id":"chatcmpl-CjS3FGKDoLn6D7C8N7al2izQSygLt","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" equals"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"rd8"} + + data: {"id":"chatcmpl-CjS3FGKDoLn6D7C8N7al2izQSygLt","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"qR95qHeJp"} + + data: {"id":"chatcmpl-CjS3FGKDoLn6D7C8N7al2izQSygLt","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"4"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"0SXEe3HKa"} + + data: {"id":"chatcmpl-CjS3FGKDoLn6D7C8N7al2izQSygLt","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"pFITS9wEP"} + + data: {"id":"chatcmpl-CjS3FGKDoLn6D7C8N7al2izQSygLt","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"7xbs"} + + data: {"id":"chatcmpl-CjS3FGKDoLn6D7C8N7al2izQSygLt","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":41,"completion_tokens":8,"total_tokens":49,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"InSgBUbAjV"} + + data: [DONE] + + headers: {} + status: 200 OK + code: 200 + duration: 631.07725ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + host: api.openai.com + body: '{"messages":[{"content":"You are a helpful AI assistant that generates concise, descriptive titles for conversations. You will be given a conversation history and asked to create a title that captures the main topic.","role":"system"},{"content":"Based on the following message a user sent to an AI assistant, generate a short, descriptive title (maximum 50 characters) that captures the main topic or purpose of the conversation. Return ONLY the title text, nothing else.\n\nUser message: What''s 2+2?\n\n","role":"user"}],"model":"gpt-3.5-turbo","max_tokens":20,"stream_options":{"include_usage":true},"stream":true}' + url: https://api.openai.com/v1/chat/completions + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: |+ + data: {"id":"chatcmpl-CjS3FCtxMGFtMRCCHQxldEqze9nEi","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vFW9hNRL"} + + data: {"id":"chatcmpl-CjS3FCtxMGFtMRCCHQxldEqze9nEi","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"Basic"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"XN6gg"} + + data: {"id":"chatcmpl-CjS3FCtxMGFtMRCCHQxldEqze9nEi","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Math"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"3R7ww"} + + data: {"id":"chatcmpl-CjS3FCtxMGFtMRCCHQxldEqze9nEi","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Question"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"t"} + + data: {"id":"chatcmpl-CjS3FCtxMGFtMRCCHQxldEqze9nEi","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"aIxr"} + + data: {"id":"chatcmpl-CjS3FCtxMGFtMRCCHQxldEqze9nEi","object":"chat.completion.chunk","created":1764948625,"model":"gpt-3.5-turbo-0125","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":100,"completion_tokens":3,"total_tokens":103,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"gRboDkQP"} + + data: [DONE] + + headers: {} + status: 200 OK + code: 200 + duration: 941.09175ms diff --git a/pkg/runtime/event.go b/pkg/runtime/event.go index 0e5d2535..b459f452 100644 --- a/pkg/runtime/event.go +++ b/pkg/runtime/event.go @@ -219,12 +219,11 @@ type SessionTitleEvent struct { AgentContext } -func SessionTitle(sessionID, title, agentName string) Event { +func SessionTitle(sessionID, title string) Event { return &SessionTitleEvent{ - Type: "session_title", - SessionID: sessionID, - Title: title, - AgentContext: AgentContext{AgentName: agentName}, + Type: "session_title", + SessionID: sessionID, + Title: title, } } diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index bb3c1899..6c865d8f 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -132,7 +132,7 @@ type LocalRuntime struct { elicitationEventsChannel chan Event // Current events channel for sending elicitation requests elicitationEventsChannelMux sync.RWMutex // Protects elicitationEventsChannel ragInitialized atomic.Bool - titleGenerationWg sync.WaitGroup // Wait group for title generation + titleGen *titleGenerator } type streamResult struct { @@ -210,6 +210,13 @@ func New(agents *team.Team, opts ...Opt) (*LocalRuntime, error) { return nil, err } + model := agents.Model() + if model == nil { + return nil, errors.New("no model found for the team; ensure at least one agent has a valid model") + } + + r.titleGen = newTitleGenerator(model) + slog.Debug("Creating new runtime", "agent", r.currentAgent, "available_agents", agents.Size()) return r, nil @@ -488,8 +495,7 @@ func (r *LocalRuntime) finalizeEventChannel(ctx context.Context, sess *session.S telemetry.RecordSessionEnd(ctx) - // Wait for title generation if it's in progress - r.titleGenerationWg.Wait() + r.titleGen.Wait() } // RunStream starts the agent's interaction loop and returns a channel of events @@ -543,7 +549,6 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c return } - // Emit toolset information events <- ToolsetInfo(len(agentTools), r.currentAgent) messages := sess.GetMessages(a) @@ -558,9 +563,7 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c r.registerDefaultTools() if sess.Title == "" { - r.titleGenerationWg.Go(func() { - r.generateSessionTitle(ctx, sess, events) - }) + r.titleGen.Generate(ctx, sess, events) } iteration := 0 @@ -1353,72 +1356,6 @@ func (r *LocalRuntime) handleHandoff(_ context.Context, _ *session.Session, tool }, nil } -// truncateTitle truncates a title to maxLength characters, adding an ellipsis if needed -func truncateTitle(title string, maxLength int) string { - if len(title) <= maxLength { - return title - } - // Ensure we have room for the ellipsis - if maxLength < 3 { - return "..." - } - return title[:maxLength-3] + "..." -} - -// generateSessionTitle generates a title for the session based on the first user message -func (r *LocalRuntime) generateSessionTitle(ctx context.Context, sess *session.Session, events chan Event) { - slog.Debug("Generating title for session", "session_id", sess.ID) - - firstUserMessage := sess.GetLastUserMessageContent() - if firstUserMessage == "" { - slog.Error("Failed generating session title: no user message found in session", "session_id", sess.ID) - events <- SessionTitle(sess.ID, "Untitled", r.currentAgent) - return - } - - systemPrompt := "You are a helpful AI assistant that generates concise, descriptive titles for conversations. You will be given a conversation history and asked to create a title that captures the main topic." - userPrompt := fmt.Sprintf("Based on the following message a user sent to an AI assistant, generate a short, descriptive title (maximum 50 characters) that captures the main topic or purpose of the conversation. Return ONLY the title text, nothing else.\n\nUser message: %s\n\n", firstUserMessage) - - titleModel := provider.CloneWithOptions( - ctx, - r.CurrentAgent().Model(), - options.WithStructuredOutput(nil), - options.WithMaxTokens(100), - options.WithGeneratingTitle(), - ) - newTeam := team.New( - team.WithAgents(agent.New("root", systemPrompt, agent.WithModel(titleModel))), - ) - titleSession := session.New( - session.WithUserMessage(userPrompt), - session.WithTitle("Generating title..."), - ) - - titleRuntime, err := New(newTeam, WithSessionCompaction(false)) - if err != nil { - slog.Error("Failed to create title generator runtime", "error", err) - return - } - - // Run the title generation (this will be a simple back-and-forth) - _, err = titleRuntime.Run(ctx, titleSession) - if err != nil { - slog.Error("Failed to generate session title", "session_id", sess.ID, "error", err) - return - } - - // Get the generated title from the last assistant message - title := titleSession.GetLastAssistantMessageContent() - if title == "" { - return - } - // Truncate title to 50 characters with ellipsis if needed - title = truncateTitle(title, 50) - sess.Title = title - slog.Debug("Generated session title", "session_id", sess.ID, "title", title) - events <- SessionTitle(sess.ID, title, r.currentAgent) -} - // Summarize generates a summary for the session based on the conversation history func (r *LocalRuntime) Summarize(ctx context.Context, sess *session.Session, events chan Event) { slog.Debug("Generating summary for session", "session_id", sess.ID) diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index d2d6cde5..11264dba 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -699,7 +699,7 @@ func TestGetTools_WarningHandling(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - root := agent.New("root", "test", agent.WithToolSets(tt.toolsets...)) + root := agent.New("root", "test", agent.WithToolSets(tt.toolsets...), agent.WithModel(&mockProvider{})) tm := team.New(team.WithAgents(root)) rt, err := New(tm, WithModelStore(mockModelStore{})) require.NoError(t, err) @@ -769,7 +769,7 @@ func TestSummarize_EmptySession(t *testing.T) { func TestProcessToolCalls_UnknownTool_NoToolResultMessage(t *testing.T) { // Build a runtime with a simple agent but no tools registered matching the call - root := agent.New("root", "You are a test agent") + root := agent.New("root", "You are a test agent", agent.WithModel(&mockProvider{})) tm := team.New(team.WithAgents(root)) rt, err := New(tm, WithSessionCompaction(false), WithModelStore(mockModelStore{})) @@ -856,66 +856,3 @@ func TestEmitStartupInfo(t *testing.T) { // Should be empty due to deduplication require.Empty(t, collectedEvents2, "EmitStartupInfo should not emit duplicate events") } - -func TestTruncateTitle(t *testing.T) { - tests := []struct { - name string - title string - maxLength int - expected string - }{ - { - name: "title shorter than max length", - title: "Short title", - maxLength: 50, - expected: "Short title", - }, - { - name: "title exactly at max length", - title: "This is exactly fifty characters in length now.", - maxLength: 50, - expected: "This is exactly fifty characters in length now.", - }, - { - name: "title longer than max length", - title: "This is a very long title that exceeds the maximum character limit", - maxLength: 50, - expected: "This is a very long title that exceeds the maxi...", - }, - { - name: "very short max length", - title: "Any title", - maxLength: 5, - expected: "An...", - }, - { - name: "max length less than 3", - title: "Any title", - maxLength: 2, - expected: "...", - }, - { - name: "empty title", - title: "", - maxLength: 50, - expected: "", - }, - { - name: "title with unicode characters", - title: "こんにちは、これは日本語のタイトルです。とても長いタイトルなので切り捨てられるはずです。", - maxLength: 50, - expected: "こんにちは、これは日本語のタイトルです。とても長いタイトルなので切り捨てられるはずです。"[:47] + "...", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := truncateTitle(tt.title, tt.maxLength) - require.Equal(t, tt.expected, result) - // Only check length constraint if maxLength >= 3 (otherwise ellipsis alone is 3 chars) - if tt.maxLength >= 3 { - require.LessOrEqual(t, len(result), tt.maxLength) - } - }) - } -} diff --git a/pkg/runtime/title_generator.go b/pkg/runtime/title_generator.go new file mode 100644 index 00000000..ede3902b --- /dev/null +++ b/pkg/runtime/title_generator.go @@ -0,0 +1,89 @@ +package runtime + +import ( + "context" + "fmt" + "log/slog" + "sync" + + "github.com/docker/cagent/pkg/agent" + "github.com/docker/cagent/pkg/model/provider" + "github.com/docker/cagent/pkg/model/provider/options" + "github.com/docker/cagent/pkg/session" + "github.com/docker/cagent/pkg/team" +) + +const ( + titleSystemPrompt = "You are a helpful AI assistant that generates concise, descriptive titles for conversations. You will be given a conversation history and asked to create a title that captures the main topic." + titleUserPromptFormat = "Based on the following message a user sent to an AI assistant, generate a short, descriptive title (maximum 50 characters) that captures the main topic or purpose of the conversation. Return ONLY the title text, nothing else.\n\nUser message: %s\n\n" +) + +type titleGenerator struct { + wg sync.WaitGroup + model provider.Provider +} + +func newTitleGenerator(model provider.Provider) *titleGenerator { + return &titleGenerator{ + model: model, + } +} + +func (t *titleGenerator) Generate(ctx context.Context, sess *session.Session, events chan<- Event) { + t.wg.Go(func() { + t.generate(ctx, sess, events) + }) +} + +func (t *titleGenerator) Wait() { + t.wg.Wait() +} + +func (t *titleGenerator) generate(ctx context.Context, sess *session.Session, events chan<- Event) { + slog.Debug("Generating title for session", "session_id", sess.ID) + + firstUserMessage := sess.GetLastUserMessageContent() + if firstUserMessage == "" { + return + } + + userPrompt := fmt.Sprintf(titleUserPromptFormat, firstUserMessage) + + titleModel := provider.CloneWithOptions( + ctx, + t.model, + options.WithStructuredOutput(nil), + options.WithMaxTokens(20), + options.WithGeneratingTitle(), + ) + + newTeam := team.New( + team.WithAgents(agent.New("root", titleSystemPrompt, agent.WithModel(titleModel))), + ) + + titleSession := session.New( + session.WithUserMessage(userPrompt), + session.WithTitle("Generating title..."), + ) + + titleRuntime, err := New(newTeam, WithSessionCompaction(false)) + if err != nil { + slog.Error("Failed to create title generator runtime", "error", err) + return + } + + _, err = titleRuntime.Run(ctx, titleSession) + if err != nil { + slog.Error("Failed to generate session title", "session_id", sess.ID, "error", err) + return + } + + title := titleSession.GetLastAssistantMessageContent() + if title == "" { + return + } + + sess.Title = title + slog.Debug("Generated session title", "session_id", sess.ID, "title", title) + events <- SessionTitle(sess.ID, title) +} diff --git a/pkg/team/team.go b/pkg/team/team.go index eadaa4c0..64ee7970 100644 --- a/pkg/team/team.go +++ b/pkg/team/team.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/docker/cagent/pkg/agent" + "github.com/docker/cagent/pkg/model/provider" "github.com/docker/cagent/pkg/rag" ) @@ -66,6 +67,21 @@ func (t *Team) Agent(name string) (*agent.Agent, error) { return found, nil } +func (t *Team) Model() provider.Provider { + root, err := t.Agent("root") + if err == nil { + return root.Model() + } + + for _, agentName := range t.AgentNames() { + a, err := t.Agent(agentName) + if err == nil { + return a.Model() + } + } + return nil +} + func (t *Team) Size() int { return len(t.agents) }