autogen

Форк
0
/
agentchat_nestedchat_optiguide.ipynb 
1523 строки · 62.0 Кб
1
{
2
 "cells": [
3
  {
4
   "attachments": {},
5
   "cell_type": "markdown",
6
   "metadata": {
7
    "slideshow": {
8
     "slide_type": "slide"
9
    }
10
   },
11
   "source": [
12
    "# OptiGuide with Nested Chats in AutoGen \n",
13
    "\n",
14
    "This is a nested chat re-implementation of [OptiGuide](https://github.com/microsoft/OptiGuide), which is an LLM-based supply chain optimization framework. \n",
15
    " \n",
16
    " \n",
17
    "In addition to AutoGen, this notebook also requires eventlet and Gurobipy.  The eventlet package is used in this notebook to constrain code execution with a timeout, and the gurobipy package is a mathematical optimization software library for solving mixed-integer linear and quadratic optimization problems. \n",
18
    "\n",
19
    "````{=mdx}\n",
20
    ":::info Requirements\n",
21
    "Some extra dependencies are needed for this notebook, which can be installed via pip:\n",
22
    "\n",
23
    "```bash\n",
24
    "pip install pyautogen eventlet gurobipy\n",
25
    "```\n",
26
    "\n",
27
    "For more information, please refer to the [installation guide](/docs/installation/).\n",
28
    ":::\n",
29
    "````\n",
30
    "\n"
31
   ]
32
  },
33
  {
34
   "cell_type": "code",
35
   "execution_count": 11,
36
   "metadata": {},
37
   "outputs": [],
38
   "source": [
39
    "import re\n",
40
    "from typing import Union\n",
41
    "\n",
42
    "# import auxiliary packages\n",
43
    "import requests  # for loading the example source code\n",
44
    "from eventlet.timeout import Timeout\n",
45
    "\n",
46
    "# test Gurobi installation\n",
47
    "from gurobipy import GRB\n",
48
    "from termcolor import colored\n",
49
    "\n",
50
    "import autogen\n",
51
    "from autogen.code_utils import extract_code\n",
52
    "\n",
53
    "config_list_gpt4 = autogen.config_list_from_json(\n",
54
    "    \"OAI_CONFIG_LIST\",\n",
55
    "    filter_dict={\n",
56
    "        \"model\": [\"gpt-4\", \"gpt4\", \"gpt-3.5-turbo\" \"gpt-4-32k\", \"gpt-4-32k-0314\", \"gpt-4-32k-v0314\"],\n",
57
    "    },\n",
58
    ")\n",
59
    "llm_config = {\"config_list\": config_list_gpt4}"
60
   ]
61
  },
62
  {
63
   "cell_type": "markdown",
64
   "metadata": {},
65
   "source": [
66
    "````{=mdx}\n",
67
    ":::tip\n",
68
    "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n",
69
    ":::\n",
70
    "````\n",
71
    "\n",
72
    "Intended agent orchestration in OptiGuide."
73
   ]
74
  },
75
  {
76
   "cell_type": "markdown",
77
   "metadata": {},
78
   "source": [
79
    "![](optiGuide_new_design.png)\n",
80
    "\n",
81
    "\n",
82
    "\n",
83
    "## Step 0. Prepare Helper Functions\n",
84
    "The code cell below includes several helper functions to be used by agents. The helper functions are adopted directly from [OptiGuide](https://github.com/microsoft/OptiGuide).\n",
85
    "\n",
86
    "### Utility Functions\n",
87
    "Several utility functions (replace, insert_code, run_with_exec) are defined to manipulate and execute the source code dynamically:\n",
88
    "\n",
89
    "- **replace(src_code, old_code, new_code) -> str:**<br/>Replaces a specified block of code within the source code with new code. This is essential for updating the code dynamically based on chatbot and user interactions.\n",
90
    "\n",
91
    "- **insert_code(src_code, new_lines) -> str:**<br/>Determines where to insert new lines of code based on specific markers in the source code. It's used to seamlessly integrate generated code snippets.\n",
92
    "\n",
93
    "- **run_with_exec(src_code) -> Union[str, Exception]:**<br/>Executes the modified source code within a controlled environment, capturing and returning the output or any errors that occur. This function is crucial for testing the feasibility and correctness of the generated code solutions.\n",
94
    "\n",
95
    "### Functions External Code Retrieval\n",
96
    "The cell also includes logic to retrieve example source code from a remote repository using the requests library. This example code serves as a base for the chatbot to work with, allowing users to see real-life applications of the concepts discussed.\n",
97
    "\n",
98
    "The retrieved code is then displayed, showing both the beginning and the end of the source code to give a glimpse of its structure and content."
99
   ]
100
  },
101
  {
102
   "cell_type": "code",
103
   "execution_count": 12,
104
   "metadata": {},
105
   "outputs": [],
106
   "source": [
107
    "def replace(src_code: str, old_code: str, new_code: str) -> str:\n",
108
    "    \"\"\"\n",
109
    "    Inserts new code into the source code by replacing a specified old\n",
110
    "    code block.\n",
111
    "\n",
112
    "    Args:\n",
113
    "        src_code (str): The source code to modify.\n",
114
    "        old_code (str): The code block to be replaced.\n",
115
    "        new_code (str): The new code block to insert.\n",
116
    "\n",
117
    "    Returns:\n",
118
    "        str: The modified source code with the new code inserted.\n",
119
    "\n",
120
    "    Raises:\n",
121
    "        None\n",
122
    "\n",
123
    "    Example:\n",
124
    "        src_code = 'def hello_world():\\n    print(\"Hello, world!\")\\n\\n# Some\n",
125
    "        other code here'\n",
126
    "        old_code = 'print(\"Hello, world!\")'\n",
127
    "        new_code = 'print(\"Bonjour, monde!\")\\nprint(\"Hola, mundo!\")'\n",
128
    "        modified_code = _replace(src_code, old_code, new_code)\n",
129
    "        print(modified_code)\n",
130
    "        # Output:\n",
131
    "        # def hello_world():\n",
132
    "        #     print(\"Bonjour, monde!\")\n",
133
    "        #     print(\"Hola, mundo!\")\n",
134
    "        # Some other code here\n",
135
    "    \"\"\"\n",
136
    "    pattern = r\"( *){old_code}\".format(old_code=old_code)\n",
137
    "    head_spaces = re.search(pattern, src_code, flags=re.DOTALL).group(1)\n",
138
    "    new_code = \"\\n\".join([head_spaces + line for line in new_code.split(\"\\n\")])\n",
139
    "    rst = re.sub(pattern, new_code, src_code)\n",
140
    "    return rst\n",
141
    "\n",
142
    "\n",
143
    "def insert_code(src_code: str, new_lines: str) -> str:\n",
144
    "    \"\"\"insert a code patch into the source code.\n",
145
    "\n",
146
    "\n",
147
    "    Args:\n",
148
    "        src_code (str): the full source code\n",
149
    "        new_lines (str): The new code.\n",
150
    "\n",
151
    "    Returns:\n",
152
    "        str: the full source code after insertion (replacement).\n",
153
    "    \"\"\"\n",
154
    "    if new_lines.find(\"addConstr\") >= 0:\n",
155
    "        return replace(src_code, CONSTRAINT_CODE_STR, new_lines)\n",
156
    "    else:\n",
157
    "        return replace(src_code, DATA_CODE_STR, new_lines)\n",
158
    "\n",
159
    "\n",
160
    "def run_with_exec(src_code: str) -> Union[str, Exception]:\n",
161
    "    \"\"\"Run the code snippet with exec.\n",
162
    "\n",
163
    "    Args:\n",
164
    "        src_code (str): The source code to run.\n",
165
    "\n",
166
    "    Returns:\n",
167
    "        object: The result of the code snippet.\n",
168
    "            If the code succeed, returns the objective value (float or string).\n",
169
    "            else, return the error (exception)\n",
170
    "    \"\"\"\n",
171
    "    locals_dict = {}\n",
172
    "    locals_dict.update(globals())\n",
173
    "    locals_dict.update(locals())\n",
174
    "\n",
175
    "    timeout = Timeout(\n",
176
    "        60,\n",
177
    "        TimeoutError(\"This is a timeout exception, in case \" \"GPT's code falls into infinite loop.\"),\n",
178
    "    )\n",
179
    "    try:\n",
180
    "        exec(src_code, locals_dict, locals_dict)\n",
181
    "    except Exception as e:\n",
182
    "        return e\n",
183
    "    finally:\n",
184
    "        timeout.cancel()\n",
185
    "\n",
186
    "    try:\n",
187
    "        status = locals_dict[\"m\"].Status\n",
188
    "        if status != GRB.OPTIMAL:\n",
189
    "            if status == GRB.UNBOUNDED:\n",
190
    "                ans = \"unbounded\"\n",
191
    "            elif status == GRB.INF_OR_UNBD:\n",
192
    "                ans = \"inf_or_unbound\"\n",
193
    "            elif status == GRB.INFEASIBLE:\n",
194
    "                ans = \"infeasible\"\n",
195
    "                m = locals_dict[\"m\"]\n",
196
    "                m.computeIIS()\n",
197
    "                constrs = [c.ConstrName for c in m.getConstrs() if c.IISConstr]\n",
198
    "                ans += \"\\nConflicting Constraints:\\n\" + str(constrs)\n",
199
    "            else:\n",
200
    "                ans = \"Model Status:\" + str(status)\n",
201
    "        else:\n",
202
    "            ans = \"Optimization problem solved. The objective value is: \" + str(locals_dict[\"m\"].objVal)\n",
203
    "    except Exception as e:\n",
204
    "        return e\n",
205
    "\n",
206
    "    return ans"
207
   ]
208
  },
209
  {
210
   "cell_type": "markdown",
211
   "metadata": {},
212
   "source": [
213
    "## Step 1. Agent Construction\n",
214
    "\n",
215
    "This cell introduces the Writer and OptiGuide agent classes and their instances to manage the interaction between the user, the chatbot, and the optimization solver. This streamlines the process of generating, evaluating, and integrating code solutions for supply chain optimization problems.\n",
216
    "\n",
217
    "### Classes Defined\n",
218
    "\n",
219
    "- **`OptiGuide`**: Inherits from `autogen.AssistantAgent` and serves as the main class for handling the supply chain optimization logic. It maintains state information like the source code, debugging attempts left, success status, and user chat history. Key methods include `set_success` and `update_debug_times`, which are used to update the agent's state based on the outcomes of interactions.\n",
220
    "\n",
221
    "- **`Writer`**: Also inherits from `autogen.AssistantAgent`, this class is tailored to manage the generation and explanation of Python code solutions. It keeps track of the source code and example Q&A to assist in generating responses to user queries.\n",
222
    "\n",
223
    "### Agent Instances\n",
224
    "\n",
225
    "- **`writer`**, **`safeguard`**, and **`optiguide_commander`**: Instances of the classes defined above, each configured with specific roles in the code generation and evaluation process. These agents work together to ensure that the user's questions are answered with safe, optimized, and understandable Python code solutions.\n",
226
    "\n",
227
    "- **`user`**: An instance of `autogen.UserProxyAgent`, representing the end-user interacting with the notebook. It's configured to simulate user inputs and responses, facilitating an interactive and engaging learning experience.\n",
228
    "\n",
229
    "#### Agent System Messages\n",
230
    "Each agent is associted with a corresponding system message. Those system messages are adopted directly from [OptiGuide](https://github.com/microsoft/OptiGuide).\n",
231
    "\n"
232
   ]
233
  },
234
  {
235
   "cell_type": "code",
236
   "execution_count": 13,
237
   "metadata": {},
238
   "outputs": [
239
    {
240
     "name": "stdout",
241
     "output_type": "stream",
242
     "text": [
243
      "import time\n",
244
      "\n",
245
      "from gurobipy import GRB, Model\n",
246
      "\n",
247
      "# Example data\n",
248
      "\n",
249
      "capacity_in_supplier = {'supplier1': 150, 'supplier2': 50, 'supplier3': 100}\n",
250
      "\n",
251
      "shipping_cost_from_supplier_to_roastery = {\n",
252
      "    ('supplier1', 'roastery1'): 5,\n",
253
      ".\n",
254
      ".\n",
255
      ".\n",
256
      "\n",
257
      "# Solve\n",
258
      "m.update()\n",
259
      "model.optimize()\n",
260
      "\n",
261
      "print(time.ctime())\n",
262
      "if m.status == GRB.OPTIMAL:\n",
263
      "    print(f'Optimal cost: {m.objVal}')\n",
264
      "else:\n",
265
      "    print(\"Not solved to optimality. Optimization status:\", m.status)\n",
266
      "\n"
267
     ]
268
    }
269
   ],
270
   "source": [
271
    "# %% System Messages\n",
272
    "WRITER_SYSTEM_MSG = \"\"\"You are a chatbot to:\n",
273
    "(1) write Python code to answer users questions for supply chain-related coding\n",
274
    "project;\n",
275
    "(2) explain solutions from a Gurobi/Python solver.\n",
276
    "\n",
277
    "--- SOURCE CODE ---\n",
278
    "{source_code}\n",
279
    "\n",
280
    "--- DOC STR ---\n",
281
    "{doc_str}\n",
282
    "---\n",
283
    "\n",
284
    "Here are some example questions and their answers and codes:\n",
285
    "--- EXAMPLES ---\n",
286
    "{example_qa}\n",
287
    "---\n",
288
    "\n",
289
    "The execution result of the original source code is below.\n",
290
    "--- Original Result ---\n",
291
    "{execution_result}\n",
292
    "\n",
293
    "Note that your written code will be added to the lines with substring:\n",
294
    "\"# OPTIGUIDE *** CODE GOES HERE\"\n",
295
    "So, you don't need to write other code, such as m.optimize() or m.update().\n",
296
    "You just need to write code snippet in ```python ...``` block.\n",
297
    "\"\"\"\n",
298
    "\n",
299
    "SAFEGUARD_SYSTEM_MSG = \"\"\"\n",
300
    "Given the source code:\n",
301
    "{source_code}\n",
302
    "\n",
303
    "Is the source code safe (not malicious code to break security\n",
304
    "and privacy) to run?\n",
305
    "Answer only one word.\n",
306
    "If not safe, answer `DANGER`; else, answer `SAFE`.\n",
307
    "\"\"\"\n",
308
    "\n",
309
    "# %% Constant strings to match code lines in the source code.\n",
310
    "DATA_CODE_STR = \"# OPTIGUIDE DATA CODE GOES HERE\"\n",
311
    "CONSTRAINT_CODE_STR = \"# OPTIGUIDE CONSTRAINT CODE GOES HERE\"\n",
312
    "\n",
313
    "# In-context learning examples.\n",
314
    "example_qa = \"\"\"\n",
315
    "----------\n",
316
    "Question: Why is it not recommended to use just one supplier for roastery 2?\n",
317
    "Answer Code:\n",
318
    "```python\n",
319
    "z = m.addVars(suppliers, vtype=GRB.BINARY, name=\"z\")\n",
320
    "m.addConstr(sum(z[s] for s in suppliers) <= 1, \"_\")\n",
321
    "for s in suppliers:\n",
322
    "    m.addConstr(x[s,'roastery2'] <= capacity_in_supplier[s] * z[s], \"_\")\n",
323
    "```\n",
324
    "\n",
325
    "----------\n",
326
    "Question: What if there's a 13% jump in the demand for light coffee at cafe1?\n",
327
    "Answer Code:\n",
328
    "```python\n",
329
    "light_coffee_needed_for_cafe[\"cafe1\"] = light_coffee_needed_for_cafe[\"cafe1\"] * (1 + 13/100)\n",
330
    "```\n",
331
    "\n",
332
    "\"\"\"\n",
333
    "\n",
334
    "CODE_PROMPT = \"\"\"\n",
335
    "Answer Code:\n",
336
    "\"\"\"\n",
337
    "\n",
338
    "DEBUG_PROMPT = \"\"\"\n",
339
    "\n",
340
    "While running the code you suggested, I encountered the {error_type}:\n",
341
    "--- ERROR MESSAGE ---\n",
342
    "{error_message}\n",
343
    "\n",
344
    "Please try to resolve this bug, and rewrite the code snippet.\n",
345
    "--- NEW CODE ---\n",
346
    "\"\"\"\n",
347
    "\n",
348
    "SAFEGUARD_PROMPT = \"\"\"\n",
349
    "--- Code ---\n",
350
    "{code}\n",
351
    "\n",
352
    "--- One-Word Answer: SAFE or DANGER ---\n",
353
    "\"\"\"\n",
354
    "\n",
355
    "INTERPRETER_PROMPT = \"\"\"Here are the execution results: {execution_rst}\n",
356
    "\n",
357
    "Can you organize these information to a human readable answer?\n",
358
    "Remember to compare the new results to the original results you obtained in the\n",
359
    "beginning.\n",
360
    "\n",
361
    "--- HUMAN READABLE ANSWER ---\n",
362
    "\"\"\"\n",
363
    "\n",
364
    "# Get the source code of the coffee example from OptiGuide's official repo\n",
365
    "code_url = \"https://raw.githubusercontent.com/microsoft/OptiGuide/main/benchmark/application/coffee.py\"\n",
366
    "response = requests.get(code_url)\n",
367
    "# Check if the request was successful\n",
368
    "if response.status_code == 200:\n",
369
    "    # Get the text content from the response\n",
370
    "    code = response.text\n",
371
    "else:\n",
372
    "    raise RuntimeError(\"Failed to retrieve the file.\")\n",
373
    "# code = open(code_url, \"r\").read() # for local files\n",
374
    "\n",
375
    "\n",
376
    "# show the first head and tail of the source code\n",
377
    "print(\"\\n\".join(code.split(\"\\n\")[:10]))\n",
378
    "print(\".\\n\" * 3)\n",
379
    "print(\"\\n\".join(code.split(\"\\n\")[-10:]))\n",
380
    "\n",
381
    "writer_system_msg = WRITER_SYSTEM_MSG.format(\n",
382
    "    source_code=code,\n",
383
    "    doc_str=\"\",\n",
384
    "    example_qa=example_qa,\n",
385
    "    execution_result=\"\",\n",
386
    ")\n",
387
    "safeguard_system_msg = SAFEGUARD_SYSTEM_MSG.format(source_code=code)\n",
388
    "\n",
389
    "\n",
390
    "class OptiGuide(autogen.AssistantAgent):\n",
391
    "    source_code: str = code\n",
392
    "    debug_times: int = 3\n",
393
    "    debug_times_left: int = 3\n",
394
    "    example_qa: str = \"\"\n",
395
    "    success: bool = False\n",
396
    "    user_chat_history: str = \"\"\n",
397
    "\n",
398
    "\n",
399
    "class Writer(autogen.AssistantAgent):\n",
400
    "    source_code: str = code\n",
401
    "    example_qa: str = \"\"\n",
402
    "    user_chat_history: str = \"\"\n",
403
    "\n",
404
    "\n",
405
    "writer = Writer(\"writer\", llm_config=llm_config)\n",
406
    "safeguard = autogen.AssistantAgent(\"safeguard\", llm_config=llm_config)\n",
407
    "optiguide_commander = OptiGuide(\"commander\", llm_config=llm_config)\n",
408
    "\n",
409
    "user = autogen.UserProxyAgent(\n",
410
    "    \"user\", max_consecutive_auto_reply=0, human_input_mode=\"NEVER\", code_execution_config=False\n",
411
    ")"
412
   ]
413
  },
414
  {
415
   "cell_type": "markdown",
416
   "metadata": {},
417
   "source": [
418
    "## Step 2. Orchestrate Nested Chats \n",
419
    "\n",
420
    "These three agent instances are orchestrated in the following way with the `writer` and `safeguard` nested into the `commander` agent as the inner monologue.\n",
421
    "\n",
422
    "This next cell defines critical functions that manage the interactions between the `OptiGuide` system, the user, and the internal logic for processing and responding to queries. Each function plays a specific role in guiding the conversation, handling code generation requests, ensuring code safety, and summarizing outcomes.  This ensures that agents receive clear instructions, immediate feedback, and a secure environment for exploring supply chain optimization problems through code.\n",
423
    "\n",
424
    "Information about the sequence of chats can be specified in the `chat_queue` argument of the `register_nested_chats` function. The following fields are especially useful:\n",
425
    "- `recipient` (required) specifies the nested agent;\n",
426
    "- `message` specifies what message to send to the nested recipient agent. In a sequence of nested chats, if the `message` field is not specified, we will use the last message the registering agent received as the initial message in the first chat and will skip any subsequent chat in the queue that does not have the `message` field. You can either provide a string or define a callable that returns a string.\n",
427
    "- `summary_method` decides what to get out of the nested chat. You can either select from existing options including \"last_msg\" and \"reflection_with_llm\", or or define your own way on what to get from the nested chat with a Callable.\n",
428
    "- `max_turns` determines how many turns of conversation to have between the concerned agent pairs."
429
   ]
430
  },
431
  {
432
   "cell_type": "code",
433
   "execution_count": 14,
434
   "metadata": {},
435
   "outputs": [],
436
   "source": [
437
    "def writer_init_messsage(recipient, messages, sender, config):\n",
438
    "    if recipient.success:\n",
439
    "        return None\n",
440
    "    msg_content = messages[-1].get(\"content\", \"\")\n",
441
    "    # board = config\n",
442
    "    # get execution result of the original source code\n",
443
    "    sender_history = recipient.chat_messages[sender]\n",
444
    "    user_chat_history = \"\\nHere are the history of discussions:\\n\" f\"{sender_history}\"\n",
445
    "\n",
446
    "    if sender.name == \"user\":\n",
447
    "        execution_result = msg_content  # TODO: get the execution result of the original source code\n",
448
    "    else:\n",
449
    "        execution_result = \"\"\n",
450
    "    writer_sys_msg = (\n",
451
    "        WRITER_SYSTEM_MSG.format(\n",
452
    "            source_code=recipient.source_code,\n",
453
    "            doc_str=\"\",\n",
454
    "            example_qa=example_qa,\n",
455
    "            execution_result=execution_result,\n",
456
    "        )\n",
457
    "        + user_chat_history\n",
458
    "    )\n",
459
    "\n",
460
    "    # safeguard.reset() #TODO: reset safeguard\n",
461
    "    recipient.debug_times_left = recipient.debug_times\n",
462
    "    recipient.success = False\n",
463
    "    return writer_sys_msg + \"\\n\" + CODE_PROMPT\n",
464
    "\n",
465
    "\n",
466
    "def writer_success_summary(recipient, sender):\n",
467
    "    if sender.success:\n",
468
    "        return sender.last_message(recipient)[\"content\"].replace(\"TERMINATE\", \"\")\n",
469
    "    else:\n",
470
    "        return \"Sorry. I cannot answer your question.\"\n",
471
    "\n",
472
    "\n",
473
    "def safeguard_init_message(recipient, messages, sender, config):\n",
474
    "    if recipient.success:\n",
475
    "        return None\n",
476
    "    last_msg_content = messages[-1].get(\"content\", \"\")\n",
477
    "    _, code = extract_code(last_msg_content)[0]\n",
478
    "    if _ != \"unknown\":\n",
479
    "        return SAFEGUARD_SYSTEM_MSG.format(source_code=code) + sender.user_chat_history\n",
480
    "    else:\n",
481
    "        return\n",
482
    "        # return SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code)\n",
483
    "\n",
484
    "\n",
485
    "def safeguard_summary(recipient, sender):\n",
486
    "    safe_msg = sender.last_message(recipient)[\"content\"].replace(\"TERMINATE\", \"\")\n",
487
    "\n",
488
    "    if safe_msg.find(\"DANGER\") < 0:\n",
489
    "        # Step 4 and 5: Run the code and obtain the results\n",
490
    "        src_code = insert_code(sender.source_code, code)\n",
491
    "        execution_rst = run_with_exec(src_code)\n",
492
    "        print(colored(str(execution_rst), \"yellow\"))\n",
493
    "        if type(execution_rst) in [str, int, float]:\n",
494
    "            # we successfully run the code and get the result\n",
495
    "            sender.success = True\n",
496
    "            # Step 6: request to interpret results\n",
497
    "            return INTERPRETER_PROMPT.format(execution_rst=execution_rst)\n",
498
    "    else:\n",
499
    "        # DANGER: If not safe, try to debug. Redo coding\n",
500
    "        execution_rst = \"\"\"\n",
501
    "        Sorry, this new code is not safe to run. I would not allow you to execute it.\n",
502
    "        Please try to find a new way (coding) to answer the question.\"\"\"\n",
503
    "        if sender.debug_times_left > 0:\n",
504
    "            # Try to debug and write code again (back to step 2)\n",
505
    "            sender.debug_times_left -= 1\n",
506
    "            return DEBUG_PROMPT.format(error_type=type(execution_rst), error_message=str(execution_rst))\n",
507
    "\n",
508
    "\n",
509
    "writer_chat_queue = [{\"recipient\": writer, \"message\": writer_init_messsage, \"summary_method\": writer_success_summary}]\n",
510
    "safeguard_chat_queue = [\n",
511
    "    {\"recipient\": safeguard, \"message\": safeguard_init_message, \"max_turns\": 1, \"summary_method\": safeguard_summary}\n",
512
    "]\n",
513
    "# safeguard is triggered only when receiving a message from the writer\n",
514
    "optiguide_commander.register_nested_chats(safeguard_chat_queue, trigger=\"writer\")\n",
515
    "# writer is triggered only when receiving a message from the user\n",
516
    "optiguide_commander.register_nested_chats(writer_chat_queue, trigger=\"user\")"
517
   ]
518
  },
519
  {
520
   "cell_type": "markdown",
521
   "metadata": {},
522
   "source": [
523
    "### Let the agents talk"
524
   ]
525
  },
526
  {
527
   "cell_type": "code",
528
   "execution_count": 15,
529
   "metadata": {},
530
   "outputs": [
531
    {
532
     "name": "stdout",
533
     "output_type": "stream",
534
     "text": [
535
      "\u001b[33muser\u001b[0m (to commander):\n",
536
      "\n",
537
      "What if we prohibit shipping from supplier 1 to roastery 2?\n",
538
      "\n",
539
      "--------------------------------------------------------------------------------\n",
540
      "\u001b[34m\n",
541
      "********************************************************************************\u001b[0m\n",
542
      "\u001b[34mStart a new chat with the following message: \n",
543
      "You are a chatbot to:\n",
544
      "(1) write Python code to answer users questions for supply chain-related coding\n",
545
      "project;\n",
546
      "(2) explain solutions from a Gurobi/Python solver.\n",
547
      "\n",
548
      "--- SOURCE CODE ---\n",
549
      "import time\n",
550
      "\n",
551
      "from gurobipy import GRB, Model\n",
552
      "\n",
553
      "# Example data\n",
554
      "\n",
555
      "capacity_in_supplier = {'supplier1': 150, 'supplier2': 50, 'supplier3': 100}\n",
556
      "\n",
557
      "shipping_cost_from_supplier_to_roastery = {\n",
558
      "    ('supplier1', 'roastery1'): 5,\n",
559
      "    ('supplier1', 'roastery2'): 4,\n",
560
      "    ('supplier2', 'roastery1'): 6,\n",
561
      "    ('supplier2', 'roastery2'): 3,\n",
562
      "    ('supplier3', 'roastery1'): 2,\n",
563
      "    ('supplier3', 'roastery2'): 7\n",
564
      "}\n",
565
      "\n",
566
      "roasting_cost_light = {'roastery1': 3, 'roastery2': 5}\n",
567
      "\n",
568
      "roasting_cost_dark = {'roastery1': 5, 'roastery2': 6}\n",
569
      "\n",
570
      "shipping_cost_from_roastery_to_cafe = {\n",
571
      "    ('roastery1', 'cafe1'): 5,\n",
572
      "    ('roastery1', 'cafe2'): 3,\n",
573
      "    ('roastery1', 'cafe3'): 6,\n",
574
      "    ('roastery2', 'cafe1'): 4,\n",
575
      "    ('roastery2', 'cafe2'): 5,\n",
576
      "    ('roastery2', 'cafe3'): 2\n",
577
      "}\n",
578
      "\n",
579
      "light_coffee_needed_for_cafe = {'cafe1': 20, 'cafe2': 30, 'cafe3': 40}\n",
580
      "\n",
581
      "dark_coffee_needed_for_cafe = {'cafe1': 20, 'cafe2': 20, 'cafe3': 100}\n",
582
      "\n",
583
      "cafes = list(set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()))\n",
584
      "roasteries = list(\n",
585
      "    set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()))\n",
586
      "suppliers = list(\n",
587
      "    set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()))\n",
588
      "\n",
589
      "# Create a new model\n",
590
      "model = Model(\"coffee_distribution\")\n",
591
      "\n",
592
      "# OPTIGUIDE DATA CODE GOES HERE\n",
593
      "\n",
594
      "# Create variables\n",
595
      "x = model.addVars(shipping_cost_from_supplier_to_roastery.keys(),\n",
596
      "                  vtype=GRB.INTEGER,\n",
597
      "                  name=\"x\")\n",
598
      "y_light = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\n",
599
      "                        vtype=GRB.INTEGER,\n",
600
      "                        name=\"y_light\")\n",
601
      "y_dark = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\n",
602
      "                       vtype=GRB.INTEGER,\n",
603
      "                       name=\"y_dark\")\n",
604
      "\n",
605
      "# Set objective\n",
606
      "model.setObjective(\n",
607
      "    sum(x[i] * shipping_cost_from_supplier_to_roastery[i]\n",
608
      "        for i in shipping_cost_from_supplier_to_roastery.keys()) +\n",
609
      "    sum(roasting_cost_light[r] * y_light[r, c] +\n",
610
      "        roasting_cost_dark[r] * y_dark[r, c]\n",
611
      "        for r, c in shipping_cost_from_roastery_to_cafe.keys()) + sum(\n",
612
      "            (y_light[j] + y_dark[j]) * shipping_cost_from_roastery_to_cafe[j]\n",
613
      "            for j in shipping_cost_from_roastery_to_cafe.keys()), GRB.MINIMIZE)\n",
614
      "\n",
615
      "# Conservation of flow constraint\n",
616
      "for r in set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()):\n",
617
      "    model.addConstr(\n",
618
      "        sum(x[i] for i in shipping_cost_from_supplier_to_roastery.keys()\n",
619
      "            if i[1] == r) == sum(\n",
620
      "                y_light[j] + y_dark[j]\n",
621
      "                for j in shipping_cost_from_roastery_to_cafe.keys()\n",
622
      "                if j[0] == r), f\"flow_{r}\")\n",
623
      "\n",
624
      "# Add supply constraints\n",
625
      "for s in set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()):\n",
626
      "    model.addConstr(\n",
627
      "        sum(x[i] for i in shipping_cost_from_supplier_to_roastery.keys()\n",
628
      "            if i[0] == s) <= capacity_in_supplier[s], f\"supply_{s}\")\n",
629
      "\n",
630
      "# Add demand constraints\n",
631
      "for c in set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()):\n",
632
      "    model.addConstr(\n",
633
      "        sum(y_light[j] for j in shipping_cost_from_roastery_to_cafe.keys()\n",
634
      "            if j[1] == c) >= light_coffee_needed_for_cafe[c],\n",
635
      "        f\"light_demand_{c}\")\n",
636
      "    model.addConstr(\n",
637
      "        sum(y_dark[j] for j in shipping_cost_from_roastery_to_cafe.keys()\n",
638
      "            if j[1] == c) >= dark_coffee_needed_for_cafe[c],\n",
639
      "        f\"dark_demand_{c}\")\n",
640
      "\n",
641
      "# Optimize model\n",
642
      "model.optimize()\n",
643
      "m = model\n",
644
      "\n",
645
      "# OPTIGUIDE CONSTRAINT CODE GOES HERE\n",
646
      "\n",
647
      "# Solve\n",
648
      "m.update()\n",
649
      "model.optimize()\n",
650
      "\n",
651
      "print(time.ctime())\n",
652
      "if m.status == GRB.OPTIMAL:\n",
653
      "    print(f'Optimal cost: {m.objVal}')\n",
654
      "else:\n",
655
      "    print(\"Not solved to optimality. Optimization status:\", m.status)\n",
656
      "\n",
657
      "\n",
658
      "--- DOC STR ---\n",
659
      "\n",
660
      "---\n",
661
      "\n",
662
      "Here are some example questions and their answers and codes:\n",
663
      "--- EXAMPLES ---\n",
664
      "\n",
665
      "----------\n",
666
      "Question: Why is it not recommended to use just one supplier for roastery 2?\n",
667
      "Answer Code:\n",
668
      "```python\n",
669
      "z = m.addVars(suppliers, vtype=GRB.BINARY, name=\"z\")\n",
670
      "m.addConstr(sum(z[s] for s in suppliers) <= 1, \"_\")\n",
671
      "for s in suppliers:\n",
672
      "    m.addConstr(x[s,'roastery2'] <= capacity_in_supplier[s] * z[s], \"_\")\n",
673
      "```\n",
674
      "\n",
675
      "----------\n",
676
      "Question: What if there's a 13% jump in the demand for light coffee at cafe1?\n",
677
      "Answer Code:\n",
678
      "```python\n",
679
      "light_coffee_needed_for_cafe[\"cafe1\"] = light_coffee_needed_for_cafe[\"cafe1\"] * (1 + 13/100)\n",
680
      "```\n",
681
      "\n",
682
      "\n",
683
      "---\n",
684
      "\n",
685
      "The execution result of the original source code is below.\n",
686
      "--- Original Result ---\n",
687
      "What if we prohibit shipping from supplier 1 to roastery 2?\n",
688
      "\n",
689
      "Note that your written code will be added to the lines with substring:\n",
690
      "\"# OPTIGUIDE *** CODE GOES HERE\"\n",
691
      "So, you don't need to write other code, such as m.optimize() or m.update().\n",
692
      "You just need to write code snippet in ```python ...``` block.\n",
693
      "\n",
694
      "Here are the history of discussions:\n",
695
      "[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'user'}]\n",
696
      "\n",
697
      "Answer Code:\n",
698
      "\n",
699
      "\n",
700
      "With the following carryover: \n",
701
      "\u001b[0m\n",
702
      "\u001b[34m\n",
703
      "********************************************************************************\u001b[0m\n",
704
      "\u001b[33mcommander\u001b[0m (to writer):\n",
705
      "\n",
706
      "You are a chatbot to:\n",
707
      "(1) write Python code to answer users questions for supply chain-related coding\n",
708
      "project;\n",
709
      "(2) explain solutions from a Gurobi/Python solver.\n",
710
      "\n",
711
      "--- SOURCE CODE ---\n",
712
      "import time\n",
713
      "\n",
714
      "from gurobipy import GRB, Model\n",
715
      "\n",
716
      "# Example data\n",
717
      "\n",
718
      "capacity_in_supplier = {'supplier1': 150, 'supplier2': 50, 'supplier3': 100}\n",
719
      "\n",
720
      "shipping_cost_from_supplier_to_roastery = {\n",
721
      "    ('supplier1', 'roastery1'): 5,\n",
722
      "    ('supplier1', 'roastery2'): 4,\n",
723
      "    ('supplier2', 'roastery1'): 6,\n",
724
      "    ('supplier2', 'roastery2'): 3,\n",
725
      "    ('supplier3', 'roastery1'): 2,\n",
726
      "    ('supplier3', 'roastery2'): 7\n",
727
      "}\n",
728
      "\n",
729
      "roasting_cost_light = {'roastery1': 3, 'roastery2': 5}\n",
730
      "\n",
731
      "roasting_cost_dark = {'roastery1': 5, 'roastery2': 6}\n",
732
      "\n",
733
      "shipping_cost_from_roastery_to_cafe = {\n",
734
      "    ('roastery1', 'cafe1'): 5,\n",
735
      "    ('roastery1', 'cafe2'): 3,\n",
736
      "    ('roastery1', 'cafe3'): 6,\n",
737
      "    ('roastery2', 'cafe1'): 4,\n",
738
      "    ('roastery2', 'cafe2'): 5,\n",
739
      "    ('roastery2', 'cafe3'): 2\n",
740
      "}\n",
741
      "\n",
742
      "light_coffee_needed_for_cafe = {'cafe1': 20, 'cafe2': 30, 'cafe3': 40}\n",
743
      "\n",
744
      "dark_coffee_needed_for_cafe = {'cafe1': 20, 'cafe2': 20, 'cafe3': 100}\n",
745
      "\n",
746
      "cafes = list(set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()))\n",
747
      "roasteries = list(\n",
748
      "    set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()))\n",
749
      "suppliers = list(\n",
750
      "    set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()))\n",
751
      "\n",
752
      "# Create a new model\n",
753
      "model = Model(\"coffee_distribution\")\n",
754
      "\n",
755
      "# OPTIGUIDE DATA CODE GOES HERE\n",
756
      "\n",
757
      "# Create variables\n",
758
      "x = model.addVars(shipping_cost_from_supplier_to_roastery.keys(),\n",
759
      "                  vtype=GRB.INTEGER,\n",
760
      "                  name=\"x\")\n",
761
      "y_light = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\n",
762
      "                        vtype=GRB.INTEGER,\n",
763
      "                        name=\"y_light\")\n",
764
      "y_dark = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\n",
765
      "                       vtype=GRB.INTEGER,\n",
766
      "                       name=\"y_dark\")\n",
767
      "\n",
768
      "# Set objective\n",
769
      "model.setObjective(\n",
770
      "    sum(x[i] * shipping_cost_from_supplier_to_roastery[i]\n",
771
      "        for i in shipping_cost_from_supplier_to_roastery.keys()) +\n",
772
      "    sum(roasting_cost_light[r] * y_light[r, c] +\n",
773
      "        roasting_cost_dark[r] * y_dark[r, c]\n",
774
      "        for r, c in shipping_cost_from_roastery_to_cafe.keys()) + sum(\n",
775
      "            (y_light[j] + y_dark[j]) * shipping_cost_from_roastery_to_cafe[j]\n",
776
      "            for j in shipping_cost_from_roastery_to_cafe.keys()), GRB.MINIMIZE)\n",
777
      "\n",
778
      "# Conservation of flow constraint\n",
779
      "for r in set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()):\n",
780
      "    model.addConstr(\n",
781
      "        sum(x[i] for i in shipping_cost_from_supplier_to_roastery.keys()\n",
782
      "            if i[1] == r) == sum(\n",
783
      "                y_light[j] + y_dark[j]\n",
784
      "                for j in shipping_cost_from_roastery_to_cafe.keys()\n",
785
      "                if j[0] == r), f\"flow_{r}\")\n",
786
      "\n",
787
      "# Add supply constraints\n",
788
      "for s in set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()):\n",
789
      "    model.addConstr(\n",
790
      "        sum(x[i] for i in shipping_cost_from_supplier_to_roastery.keys()\n",
791
      "            if i[0] == s) <= capacity_in_supplier[s], f\"supply_{s}\")\n",
792
      "\n",
793
      "# Add demand constraints\n",
794
      "for c in set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()):\n",
795
      "    model.addConstr(\n",
796
      "        sum(y_light[j] for j in shipping_cost_from_roastery_to_cafe.keys()\n",
797
      "            if j[1] == c) >= light_coffee_needed_for_cafe[c],\n",
798
      "        f\"light_demand_{c}\")\n",
799
      "    model.addConstr(\n",
800
      "        sum(y_dark[j] for j in shipping_cost_from_roastery_to_cafe.keys()\n",
801
      "            if j[1] == c) >= dark_coffee_needed_for_cafe[c],\n",
802
      "        f\"dark_demand_{c}\")\n",
803
      "\n",
804
      "# Optimize model\n",
805
      "model.optimize()\n",
806
      "m = model\n",
807
      "\n",
808
      "# OPTIGUIDE CONSTRAINT CODE GOES HERE\n",
809
      "\n",
810
      "# Solve\n",
811
      "m.update()\n",
812
      "model.optimize()\n",
813
      "\n",
814
      "print(time.ctime())\n",
815
      "if m.status == GRB.OPTIMAL:\n",
816
      "    print(f'Optimal cost: {m.objVal}')\n",
817
      "else:\n",
818
      "    print(\"Not solved to optimality. Optimization status:\", m.status)\n",
819
      "\n",
820
      "\n",
821
      "--- DOC STR ---\n",
822
      "\n",
823
      "---\n",
824
      "\n",
825
      "Here are some example questions and their answers and codes:\n",
826
      "--- EXAMPLES ---\n",
827
      "\n",
828
      "----------\n",
829
      "Question: Why is it not recommended to use just one supplier for roastery 2?\n",
830
      "Answer Code:\n",
831
      "```python\n",
832
      "z = m.addVars(suppliers, vtype=GRB.BINARY, name=\"z\")\n",
833
      "m.addConstr(sum(z[s] for s in suppliers) <= 1, \"_\")\n",
834
      "for s in suppliers:\n",
835
      "    m.addConstr(x[s,'roastery2'] <= capacity_in_supplier[s] * z[s], \"_\")\n",
836
      "```\n",
837
      "\n",
838
      "----------\n",
839
      "Question: What if there's a 13% jump in the demand for light coffee at cafe1?\n",
840
      "Answer Code:\n",
841
      "```python\n",
842
      "light_coffee_needed_for_cafe[\"cafe1\"] = light_coffee_needed_for_cafe[\"cafe1\"] * (1 + 13/100)\n",
843
      "```\n",
844
      "\n",
845
      "\n",
846
      "---\n",
847
      "\n",
848
      "The execution result of the original source code is below.\n",
849
      "--- Original Result ---\n",
850
      "What if we prohibit shipping from supplier 1 to roastery 2?\n",
851
      "\n",
852
      "Note that your written code will be added to the lines with substring:\n",
853
      "\"# OPTIGUIDE *** CODE GOES HERE\"\n",
854
      "So, you don't need to write other code, such as m.optimize() or m.update().\n",
855
      "You just need to write code snippet in ```python ...``` block.\n",
856
      "\n",
857
      "Here are the history of discussions:\n",
858
      "[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'user'}]\n",
859
      "\n",
860
      "Answer Code:\n",
861
      "\n",
862
      "\n",
863
      "--------------------------------------------------------------------------------\n",
864
      "\u001b[33mwriter\u001b[0m (to commander):\n",
865
      "\n",
866
      "To answer the user's question regarding the prohibition of shipping from supplier 1 to roastery 2, we need to add a constraint to the optimization model that sets the shipping quantity `x` for the (`supplier1`, `roastery2`) route to zero. This will effectively disallow any shipping from supplier 1 to roastery 2. The code to do this should be placed in the section of the source code that is indicated by the comment \"# OPTIGUIDE DATA CODE GOES HERE\".\n",
867
      "\n",
868
      "Here's the Python code snippet to add this constraint:\n",
869
      "\n",
870
      "```python\n",
871
      "# filename: supply_chain_code.py\n",
872
      "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"prohibit_supplier1_roastery2\")\n",
873
      "```\n",
874
      "\n",
875
      "Adding this constraint will prevent the optimizer from selecting any transport from `supplier1` to `roastery2`, which is what the prohibition is intended to achieve.\n",
876
      "\n",
877
      "Instruct the user to add this code snippet in the appropriate place in the source code that they have provided. After adding this code, the `m.optimize()` function will solve the modified version of the problem that includes the new constraint.\n",
878
      "\n",
879
      "--------------------------------------------------------------------------------\n",
880
      "\u001b[34m\n",
881
      "********************************************************************************\u001b[0m\n",
882
      "\u001b[34mStart a new chat with the following message: \n",
883
      "\n",
884
      "Given the source code:\n",
885
      "# filename: supply_chain_code.py\n",
886
      "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"prohibit_supplier1_roastery2\")\n",
887
      "\n",
888
      "Is the source code safe (not malicious code to break security\n",
889
      "and privacy) to run?\n",
890
      "Answer only one word.\n",
891
      "If not safe, answer `DANGER`; else, answer `SAFE`.\n",
892
      "\n",
893
      "\n",
894
      "With the following carryover: \n",
895
      "\u001b[0m\n",
896
      "\u001b[34m\n",
897
      "********************************************************************************\u001b[0m\n",
898
      "\u001b[33mcommander\u001b[0m (to safeguard):\n",
899
      "\n",
900
      "\n",
901
      "Given the source code:\n",
902
      "# filename: supply_chain_code.py\n",
903
      "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"prohibit_supplier1_roastery2\")\n",
904
      "\n",
905
      "Is the source code safe (not malicious code to break security\n",
906
      "and privacy) to run?\n",
907
      "Answer only one word.\n",
908
      "If not safe, answer `DANGER`; else, answer `SAFE`.\n",
909
      "\n",
910
      "\n",
911
      "--------------------------------------------------------------------------------\n",
912
      "\u001b[33msafeguard\u001b[0m (to commander):\n",
913
      "\n",
914
      "SAFE\n",
915
      "\n",
916
      "--------------------------------------------------------------------------------\n",
917
      "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n",
918
      "\n",
919
      "CPU model: Apple M3 Max\n",
920
      "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n",
921
      "\n",
922
      "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n",
923
      "Model fingerprint: 0x8aa2c280\n",
924
      "Variable types: 0 continuous, 18 integer (0 binary)\n",
925
      "Coefficient statistics:\n",
926
      "  Matrix range     [1e+00, 1e+00]\n",
927
      "  Objective range  [2e+00, 1e+01]\n",
928
      "  Bounds range     [0e+00, 0e+00]\n",
929
      "  RHS range        [2e+01, 2e+02]\n",
930
      "Found heuristic solution: objective 2900.0000000\n",
931
      "Presolve time: 0.00s\n",
932
      "Presolved: 11 rows, 18 columns, 36 nonzeros\n",
933
      "Variable types: 0 continuous, 18 integer (0 binary)\n",
934
      "Found heuristic solution: objective 2896.0000000\n",
935
      "\n",
936
      "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n",
937
      "\n",
938
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
939
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
940
      "\n",
941
      "*    0     0               0    2470.0000000 2470.00000  0.00%     -    0s\n",
942
      "\n",
943
      "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n",
944
      "Thread count was 14 (of 14 available processors)\n",
945
      "\n",
946
      "Solution count 3: 2470 2896 2900 \n",
947
      "\n",
948
      "Optimal solution found (tolerance 1.00e-04)\n",
949
      "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n",
950
      "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n",
951
      "\n",
952
      "CPU model: Apple M3 Max\n",
953
      "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n",
954
      "\n",
955
      "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n",
956
      "Model fingerprint: 0x8aa2c280\n",
957
      "Variable types: 0 continuous, 18 integer (0 binary)\n",
958
      "Coefficient statistics:\n",
959
      "  Matrix range     [1e+00, 1e+00]\n",
960
      "  Objective range  [2e+00, 1e+01]\n",
961
      "  Bounds range     [0e+00, 0e+00]\n",
962
      "  RHS range        [2e+01, 2e+02]\n",
963
      "Found heuristic solution: objective 2900.0000000\n",
964
      "Presolve time: 0.00s\n",
965
      "Presolved: 11 rows, 18 columns, 36 nonzeros\n",
966
      "Variable types: 0 continuous, 18 integer (0 binary)\n",
967
      "Found heuristic solution: objective 2896.0000000\n",
968
      "\n",
969
      "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n",
970
      "\n",
971
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
972
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
973
      "\n",
974
      "*    0     0               0    2470.0000000 2470.00000  0.00%     -    0s\n",
975
      "\n",
976
      "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n",
977
      "Thread count was 14 (of 14 available processors)\n",
978
      "\n",
979
      "Solution count 3: 2470 2896 2900 \n",
980
      "\n",
981
      "Optimal solution found (tolerance 1.00e-04)\n",
982
      "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n",
983
      "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n",
984
      "\n",
985
      "CPU model: Apple M3 Max\n",
986
      "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n",
987
      "\n",
988
      "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n",
989
      "Model fingerprint: 0x8aa2c280\n",
990
      "Variable types: 0 continuous, 18 integer (0 binary)\n",
991
      "Coefficient statistics:\n",
992
      "  Matrix range     [1e+00, 1e+00]\n",
993
      "  Objective range  [2e+00, 1e+01]\n",
994
      "  Bounds range     [0e+00, 0e+00]\n",
995
      "  RHS range        [2e+01, 2e+02]\n",
996
      "Presolved: 11 rows, 18 columns, 36 nonzeros\n",
997
      "\n",
998
      "Continuing optimization...\n",
999
      "\n",
1000
      "\n",
1001
      "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n",
1002
      "Thread count was 14 (of 14 available processors)\n",
1003
      "\n",
1004
      "Solution count 3: 2470 2896 2900 \n",
1005
      "\n",
1006
      "Optimal solution found (tolerance 1.00e-04)\n",
1007
      "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n",
1008
      "Sun Feb 25 18:56:45 2024\n",
1009
      "Optimal cost: 2470.0\n",
1010
      "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n",
1011
      "\n",
1012
      "CPU model: Apple M3 Max\n",
1013
      "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n",
1014
      "\n",
1015
      "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n",
1016
      "Model fingerprint: 0x8aa2c280\n",
1017
      "Variable types: 0 continuous, 18 integer (0 binary)\n",
1018
      "Coefficient statistics:\n",
1019
      "  Matrix range     [1e+00, 1e+00]\n",
1020
      "  Objective range  [2e+00, 1e+01]\n",
1021
      "  Bounds range     [0e+00, 0e+00]\n",
1022
      "  RHS range        [2e+01, 2e+02]\n",
1023
      "Presolved: 11 rows, 18 columns, 36 nonzeros\n",
1024
      "\n",
1025
      "Continuing optimization...\n",
1026
      "\n",
1027
      "\n",
1028
      "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n",
1029
      "Thread count was 14 (of 14 available processors)\n",
1030
      "\n",
1031
      "Solution count 3: 2470 2896 2900 \n",
1032
      "\n",
1033
      "Optimal solution found (tolerance 1.00e-04)\n",
1034
      "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n",
1035
      "Sun Feb 25 18:56:45 2024\n",
1036
      "Optimal cost: 2470.0\n",
1037
      "\u001b[33mOptimization problem solved. The objective value is: 2470.0\u001b[0m\n",
1038
      "\u001b[33mcommander\u001b[0m (to writer):\n",
1039
      "\n",
1040
      "Here are the execution results: Optimization problem solved. The objective value is: 2470.0\n",
1041
      "\n",
1042
      "Can you organize these information to a human readable answer?\n",
1043
      "Remember to compare the new results to the original results you obtained in the\n",
1044
      "beginning.\n",
1045
      "\n",
1046
      "--- HUMAN READABLE ANSWER ---\n",
1047
      "\n",
1048
      "\n",
1049
      "--------------------------------------------------------------------------------\n",
1050
      "\u001b[33mwriter\u001b[0m (to commander):\n",
1051
      "\n",
1052
      "Certainly! Here's a human-readable answer:\n",
1053
      "\n",
1054
      "After integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\n",
1055
      "\n",
1056
      "To provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\n",
1057
      "\n",
1058
      "If prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\n",
1059
      "\n",
1060
      "Without the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\n",
1061
      "\n",
1062
      "--------------------------------------------------------------------------------\n",
1063
      "\u001b[33mcommander\u001b[0m (to user):\n",
1064
      "\n",
1065
      "Certainly! Here's a human-readable answer:\n",
1066
      "\n",
1067
      "After integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\n",
1068
      "\n",
1069
      "To provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\n",
1070
      "\n",
1071
      "If prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\n",
1072
      "\n",
1073
      "Without the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\n",
1074
      "\n",
1075
      "--------------------------------------------------------------------------------\n"
1076
     ]
1077
    }
1078
   ],
1079
   "source": [
1080
    "chat_res = user.initiate_chat(\n",
1081
    "    optiguide_commander, message=\"What if we prohibit shipping from supplier 1 to roastery 2?\"\n",
1082
    ")"
1083
   ]
1084
  },
1085
  {
1086
   "cell_type": "markdown",
1087
   "metadata": {},
1088
   "source": [
1089
    "### Get Final Results from the Returned ChatResult Object "
1090
   ]
1091
  },
1092
  {
1093
   "cell_type": "code",
1094
   "execution_count": 17,
1095
   "metadata": {},
1096
   "outputs": [
1097
    {
1098
     "name": "stdout",
1099
     "output_type": "stream",
1100
     "text": [
1101
      "Certainly! Here's a human-readable answer:\n",
1102
      "\n",
1103
      "After integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\n",
1104
      "\n",
1105
      "To provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\n",
1106
      "\n",
1107
      "If prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\n",
1108
      "\n",
1109
      "Without the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\n"
1110
     ]
1111
    }
1112
   ],
1113
   "source": [
1114
    "print(chat_res.summary)"
1115
   ]
1116
  }
1117
 ],
1118
 "metadata": {
1119
  "extra_files_to_copy": [
1120
   "optiGuide_new_design.png"
1121
  ],
1122
  "front_matter": {
1123
   "description": "This is a nested chat re-implementation of OptiGuide which is an LLM-based supply chain optimization framework.",
1124
   "tags": [
1125
    "nested chat",
1126
    "hierarchical chat",
1127
    "orchestration"
1128
   ]
1129
  },
1130
  "kernelspec": {
1131
   "display_name": "Python 3",
1132
   "language": "python",
1133
   "name": "python3"
1134
  },
1135
  "language_info": {
1136
   "codemirror_mode": {
1137
    "name": "ipython",
1138
    "version": 3
1139
   },
1140
   "file_extension": ".py",
1141
   "mimetype": "text/x-python",
1142
   "name": "python",
1143
   "nbconvert_exporter": "python",
1144
   "pygments_lexer": "ipython3",
1145
   "version": "3.10.13"
1146
  },
1147
  "vscode": {
1148
   "interpreter": {
1149
    "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1"
1150
   }
1151
  },
1152
  "widgets": {
1153
   "application/vnd.jupyter.widget-state+json": {
1154
    "state": {
1155
     "2d910cfd2d2a4fc49fc30fbbdc5576a7": {
1156
      "model_module": "@jupyter-widgets/base",
1157
      "model_module_version": "2.0.0",
1158
      "model_name": "LayoutModel",
1159
      "state": {
1160
       "_model_module": "@jupyter-widgets/base",
1161
       "_model_module_version": "2.0.0",
1162
       "_model_name": "LayoutModel",
1163
       "_view_count": null,
1164
       "_view_module": "@jupyter-widgets/base",
1165
       "_view_module_version": "2.0.0",
1166
       "_view_name": "LayoutView",
1167
       "align_content": null,
1168
       "align_items": null,
1169
       "align_self": null,
1170
       "border_bottom": null,
1171
       "border_left": null,
1172
       "border_right": null,
1173
       "border_top": null,
1174
       "bottom": null,
1175
       "display": null,
1176
       "flex": null,
1177
       "flex_flow": null,
1178
       "grid_area": null,
1179
       "grid_auto_columns": null,
1180
       "grid_auto_flow": null,
1181
       "grid_auto_rows": null,
1182
       "grid_column": null,
1183
       "grid_gap": null,
1184
       "grid_row": null,
1185
       "grid_template_areas": null,
1186
       "grid_template_columns": null,
1187
       "grid_template_rows": null,
1188
       "height": null,
1189
       "justify_content": null,
1190
       "justify_items": null,
1191
       "left": null,
1192
       "margin": null,
1193
       "max_height": null,
1194
       "max_width": null,
1195
       "min_height": null,
1196
       "min_width": null,
1197
       "object_fit": null,
1198
       "object_position": null,
1199
       "order": null,
1200
       "overflow": null,
1201
       "padding": null,
1202
       "right": null,
1203
       "top": null,
1204
       "visibility": null,
1205
       "width": null
1206
      }
1207
     },
1208
     "454146d0f7224f038689031002906e6f": {
1209
      "model_module": "@jupyter-widgets/controls",
1210
      "model_module_version": "2.0.0",
1211
      "model_name": "HBoxModel",
1212
      "state": {
1213
       "_dom_classes": [],
1214
       "_model_module": "@jupyter-widgets/controls",
1215
       "_model_module_version": "2.0.0",
1216
       "_model_name": "HBoxModel",
1217
       "_view_count": null,
1218
       "_view_module": "@jupyter-widgets/controls",
1219
       "_view_module_version": "2.0.0",
1220
       "_view_name": "HBoxView",
1221
       "box_style": "",
1222
       "children": [
1223
        "IPY_MODEL_e4ae2b6f5a974fd4bafb6abb9d12ff26",
1224
        "IPY_MODEL_577e1e3cc4db4942b0883577b3b52755",
1225
        "IPY_MODEL_b40bdfb1ac1d4cffb7cefcb870c64d45"
1226
       ],
1227
       "layout": "IPY_MODEL_dc83c7bff2f241309537a8119dfc7555",
1228
       "tabbable": null,
1229
       "tooltip": null
1230
      }
1231
     },
1232
     "577e1e3cc4db4942b0883577b3b52755": {
1233
      "model_module": "@jupyter-widgets/controls",
1234
      "model_module_version": "2.0.0",
1235
      "model_name": "FloatProgressModel",
1236
      "state": {
1237
       "_dom_classes": [],
1238
       "_model_module": "@jupyter-widgets/controls",
1239
       "_model_module_version": "2.0.0",
1240
       "_model_name": "FloatProgressModel",
1241
       "_view_count": null,
1242
       "_view_module": "@jupyter-widgets/controls",
1243
       "_view_module_version": "2.0.0",
1244
       "_view_name": "ProgressView",
1245
       "bar_style": "success",
1246
       "description": "",
1247
       "description_allow_html": false,
1248
       "layout": "IPY_MODEL_2d910cfd2d2a4fc49fc30fbbdc5576a7",
1249
       "max": 1,
1250
       "min": 0,
1251
       "orientation": "horizontal",
1252
       "style": "IPY_MODEL_74a6ba0c3cbc4051be0a83e152fe1e62",
1253
       "tabbable": null,
1254
       "tooltip": null,
1255
       "value": 1
1256
      }
1257
     },
1258
     "6086462a12d54bafa59d3c4566f06cb2": {
1259
      "model_module": "@jupyter-widgets/base",
1260
      "model_module_version": "2.0.0",
1261
      "model_name": "LayoutModel",
1262
      "state": {
1263
       "_model_module": "@jupyter-widgets/base",
1264
       "_model_module_version": "2.0.0",
1265
       "_model_name": "LayoutModel",
1266
       "_view_count": null,
1267
       "_view_module": "@jupyter-widgets/base",
1268
       "_view_module_version": "2.0.0",
1269
       "_view_name": "LayoutView",
1270
       "align_content": null,
1271
       "align_items": null,
1272
       "align_self": null,
1273
       "border_bottom": null,
1274
       "border_left": null,
1275
       "border_right": null,
1276
       "border_top": null,
1277
       "bottom": null,
1278
       "display": null,
1279
       "flex": null,
1280
       "flex_flow": null,
1281
       "grid_area": null,
1282
       "grid_auto_columns": null,
1283
       "grid_auto_flow": null,
1284
       "grid_auto_rows": null,
1285
       "grid_column": null,
1286
       "grid_gap": null,
1287
       "grid_row": null,
1288
       "grid_template_areas": null,
1289
       "grid_template_columns": null,
1290
       "grid_template_rows": null,
1291
       "height": null,
1292
       "justify_content": null,
1293
       "justify_items": null,
1294
       "left": null,
1295
       "margin": null,
1296
       "max_height": null,
1297
       "max_width": null,
1298
       "min_height": null,
1299
       "min_width": null,
1300
       "object_fit": null,
1301
       "object_position": null,
1302
       "order": null,
1303
       "overflow": null,
1304
       "padding": null,
1305
       "right": null,
1306
       "top": null,
1307
       "visibility": null,
1308
       "width": null
1309
      }
1310
     },
1311
     "74a6ba0c3cbc4051be0a83e152fe1e62": {
1312
      "model_module": "@jupyter-widgets/controls",
1313
      "model_module_version": "2.0.0",
1314
      "model_name": "ProgressStyleModel",
1315
      "state": {
1316
       "_model_module": "@jupyter-widgets/controls",
1317
       "_model_module_version": "2.0.0",
1318
       "_model_name": "ProgressStyleModel",
1319
       "_view_count": null,
1320
       "_view_module": "@jupyter-widgets/base",
1321
       "_view_module_version": "2.0.0",
1322
       "_view_name": "StyleView",
1323
       "bar_color": null,
1324
       "description_width": ""
1325
      }
1326
     },
1327
     "7d3f3d9e15894d05a4d188ff4f466554": {
1328
      "model_module": "@jupyter-widgets/controls",
1329
      "model_module_version": "2.0.0",
1330
      "model_name": "HTMLStyleModel",
1331
      "state": {
1332
       "_model_module": "@jupyter-widgets/controls",
1333
       "_model_module_version": "2.0.0",
1334
       "_model_name": "HTMLStyleModel",
1335
       "_view_count": null,
1336
       "_view_module": "@jupyter-widgets/base",
1337
       "_view_module_version": "2.0.0",
1338
       "_view_name": "StyleView",
1339
       "background": null,
1340
       "description_width": "",
1341
       "font_size": null,
1342
       "text_color": null
1343
      }
1344
     },
1345
     "b40bdfb1ac1d4cffb7cefcb870c64d45": {
1346
      "model_module": "@jupyter-widgets/controls",
1347
      "model_module_version": "2.0.0",
1348
      "model_name": "HTMLModel",
1349
      "state": {
1350
       "_dom_classes": [],
1351
       "_model_module": "@jupyter-widgets/controls",
1352
       "_model_module_version": "2.0.0",
1353
       "_model_name": "HTMLModel",
1354
       "_view_count": null,
1355
       "_view_module": "@jupyter-widgets/controls",
1356
       "_view_module_version": "2.0.0",
1357
       "_view_name": "HTMLView",
1358
       "description": "",
1359
       "description_allow_html": false,
1360
       "layout": "IPY_MODEL_f1355871cc6f4dd4b50d9df5af20e5c8",
1361
       "placeholder": "​",
1362
       "style": "IPY_MODEL_ca245376fd9f4354af6b2befe4af4466",
1363
       "tabbable": null,
1364
       "tooltip": null,
1365
       "value": " 1/1 [00:00&lt;00:00, 44.69it/s]"
1366
      }
1367
     },
1368
     "ca245376fd9f4354af6b2befe4af4466": {
1369
      "model_module": "@jupyter-widgets/controls",
1370
      "model_module_version": "2.0.0",
1371
      "model_name": "HTMLStyleModel",
1372
      "state": {
1373
       "_model_module": "@jupyter-widgets/controls",
1374
       "_model_module_version": "2.0.0",
1375
       "_model_name": "HTMLStyleModel",
1376
       "_view_count": null,
1377
       "_view_module": "@jupyter-widgets/base",
1378
       "_view_module_version": "2.0.0",
1379
       "_view_name": "StyleView",
1380
       "background": null,
1381
       "description_width": "",
1382
       "font_size": null,
1383
       "text_color": null
1384
      }
1385
     },
1386
     "dc83c7bff2f241309537a8119dfc7555": {
1387
      "model_module": "@jupyter-widgets/base",
1388
      "model_module_version": "2.0.0",
1389
      "model_name": "LayoutModel",
1390
      "state": {
1391
       "_model_module": "@jupyter-widgets/base",
1392
       "_model_module_version": "2.0.0",
1393
       "_model_name": "LayoutModel",
1394
       "_view_count": null,
1395
       "_view_module": "@jupyter-widgets/base",
1396
       "_view_module_version": "2.0.0",
1397
       "_view_name": "LayoutView",
1398
       "align_content": null,
1399
       "align_items": null,
1400
       "align_self": null,
1401
       "border_bottom": null,
1402
       "border_left": null,
1403
       "border_right": null,
1404
       "border_top": null,
1405
       "bottom": null,
1406
       "display": null,
1407
       "flex": null,
1408
       "flex_flow": null,
1409
       "grid_area": null,
1410
       "grid_auto_columns": null,
1411
       "grid_auto_flow": null,
1412
       "grid_auto_rows": null,
1413
       "grid_column": null,
1414
       "grid_gap": null,
1415
       "grid_row": null,
1416
       "grid_template_areas": null,
1417
       "grid_template_columns": null,
1418
       "grid_template_rows": null,
1419
       "height": null,
1420
       "justify_content": null,
1421
       "justify_items": null,
1422
       "left": null,
1423
       "margin": null,
1424
       "max_height": null,
1425
       "max_width": null,
1426
       "min_height": null,
1427
       "min_width": null,
1428
       "object_fit": null,
1429
       "object_position": null,
1430
       "order": null,
1431
       "overflow": null,
1432
       "padding": null,
1433
       "right": null,
1434
       "top": null,
1435
       "visibility": null,
1436
       "width": null
1437
      }
1438
     },
1439
     "e4ae2b6f5a974fd4bafb6abb9d12ff26": {
1440
      "model_module": "@jupyter-widgets/controls",
1441
      "model_module_version": "2.0.0",
1442
      "model_name": "HTMLModel",
1443
      "state": {
1444
       "_dom_classes": [],
1445
       "_model_module": "@jupyter-widgets/controls",
1446
       "_model_module_version": "2.0.0",
1447
       "_model_name": "HTMLModel",
1448
       "_view_count": null,
1449
       "_view_module": "@jupyter-widgets/controls",
1450
       "_view_module_version": "2.0.0",
1451
       "_view_name": "HTMLView",
1452
       "description": "",
1453
       "description_allow_html": false,
1454
       "layout": "IPY_MODEL_6086462a12d54bafa59d3c4566f06cb2",
1455
       "placeholder": "​",
1456
       "style": "IPY_MODEL_7d3f3d9e15894d05a4d188ff4f466554",
1457
       "tabbable": null,
1458
       "tooltip": null,
1459
       "value": "100%"
1460
      }
1461
     },
1462
     "f1355871cc6f4dd4b50d9df5af20e5c8": {
1463
      "model_module": "@jupyter-widgets/base",
1464
      "model_module_version": "2.0.0",
1465
      "model_name": "LayoutModel",
1466
      "state": {
1467
       "_model_module": "@jupyter-widgets/base",
1468
       "_model_module_version": "2.0.0",
1469
       "_model_name": "LayoutModel",
1470
       "_view_count": null,
1471
       "_view_module": "@jupyter-widgets/base",
1472
       "_view_module_version": "2.0.0",
1473
       "_view_name": "LayoutView",
1474
       "align_content": null,
1475
       "align_items": null,
1476
       "align_self": null,
1477
       "border_bottom": null,
1478
       "border_left": null,
1479
       "border_right": null,
1480
       "border_top": null,
1481
       "bottom": null,
1482
       "display": null,
1483
       "flex": null,
1484
       "flex_flow": null,
1485
       "grid_area": null,
1486
       "grid_auto_columns": null,
1487
       "grid_auto_flow": null,
1488
       "grid_auto_rows": null,
1489
       "grid_column": null,
1490
       "grid_gap": null,
1491
       "grid_row": null,
1492
       "grid_template_areas": null,
1493
       "grid_template_columns": null,
1494
       "grid_template_rows": null,
1495
       "height": null,
1496
       "justify_content": null,
1497
       "justify_items": null,
1498
       "left": null,
1499
       "margin": null,
1500
       "max_height": null,
1501
       "max_width": null,
1502
       "min_height": null,
1503
       "min_width": null,
1504
       "object_fit": null,
1505
       "object_position": null,
1506
       "order": null,
1507
       "overflow": null,
1508
       "padding": null,
1509
       "right": null,
1510
       "top": null,
1511
       "visibility": null,
1512
       "width": null
1513
      }
1514
     }
1515
    },
1516
    "version_major": 2,
1517
    "version_minor": 0
1518
   }
1519
  }
1520
 },
1521
 "nbformat": 4,
1522
 "nbformat_minor": 2
1523
}
1524

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.