AI Assisted Development

Index of All Documentation » Wing Pro Reference Manual »


This page gives some examples of the kinds of tasks you can complete with AI assisted development in Wing Pro. For a quicker introduction to using AI-assisted development in Wing, see the AI Quick Start Guide.

Wing Pro's Artificial Intelligence (AI) capabilities can be used to suggest new code at the current insertion point, modify and write new code according to a description of its intended functionality and design, and chat iteratively about existing code or a development task.

AI-assisted development is an extremely powerful and productive paradigm that can replace manual development with AI-assisted generation or editing of larger chunks of code. Your role changes to one of directing an intelligent assistant capable of completing a wide range of programming tasks in relatively short periods of time. Instead of typing out code by hand every step of the way, you are essentially directing someone else to work through the logical steps of software development.

AI-assisted development cannot replace you as a programmer, because it does not have a true understanding of the development process, but it can considerably reduce the tedium that is often associated with software development. You will still need to review and accept or reject the AI's work, and will need to learn how to create effective requests that provide the AI both with the necessary relevant context and also clear and specific instructions.

Wing currently supports four types of AI-assisted development: (1) AI code suggestion, where you request the AI assistant to make suggestions of code to add at the current insertion point, (2) description-driven code development, where you ask the AI to write new code at the current insertion point, (3) AI-assisted code transformation, where you ask the AI assistant to make specific changes to existing code, and (4) AI chat, where you ask the AI about code or a development task in an environment that prevents it from directly changing your code.

AI Code Suggestion

You can use AI code suggestion at any point in your code to insert new code appropriate to that context. The AI uses the code that surrounds your insertion point as instructions for what it should insert. It works remarkably well when the code and comments that precede the current insertion point make it clear what you want to insert.

For example if you enter the following into the editor and request AI code suggestion at the point indicated by | (by pressing Ctrl-? or using the items int he AI menu) then the AI will try to implement the function for you:

def compute_fibobnacci_sequence|

One possible result is:

def compute_fibonacci_sequence(n):
    a, b = 0, 1
    sequence = []
    while len(sequence) < n:
        sequence.append(a)
        a, b = b, a + b
    return sequence

Another example is to use AI suggestion to fill in the implementation of methods in a partially completed class. Again, | is the position of the caret in the editor:

class Test:

  def __init__(self):
    self.people = {}

  def add_person(self, name, age, address):
    self.people[name] = (age, address)

  def remove_person|

This might implement the missing method as follows:

def remove_person(self, name):
  if name in self.people:
    del self.people[name]

Much more complex tasks are also often successful, if there is enough existing code to work from, either in the current file, in other provided context, or in the AI's training set. Most importantly, the context at the insertion point must make it clear what code should be placed there. If that is not the case (and often it is not) then you will get much better results by using description-driven development, as described below.

Description-Driven Development

You can also write new code at the current insertion point, according to a description of its functionality and design. For example, let's start with a file that contains the following:

import sys
import time

class Test:

   def __init__(self):
      self.people = {}

   def add_person(self, name, age, address):
      self.people[name] = (age, address)
      sys.stderr.write('added {}\n'.format(name))

   def remove_person(self, name):
      if name in self.people:
         del self.people[name]
      sys.stderr.write('removed {}\n'.format(name))

   def update_person(self, name, age=None, address=None):
      if name in self.people:
         if age is not None:
            self.people[name] = (age, self.people[name][1])
         if address is not None:
            self.people[name] = (self.people[name][0], address)
      sys.stderr.write('updated {}\n'.format(name))

Placing the insertion point at the end of the file, the AI Coder tool in Wing can be used to request the AI to "write asynchronous RPC client and server classes that use sockets and JSON to expose the public methods of class Test." This produces the following result:

import json
import socket
import asyncio

class TestRPCServer:
   def __init__(self, host, port, test_instance):
      self.host = host
      self.port = port
      self.test_instance = test_instance
      self.loop = asyncio.get_event_loop()
      self.server = None

   async def handle_client(self, reader, writer):
      while True:
         data = await reader.read(1024)
         if not data:
            break
         request = json.loads(data.decode())
         method = request.get('method')
         params = request.get('params', {})
         response = self.handle_request(method, params)
         writer.write(json.dumps(response).encode())
         await writer.drain()
      writer.close()

   def handle_request(self, method, params):
      try:
         if hasattr(self.test_instance, method) and callable(getattr(self.test_instance, method)):
            result = getattr(self.test_instance, method)(**params)
            return {'result': result, 'error': None}
         else:
            return {'result': None, 'error': f'Method {method} not found'}
      except Exception as e:
         return {'result': None, 'error': str(e)}

   def start_server(self):
      self.server = asyncio.start_server(self.handle_client, self.host, self.port)
      self.loop.run_until_complete(self.server)
      self.loop.run_forever()

class TestRPCClient:
   def __init__(self, host, port):
      self.host = host
      self.port = port
      self.loop = asyncio.get_event_loop()

   async def send_request(self, method, **params):
      reader, writer = await asyncio.open_connection(self.host, self.port)
      request = json.dumps({'method': method, 'params': params})
      writer.write(request.encode())
      await writer.drain()
      response = await reader.read(1024)
      writer.close()
      return json.loads(response.decode())

   def call(self, method, **params):
      return self.loop.run_until_complete(self.send_request(method, **params))

def start_server():
   test_instance = Test()
   server = TestRPCServer('localhost', 8888, test_instance)
   server.start_server()

Next you can ask it to "write an example that starts TestRPCServer in a separate process and then makes requests using TestRPCClient." This generates something like the following:

def start_rpc_server():
   test = Test()
   test_rpc_server = TestRPCServer('localhost', 8888, test)
   test_rpc_server.start_server()

if __name__ == '__main__':
   from multiprocessing import Process

   server_process = Process(target=start_rpc_server)
   server_process.start()

   try:
      time.sleep(1)  # Allow server process to start
      test_rpc_client = TestRPCClient('localhost', 8888)

      # Example usage of the TestRPCClient
      print(test_rpc_client.call('add_person', name='Alice', age=30, address='123 Main St'))
      print(test_rpc_client.call('update_person', name='Alice', age=31))
      print(test_rpc_client.call('remove_person', name='Alice'))
   finally:
      server_process.terminate()
      server_process.join()

If you try the above examples be aware that, just as with humans, work created by an AI will vary with each invocation, even if the context and request are the exactly the same.

Of course the code produced by AI is not always complete and correct. For the examples above, it took several attempts before the AI placed the 'start_rpc_server' function at the top level of the module, so that the server child process would be able to access it. In some of the attempts the necessary 'time.sleep(1)' was missing, so the client attempted to connect to the server too early.

These are typical of the kinds of errors that generative AI may make, often similar to those made by humans writing the same code. You still need to know what you are doing, will need to find and fix the bugs using Wing's debugger, and will sometimes have to decide to throw away an implementation rather than proceeding with it. Even so, on the whole you will spend far less time writing code manually.

AI Transformation

AI transformation lets you rewrite or redesign code according to a written description of the change you want to make. It is also implemented by Wing's AI Coder tool and works the same way as description-driven development, after first indicating the target code that the transformation is allowed to change. As an example, let's start with the following code we wrote earlier using AI code suggestion:

def compute_fibonacci_sequence(n):
    a, b = 0, 1
    sequence = []
    while len(sequence) < n:
        sequence.append(a)
        a, b = b, a + b
    return sequence

After selecting the code, asking the AI Coder to "convert this into a Python generator that goes on forever and doesn't stop after n iterations" results in the following new code:

def compute_fibonacci_sequence():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

Indeed, this code is correct and will work if invoked as follows:

fib = compute_fibonacci_sequence()
for i in fib:
  print(i)

Here's another example of improving some code with AI transformation. This is code that was also written mostly using AI suggestion:

class Test:

  def __init__(self):
    self.people = {}

  def add_person(self, name, age, address):
    self.people[name] = (age, address)

  def remove_person(self, name):
    if name in self.people:
      del self.people[name]

  def update_person(self, name, age=None, address=None):
    if name in self.people:
        if age is not None:
            self.people[name] = (age, self.people[name][1])
        if address is not None:
            self.people[name] = (self.people[name][0], address)

Requesting the AI Coder to "clean up this code" results in the following arguably better version of the remove_person and update_person methods:

class Test:

    def __init__(self):
        self.people = {}

    def add_person(self, name, age, address):
        self.people[name] = {'age': age, 'address': address}

    def remove_person(self, name):
        self.people.pop(name, None)

    def update_person(self, name, age=None, address=None):
        person = self.people.get(name)
        if person:
            if age is not None:
                person['age'] = age
            if address is not None:
                person['address'] = address

Now the code can be documented, for example by asking AI Coder to "add documentation strings written in French":

class Test:
    """Représente une collection de personnes."""

    def __init__(self):
        """Initialise un dictionnaire vide pour stocker les informations des personnes."""
        self.people = {}

    def add_person(self, name, age, address):
        """Ajoute une personne avec son nom, âge et adresse au dictionnaire.

        Arguments:
        name -- le nom de la personne à ajouter
        age -- l'âge de la personne
        address -- l'adresse de la personne
        """
        self.people[name] = {'age': age, 'address': address}

    def remove_person(self, name):
        """Supprime une personne du dictionnaire par son nom.

        Arguments:
        name -- le nom de la personne à supprimer
        """
        self.people.pop(name, None)

    def update_person(self, name, age=None, address=None):
        """Met à jour l'âge et/ou l'adresse d'une personne dans le dictionnaire.

        Arguments:
        name -- le nom de la personne à mettre à jour
        age -- le nouvel âge de la personne (facultatif)
        address -- la nouvelle adresse de la personne (facultatif)
        """
        person = self.people.get(name)
        if person:
            if age is not None:
                person['age'] = age
            if address is not None:
                person['address'] = address

Other example AI transformation requests that you might try include "add a summary comment at the top", "add a unittest class that tests class A", and even things like "convert this code from asynchronous to threaded".

AI Chat

The AI Chat tool in Wing lets you ask about a file, selection, or other unit of code in a chat session, where you can build and refine your requests iteratively in a conversation. You can ask the AI to summarize the purpose of code, then drill down in more detail as needed. You can produce example code that uses code that you are focusing on. You can ask the AI to suggest how to rewrite code to add certain functionality or how to adapt it for a particular use. And you can ask the AI to find problems or suggest general improvements by inspecting code.

For example, starting with the following code open in an editor, you might ask the AI Chat "Can you suggest a way to redesign this code so it'll work asynchronously?"

import threading
import time

def print_numbers():
  for i in range(10):
    time.sleep(1)
    print(i)

# Create a thread
t = threading.Thread(target=print_numbers)

# Start the thread
t.start()

# Main thread continues to run
for i in range(10,20):
  time.sleep(1.5)
  print(i)

# Wait for the thread to complete
t.join()

print("Done")

This produces a reply that includes working code and an explanation of how it works:

To redesign the code to work asynchronously, you can use the `asyncio` library in Python,
which provides a framework for writing concurrent code using the async/await syntax. Here's
how you can modify your code to use `asyncio`.
import asyncio

async def print_numbers_async():
  for i in range(10):
    await asyncio.sleep(1)  # Non-blocking sleep
    print(i)

async def print_numbers_main():
  for i in range(10, 20):
    await asyncio.sleep(1.5)  # Non-blocking sleep
    print(i)

async def main():
  # Schedule both tasks to run concurrently
  task1 = asyncio.create_task(print_numbers_async())
  task2 = asyncio.create_task(print_numbers_main())

  # Wait for both tasks to complete
  await task1
  await task2

  print("Done")

# Run the main function in the asyncio event loop
asyncio.run(main())
In this code:

- The `async def` syntax is used to define coroutine functions `print_numbers_async` and
  `print_numbers_main`.
- `await asyncio.sleep(x)` is the asynchronous equivalent of `time.sleep(x)` but doesn't
  block the event loop, allowing other tasks to run in the meantime.
- `asyncio.create_task()` schedules the coroutines to be run concurrently.
- The `main()` coroutine is the entry point for the asynchronous program, which awaits the
  completion of the other tasks.
- `asyncio.run(main())` starts the event loop and executes the `main()` coroutine.

This code will execute the `print_numbers_async()` and `print_numbers_main()` functions
concurrently, printing numbers from two ranges simultaneously in an asynchronous manner.
After both functions have finished executing, it will print "Done". Remember that
asynchronous programming is about concurrency, not parallelism, and it's particularly
well-suited for IO-bound operations. If you need to run CPU-bound operations in parallel,
you would still want to use threading or multiprocessing.

From here, you can right-click to move code into the editor, or refine your request, ask about specific details, and request further changes to the code that was produced.

Getting Started

The best way to get a feel for the power of AI assisted development is to try requests like these in your own code base. See the AI Quick Start Guide to get up and running.

Section Contents