Friday, June 21, 2024

Old School Code Generation, Combined with the New

 While Generative AI can help with some problems, what if you want to generate a lot of code consistently? The Telosys code generator might be the right tool. Let's take a look at templates for a Spring Boot application with some help from deepseek-coder:6.7b and starcoder2:3b.


DeepSeek

Prompt: Write a velocity template for the telosys code generator to create a java record

#set( $className = $entityName )
#set( $package = "com.example" )

import java.time.*;

public record ${className}(
    #foreach($field in $fields)
        #if($field.type == 'DATETIME')
            ${field.name} LocalDateTime,
        #elseif($field.type == 'DATE')
            ${field.name} LocalDate,
        #else
            ${field.name} ${field.javaType},
        #end
    #end    
) { }

It also supplied the usual support text with additional information (not shown).
The basic syntax is reasonable (error with a comma at the end, the types are in the wrong place, missing java time in javaType) and roughly matches a manually created version.
 
Here is a manually created version for comparison:
 
package ${target.javaPackageFromFolder(${SRC})};

#foreach( $import in $java.imports($entity) )
import $import;
#end

public record ${entity.name}(#foreach( $attribute in $entity.attributes )
$attribute.type $attribute.name#if( $foreach.hasNext ), #end
#end) {}



StarCoder

The code gets more interesting once a function is created. Once a select query is created in the repository, starcoder2:3b can use that information to auto generate the rest.

    private static final String insertSql = """
          insert into ${entity} (
#foreach( $column in $entity.sqlColumns  )
           #if( $foreach.index!= 0 ),#else,#end ${column}
#end
          ) values (
#foreach( $column in $entity.sqlColumns  )
           #if( $foreach.index == 1 ),#else,#end :${column}
#end
          );
         """;
    
    private static final String updateSql = """
          update ${entity} set
#foreach( $column in $entity.sqlColumns  )
           #if( $foreach.index!= 0 ),#else,#end ${column} = :${column}
#end
          where 1=1;
         """;
    
    private static final String deleteSql = """
          delete from ${entity} where 1=1;
         """;


The original manual version used entity.sqlColumns. It might make sense to spend extra time getting the first function in a class right to get a slightly closer-to-being-correct version of the rest of the code.

The Template

The full template that creates a service side application with select/insert/update code from an existing database is available on github. The generated code is available on the generated branch. Including the prompts that were used.

Conclusion


DeepSeek can be useful for general syntax and StarCoder helpful for adding code inline. For consistent, generated code, Telosys is very good.

Tuesday, May 28, 2024

Continue Continued

 It has been about three months since the post on the Continue plugin, showing how to run LLM models locally using ollama, so there is a certain amount of information that is out of date. Continue is now much easier to use and there are newer models available to run code generation locally. The latest version of Continue also has inline code completion using the starcoder2 model.

 

ollama

  • Install ollama from https://ollama.com/  
  • Open a terminal window (these commands were run on an M1 MacBook Pro)
  • Run ollama ls This should start ollama and show that no models are available
  • Run ollama pull deepseek-coder:6.7b
  • Run ollama pull starcoder2:3b
  • When this completes run ollama ls again and you should see

NAME                    ID              SIZE      MODIFIED       
starcoder2:3b          f67ae0f64584    1.7 GB    29 seconds ago      
deepseek-coder:6.7b    ce298d984115    3.8 GB    About a minute ago

 

Continue

  • In VSCode, install the Continue plugin from Extensions sidebar (https://continue.dev/)
  • Open the Continue sidebar (Command+L can be used if it is not easy to find)
  • The models will be loaded automatically. This may take a minute.
  • Select Ollama - deepseek-coder:6.7b from the dropdown
  • Select the Config gear
  • Change allowAnonymousTelemetry to false

 

Conclusion

 Generative AI for code generation is more popular now than three months ago. And protecting digital assets will always be important. Running code generation 100% offline is a safe way to benefit from Generative AI while avoiding the pitfalls of sharing code in the cloud. The Continue plugin, which continues to improve, is a good way to accomplish this.

 

 



Tuesday, April 2, 2024

DeepSeek 6.7B Prompts in the Same Context

 In the previous post, code was generated in two different ways within the Continue plugin, inline and maintaining context. This time I'm going to focus on maintaining context and try to generate code with different prompts to see how easily code can be modified. All of these examples are done in Continue using the DeepSeek 6.7b model and would need to be copied into an actual file. All the files are available on github.

pom.xml

prompt: create a pom file for spring boot

     ⋮
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
     ⋮
  <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- Add your other dependencies here -->
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        <    class Solution {
    public boolean isAnagram(String s, String t) {
     ⋮
 
Some of it is close. Some does not apply at all. One of the problems with Generative AI seems to be you never really know what you are going to get. Full file
 
 
prompt: change spring boot to a parent
 
Not the best prompt. I was trying to remove the unneeded dependency on spring-boot-starter. This version did remove the non-XML at the bottom of the file though. This is one of the interesting parts of Generative AI. Each prompt generates something new. Full file
 
 
prompt: Use spring boot version 3.2.3
 
I'll come back to the dependency later. Trying to update the versions first. Full file
 
   
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
 
 
prompt: Use java 21
 
 
    <properties>
        <java.version>21</java.version> <!-- Use Java 21 -->
    </properties>
 
 
prompt: Add dependencies for spring boot jdbc and web 
 
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

 
 
 
prompt: Remove spring-boot-starter dependency
 
  <dependencies>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Add your other dependencies here -->
    </dependencies>

 
 
This turned out OK, but I think it was mostly luck. Things do not go quite as well in the next part with docker compose. Full file
 

docker-compose.yml

 prompt: create a docker-compose file to start postgres
 
 version: '3'
services:
  db:
    image: postgres:latest
    environment:
      POSTGRES_USER: "postgres"
      POSTGRES_PASSWORD: "mysecretpassword"
    volumes:
      - ./data:/var/lib/postgresql/data
    ports:
      - 5432:5432



prompt: Change password to postgres
 
version: '3'
services:
  db:
    image: postgres:latest
    environment:
      POSTGRES_USER: "postgres"
      POSTGRES_PASSWORD: "postgres"
    volumes:
        - .:/var/lib/postgresql/data
    ports:
        - 5432:5432


Changes the password but loses the volume.


prompt: Add restart unless-stopped
 
version: '3'
services:
  db:
    image: postgres:latest
    environment:
      POSTGRES_USER: "postgres"
      POSTGERS_PASSWORD: "postgres"
    volumes:
      - .:/var/lib/postgresql/data
    ports:
      - 5432:5432
    restart: unless-stopped


I was hoping it would self repair but it did not. Also changed POSTGRES_PASSWORD.
 
 
I tried again with with prompts trying to prevent a change.


prompt: create a docker-compose file to start postgres
 
version: '3'
services:
  db:
    image: postgres:latest
    restart: always
    environment:
      POSTGRES_USER: exampleuser
      POSTGRES_PASSWORD: examplepassword
      POSTGRES_DB: exampledb
    volumes:
      - ./data:/var/lib/postgresql/data
    ports:
      - 5432:5432


Added a POSTGRES_DB this time.


prompt: Change user and password to postgres. Do not change anything else.
 
version: '3'
services:
  db:
    image: postgres:latest
    restart: always
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: exampledb
    volumes:
       - .:/var/lib/postgresql/data
    ports:
       - 5432:5432

 
Still changed the volume.
 

prompt: Add restart unless-stopped. Do not change anything else.
 
version: '3'
services:
  db:
    image: postgres:latest
    restart: unless-stopped
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: exampledb
    volumes:
        - .: /var/lib/postgresql/data
    ports:
        - 5432:5432


At least it did not misspell POSTGRES this time.

application.properties

prompt: create application.properties with no banner
 
# Banner Disabling
spring.main.banner-mode=off


Worked.


prompt: turn on virtual threads in application.properties
 
Couldn't do this,  because the model is too old. It thinks it is a preview feature.

SQL

prompt: create a postgresql fruit table and fill it with fruit
 
CREATE TABLE fruit(
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    color VARCHAR(100)
);

INSERT INTO fruit (name, color) VALUES ('Apple', 'Red');
INSERT INTO fruit (name, color) VALUES ('Banana', 'Yellow');
INSERT INTO fruit (name, color) VALUES ('Cherry', 'Red');
INSERT INTO fruit (name, color) VALUES ('Date', 'Yellowish Brown');


Fun that it can create this.

Conclusion

While Generative AI can be used to generate some code quickly, the output is inconsistent. Each prompt, even with context, can change the result. It is therefore important for the code to be thoroughly reviewed.


Wednesday, March 6, 2024

Large Language Model Code Generation with DeepSeek 6.7B

There are two main input methods that I use from the Continue plugin (Installation Instructions from Part 1).

  • Getting a response back in the Continue plugin and maintaining context.
  • Use /edit and having the results displayed as a diff directly in the code.

 Let's take a look at a few examples. The final output for these are available on github. All examples were run from an M1 MacBook Pro with 32GB RAM running ollama and the DeepSeek 6.7B model.

Creating Code in the Continue Console

Simple Example

Command+L to start a new session and open the Continue prompt

 prompt:  create a pom file for a spring boot application with spring jdbc, spring mvc, and flyway support

This has some older version numbers, so I tried again with a new session

prompt: create a pom file for for java 21 for a spring boot 3.2.3 application with spring jdbc, spring mvc, and flyway support

Here is part of the result. The full file is available on github.

<dependency>
  <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
     <version>${spring-boot.version}</version>
     <dependency>
       <groupId>org.flywaydb</groupId>
        ⋮
     </dependency>
</dependency>

A bit odd that there is a dependency in a dependency. I'm going to try to find better prompts in the next blog post.

Creating more code from an existing context

 The prompt can be aware of previous prompts. Here is an example using SQL. Command+L to start a new prompt. (Until you start a new session, Continue will keep the current context.)

Step 1

prompt: Create flyway migration for postgreSQL to create an information table with a generated UUID primary key, a required 2000 character description, and required json data

CREATE TABLE information (
  id uuid DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
  description VARCHAR(2000) NOT NULL,
  json_data JSONB NOT NULL
);

Step 2

prompt: Create a postgreSQL user table with a user name primary key and a required 2000 character full name

CREATE TABLE users (
  user_name VARCHAR(50) PRIMARY KEY NOT NULL, -- Username must be unique and not null
  full_name VARCHAR(2000) NOT NULL -- Full name is required
);

Step 3

prompt: Create a postgreSQL rating table with references to the previous two tables and a required integer rating

CREATE TABLE rating (
 id SERIAL PRIMARY KEY, -- Auto-incrementing integer ID
 user_name VARCHAR(50) NOT NULL REFERENCES users(user_name), -- References the 'users' table
 info_id uuid NOT NULL REFERENCES information(id), -- References the 'information' table
 rating INTEGER NOT NULL, -- The required integer rating
);

The prompt was still in the same context, so I could reference the previous two commands.

It did create the SQL with a surrogate primary key. Usually a good idea, but maybe not necessary here.

Step 4

prompt:  Same again, but use the two foreign keys as a compound primary key

This removed “id SERIAL PRIMARY KEY, -- Auto-incrementing integer ID

CREATE TABLE rating (
    user_name VARCHAR(50) NOT NULL REFERENCES users(user_name), -- References the 'users' table
    info_id uuid NOT NULL REFERENCES information(id), -- References the 'information' table
    rating INTEGER NOT NULL,  -- The required integer rating
    PRIMARY KEY (user_name, info_id) -- Makes user_name and info_id together a primary key
);

Creating code from existing code

Highlight the generated information table ddl from the previous section.

Command+Shift+L for a new prompt
prompt: Create a class using spring jdbc to insert and update

prompt:  And add delete

Adding delete works because the prompt has the original ddl in context as well as the previous prompt. 

public void deleteInformation(UUID id) {
    jdbcTemplate.update("DELETE FROM information WHERE id = ?", id.toString());
}

Creating code directly in the source

Adding code to a specific file

Create a file called UserService.java and highlight a blank line

Command+L (The first file selected is the default destination for the generated output)

Highlight the create users ddl (generated earlier)

Command+L

prompt: /edit Add method using namedJdbcTemplate to insert a user (the /edit has to be typed, the rest of the prompt can be pasted)

Command+Shift+Enter to save

Adding code to existing code

When adding code to directly to a file there is not as much context as creating code in Continue.

Highlight the addUser method

 Command+Shift+L (trying to keep the same context, but does not quite work)

prompt:  /edit add update method below addUser method

Command+Shift+Enter to save (Without the below clause, the update method will overwrite the add method) 

public void updateUser(String oldUserName, String newUserName, String newFullName) {
    String sql = "UPDATE users SET user_name=:newUsername, full_name=:newFullName WHERE user_name=:oldUsername";

    MapSqlParameterSource parameters = new MapSqlParameterSource();
    parameters.addValue("newUsername", newUserName);
    parameters.addValue("newFullName", newFullName);
    parameters.addValue("oldUsername", oldUserName);

    namedParameterJdbcTemplate.update(sql, parameters);

}

 

Hopefully this is enough to get started. Next I'm going to try to get better outputs from different prompts.

 A few more

I tried a few more prompts, listed below, so there are a few more files in github.

  • Highlight the rating table sql (Command+L). Create a class using namedjdbctemplate to insert and update.
  • What is the command to create a vite project for React and typescript?


 Conclusion

 These are two useful ways to generate code. Next I'm going to try to get better outputs from different prompts.


 

Friday, February 23, 2024

Large Language Model (LLM) Coding Assistance

There is an updated blog post on Continue

 

With all the hype surrounding Generative AI/LLM, and all the hallucinations mentioned in the news, what are these actually good for?

As it turns out LLMs trained for code generation are helpful. But what if you don’t want your code going to some cloud provider? This is a great solution for that.

Here is the plan:

  • Install Ollama and load the model
  • Install Continue
  • Try it out
  • Conclusion

Install ollama and load the model

Ollama allows you to run models locally:

  • Install ollama from https://ollama.com/  
  • Open a terminal window (these commands were run on an M1 MacBook Pro)
  • Run ollama list This should start ollama and show that no models are available
  • Run ollama pull g1ibby/deepseek:6.7b (that is a one after the g)
  • When this completes run ollama list again and you should see

NAME                    ID              SIZE      MODIFIED       
g1ibby/deepseek:6.7b    f7f889d53789    3.8 GB    15 seconds ago

Install Continue

Continue allows an IDE to access a model running in ollama

  • In VSCode, install the Continue plugin from Extensions sidebar (https://continue.dev/)
  • Open the Continue sidebar 
  • Select the Config gear
  • And replace the existing “models” section with

  "allowAnonymousTelemetry": false,
  "models": [
    {
      "title": "DeepSeek 6.7B",
      "provider": "ollama",
      "model": "g1ibby/deepseek:6.7b"
    }
  ],

  • Note: Setting allowAnonymousTelemetry keeps the model completely offline
  • Note: The deepseek model is a reasonably accurate code generator on the tasks I tried
  • Select DeepSeek 6.7B in the dropdown to the left of the Config gear
  • You may also want to move Continue to the right sidebar

Try it out

Let's say you want to store some data in PostgreSQL.

  • Press Command+L (Control+L on Linux). This will open a new Continue session (The extension also has an IDE-specific tutorial for IntelliJ showing keyboard shortcuts.)
  • Type Add SQL for the postgreSQL database to create a table with the following 4 columns: a uuid primary key, a required first name, a required last name, and an optional note

 This will generate something like:

Here is a simple example of how you could create such a table in PostgreSQL using SQL commands:

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),  -- Generate a new unique UUID for each row
    first_name VARCHAR(50) NOT NULL,                   -- Required first name
    last_name VARCHAR(50) NOT NULL,                    -- Required last name
    note TEXT                                           -- Optional note
);
In this script:

 * UUID PRIMARY KEY DEFAULT uuid_generate_v4() creates a column of UUID type as the primary key. The function uuid_generate_v4() is used to generate a new unique UUID for each row.
 * VARCHAR(50) NOT NULL defines a string field that can't be null and has a maximum length of 50 characters.
 * TEXT is just the data type for text fields, which can store large amounts of text data (up to ~1GB).
Please replace the column names and types with your preferred ones if needed. The above script assumes you have the UUID extension installed in PostgreSQL. If not, you would need to install it first using CREATE EXTENSION IF NOT EXISTS "uuid-ossp";.

Usually there is something wrong with the answer (such as gen_random_uuid now being a standard part of PostgreSQL 16 so no extension is needed), but it usually seems to be close enough to be helpful to experienced developers and save time. The time it takes on an M1 Mac seems to vary between almost instant to a few seconds.

A function can also be put directly into the code by prefixing the query with /edit for example inside a typescript file

  • Select a blank line and press Command+Shift+L
  • Type /edit create a function to remove all spaces from a string

And about a second later you have (again not quite correct - bad variable name because there is no context and missing the type)

function removeSpaces(str) {
  return str.replace(/\s/g, '');
}

Directly in your ts file

Conclusion

Quick code generation using LLMs seems like a useful helper for any developer, although everything that it generates needs to be verified and tested. I need to spend a lot more time with this to see how it performs with different coding tasks, and I'm planning on sharing these in future posts.