diff --git a/docs/modules/agents/agent_toolkits/sql_database.ipynb b/docs/modules/agents/agent_toolkits/sql_database.ipynb index f9cb0895960..5892e3606a7 100644 --- a/docs/modules/agents/agent_toolkits/sql_database.ipynb +++ b/docs/modules/agents/agent_toolkits/sql_database.ipynb @@ -59,6 +59,20 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a88b8e4f", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chat_models.openai import ChatOpenAI\n", + "from langchain.agents.chat.base import ChatAgent\n", + "\n", + "agent = ChatAgent.from_chat_model_and_tools(ChatOpenAI(temperature=0), toolkit.get_tools())\n", + "agent_executor.agent = agent" + ] + }, { "cell_type": "markdown", "id": "36ae48c7-cb08-4fef-977e-c7d4b96a464b", @@ -69,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "ff70e83d-5ad0-4fc7-bb96-27d82ac166d7", "metadata": { "tags": [] @@ -82,12 +96,26 @@ "\n", "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mAction: list_tables_sql_db\n", - "Action Input: \"\"\u001b[0m\n", - "Observation: \u001b[38;5;200m\u001b[1;3mArtist, Invoice, Playlist, Genre, Album, PlaylistTrack, Track, InvoiceLine, MediaType, Employee, Customer\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I should look at the schema of the playlisttrack table\n", - "Action: schema_sql_db\n", - "Action Input: \"PlaylistTrack\"\u001b[0m\n", + "\u001b[32;1m\u001b[1;3mThought: I need to check the schema of the database to see if there is a table called \"play list track\"\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"list_tables_sql_db\",\n", + " \"action_input\": \"\"\n", + "}\n", + "```\n", + "\u001b[0m\n", + "Observation: \u001b[38;5;200m\u001b[1;3mCustomer, Invoice, Track, Artist, Genre, Employee, MediaType, InvoiceLine, Playlist, PlaylistTrack, Album, sales_table\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mThere is a table called \"PlaylistTrack\" in the database. I need to use the schema_sql_db tool to get the schema and sample rows for this table.\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"schema_sql_db\",\n", + " \"action_input\": \"PlaylistTrack\"\n", + "}\n", + "```\n", + "\n", + "\u001b[0m\n", "Observation: \u001b[33;1m\u001b[1;3m\n", "CREATE TABLE \"PlaylistTrack\" (\n", "\t\"PlaylistId\" INTEGER NOT NULL, \n", @@ -98,12 +126,12 @@ ")\n", "\n", "SELECT * FROM 'PlaylistTrack' LIMIT 3;\n", - "PlaylistId TrackId\n", - "1 3402\n", - "1 3389\n", - "1 3390\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n", - "Final Answer: The PlaylistTrack table has two columns, PlaylistId and TrackId, and is linked to the Playlist and Track tables.\u001b[0m\n", + "PlaylistId\tTrackId\n", + "1\t3402\n", + "1\t3389\n", + "1\t3390\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mThe PlaylistTrack table has two columns: PlaylistId and TrackId. It has a composite primary key consisting of both columns. There are foreign key constraints on both columns referencing the Playlist and Track tables respectively. The sample rows show the first three entries in the table.\n", + "Final Answer: The PlaylistTrack table has two columns: PlaylistId and TrackId. It has a composite primary key consisting of both columns. There are foreign key constraints on both columns referencing the Playlist and Track tables respectively. The sample rows show the first three entries in the table.\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -111,16 +139,16 @@ { "data": { "text/plain": [ - "'The PlaylistTrack table has two columns, PlaylistId and TrackId, and is linked to the Playlist and Track tables.'" + "'The PlaylistTrack table has two columns: PlaylistId and TrackId. It has a composite primary key consisting of both columns. There are foreign key constraints on both columns referencing the Playlist and Track tables respectively. The sample rows show the first three entries in the table.'" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "agent_executor.run(\"Describe the playlisttrack table\")" + "agent_executor.run(\"Describe the play list track table\")" ] }, { @@ -135,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 5, "id": "bea76658-a65b-47e2-b294-6d52c5556246", "metadata": { "tags": [] @@ -148,36 +176,69 @@ "\n", "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mAction: list_tables_sql_db\n", - "Action Input: \"\"\u001b[0m\n", - "Observation: \u001b[38;5;200m\u001b[1;3mGenre, PlaylistTrack, MediaType, Invoice, InvoiceLine, Track, Playlist, Customer, Album, Employee, Artist\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I should look at the schema of the PlaylistSong table\n", - "Action: schema_sql_db\n", - "Action Input: \"PlaylistSong\"\u001b[0m\n", - "Observation: \u001b[33;1m\u001b[1;3mError: table_names {'PlaylistSong'} not found in database\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I should check the spelling of the table\n", - "Action: list_tables_sql_db\n", - "Action Input: \"\"\u001b[0m\n", - "Observation: \u001b[38;5;200m\u001b[1;3mGenre, PlaylistTrack, MediaType, Invoice, InvoiceLine, Track, Playlist, Customer, Album, Employee, Artist\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m The table is called PlaylistTrack\n", - "Action: schema_sql_db\n", - "Action Input: \"PlaylistTrack\"\u001b[0m\n", + "\u001b[32;1m\u001b[1;3mThought: I need to use the `schema_sql_db` tool to get the schema and sample rows for the table that has song information. But first, I need to know the name of the table.\n", + "\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"list_tables_sql_db\",\n", + " \"action_input\": \"\"\n", + "}\n", + "```\n", + "\n", + "\u001b[0m\n", + "Observation: \u001b[38;5;200m\u001b[1;3mCustomer, Invoice, Track, Artist, Genre, Employee, MediaType, InvoiceLine, Playlist, PlaylistTrack, Album, sales_table\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mThe table that has song information is likely to be named \"Track\". I will use the `schema_sql_db` tool to get the schema and sample rows for this table.\n", + "\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"schema_sql_db\",\n", + " \"action_input\": \"Track\"\n", + "}\n", + "```\n", + "\n", + "\u001b[0m\n", "Observation: \u001b[33;1m\u001b[1;3m\n", - "CREATE TABLE \"PlaylistTrack\" (\n", - "\t\"PlaylistId\" INTEGER NOT NULL, \n", + "CREATE TABLE \"Track\" (\n", "\t\"TrackId\" INTEGER NOT NULL, \n", - "\tPRIMARY KEY (\"PlaylistId\", \"TrackId\"), \n", - "\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \n", - "\tFOREIGN KEY(\"PlaylistId\") REFERENCES \"Playlist\" (\"PlaylistId\")\n", + "\t\"Name\" NVARCHAR(200) NOT NULL, \n", + "\t\"AlbumId\" INTEGER, \n", + "\t\"MediaTypeId\" INTEGER NOT NULL, \n", + "\t\"GenreId\" INTEGER, \n", + "\t\"Composer\" NVARCHAR(220), \n", + "\t\"Milliseconds\" INTEGER NOT NULL, \n", + "\t\"Bytes\" INTEGER, \n", + "\t\"UnitPrice\" NUMERIC(10, 2) NOT NULL, \n", + "\tPRIMARY KEY (\"TrackId\"), \n", + "\tFOREIGN KEY(\"MediaTypeId\") REFERENCES \"MediaType\" (\"MediaTypeId\"), \n", + "\tFOREIGN KEY(\"GenreId\") REFERENCES \"Genre\" (\"GenreId\"), \n", + "\tFOREIGN KEY(\"AlbumId\") REFERENCES \"Album\" (\"AlbumId\")\n", ")\n", "\n", - "SELECT * FROM 'PlaylistTrack' LIMIT 3;\n", - "PlaylistId TrackId\n", - "1 3402\n", - "1 3389\n", - "1 3390\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n", - "Final Answer: The PlaylistTrack table contains two columns, PlaylistId and TrackId, which are both integers and are used to link Playlist and Track tables.\u001b[0m\n", + "SELECT * FROM 'Track' LIMIT 3;\n", + "TrackId\tName\tAlbumId\tMediaTypeId\tGenreId\tComposer\tMilliseconds\tBytes\tUnitPrice\n", + "1\tFor Those About To Rock (We Salute You)\t1\t1\t1\tAngus Young, Malcolm Young, Brian Johnson\t343719\t11170334\t0.99\n", + "2\tBalls to the Wall\t2\t2\t1\tNone\t342562\t5510424\t0.99\n", + "3\tFast As a Shark\t3\t2\t1\tF. Baltes, S. Kaufman, U. Dirkscneider & W. Hoffman\t230619\t3990994\t0.99\u001b[0m\n", + "Thought:" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/harrisonchase/workplace/langchain/langchain/sql_database.py:141: SAWarning: Dialect sqlite+pysqlite does *not* support Decimal objects natively, and SQLAlchemy must convert from floating point - rounding errors and other issues may occur. Please consider storing Decimal numbers as strings or integers on this platform for lossless storage.\n", + " sample_rows = connection.execute(command)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32;1m\u001b[1;3mThe table \"Track\" has columns for TrackId, Name, AlbumId, MediaTypeId, GenreId, Composer, Milliseconds, Bytes, and UnitPrice. The sample rows show the first three tracks in the table. \n", + "\n", + "Final Answer: The table that has song information is named \"Track\" and has columns for TrackId, Name, AlbumId, MediaTypeId, GenreId, Composer, Milliseconds, Bytes, and UnitPrice.\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -185,16 +246,16 @@ { "data": { "text/plain": [ - "'The PlaylistTrack table contains two columns, PlaylistId and TrackId, which are both integers and are used to link Playlist and Track tables.'" + "'The table that has song information is named \"Track\" and has columns for TrackId, Name, AlbumId, MediaTypeId, GenreId, Composer, Milliseconds, Bytes, and UnitPrice.'" ] }, - "execution_count": 15, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "agent_executor.run(\"Describe the playlistsong table\")" + "agent_executor.run(\"Describe the table for that has song information\")" ] }, { @@ -207,7 +268,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "id": "17bea710-4a23-4de0-b48e-21d57be48293", "metadata": { "tags": [] @@ -220,63 +281,57 @@ "\n", "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mAction: list_tables_sql_db\n", - "Action Input: \"\"\u001b[0m\n", - "Observation: \u001b[38;5;200m\u001b[1;3mInvoice, MediaType, Artist, InvoiceLine, Genre, Playlist, Employee, Album, PlaylistTrack, Track, Customer\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I should look at the schema of the relevant tables to see what columns I can use.\n", - "Action: schema_sql_db\n", - "Action Input: \"Invoice, Customer\"\u001b[0m\n", - "Observation: \u001b[33;1m\u001b[1;3m\n", - "CREATE TABLE \"Customer\" (\n", - "\t\"CustomerId\" INTEGER NOT NULL, \n", - "\t\"FirstName\" NVARCHAR(40) NOT NULL, \n", - "\t\"LastName\" NVARCHAR(20) NOT NULL, \n", - "\t\"Company\" NVARCHAR(80), \n", - "\t\"Address\" NVARCHAR(70), \n", - "\t\"City\" NVARCHAR(40), \n", - "\t\"State\" NVARCHAR(40), \n", - "\t\"Country\" NVARCHAR(40), \n", - "\t\"PostalCode\" NVARCHAR(10), \n", - "\t\"Phone\" NVARCHAR(24), \n", - "\t\"Fax\" NVARCHAR(24), \n", - "\t\"Email\" NVARCHAR(60) NOT NULL, \n", - "\t\"SupportRepId\" INTEGER, \n", - "\tPRIMARY KEY (\"CustomerId\"), \n", - "\tFOREIGN KEY(\"SupportRepId\") REFERENCES \"Employee\" (\"EmployeeId\")\n", - ")\n", + "\u001b[32;1m\u001b[1;3mQuestion: List the total sales per country. Which country's customers spent the most?\n", + "Thought: I need to retrieve the total sales per country and then find the country with the highest total sales.\n", + "Action:\n", + "```\n", + "{\"action\": \"query_sql_db\", \"action_input\": \"SELECT SUM(sales) AS total_sales, country FROM sales_table GROUP BY country ORDER BY total_sales DESC LIMIT 1\"}\n", + "```\n", + "\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3m[(900, 'Japan')]\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mI need to modify the query to retrieve the country with the highest total sales, not just the first one in the list.\n", + "Action:\n", + "```\n", + "{\"action\": \"query_sql_db\", \"action_input\": \"SELECT SUM(sales) AS total_sales, country FROM sales_table GROUP BY country ORDER BY total_sales DESC LIMIT 1\"}\n", + "```\n", "\n", - "SELECT * FROM 'Customer' LIMIT 3;\n", - "CustomerId FirstName LastName Company Address City State Country PostalCode Phone Fax Email SupportRepId\n", - "1 Luís Gonçalves Embraer - Empresa Brasileira de Aeronáutica S.A. Av. Brigadeiro Faria Lima, 2170 São José dos Campos SP Brazil 12227-000 +55 (12) 3923-5555 +55 (12) 3923-5566 luisg@embraer.com.br 3\n", - "2 Leonie Köhler None Theodor-Heuss-Straße 34 Stuttgart None Germany 70174 +49 0711 2842222 None leonekohler@surfeu.de 5\n", - "3 François Tremblay None 1498 rue Bélanger Montréal QC Canada H2G 1A7 +1 (514) 721-4711 None ftremblay@gmail.com 3\n", + "\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3m[(900, 'Japan')]\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mI need to modify the query to retrieve the country with the highest total sales, not just the first one in the list.\n", + "Action:\n", + "```\n", + "{\"action\": \"query_sql_db\", \"action_input\": \"SELECT SUM(sales) AS total_sales, country FROM sales_table GROUP BY country ORDER BY total_sales DESC LIMIT 1\"}\n", + "```\n", + "\n", + "\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3m[(900, 'Japan')]\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mI need to modify the query to retrieve the country with the highest total sales, not just the first one in the list.\n", + "Action:\n", + "```\n", + "{\"action\": \"query_sql_db\", \"action_input\": \"SELECT SUM(sales) AS total_sales, country FROM sales_table GROUP BY country ORDER BY total_sales DESC LIMIT 1\"}\n", + "```\n", + "\n", + "\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3m[(900, 'Japan')]\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mI need to modify the query to retrieve all the countries and their total sales, then find the country with the highest total sales.\n", + "Action:\n", + "```\n", + "{\"action\": \"query_sql_db\", \"action_input\": \"SELECT SUM(sales) AS total_sales, country FROM sales_table GROUP BY country ORDER BY total_sales DESC\"}\n", + "```\n", + "\n", + "\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3m[(900, 'Japan'), (800, 'Australia'), (700, 'UK'), (600, 'Germany'), (500, 'France'), (400, 'Brazil'), (300, 'Mexico'), (200, 'Canada'), (100, 'USA')]\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mI need to select the first row of the result to get the country with the highest total sales.\n", + "Action:\n", + "```\n", + "{\"action\": \"query_sql_db\", \"action_input\": \"SELECT SUM(sales) AS total_sales, country FROM sales_table GROUP BY country ORDER BY total_sales DESC LIMIT 1\"}\n", + "```\n", "\n", "\n", - "CREATE TABLE \"Invoice\" (\n", - "\t\"InvoiceId\" INTEGER NOT NULL, \n", - "\t\"CustomerId\" INTEGER NOT NULL, \n", - "\t\"InvoiceDate\" DATETIME NOT NULL, \n", - "\t\"BillingAddress\" NVARCHAR(70), \n", - "\t\"BillingCity\" NVARCHAR(40), \n", - "\t\"BillingState\" NVARCHAR(40), \n", - "\t\"BillingCountry\" NVARCHAR(40), \n", - "\t\"BillingPostalCode\" NVARCHAR(10), \n", - "\t\"Total\" NUMERIC(10, 2) NOT NULL, \n", - "\tPRIMARY KEY (\"InvoiceId\"), \n", - "\tFOREIGN KEY(\"CustomerId\") REFERENCES \"Customer\" (\"CustomerId\")\n", - ")\n", - "\n", - "SELECT * FROM 'Invoice' LIMIT 3;\n", - "InvoiceId CustomerId InvoiceDate BillingAddress BillingCity BillingState BillingCountry BillingPostalCode Total\n", - "1 2 2009-01-01 00:00:00 Theodor-Heuss-Straße 34 Stuttgart None Germany 70174 1.98\n", - "2 4 2009-01-02 00:00:00 Ullevålsveien 14 Oslo None Norway 0171 3.96\n", - "3 8 2009-01-03 00:00:00 Grétrystraat 63 Brussels None Belgium 1000 5.94\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I should query the Invoice and Customer tables to get the total sales per country.\n", - "Action: query_sql_db\n", - "Action Input: SELECT c.Country, SUM(i.Total) AS TotalSales FROM Invoice i INNER JOIN Customer c ON i.CustomerId = c.CustomerId GROUP BY c.Country ORDER BY TotalSales DESC LIMIT 10\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3m[('USA', 523.0600000000003), ('Canada', 303.9599999999999), ('France', 195.09999999999994), ('Brazil', 190.09999999999997), ('Germany', 156.48), ('United Kingdom', 112.85999999999999), ('Czech Republic', 90.24000000000001), ('Portugal', 77.23999999999998), ('India', 75.25999999999999), ('Chile', 46.62)]\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n", - "Final Answer: The customers from the USA spent the most, with a total of $523.06.\u001b[0m\n", + "\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3m[(900, 'Japan')]\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mThe country with the highest total sales is Japan.\n", + "Final Answer: Japan\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -284,10 +339,10 @@ { "data": { "text/plain": [ - "'The customers from the USA spent the most, with a total of $523.06.'" + "'Japan'" ] }, - "execution_count": 8, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -311,12 +366,17 @@ "\n", "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mAction: list_tables_sql_db\n", - "Action Input: \"\"\u001b[0m\n", - "Observation: \u001b[38;5;200m\u001b[1;3mInvoice, MediaType, Artist, InvoiceLine, Genre, Playlist, Employee, Album, PlaylistTrack, Track, Customer\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I should look at the schema of the Playlist and PlaylistTrack tables to see what columns I can use.\n", - "Action: schema_sql_db\n", - "Action Input: \"Playlist, PlaylistTrack\"\u001b[0m\n", + "\u001b[32;1m\u001b[1;3mThought: I need to retrieve data from multiple tables, so I need to check the schema of the tables and make sure I have the correct column names to join the tables. Then I can use a SQL query to get the total number of tracks in each playlist.\n", + "\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"schema_sql_db\",\n", + " \"action_input\": \"Playlist, PlaylistTrack, Track\"\n", + "}\n", + "```\n", + "\n", + "\u001b[0m\n", "Observation: \u001b[33;1m\u001b[1;3m\n", "CREATE TABLE \"Playlist\" (\n", "\t\"PlaylistId\" INTEGER NOT NULL, \n", @@ -325,10 +385,33 @@ ")\n", "\n", "SELECT * FROM 'Playlist' LIMIT 3;\n", - "PlaylistId Name\n", - "1 Music\n", - "2 Movies\n", - "3 TV Shows\n", + "PlaylistId\tName\n", + "1\tMusic\n", + "2\tMovies\n", + "3\tTV Shows\n", + "\n", + "\n", + "CREATE TABLE \"Track\" (\n", + "\t\"TrackId\" INTEGER NOT NULL, \n", + "\t\"Name\" NVARCHAR(200) NOT NULL, \n", + "\t\"AlbumId\" INTEGER, \n", + "\t\"MediaTypeId\" INTEGER NOT NULL, \n", + "\t\"GenreId\" INTEGER, \n", + "\t\"Composer\" NVARCHAR(220), \n", + "\t\"Milliseconds\" INTEGER NOT NULL, \n", + "\t\"Bytes\" INTEGER, \n", + "\t\"UnitPrice\" NUMERIC(10, 2) NOT NULL, \n", + "\tPRIMARY KEY (\"TrackId\"), \n", + "\tFOREIGN KEY(\"MediaTypeId\") REFERENCES \"MediaType\" (\"MediaTypeId\"), \n", + "\tFOREIGN KEY(\"GenreId\") REFERENCES \"Genre\" (\"GenreId\"), \n", + "\tFOREIGN KEY(\"AlbumId\") REFERENCES \"Album\" (\"AlbumId\")\n", + ")\n", + "\n", + "SELECT * FROM 'Track' LIMIT 3;\n", + "TrackId\tName\tAlbumId\tMediaTypeId\tGenreId\tComposer\tMilliseconds\tBytes\tUnitPrice\n", + "1\tFor Those About To Rock (We Salute You)\t1\t1\t1\tAngus Young, Malcolm Young, Brian Johnson\t343719\t11170334\t0.99\n", + "2\tBalls to the Wall\t2\t2\t1\tNone\t342562\t5510424\t0.99\n", + "3\tFast As a Shark\t3\t2\t1\tF. Baltes, S. Kaufman, U. Dirkscneider & W. Hoffman\t230619\t3990994\t0.99\n", "\n", "\n", "CREATE TABLE \"PlaylistTrack\" (\n", @@ -340,22 +423,25 @@ ")\n", "\n", "SELECT * FROM 'PlaylistTrack' LIMIT 3;\n", - "PlaylistId TrackId\n", - "1 3402\n", - "1 3389\n", - "1 3390\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I can use a SELECT statement to get the total number of tracks in each playlist.\n", - "Action: query_checker_sql_db\n", - "Action Input: SELECT Playlist.Name, COUNT(PlaylistTrack.TrackId) AS TotalTracks FROM Playlist INNER JOIN PlaylistTrack ON Playlist.PlaylistId = PlaylistTrack.PlaylistId GROUP BY Playlist.Name\u001b[0m\n", - "Observation: \u001b[31;1m\u001b[1;3m\n", + "PlaylistId\tTrackId\n", + "1\t3402\n", + "1\t3389\n", + "1\t3390\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mNow that I have checked the schema of the tables, I can use a SQL query to get the total number of tracks in each playlist.\n", "\n", - "SELECT Playlist.Name, COUNT(PlaylistTrack.TrackId) AS TotalTracks FROM Playlist INNER JOIN PlaylistTrack ON Playlist.PlaylistId = PlaylistTrack.PlaylistId GROUP BY Playlist.Name\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m The query looks correct, I can now execute it.\n", - "Action: query_sql_db\n", - "Action Input: SELECT Playlist.Name, COUNT(PlaylistTrack.TrackId) AS TotalTracks FROM Playlist INNER JOIN PlaylistTrack ON Playlist.PlaylistId = PlaylistTrack.PlaylistId GROUP BY Playlist.Name LIMIT 10\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3m[('90’s Music', 1477), ('Brazilian Music', 39), ('Classical', 75), ('Classical 101 - Deep Cuts', 25), ('Classical 101 - Next Steps', 25), ('Classical 101 - The Basics', 25), ('Grunge', 15), ('Heavy Metal Classic', 26), ('Music', 6580), ('Music Videos', 1)]\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n", - "Final Answer: The total number of tracks in each playlist are: '90’s Music' (1477), 'Brazilian Music' (39), 'Classical' (75), 'Classical 101 - Deep Cuts' (25), 'Classical 101 - Next Steps' (25), 'Classical 101 - The Basics' (25), 'Grunge' (15), 'Heavy Metal Classic' (26), 'Music' (6580), 'Music Videos' (1).\u001b[0m\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"query_sql_db\",\n", + " \"action_input\": \"SELECT Playlist.Name, COUNT(PlaylistTrack.TrackId) AS 'Total Tracks' FROM Playlist JOIN PlaylistTrack ON Playlist.PlaylistId = PlaylistTrack.PlaylistId GROUP BY Playlist.Name\"\n", + "}\n", + "```\n", + "\n", + "\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3m[('90’s Music', 1477), ('Brazilian Music', 39), ('Classical', 75), ('Classical 101 - Deep Cuts', 25), ('Classical 101 - Next Steps', 25), ('Classical 101 - The Basics', 25), ('Grunge', 15), ('Heavy Metal Classic', 26), ('Music', 6580), ('Music Videos', 1), ('On-The-Go 1', 1), ('TV Shows', 426)]\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mI now know the final answer.\n", + "\n", + "Final Answer: The total number of tracks in each playlist is displayed in the following format: [('90’s Music', 1477), ('Brazilian Music', 39), ('Classical', 75), ('Classical 101 - Deep Cuts', 25), ('Classical 101 - Next Steps', 25), ('Classical 101 - The Basics', 25), ('Grunge', 15), ('Heavy Metal Classic', 26), ('Music', 6580), ('Music Videos', 1), ('On-The-Go 1', 1), ('TV Shows', 426)].\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -363,7 +449,7 @@ { "data": { "text/plain": [ - "\"The total number of tracks in each playlist are: '90’s Music' (1477), 'Brazilian Music' (39), 'Classical' (75), 'Classical 101 - Deep Cuts' (25), 'Classical 101 - Next Steps' (25), 'Classical 101 - The Basics' (25), 'Grunge' (15), 'Heavy Metal Classic' (26), 'Music' (6580), 'Music Videos' (1).\"" + "\"The total number of tracks in each playlist is displayed in the following format: [('90’s Music', 1477), ('Brazilian Music', 39), ('Classical', 75), ('Classical 101 - Deep Cuts', 25), ('Classical 101 - Next Steps', 25), ('Classical 101 - The Basics', 25), ('Grunge', 15), ('Heavy Metal Classic', 26), ('Music', 6580), ('Music Videos', 1), ('On-The-Go 1', 1), ('TV Shows', 426)].\"" ] }, "execution_count": 7, @@ -387,7 +473,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 8, "id": "9fe4901e-f9e1-4022-b6bc-80e2b2d6a3a4", "metadata": { "tags": [] @@ -400,89 +486,100 @@ "\n", "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mAction: list_tables_sql_db\n", - "Action Input: \"\"\u001b[0m\n", - "Observation: \u001b[38;5;200m\u001b[1;3mMediaType, Track, Invoice, Album, Playlist, Customer, Employee, InvoiceLine, PlaylistTrack, Genre, Artist\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I should look at the schema of the Artist, InvoiceLine, and Track tables to see what columns I can use.\n", - "Action: schema_sql_db\n", - "Action Input: \"Artist, InvoiceLine, Track\"\u001b[0m\n", - "Observation: \u001b[33;1m\u001b[1;3m\n", - "CREATE TABLE \"Artist\" (\n", - "\t\"ArtistId\" INTEGER NOT NULL, \n", - "\t\"Name\" NVARCHAR(120), \n", - "\tPRIMARY KEY (\"ArtistId\")\n", - ")\n", + "\u001b[32;1m\u001b[1;3mThought: I need to query the database to get the best selling artists. I should use the `query_sql_db` tool for this.\n", "\n", - "SELECT * FROM 'Artist' LIMIT 3;\n", - "ArtistId Name\n", - "1 AC/DC\n", - "2 Accept\n", - "3 Aerosmith\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"query_sql_db\",\n", + " \"action_input\": \"SELECT artist, SUM(sales) AS total_sales FROM sales GROUP BY artist ORDER BY total_sales DESC LIMIT 3\"\n", + "}\n", + "```\n", "\n", - "\n", - "CREATE TABLE \"Track\" (\n", - "\t\"TrackId\" INTEGER NOT NULL, \n", - "\t\"Name\" NVARCHAR(200) NOT NULL, \n", - "\t\"AlbumId\" INTEGER, \n", - "\t\"MediaTypeId\" INTEGER NOT NULL, \n", - "\t\"GenreId\" INTEGER, \n", - "\t\"Composer\" NVARCHAR(220), \n", - "\t\"Milliseconds\" INTEGER NOT NULL, \n", - "\t\"Bytes\" INTEGER, \n", - "\t\"UnitPrice\" NUMERIC(10, 2) NOT NULL, \n", - "\tPRIMARY KEY (\"TrackId\"), \n", - "\tFOREIGN KEY(\"MediaTypeId\") REFERENCES \"MediaType\" (\"MediaTypeId\"), \n", - "\tFOREIGN KEY(\"GenreId\") REFERENCES \"Genre\" (\"GenreId\"), \n", - "\tFOREIGN KEY(\"AlbumId\") REFERENCES \"Album\" (\"AlbumId\")\n", - ")\n", - "\n", - "SELECT * FROM 'Track' LIMIT 3;\n", - "TrackId Name AlbumId MediaTypeId GenreId Composer Milliseconds Bytes UnitPrice\n", - "1 For Those About To Rock (We Salute You) 1 1 1 Angus Young, Malcolm Young, Brian Johnson 343719 11170334 0.99\n", - "2 Balls to the Wall 2 2 1 None 342562 5510424 0.99\n", - "3 Fast As a Shark 3 2 1 F. Baltes, S. Kaufman, U. Dirkscneider & W. Hoffman 230619 3990994 0.99\n", - "\n", - "\n", - "CREATE TABLE \"InvoiceLine\" (\n", - "\t\"InvoiceLineId\" INTEGER NOT NULL, \n", - "\t\"InvoiceId\" INTEGER NOT NULL, \n", - "\t\"TrackId\" INTEGER NOT NULL, \n", - "\t\"UnitPrice\" NUMERIC(10, 2) NOT NULL, \n", - "\t\"Quantity\" INTEGER NOT NULL, \n", - "\tPRIMARY KEY (\"InvoiceLineId\"), \n", - "\tFOREIGN KEY(\"TrackId\") REFERENCES \"Track\" (\"TrackId\"), \n", - "\tFOREIGN KEY(\"InvoiceId\") REFERENCES \"Invoice\" (\"InvoiceId\")\n", - ")\n", - "\n", - "SELECT * FROM 'InvoiceLine' LIMIT 3;\n", - "InvoiceLineId InvoiceId TrackId UnitPrice Quantity\n", - "1 1 2 0.99 1\n", - "2 1 4 0.99 1\n", - "3 2 6 0.99 1\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I should query the database to get the top 3 best selling artists.\n", - "Action: query_sql_db\n", - "Action Input: SELECT Artist.Name, SUM(InvoiceLine.Quantity) AS TotalQuantity FROM Artist INNER JOIN Track ON Artist.ArtistId = Track.ArtistId INNER JOIN InvoiceLine ON Track.TrackId = InvoiceLine.TrackId GROUP BY Artist.Name ORDER BY TotalQuantity DESC LIMIT 3\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3mError: (sqlite3.OperationalError) no such column: Track.ArtistId\n", - "[SQL: SELECT Artist.Name, SUM(InvoiceLine.Quantity) AS TotalQuantity FROM Artist INNER JOIN Track ON Artist.ArtistId = Track.ArtistId INNER JOIN InvoiceLine ON Track.TrackId = InvoiceLine.TrackId GROUP BY Artist.Name ORDER BY TotalQuantity DESC LIMIT 3]\n", + "\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3mError: (sqlite3.OperationalError) no such table: sales\n", + "[SQL: SELECT artist, SUM(sales) AS total_sales FROM sales GROUP BY artist ORDER BY total_sales DESC LIMIT 3]\n", "(Background on this error at: https://sqlalche.me/e/14/e3q8)\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I should double check my query before executing it.\n", - "Action: query_checker_sql_db\n", - "Action Input: SELECT Artist.Name, SUM(InvoiceLine.Quantity) AS TotalQuantity FROM Artist INNER JOIN Track ON Artist.ArtistId = Track.ArtistId INNER JOIN InvoiceLine ON Track.TrackId = InvoiceLine.TrackId GROUP BY Artist.Name ORDER BY TotalQuantity DESC LIMIT 3\u001b[0m\n", - "Observation: \u001b[31;1m\u001b[1;3m\n", + "Thought:\u001b[32;1m\u001b[1;3mI need to check if the table \"sales\" exists in the database before running the query. I should use the `list_tables_sql_db` tool for this.\n", "\n", - "SELECT Artist.Name, SUM(InvoiceLine.Quantity) AS TotalQuantity \n", - "FROM Artist \n", - "INNER JOIN Track ON Artist.ArtistId = Track.ArtistId \n", - "INNER JOIN InvoiceLine ON Track.TrackId = InvoiceLine.TrackId \n", - "GROUP BY Artist.Name \n", - "ORDER BY TotalQuantity DESC \n", - "LIMIT 3;\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n", - "Action: query_sql_db\n", - "Action Input: SELECT Artist.Name, SUM(InvoiceLine.Quantity) AS TotalQuantity FROM Artist INNER JOIN Album ON Artist.ArtistId = Album.ArtistId INNER JOIN Track ON Album.AlbumId = Track.AlbumId INNER JOIN InvoiceLine ON Track.TrackId = InvoiceLine.TrackId GROUP BY Artist.Name ORDER BY TotalQuantity DESC LIMIT 3\u001b[0m\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"list_tables_sql_db\",\n", + " \"action_input\": \"\"\n", + "}\n", + "```\n", + "\n", + "\u001b[0m\n", + "Observation: \u001b[38;5;200m\u001b[1;3mCustomer, Invoice, Track, Artist, Genre, Employee, MediaType, InvoiceLine, Playlist, PlaylistTrack, Album, sales_table\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mThe table \"sales_table\" exists in the database. I can now use the `query_sql_db` tool to get the best selling artists.\n", + "\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"query_sql_db\",\n", + " \"action_input\": \"SELECT artist, SUM(sales) AS total_sales FROM sales_table GROUP BY artist ORDER BY total_sales DESC LIMIT 3\"\n", + "}\n", + "```\n", + "\n", + "\n", + "\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3mError: (sqlite3.OperationalError) no such column: artist\n", + "[SQL: SELECT artist, SUM(sales) AS total_sales FROM sales_table GROUP BY artist ORDER BY total_sales DESC LIMIT 3]\n", + "(Background on this error at: https://sqlalche.me/e/14/e3q8)\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mI need to check the schema of the `sales_table` to see what columns it has. I should use the `schema_sql_db` tool for this.\n", + "\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"schema_sql_db\",\n", + " \"action_input\": \"sales_table\"\n", + "}\n", + "```\n", + "\n", + "\n", + "\u001b[0m\n", + "Observation: \u001b[33;1m\u001b[1;3m\n", + "CREATE TABLE sales_table (\n", + "\tcountry TEXT, \n", + "\tsales INTEGER\n", + ")\n", + "\n", + "SELECT * FROM 'sales_table' LIMIT 3;\n", + "country\tsales\n", + "USA\t100\n", + "Canada\t200\n", + "Mexico\t300\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mThe `sales_table` only has two columns: `country` and `sales`. I need to find the correct table that has the `artist` column. I should use the `list_tables_sql_db` tool for this.\n", + "\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"list_tables_sql_db\",\n", + " \"action_input\": \"\"\n", + "}\n", + "```\n", + "\n", + "\n", + "\n", + "\u001b[0m\n", + "Observation: \u001b[38;5;200m\u001b[1;3mCustomer, Invoice, Track, Artist, Genre, Employee, MediaType, InvoiceLine, Playlist, PlaylistTrack, Album, sales_table\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mThe `Artist` table has the `artist` column. I can now use the `query_sql_db` tool to get the best selling artists.\n", + "\n", + "Action:\n", + "```\n", + "{\n", + " \"action\": \"query_sql_db\",\n", + " \"action_input\": \"SELECT Artist.Name, SUM(InvoiceLine.Quantity) AS total_sales FROM InvoiceLine JOIN Track ON InvoiceLine.TrackId = Track.TrackId JOIN Album ON Track.AlbumId = Album.AlbumId JOIN Artist ON Album.ArtistId = Artist.ArtistId GROUP BY Artist.Name ORDER BY total_sales DESC LIMIT 3\"\n", + "}\n", + "```\n", + "\n", + "\n", + "\n", + "\u001b[0m\n", "Observation: \u001b[36;1m\u001b[1;3m[('Iron Maiden', 140), ('U2', 107), ('Metallica', 91)]\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n", - "Final Answer: The top 3 best selling artists are Iron Maiden, U2, and Metallica.\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mThe top 3 best selling artists are Iron Maiden, U2, and Metallica.\n", + "Final Answer: Iron Maiden, U2, Metallica.\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -490,10 +587,10 @@ { "data": { "text/plain": [ - "'The top 3 best selling artists are Iron Maiden, U2, and Metallica.'" + "'Iron Maiden, U2, Metallica.'" ] }, - "execution_count": 16, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -501,6 +598,14 @@ "source": [ "agent_executor.run(\"Who are the top 3 best selling artists?\")" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "512180bf", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -519,7 +624,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.9.1" } }, "nbformat": 4, diff --git a/docs/modules/agents/examples/chat_agent.ipynb b/docs/modules/agents/examples/chat_agent.ipynb index 6e1bf02a587..81aa96f0649 100644 --- a/docs/modules/agents/examples/chat_agent.ipynb +++ b/docs/modules/agents/examples/chat_agent.ipynb @@ -70,16 +70,8 @@ "outputs": [], "source": [ "from langchain.chat_models.openai import ChatOpenAI\n", - "from langchain.agents.chat.base import ChatAgent" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "cd04186a", - "metadata": {}, - "outputs": [], - "source": [ + "from langchain.agents.chat.base import ChatAgent\n", + "\n", "agent = ChatAgent.from_chat_model_and_tools(ChatOpenAI(temperature=0), tools)" ] }, diff --git a/langchain/agents/chat/base.py b/langchain/agents/chat/base.py index 3f5ff581736..4d9be3a5666 100644 --- a/langchain/agents/chat/base.py +++ b/langchain/agents/chat/base.py @@ -1,14 +1,43 @@ -from typing import List, Optional, Sequence +from typing import List, Optional, Sequence, Tuple from langchain.agents.chat.prompt import FORMAT_INSTRUCTIONS, PREFIX, SUFFIX from langchain.agents.mrkl.base import ZeroShotAgent from langchain.prompts.base import BasePromptTemplate from langchain.prompts.chat import ChatPromptTemplate from langchain.prompts.prompt import PromptTemplate +from langchain.schema import AgentAction from langchain.tools import BaseTool +FINAL_ANSWER_ACTION = "Final Answer:" + class ChatAgent(ZeroShotAgent): + def _construct_scratchpad( + self, intermediate_steps: List[Tuple[AgentAction, str]] + ) -> str: + agent_scratchpad = super()._construct_scratchpad(intermediate_steps) + if agent_scratchpad: + return ( + f"This was your previous work " + f"(but I haven't seen any of it! I only see what " + f"you return as final answer):\n{agent_scratchpad}" + ) + else: + return agent_scratchpad + + def _extract_tool_and_input(self, text: str) -> Optional[Tuple[str, str]]: + if FINAL_ANSWER_ACTION in text: + return "Final Answer", text.split(FINAL_ANSWER_ACTION)[-1].strip() + _, action, _ = text.split("```") + import json + + foo = json.loads(action.strip()) + return foo["action"], foo["action_input"] + + @property + def _stop(self) -> List[str]: + return ["Observation:"] + @classmethod def create_prompt( cls, @@ -23,7 +52,7 @@ class ChatAgent(ZeroShotAgent): format_instructions = format_instructions.format(tool_names=tool_names) template = "\n\n".join([prefix, tool_strings, format_instructions, suffix]) messages = [ - ("system", PromptTemplate.from_template(template)), + ("user", PromptTemplate.from_template(template)), ("user", PromptTemplate.from_template("{input}\n\n{agent_scratchpad}")), ] return ChatPromptTemplate( diff --git a/langchain/agents/chat/prompt.py b/langchain/agents/chat/prompt.py index daed3cc59ac..e3d69d79cd4 100644 --- a/langchain/agents/chat/prompt.py +++ b/langchain/agents/chat/prompt.py @@ -1,13 +1,18 @@ # flake8: noqa PREFIX = """Answer the following questions as best you can. You have access to the following tools:""" -FORMAT_INSTRUCTIONS = """Use the following format: +FORMAT_INSTRUCTIONS = """The way you use the tools is by specifying a json blob. +Specifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here). + +ALWAYS use the following format: Question: the input question you must answer Thought: you should always think about what to do -Action: the action to take, should be one of [{tool_names}] -Action Input: the input to the action +Action: +``` +$JSON_BLOB +``` Observation: the result of the action -... (this Thought/Action/Action Input/Observation can repeat N times) +... (this Thought/Action/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question""" SUFFIX = """Begin! Reminder to always use the exact characters `Final Answer` when responding.""" diff --git a/langchain/agents/mrkl/base.py b/langchain/agents/mrkl/base.py index 359bbabffd7..3086fd06bd9 100644 --- a/langchain/agents/mrkl/base.py +++ b/langchain/agents/mrkl/base.py @@ -46,7 +46,7 @@ def get_action_and_input(llm_output: str) -> Tuple[str, str]: raise ValueError(f"Could not parse LLM output: `{llm_output}`") action = match.group(1).strip() action_input = match.group(2) - return action, action_input.strip(" ").strip('"') + return action, action_input.strip().strip('"') class ZeroShotAgent(Agent): diff --git a/langchain/chat_models/openai.py b/langchain/chat_models/openai.py index 2351c507d0c..dfbcc817f60 100644 --- a/langchain/chat_models/openai.py +++ b/langchain/chat_models/openai.py @@ -146,8 +146,3 @@ class ChatOpenAI(BaseChatModel, BaseModel): def _identifying_params(self) -> Mapping[str, Any]: """Get the identifying parameters.""" return {**{"model_name": self.model_name}, **self._default_params} - - @property - def _llm_type(self) -> str: - """Return type of chat.""" - return "openai" diff --git a/langchain/evaluation/qa/chat_comp_chain.py b/langchain/evaluation/qa/chat_comp_chain.py index 2ee86e59f9a..24d1a6f7469 100644 --- a/langchain/evaluation/qa/chat_comp_chain.py +++ b/langchain/evaluation/qa/chat_comp_chain.py @@ -1,78 +1,31 @@ """LLM Chain specifically for evaluating question answering.""" from __future__ import annotations -from abc import ABC, abstractmethod -from typing import Any, Dict, List +from typing import Any, List -from pydantic import BaseModel - -from langchain.chat.base import BaseChatChain +from langchain.chains.llm import ChatModelChain from langchain.chat_models.base import BaseChatModel -from langchain.evaluation.qa.eval_prompt import ( - CHAT_COMPARISON_INSTRUCTIONS, - CHAT_COMPARISON_RESPONSE_TEMPLATE, -) -from langchain.schema import ChatMessage +from langchain.evaluation.qa.eval_prompt import CHAT_COMP_PROMPT +from langchain.prompts.base import BasePromptTemplate -class ChatPromptTemplate(BaseModel, ABC): - input_variables: List[str] - - @abstractmethod - def format(self, *args: Any, **kwargs: Any) -> List[ChatMessage]: - """Format chat prompt template.""" - - -class EvalPrompt(ChatPromptTemplate): - input_variables: List[str] = ["query", "answer", "student_a", "student_b"] - - def format( - self, *, query: str, answer: str, student_a: str, student_b: str, **kwargs: Any - ) -> List[ChatMessage]: - return [ - ChatMessage(text=CHAT_COMPARISON_INSTRUCTIONS, role="system"), - ChatMessage( - text=CHAT_COMPARISON_RESPONSE_TEMPLATE.format( - query=query, answer=answer, student_a=student_a, student_b=student_b - ), - role="user", - ), - ] - - -PROMPT = EvalPrompt() - - -class QACompChatChain(BaseChatChain): - """Chat Chain specifically for evaluating question answering.""" - - model: BaseChatModel - prompt: ChatPromptTemplate - - @property - def input_keys(self) -> List[str]: - return self.prompt.input_variables - - @property - def output_keys(self) -> List[str]: - return ["text"] +class QACompChatChain(ChatModelChain): + """LLM Chain specifically for evaluating question answering.""" @classmethod - def from_model( - cls, model: BaseChatModel, prompt: ChatPromptTemplate = PROMPT, **kwargs: Any + def from_llm( + cls, + llm: BaseChatModel, + prompt: BasePromptTemplate = CHAT_COMP_PROMPT, + **kwargs: Any, ) -> QACompChatChain: - expected_input_vars = {"query", "answer", "student_a", "student_b"} + expected_input_vars = {"query", "answer", "result"} if expected_input_vars != set(prompt.input_variables): raise ValueError( f"Input variables should be {expected_input_vars}, " f"but got {prompt.input_variables}" ) - return cls(model=model, prompt=prompt, **kwargs) - - def _call(self, inputs: Dict[str, str]) -> Dict[str, str]: - messages = self.prompt.format(**inputs) - result = self.model.run(messages) - return {"text": result.text} + return cls(llm=llm, prompt=prompt, **kwargs) def evaluate( self, diff --git a/langchain/evaluation/qa/chat_eval_chain.py b/langchain/evaluation/qa/chat_eval_chain.py index edeb8d70de6..bd7a3da9282 100644 --- a/langchain/evaluation/qa/chat_eval_chain.py +++ b/langchain/evaluation/qa/chat_eval_chain.py @@ -1,78 +1,28 @@ """LLM Chain specifically for evaluating question answering.""" from __future__ import annotations -from abc import ABC, abstractmethod -from typing import Any, Dict, List +from typing import Any, List -from pydantic import BaseModel - -from langchain.chat.base import BaseChatChain +from langchain.chains.llm import ChatModelChain from langchain.chat_models.base import BaseChatModel -from langchain.evaluation.qa.eval_prompt import ( - CHAT_INSTRUCTIONS, - CHAT_RESPONSE_TEMPLATE, -) -from langchain.schema import ChatMessage +from langchain.evaluation.qa.eval_prompt import CHAT_PROMPT +from langchain.prompts.base import BasePromptTemplate -class ChatPromptTemplate(BaseModel, ABC): - input_variables: List[str] - - @abstractmethod - def format(self, *, query: str, answer: str, result: str) -> List[ChatMessage]: - """Format chat prompt template.""" - - -class EvalPrompt(ChatPromptTemplate): - input_variables: List[str] = ["query", "answer", "result"] - - def format( - self, *, query: str, answer: str, result: str, **kwargs: Any - ) -> List[ChatMessage]: - return [ - ChatMessage(text=CHAT_INSTRUCTIONS, role="system"), - ChatMessage( - text=CHAT_RESPONSE_TEMPLATE.format( - query=query, answer=answer, result=result - ), - role="user", - ), - ] - - -PROMPT = EvalPrompt() - - -class QAEvalChatChain(BaseChatChain): - """Chat Chain specifically for evaluating question answering.""" - - model: BaseChatModel - prompt: ChatPromptTemplate - - @property - def input_keys(self) -> List[str]: - return self.prompt.input_variables - - @property - def output_keys(self) -> List[str]: - return ["text"] +class QAEvalChain(ChatModelChain): + """LLM Chain specifically for evaluating question answering.""" @classmethod - def from_model( - cls, model: BaseChatModel, prompt: ChatPromptTemplate = PROMPT, **kwargs: Any - ) -> QAEvalChatChain: + def from_llm( + cls, llm: BaseChatModel, prompt: BasePromptTemplate = CHAT_PROMPT, **kwargs: Any + ) -> QAEvalChain: expected_input_vars = {"query", "answer", "result"} if expected_input_vars != set(prompt.input_variables): raise ValueError( f"Input variables should be {expected_input_vars}, " f"but got {prompt.input_variables}" ) - return cls(model=model, prompt=prompt, **kwargs) - - def _call(self, inputs: Dict[str, str]) -> Dict[str, str]: - messages = self.prompt.format(**inputs) - result = self.model.run(messages) - return {"text": result.text} + return cls(llm=llm, prompt=prompt, **kwargs) def evaluate( self, @@ -91,5 +41,5 @@ class QAEvalChatChain(BaseChatChain): } for i, example in enumerate(examples) ] - results = [self(inp) for inp in inputs] - return results + + return self.apply(inputs) diff --git a/langchain/evaluation/qa/eval_prompt.py b/langchain/evaluation/qa/eval_prompt.py index ad7de98be1f..21666bfa347 100644 --- a/langchain/evaluation/qa/eval_prompt.py +++ b/langchain/evaluation/qa/eval_prompt.py @@ -1,5 +1,6 @@ # flake8: noqa from langchain.prompts import PromptTemplate +from langchain.prompts.chat import ChatPromptTemplate template = """You are a teacher grading a quiz. You are given a question, the student's answer, and the true answer, and are asked to score it as either CORRECT or INCORRECT. @@ -37,3 +38,16 @@ CHAT_COMPARISON_RESPONSE_TEMPLATE = """QUESTION: {query} TRUE ANSWER: {answer} STUDENT A ANSWER: {student_a} STUDENT B ANSWER: {student_b}""" + + +CHAT_PROMPT = ChatPromptTemplate.from_strings( + [("system", CHAT_INSTRUCTIONS), ("user", CHAT_RESPONSE_TEMPLATE)] +) + + +CHAT_COMP_PROMPT = ChatPromptTemplate.from_strings( + [ + ("system", CHAT_COMPARISON_INSTRUCTIONS), + ("user", CHAT_COMPARISON_RESPONSE_TEMPLATE), + ] +) diff --git a/langchain/prompts/chat.py b/langchain/prompts/chat.py index 19004d68f8a..e0d57c961f1 100644 --- a/langchain/prompts/chat.py +++ b/langchain/prompts/chat.py @@ -1,7 +1,10 @@ +from __future__ import annotations + from abc import ABC -from typing import Any, List, Tuple +from typing import Any, Callable, List, Tuple, Union from langchain.prompts.base import BasePromptTemplate +from langchain.prompts.prompt import PromptTemplate from langchain.schema import ChatMessage @@ -9,11 +12,20 @@ class ChatPromptTemplate(BasePromptTemplate, ABC): input_variables: List[str] messages: List[Tuple[str, BasePromptTemplate]] + @classmethod + def from_strings(cls, string_messages: List[Tuple[str, str]]) -> ChatPromptTemplate: + messages = [ + (role, PromptTemplate.from_template(template)) + for role, template in string_messages + ] + input_vars = set([m.input_variables] for _, m in messages) + return cls(input_variables=list(input_vars), messages=messages) + def format(self, **kwargs: Any) -> str: return str(self.format_chat(**kwargs)) def format_chat(self, **kwargs: Any) -> List[ChatMessage]: - """To be implemented by subclasses.""" + """Format message templates.""" result = [] for role, prompt in self.messages: rel_params = { @@ -23,6 +35,9 @@ class ChatPromptTemplate(BasePromptTemplate, ABC): result.append(ChatMessage(text=message, role=role)) return result + def partial(self, **kwargs: Union[str, Callable[[], str]]) -> BasePromptTemplate: + raise NotImplementedError + @property def _prompt_type(self) -> str: raise NotImplementedError