AI Assisted Development
Wing Pro allows the use of Artificial Intelligence (AI) to suggest new code at the current insertion point, to refactor existing code of all types, to write new code according to a description of its intended functionality and design, and to 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) AI-assisted refactoring, where you ask the AI assistant to make specific changes to a selection or a whole file, and (3) description-driven code development, where you ask the AI to write new code at the current insertion point, according to a description of its functionality and design, and (4) AI chat, where you can 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 precedes 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 | 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, you can use description-driven development, as described below.
AI Refactoring
AI refactoring lets you rewrite or redesign code according to a written description of the change you want to make. 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
Asking the AI to "convert this into a Python generator that goes on forever and doesn't stop after n iterations" results in the following 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:
if i > 100:
break
print(i)
Here's another example of improving some code with AI refactoring. 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 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 the AI 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 refactoring 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".
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, we can 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 in about 20 seconds:
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 Chat
AI Chat 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 try to help 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 could refine your requests, ask about specific details, or request further changes to the code what 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. You will need to configure your AI provider as described in Configuring the AI Provider and should read the rest of this chapter to learn how to use AI suggestion, refactoring, description-driven development, and chat.
AI assisted development is an experimental feature with some limitations imposed by the computational realities of generative AI. Be sure to read also Improving Results Obtained from AI Assisted Development. Please report bugs with Submit Bug Report in Wing's Help menu or by emailing us at support@wingware.com.
Section Contents
9.1. AI Driven Code Refactoring
9.2. Description-Driven Development
9.3. AI Chat Assistant
9.4. Configuring the AI Provider
9.5. AI Request Context
9.6. Improving Results Obtained from AI Assisted Development
9.7. Managing Cost
9.8. AI Assisted Development Privacy and Data Protection