llm-codegen-research 1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- llm_codegen_research-1.2/LICENSE +21 -0
- llm_codegen_research-1.2/PKG-INFO +101 -0
- llm_codegen_research-1.2/README.md +84 -0
- llm_codegen_research-1.2/pyproject.toml +39 -0
- llm_codegen_research-1.2/setup.cfg +4 -0
- llm_codegen_research-1.2/src/llm_cgr/__init__.py +19 -0
- llm_codegen_research-1.2/src/llm_cgr/analyse/__init__.py +10 -0
- llm_codegen_research-1.2/src/llm_cgr/analyse/classes.py +139 -0
- llm_codegen_research-1.2/src/llm_cgr/analyse/languages/__init__.py +24 -0
- llm_codegen_research-1.2/src/llm_cgr/analyse/languages/code_data.py +34 -0
- llm_codegen_research-1.2/src/llm_cgr/analyse/languages/python.py +86 -0
- llm_codegen_research-1.2/src/llm_cgr/analyse/regexes.py +10 -0
- llm_codegen_research-1.2/src/llm_cgr/defaults.py +10 -0
- llm_codegen_research-1.2/src/llm_cgr/json_utils.py +24 -0
- llm_codegen_research-1.2/src/llm_cgr/py.typed +0 -0
- llm_codegen_research-1.2/src/llm_cgr/query/__init__.py +18 -0
- llm_codegen_research-1.2/src/llm_cgr/query/api_utils.py +99 -0
- llm_codegen_research-1.2/src/llm_cgr/query/generate.py +149 -0
- llm_codegen_research-1.2/src/llm_cgr/query/prompts.py +13 -0
- llm_codegen_research-1.2/src/llm_cgr/query/protocol.py +20 -0
- llm_codegen_research-1.2/src/llm_codegen_research.egg-info/PKG-INFO +101 -0
- llm_codegen_research-1.2/src/llm_codegen_research.egg-info/SOURCES.txt +25 -0
- llm_codegen_research-1.2/src/llm_codegen_research.egg-info/dependency_links.txt +1 -0
- llm_codegen_research-1.2/src/llm_codegen_research.egg-info/requires.txt +3 -0
- llm_codegen_research-1.2/src/llm_codegen_research.egg-info/top_level.txt +1 -0
- llm_codegen_research-1.2/tests/test_analyse.py +125 -0
- llm_codegen_research-1.2/tests/test_query.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Luke Twist
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llm-codegen-research
|
|
3
|
+
Version: 1.2
|
|
4
|
+
Summary: Utilities for use when research code-generation by LLMs.
|
|
5
|
+
Author-email: Lukas Twist <itsluketwist@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/itsluketwist/llm-codegen-research
|
|
7
|
+
Keywords: llm,code-generation,research,prompting,nlp
|
|
8
|
+
Classifier: Programming Language :: Python
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: anthropic>=0.49.0
|
|
14
|
+
Requires-Dist: openai>=1.78.1
|
|
15
|
+
Requires-Dist: together>=1.5.8
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# **llm-codegen-research**
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+

|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<div>
|
|
27
|
+
<!-- badges from : https://shields.io/ -->
|
|
28
|
+
<!-- logos available : https://simpleicons.org/ -->
|
|
29
|
+
<a href="https://opensource.org/licenses/MIT">
|
|
30
|
+
<img alt="MIT License" src="https://img.shields.io/badge/Licence-MIT-yellow?style=for-the-badge&logo=docs&logoColor=white" />
|
|
31
|
+
</a>
|
|
32
|
+
<a href="https://www.python.org/">
|
|
33
|
+
<img alt="Python 3" src="https://img.shields.io/badge/Python_3-blue?style=for-the-badge&logo=python&logoColor=white" />
|
|
34
|
+
</a>
|
|
35
|
+
<a href="https://openai.com/blog/openai-api/">
|
|
36
|
+
<img alt="OpenAI API" src="https://img.shields.io/badge/OpenAI_API-412991?style=for-the-badge&logo=openai&logoColor=white" />
|
|
37
|
+
</a>
|
|
38
|
+
<a href="https://www.anthropic.com/api/">
|
|
39
|
+
<img alt="Anthropic API" src="https://img.shields.io/badge/Claude_API-D97757?style=for-the-badge&logo=claude&logoColor=white" />
|
|
40
|
+
</a>
|
|
41
|
+
<a href="https://api.together.ai/">
|
|
42
|
+
<img alt="together.ai API" src="https://img.shields.io/badge/together.ai_API-B5B5B5?style=for-the-badge&logoColor=white" />
|
|
43
|
+
</a>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## *usage*
|
|
48
|
+
|
|
49
|
+
A collection of methods and classes I repeatedly use when conducting research on LLM code-generation.
|
|
50
|
+
Covers both prompting various LLMs, and analysing the markdown responses.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from llm_cgr import quick_complete, Markdown
|
|
54
|
+
|
|
55
|
+
response = quick_complete("Write python code to generate the nth fibonacci number.")
|
|
56
|
+
|
|
57
|
+
markdown = Markdown(text=response)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## *installation*
|
|
61
|
+
|
|
62
|
+
Install directly from PyPI, using pip:
|
|
63
|
+
|
|
64
|
+
```shell
|
|
65
|
+
pip install llm-codegen-research
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## *development*
|
|
69
|
+
|
|
70
|
+
Clone the repository code:
|
|
71
|
+
|
|
72
|
+
```shell
|
|
73
|
+
git clone https://github.com/itsluketwist/llm-codegen-research.git
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
We use [`uv`](https://astral.sh/blog/uv) for project management.
|
|
77
|
+
Once cloned, create a virtual environment and install uv and the project:
|
|
78
|
+
|
|
79
|
+
```shell
|
|
80
|
+
python -m venv .venv
|
|
81
|
+
|
|
82
|
+
. .venv/bin/activate
|
|
83
|
+
|
|
84
|
+
pip install uv
|
|
85
|
+
|
|
86
|
+
uv sync
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Use `make` commands to lint and test:
|
|
90
|
+
|
|
91
|
+
```shell
|
|
92
|
+
make lint
|
|
93
|
+
|
|
94
|
+
make test
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Use `uv` to add new dependencies into the project and `uv.lock`:
|
|
98
|
+
|
|
99
|
+
```shell
|
|
100
|
+
uv add openai
|
|
101
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# **llm-codegen-research**
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<div>
|
|
10
|
+
<!-- badges from : https://shields.io/ -->
|
|
11
|
+
<!-- logos available : https://simpleicons.org/ -->
|
|
12
|
+
<a href="https://opensource.org/licenses/MIT">
|
|
13
|
+
<img alt="MIT License" src="https://img.shields.io/badge/Licence-MIT-yellow?style=for-the-badge&logo=docs&logoColor=white" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://www.python.org/">
|
|
16
|
+
<img alt="Python 3" src="https://img.shields.io/badge/Python_3-blue?style=for-the-badge&logo=python&logoColor=white" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://openai.com/blog/openai-api/">
|
|
19
|
+
<img alt="OpenAI API" src="https://img.shields.io/badge/OpenAI_API-412991?style=for-the-badge&logo=openai&logoColor=white" />
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://www.anthropic.com/api/">
|
|
22
|
+
<img alt="Anthropic API" src="https://img.shields.io/badge/Claude_API-D97757?style=for-the-badge&logo=claude&logoColor=white" />
|
|
23
|
+
</a>
|
|
24
|
+
<a href="https://api.together.ai/">
|
|
25
|
+
<img alt="together.ai API" src="https://img.shields.io/badge/together.ai_API-B5B5B5?style=for-the-badge&logoColor=white" />
|
|
26
|
+
</a>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## *usage*
|
|
31
|
+
|
|
32
|
+
A collection of methods and classes I repeatedly use when conducting research on LLM code-generation.
|
|
33
|
+
Covers both prompting various LLMs, and analysing the markdown responses.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from llm_cgr import quick_complete, Markdown
|
|
37
|
+
|
|
38
|
+
response = quick_complete("Write python code to generate the nth fibonacci number.")
|
|
39
|
+
|
|
40
|
+
markdown = Markdown(text=response)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## *installation*
|
|
44
|
+
|
|
45
|
+
Install directly from PyPI, using pip:
|
|
46
|
+
|
|
47
|
+
```shell
|
|
48
|
+
pip install llm-codegen-research
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## *development*
|
|
52
|
+
|
|
53
|
+
Clone the repository code:
|
|
54
|
+
|
|
55
|
+
```shell
|
|
56
|
+
git clone https://github.com/itsluketwist/llm-codegen-research.git
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
We use [`uv`](https://astral.sh/blog/uv) for project management.
|
|
60
|
+
Once cloned, create a virtual environment and install uv and the project:
|
|
61
|
+
|
|
62
|
+
```shell
|
|
63
|
+
python -m venv .venv
|
|
64
|
+
|
|
65
|
+
. .venv/bin/activate
|
|
66
|
+
|
|
67
|
+
pip install uv
|
|
68
|
+
|
|
69
|
+
uv sync
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Use `make` commands to lint and test:
|
|
73
|
+
|
|
74
|
+
```shell
|
|
75
|
+
make lint
|
|
76
|
+
|
|
77
|
+
make test
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use `uv` to add new dependencies into the project and `uv.lock`:
|
|
81
|
+
|
|
82
|
+
```shell
|
|
83
|
+
uv add openai
|
|
84
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Package specification, as defined here:
|
|
2
|
+
# https://packaging.python.org/en/latest/specifications/pyproject-toml/#pyproject-toml-spec
|
|
3
|
+
|
|
4
|
+
[build-system]
|
|
5
|
+
requires = ["setuptools", "setuptools-git-versioning<2"]
|
|
6
|
+
build-backend = "setuptools.build_meta"
|
|
7
|
+
|
|
8
|
+
[tool.setuptools-git-versioning]
|
|
9
|
+
enabled = true
|
|
10
|
+
|
|
11
|
+
[project]
|
|
12
|
+
name = "llm-codegen-research"
|
|
13
|
+
dynamic = ["version"]
|
|
14
|
+
description = "Utilities for use when research code-generation by LLMs."
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
authors = [{ name = "Lukas Twist", email = "itsluketwist@gmail.com" }]
|
|
17
|
+
license-files = ["LICENSE"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
]
|
|
22
|
+
keywords = ["llm", "code-generation", "research", "prompting", "nlp"]
|
|
23
|
+
requires-python = ">=3.11"
|
|
24
|
+
dependencies = [
|
|
25
|
+
"anthropic>=0.49.0",
|
|
26
|
+
"openai>=1.78.1",
|
|
27
|
+
"together>=1.5.8",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/itsluketwist/llm-codegen-research"
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
|
+
"pre-commit>=4.2.0",
|
|
36
|
+
"pytest>=8.3.5",
|
|
37
|
+
"pytest-cov>=6.1.1",
|
|
38
|
+
"uv>=0.7.4",
|
|
39
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from llm_cgr import analyse, query
|
|
2
|
+
from llm_cgr.analyse import CodeBlock, CodeData, Markdown, analyse_code
|
|
3
|
+
from llm_cgr.json_utils import read_json, save_json
|
|
4
|
+
from llm_cgr.query import get_client, query_list, quick_generate
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"analyse",
|
|
9
|
+
"query",
|
|
10
|
+
"CodeBlock",
|
|
11
|
+
"CodeData",
|
|
12
|
+
"Markdown",
|
|
13
|
+
"analyse_code",
|
|
14
|
+
"read_json",
|
|
15
|
+
"save_json",
|
|
16
|
+
"get_client",
|
|
17
|
+
"query_list",
|
|
18
|
+
"quick_generate",
|
|
19
|
+
]
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Classes for handling markdown responses from LLMs."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from llm_cgr.analyse.languages import analyse_code
|
|
6
|
+
from llm_cgr.analyse.regexes import CODE_BLOCK_REGEX
|
|
7
|
+
from llm_cgr.defaults import DEFAULT_CODEBLOCK_LANGUAGE
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class CodeBlock:
|
|
12
|
+
"""
|
|
13
|
+
A class to represent a block of code with it's language.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
language: str | None
|
|
17
|
+
text: str
|
|
18
|
+
valid: bool | None # None if validity unknown, language not supported
|
|
19
|
+
error: str | None # only set if not valid
|
|
20
|
+
defined_funcs: list[str]
|
|
21
|
+
called_funcs: list[str]
|
|
22
|
+
packages: list[str]
|
|
23
|
+
imports: list[str]
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
language: str | None,
|
|
28
|
+
text: str,
|
|
29
|
+
default_language: str | None = DEFAULT_CODEBLOCK_LANGUAGE,
|
|
30
|
+
):
|
|
31
|
+
self.language = language.strip().lower() if language else None
|
|
32
|
+
self.text = text.strip()
|
|
33
|
+
|
|
34
|
+
code_data = analyse_code(
|
|
35
|
+
code=self.text, language=self.language or default_language
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if self.language is None and not code_data.valid:
|
|
39
|
+
# if we hit errors after defaulting the language, ignore the results!
|
|
40
|
+
self.valid = None
|
|
41
|
+
self.error = None
|
|
42
|
+
self.defined_funcs = []
|
|
43
|
+
self.called_funcs = []
|
|
44
|
+
self.packages = []
|
|
45
|
+
self.imports = []
|
|
46
|
+
|
|
47
|
+
else:
|
|
48
|
+
self.language = self.language or default_language
|
|
49
|
+
self.valid = code_data.valid
|
|
50
|
+
self.error = code_data.error
|
|
51
|
+
self.defined_funcs = code_data.defined_funcs
|
|
52
|
+
self.called_funcs = code_data.called_funcs
|
|
53
|
+
self.packages = code_data.packages
|
|
54
|
+
self.imports = code_data.imports
|
|
55
|
+
|
|
56
|
+
def __repr__(self):
|
|
57
|
+
_language = f"language={self.language or 'unspecified'}"
|
|
58
|
+
_lines = f"lines={len(self.text.splitlines())}"
|
|
59
|
+
return f"{self.__class__.__name__}({_language}, {_lines})"
|
|
60
|
+
|
|
61
|
+
def __str__(self):
|
|
62
|
+
return self.text
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def markdown(self):
|
|
66
|
+
return f"```{self.language or ''}\n{self.text}\n```"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class Markdown:
|
|
71
|
+
"""
|
|
72
|
+
A class to hold a markdown response from an LLM as a series of text and code blocks.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
text: str
|
|
76
|
+
code_blocks: list[CodeBlock]
|
|
77
|
+
code_errors: list[str]
|
|
78
|
+
languages: list[str]
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
text: str,
|
|
83
|
+
default_codeblock_language: str | None = DEFAULT_CODEBLOCK_LANGUAGE,
|
|
84
|
+
):
|
|
85
|
+
"""
|
|
86
|
+
Initialise the MarkdownResponse object with the text and code blocks.
|
|
87
|
+
Use `codeblock_language` when no language is specified for a code block.
|
|
88
|
+
"""
|
|
89
|
+
self.text = text
|
|
90
|
+
self.code_blocks = self.extract_code_blocks(
|
|
91
|
+
response=text,
|
|
92
|
+
default_language=default_codeblock_language,
|
|
93
|
+
)
|
|
94
|
+
self.code_errors = [
|
|
95
|
+
f"{i}: {cb.error}" for i, cb in enumerate(self.code_blocks) if cb.error
|
|
96
|
+
]
|
|
97
|
+
self.languages = sorted(
|
|
98
|
+
list({bl.language for bl in self.code_blocks if bl.language})
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def __repr__(self):
|
|
102
|
+
_lines = f"lines={len(self.text.splitlines())}"
|
|
103
|
+
_code_blocks = f"code_blocks={len(self.code_blocks)}"
|
|
104
|
+
_languages = f"languages={','.join(self.languages)}"
|
|
105
|
+
return f"{self.__class__.__name__}({_lines}, {_code_blocks}, {_languages})"
|
|
106
|
+
|
|
107
|
+
def __str__(self):
|
|
108
|
+
return self.text
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def extract_code_blocks(
|
|
112
|
+
response: str,
|
|
113
|
+
default_language: str | None = DEFAULT_CODEBLOCK_LANGUAGE,
|
|
114
|
+
) -> list[CodeBlock]:
|
|
115
|
+
"""
|
|
116
|
+
Extract the code blocks from the markdown formatted text.
|
|
117
|
+
"""
|
|
118
|
+
matches = CODE_BLOCK_REGEX.findall(string=response)
|
|
119
|
+
blocks = []
|
|
120
|
+
for match in matches:
|
|
121
|
+
language, text = match
|
|
122
|
+
blocks.append(
|
|
123
|
+
CodeBlock(
|
|
124
|
+
language=language if language else None,
|
|
125
|
+
text=text,
|
|
126
|
+
default_language=default_language,
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
return blocks
|
|
130
|
+
|
|
131
|
+
def first_code_block(self, language: str) -> CodeBlock | None:
|
|
132
|
+
"""
|
|
133
|
+
Return the first code block of a certain language in the response.
|
|
134
|
+
"""
|
|
135
|
+
for block in self.code_blocks:
|
|
136
|
+
if block.language == language:
|
|
137
|
+
return block
|
|
138
|
+
|
|
139
|
+
return None
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from llm_cgr.analyse.languages.code_data import CodeData
|
|
2
|
+
from llm_cgr.analyse.languages.python import analyse_python_code
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def analyse_code(code: str, language: str | None) -> CodeData:
|
|
6
|
+
"""
|
|
7
|
+
Analyse code based on the language.
|
|
8
|
+
"""
|
|
9
|
+
try:
|
|
10
|
+
if language == "python":
|
|
11
|
+
return analyse_python_code(code=code)
|
|
12
|
+
except Exception as exc:
|
|
13
|
+
return CodeData(
|
|
14
|
+
valid=False,
|
|
15
|
+
error=str(exc),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
return CodeData()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"analyse_code",
|
|
23
|
+
"CodeData",
|
|
24
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Define the CodeData class, to store code analysis data."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Iterable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class CodeData:
|
|
9
|
+
"""
|
|
10
|
+
A class to hold code analysis data.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
valid: bool | None
|
|
14
|
+
error: str | None
|
|
15
|
+
defined_funcs: list[str]
|
|
16
|
+
called_funcs: list[str]
|
|
17
|
+
packages: list[str]
|
|
18
|
+
imports: list[str]
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
valid: bool | None = None,
|
|
23
|
+
error: str | None = None,
|
|
24
|
+
defined_funcs: Iterable | None = None,
|
|
25
|
+
called_funcs: Iterable | None = None,
|
|
26
|
+
packages: Iterable | None = None,
|
|
27
|
+
imports: Iterable | None = None,
|
|
28
|
+
):
|
|
29
|
+
self.valid = valid
|
|
30
|
+
self.error = error
|
|
31
|
+
self.defined_funcs = sorted(defined_funcs) if defined_funcs else []
|
|
32
|
+
self.called_funcs = sorted(called_funcs) if called_funcs else []
|
|
33
|
+
self.packages = sorted(packages) if packages else []
|
|
34
|
+
self.imports = sorted(imports) if imports else []
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Utility functions for Python code analysis."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from llm_cgr.analyse.languages.code_data import CodeData
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
PYTHON_STDLIB = getattr(
|
|
10
|
+
sys, "stdlib_module_names", []
|
|
11
|
+
) # use this below to determine packages
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PythonAnalyser(ast.NodeVisitor):
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.defined_funcs: set[str] = set()
|
|
17
|
+
self.called_funcs: set[str] = set()
|
|
18
|
+
self.imports: set[str] = set()
|
|
19
|
+
self.packages: set[str] = set()
|
|
20
|
+
|
|
21
|
+
def visit_FunctionDef(self, node: ast.FunctionDef):
|
|
22
|
+
# save defined functions
|
|
23
|
+
self.defined_funcs.add(node.name)
|
|
24
|
+
self.generic_visit(node)
|
|
25
|
+
|
|
26
|
+
def visit_Call(self, node: ast.Call):
|
|
27
|
+
func = node.func
|
|
28
|
+
|
|
29
|
+
# save `foo()` function calls
|
|
30
|
+
if isinstance(func, ast.Name):
|
|
31
|
+
self.called_funcs.add(func.id)
|
|
32
|
+
|
|
33
|
+
# save `lib.method()` function calls
|
|
34
|
+
elif isinstance(func, ast.Attribute):
|
|
35
|
+
if isinstance(func.value, ast.Name):
|
|
36
|
+
self.called_funcs.add(f"{func.value.id}.{func.attr}")
|
|
37
|
+
else:
|
|
38
|
+
self.called_funcs.add(func.attr)
|
|
39
|
+
|
|
40
|
+
self.generic_visit(node)
|
|
41
|
+
|
|
42
|
+
def visit_Import(self, node: ast.Import):
|
|
43
|
+
# save `import module` imports
|
|
44
|
+
for alias in node.names:
|
|
45
|
+
# save all imports
|
|
46
|
+
self.imports.add(alias.name)
|
|
47
|
+
|
|
48
|
+
# save external packages
|
|
49
|
+
top_level = alias.name.split(".")[0]
|
|
50
|
+
if top_level not in PYTHON_STDLIB:
|
|
51
|
+
self.packages.add(top_level)
|
|
52
|
+
|
|
53
|
+
self.generic_visit(node)
|
|
54
|
+
|
|
55
|
+
def visit_ImportFrom(self, node: ast.ImportFrom):
|
|
56
|
+
# save `from module import thing` imports
|
|
57
|
+
module = node.module or ""
|
|
58
|
+
|
|
59
|
+
# save external packages
|
|
60
|
+
# node.level is 0 for absolute imports, 1+ for relative imports
|
|
61
|
+
if module and module not in PYTHON_STDLIB and node.level == 0:
|
|
62
|
+
self.packages.add(module.split(".")[0])
|
|
63
|
+
|
|
64
|
+
# save all imports
|
|
65
|
+
for alias in node.names:
|
|
66
|
+
full = f"{module}.{alias.name}" if module else alias.name
|
|
67
|
+
self.imports.add(full)
|
|
68
|
+
|
|
69
|
+
self.generic_visit(node)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def analyse_python_code(code: str) -> CodeData:
|
|
73
|
+
"""
|
|
74
|
+
Analyse Python code to extract functions and imports.
|
|
75
|
+
"""
|
|
76
|
+
tree = ast.parse(code)
|
|
77
|
+
analyser = PythonAnalyser()
|
|
78
|
+
analyser.visit(tree)
|
|
79
|
+
return CodeData(
|
|
80
|
+
valid=True,
|
|
81
|
+
defined_funcs=analyser.defined_funcs,
|
|
82
|
+
called_funcs=analyser.called_funcs,
|
|
83
|
+
# packages lowercase for analysis (names are case-insensitive outside of code)
|
|
84
|
+
packages=[p.lower() for p in analyser.packages],
|
|
85
|
+
imports=analyser.imports,
|
|
86
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Collection of default values for the llm_cgr package."""
|
|
2
|
+
|
|
3
|
+
# the default model to be used across tasks
|
|
4
|
+
DEFAULT_MODEL = "gpt-4.1-mini-2025-04-14"
|
|
5
|
+
|
|
6
|
+
# the default language to assume for code blocks if not specified
|
|
7
|
+
DEFAULT_CODEBLOCK_LANGUAGE = "python"
|
|
8
|
+
|
|
9
|
+
# the default max_tokens to be used when prompting models
|
|
10
|
+
DEFAULT_MAX_TOKENS = 2000
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Simple utility functions to save and read json files."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def save_json(
|
|
7
|
+
data: dict | list,
|
|
8
|
+
file_path: str,
|
|
9
|
+
):
|
|
10
|
+
"""
|
|
11
|
+
Utility to save python dictionary or list to a json file.
|
|
12
|
+
"""
|
|
13
|
+
with open(file_path, mode="w", encoding="utf-8") as f:
|
|
14
|
+
json.dump(obj=data, fp=f, indent=4)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def read_json(
|
|
18
|
+
file_path: str,
|
|
19
|
+
) -> dict | list:
|
|
20
|
+
"""
|
|
21
|
+
Utility to load python dictionary or list from a json file.
|
|
22
|
+
"""
|
|
23
|
+
with open(file_path, mode="r", encoding="utf-8") as f:
|
|
24
|
+
return json.load(fp=f)
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from llm_cgr.query.api_utils import get_client, query_list, quick_generate
|
|
2
|
+
from llm_cgr.query.generate import (
|
|
3
|
+
AnthropicGenerationAPI,
|
|
4
|
+
OpenAIGenerationAPI,
|
|
5
|
+
TogetherGenerationAPI,
|
|
6
|
+
)
|
|
7
|
+
from llm_cgr.query.protocol import GenerationProtocol
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"get_client",
|
|
12
|
+
"query_list",
|
|
13
|
+
"quick_generate",
|
|
14
|
+
"AnthropicGenerationAPI",
|
|
15
|
+
"OpenAIGenerationAPI",
|
|
16
|
+
"TogetherGenerationAPI",
|
|
17
|
+
"GenerationProtocol",
|
|
18
|
+
]
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""API utilities for interfacing with the generation models."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from llm_cgr.defaults import DEFAULT_MODEL
|
|
6
|
+
from llm_cgr.query.generate import (
|
|
7
|
+
AnthropicGenerationAPI,
|
|
8
|
+
OpenAIGenerationAPI,
|
|
9
|
+
TogetherGenerationAPI,
|
|
10
|
+
)
|
|
11
|
+
from llm_cgr.query.prompts import (
|
|
12
|
+
BASE_SYSTEM_PROMPT,
|
|
13
|
+
CODE_SYSTEM_PROMPT,
|
|
14
|
+
LIST_SYSTEM_PROMPT,
|
|
15
|
+
)
|
|
16
|
+
from llm_cgr.query.protocol import GenerationProtocol
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_client(
|
|
20
|
+
model: str,
|
|
21
|
+
type: Literal["base", "code", "list"] | None = None,
|
|
22
|
+
) -> GenerationProtocol:
|
|
23
|
+
"""
|
|
24
|
+
Initialise the correct generation interface for the given model.
|
|
25
|
+
"""
|
|
26
|
+
_system = None
|
|
27
|
+
match type:
|
|
28
|
+
case "base":
|
|
29
|
+
_system = BASE_SYSTEM_PROMPT
|
|
30
|
+
case "code":
|
|
31
|
+
_system = CODE_SYSTEM_PROMPT
|
|
32
|
+
case "list":
|
|
33
|
+
_system = LIST_SYSTEM_PROMPT
|
|
34
|
+
|
|
35
|
+
if "claude" in model:
|
|
36
|
+
return AnthropicGenerationAPI(model=model, system=_system)
|
|
37
|
+
|
|
38
|
+
if "gpt" in model or "o1" in model:
|
|
39
|
+
return OpenAIGenerationAPI(model=model, system=_system)
|
|
40
|
+
|
|
41
|
+
return TogetherGenerationAPI(model=model, system=_system)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def quick_generate(
|
|
45
|
+
user: str,
|
|
46
|
+
type: Literal["base", "code", "list"] | None = None,
|
|
47
|
+
model: str = DEFAULT_MODEL,
|
|
48
|
+
system: str | None = None,
|
|
49
|
+
temperature: float | None = None,
|
|
50
|
+
) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Simple function to quickly prompt a model for a response.
|
|
53
|
+
"""
|
|
54
|
+
client = get_client(model=model, type=type)
|
|
55
|
+
|
|
56
|
+
[result] = client.generate(
|
|
57
|
+
user=user,
|
|
58
|
+
system=system,
|
|
59
|
+
temperature=temperature,
|
|
60
|
+
)
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def query_list(
|
|
65
|
+
user: str,
|
|
66
|
+
system: str = LIST_SYSTEM_PROMPT,
|
|
67
|
+
model: str = DEFAULT_MODEL,
|
|
68
|
+
) -> list[str]:
|
|
69
|
+
"""
|
|
70
|
+
Simple function to quickly prompt a model for a list of words.
|
|
71
|
+
"""
|
|
72
|
+
_response = quick_generate(
|
|
73
|
+
user=user,
|
|
74
|
+
system=system,
|
|
75
|
+
model=model,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# sometimes the LLM will return a code block with the list inside it
|
|
79
|
+
if _response.startswith("```python"):
|
|
80
|
+
_response = _response.split("```python")[1]
|
|
81
|
+
if _response.endswith("```"):
|
|
82
|
+
_response = _response.split("```")[0]
|
|
83
|
+
_response = _response.strip()
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
_list = eval(_response)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(f"Error evaluating response.\nresponse: {_response}\nexception: {e}")
|
|
89
|
+
_list = []
|
|
90
|
+
|
|
91
|
+
if not isinstance(_list, list):
|
|
92
|
+
print(f"Error querying list. Response is not a list: {_list}")
|
|
93
|
+
_list = []
|
|
94
|
+
|
|
95
|
+
if any(not isinstance(item, str) for item in _list):
|
|
96
|
+
print(f"Error querying list. Response contains non-string items: {_list}")
|
|
97
|
+
_list = []
|
|
98
|
+
|
|
99
|
+
return _list
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Classes for access to generation APIs of LLM services."""
|
|
2
|
+
|
|
3
|
+
import anthropic
|
|
4
|
+
import openai
|
|
5
|
+
import together
|
|
6
|
+
|
|
7
|
+
from llm_cgr.defaults import DEFAULT_MAX_TOKENS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OpenAIGenerationAPI:
|
|
11
|
+
"""
|
|
12
|
+
Class to access the OpenAI API.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
model: str | None = None,
|
|
18
|
+
system: str | None = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
self._model = model
|
|
21
|
+
self._system = system or openai.NOT_GIVEN
|
|
22
|
+
self._client = openai.OpenAI()
|
|
23
|
+
|
|
24
|
+
def generate(
|
|
25
|
+
self,
|
|
26
|
+
user: str,
|
|
27
|
+
system: str | None = None,
|
|
28
|
+
model: str | None = None,
|
|
29
|
+
samples: int = 1,
|
|
30
|
+
temperature: float | None = None,
|
|
31
|
+
max_tokens: int | None = None,
|
|
32
|
+
) -> list[str]:
|
|
33
|
+
"""
|
|
34
|
+
Generate a model response from the OpenAI API.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
The text response to the prompt.
|
|
39
|
+
"""
|
|
40
|
+
_generations = []
|
|
41
|
+
for _ in range(samples):
|
|
42
|
+
response = self._client.responses.create(
|
|
43
|
+
input=user,
|
|
44
|
+
model=model or self._model,
|
|
45
|
+
instructions=system or self._system,
|
|
46
|
+
temperature=temperature or openai.NOT_GIVEN,
|
|
47
|
+
max_output_tokens=max_tokens or openai.NOT_GIVEN,
|
|
48
|
+
)
|
|
49
|
+
_generations.append(response.output_text)
|
|
50
|
+
|
|
51
|
+
return _generations
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TogetherGenerationAPI:
|
|
55
|
+
"""
|
|
56
|
+
Class to access the TogetherAI API.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
model: str | None = None,
|
|
62
|
+
system: str | None = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
self._model = model
|
|
65
|
+
self._system = system
|
|
66
|
+
self._client = together.Together()
|
|
67
|
+
|
|
68
|
+
def generate(
|
|
69
|
+
self,
|
|
70
|
+
user: str,
|
|
71
|
+
system: str | None = None,
|
|
72
|
+
model: str | None = None,
|
|
73
|
+
samples: int = 1,
|
|
74
|
+
temperature: float | None = None,
|
|
75
|
+
max_tokens: int | None = None,
|
|
76
|
+
) -> list[str]:
|
|
77
|
+
"""
|
|
78
|
+
Generate a model response from the TogetherAI API.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
The text response to the prompt.
|
|
83
|
+
"""
|
|
84
|
+
_input = [{"role": "user", "content": user}]
|
|
85
|
+
_system = system or self._system
|
|
86
|
+
if _system:
|
|
87
|
+
_input = [{"role": "system", "content": _system}] + _input
|
|
88
|
+
|
|
89
|
+
_generations = []
|
|
90
|
+
for _ in range(samples):
|
|
91
|
+
response = self._client.chat.completions.create(
|
|
92
|
+
model=model or self._model,
|
|
93
|
+
messages=_input,
|
|
94
|
+
temperature=temperature,
|
|
95
|
+
max_tokens=max_tokens,
|
|
96
|
+
)
|
|
97
|
+
_generations.append(response.choices[0].message.content)
|
|
98
|
+
|
|
99
|
+
return _generations
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class AnthropicGenerationAPI:
|
|
103
|
+
"""
|
|
104
|
+
Class to access the Anthropic Claude API.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, model: str | None = None, system: str | None = None) -> None:
|
|
108
|
+
self._model = model
|
|
109
|
+
self._system = system or anthropic.NOT_GIVEN
|
|
110
|
+
self._client = anthropic.Anthropic()
|
|
111
|
+
|
|
112
|
+
def generate(
|
|
113
|
+
self,
|
|
114
|
+
user: str,
|
|
115
|
+
system: str | None = None,
|
|
116
|
+
model: str | None = None,
|
|
117
|
+
samples: int = 1,
|
|
118
|
+
temperature: float | None = None,
|
|
119
|
+
max_tokens: int | None = None,
|
|
120
|
+
) -> list[str]:
|
|
121
|
+
"""
|
|
122
|
+
Generate a model response from the Anthropic Claude API.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
The text response to the prompt.
|
|
127
|
+
"""
|
|
128
|
+
_generations = []
|
|
129
|
+
for _ in range(samples):
|
|
130
|
+
response = self._client.messages.create(
|
|
131
|
+
model=model or self._model,
|
|
132
|
+
system=system or self._system,
|
|
133
|
+
messages=[
|
|
134
|
+
{
|
|
135
|
+
"role": "user",
|
|
136
|
+
"content": [
|
|
137
|
+
{
|
|
138
|
+
"type": "text",
|
|
139
|
+
"text": user,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
temperature=temperature or anthropic.NOT_GIVEN,
|
|
145
|
+
max_tokens=max_tokens or DEFAULT_MAX_TOKENS,
|
|
146
|
+
)
|
|
147
|
+
_generations.append(response.content[0].text)
|
|
148
|
+
|
|
149
|
+
return _generations
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""All prompts used in the project are defined here."""
|
|
2
|
+
|
|
3
|
+
# the default system prompt to be used across tasks
|
|
4
|
+
BASE_SYSTEM_PROMPT = "You are a helpful assistant."
|
|
5
|
+
|
|
6
|
+
# the default system prompt to be used across coding tasks
|
|
7
|
+
CODE_SYSTEM_PROMPT = "You are a helpful and knowledgeable code assistant!"
|
|
8
|
+
|
|
9
|
+
# the system prompt to be used for generating lists of words
|
|
10
|
+
LIST_SYSTEM_PROMPT = (
|
|
11
|
+
"You are a helpful assistant that provides lists of words.\n"
|
|
12
|
+
"You only respond in correctly formatted python lists, containing only strings."
|
|
13
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Protocol for the completion API of an LLM service."""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GenerationProtocol(Protocol):
|
|
7
|
+
"""
|
|
8
|
+
Protocol that describes how to access the generation API of an LLM service.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def generate(
|
|
12
|
+
self,
|
|
13
|
+
user: str,
|
|
14
|
+
system: str | None = None,
|
|
15
|
+
model: str | None = None,
|
|
16
|
+
samples: int = 1,
|
|
17
|
+
temperature: float | None = None,
|
|
18
|
+
max_tokens: int | None = None,
|
|
19
|
+
) -> list[str]:
|
|
20
|
+
pass
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llm-codegen-research
|
|
3
|
+
Version: 1.2
|
|
4
|
+
Summary: Utilities for use when research code-generation by LLMs.
|
|
5
|
+
Author-email: Lukas Twist <itsluketwist@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/itsluketwist/llm-codegen-research
|
|
7
|
+
Keywords: llm,code-generation,research,prompting,nlp
|
|
8
|
+
Classifier: Programming Language :: Python
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: anthropic>=0.49.0
|
|
14
|
+
Requires-Dist: openai>=1.78.1
|
|
15
|
+
Requires-Dist: together>=1.5.8
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# **llm-codegen-research**
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+

|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<div>
|
|
27
|
+
<!-- badges from : https://shields.io/ -->
|
|
28
|
+
<!-- logos available : https://simpleicons.org/ -->
|
|
29
|
+
<a href="https://opensource.org/licenses/MIT">
|
|
30
|
+
<img alt="MIT License" src="https://img.shields.io/badge/Licence-MIT-yellow?style=for-the-badge&logo=docs&logoColor=white" />
|
|
31
|
+
</a>
|
|
32
|
+
<a href="https://www.python.org/">
|
|
33
|
+
<img alt="Python 3" src="https://img.shields.io/badge/Python_3-blue?style=for-the-badge&logo=python&logoColor=white" />
|
|
34
|
+
</a>
|
|
35
|
+
<a href="https://openai.com/blog/openai-api/">
|
|
36
|
+
<img alt="OpenAI API" src="https://img.shields.io/badge/OpenAI_API-412991?style=for-the-badge&logo=openai&logoColor=white" />
|
|
37
|
+
</a>
|
|
38
|
+
<a href="https://www.anthropic.com/api/">
|
|
39
|
+
<img alt="Anthropic API" src="https://img.shields.io/badge/Claude_API-D97757?style=for-the-badge&logo=claude&logoColor=white" />
|
|
40
|
+
</a>
|
|
41
|
+
<a href="https://api.together.ai/">
|
|
42
|
+
<img alt="together.ai API" src="https://img.shields.io/badge/together.ai_API-B5B5B5?style=for-the-badge&logoColor=white" />
|
|
43
|
+
</a>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## *usage*
|
|
48
|
+
|
|
49
|
+
A collection of methods and classes I repeatedly use when conducting research on LLM code-generation.
|
|
50
|
+
Covers both prompting various LLMs, and analysing the markdown responses.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from llm_cgr import quick_complete, Markdown
|
|
54
|
+
|
|
55
|
+
response = quick_complete("Write python code to generate the nth fibonacci number.")
|
|
56
|
+
|
|
57
|
+
markdown = Markdown(text=response)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## *installation*
|
|
61
|
+
|
|
62
|
+
Install directly from PyPI, using pip:
|
|
63
|
+
|
|
64
|
+
```shell
|
|
65
|
+
pip install llm-codegen-research
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## *development*
|
|
69
|
+
|
|
70
|
+
Clone the repository code:
|
|
71
|
+
|
|
72
|
+
```shell
|
|
73
|
+
git clone https://github.com/itsluketwist/llm-codegen-research.git
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
We use [`uv`](https://astral.sh/blog/uv) for project management.
|
|
77
|
+
Once cloned, create a virtual environment and install uv and the project:
|
|
78
|
+
|
|
79
|
+
```shell
|
|
80
|
+
python -m venv .venv
|
|
81
|
+
|
|
82
|
+
. .venv/bin/activate
|
|
83
|
+
|
|
84
|
+
pip install uv
|
|
85
|
+
|
|
86
|
+
uv sync
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Use `make` commands to lint and test:
|
|
90
|
+
|
|
91
|
+
```shell
|
|
92
|
+
make lint
|
|
93
|
+
|
|
94
|
+
make test
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Use `uv` to add new dependencies into the project and `uv.lock`:
|
|
98
|
+
|
|
99
|
+
```shell
|
|
100
|
+
uv add openai
|
|
101
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/llm_cgr/__init__.py
|
|
5
|
+
src/llm_cgr/defaults.py
|
|
6
|
+
src/llm_cgr/json_utils.py
|
|
7
|
+
src/llm_cgr/py.typed
|
|
8
|
+
src/llm_cgr/analyse/__init__.py
|
|
9
|
+
src/llm_cgr/analyse/classes.py
|
|
10
|
+
src/llm_cgr/analyse/regexes.py
|
|
11
|
+
src/llm_cgr/analyse/languages/__init__.py
|
|
12
|
+
src/llm_cgr/analyse/languages/code_data.py
|
|
13
|
+
src/llm_cgr/analyse/languages/python.py
|
|
14
|
+
src/llm_cgr/query/__init__.py
|
|
15
|
+
src/llm_cgr/query/api_utils.py
|
|
16
|
+
src/llm_cgr/query/generate.py
|
|
17
|
+
src/llm_cgr/query/prompts.py
|
|
18
|
+
src/llm_cgr/query/protocol.py
|
|
19
|
+
src/llm_codegen_research.egg-info/PKG-INFO
|
|
20
|
+
src/llm_codegen_research.egg-info/SOURCES.txt
|
|
21
|
+
src/llm_codegen_research.egg-info/dependency_links.txt
|
|
22
|
+
src/llm_codegen_research.egg-info/requires.txt
|
|
23
|
+
src/llm_codegen_research.egg-info/top_level.txt
|
|
24
|
+
tests/test_analyse.py
|
|
25
|
+
tests/test_query.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
llm_cgr
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from llm_cgr import Markdown
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
TEST_LLM_RESPONSE = """
|
|
5
|
+
Here's a Python solution to process some data and return an answer.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
import numpy as np
|
|
9
|
+
from requests import get
|
|
10
|
+
import json
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
from cryptography.fernet import Fernet
|
|
13
|
+
import pandas.DataFrame
|
|
14
|
+
|
|
15
|
+
def process_data(data):
|
|
16
|
+
response = get("https://api.example.com/data")
|
|
17
|
+
data = np.array([1, 2, 3, 4, 5])
|
|
18
|
+
return np.process(data, response)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Some more code:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
import pandas as pd
|
|
25
|
+
|
|
26
|
+
csv = pd.read_csv("data.csv")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Run some code:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python script.py
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Some very bad python code:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import problem
|
|
39
|
+
|
|
40
|
+
problem.bad_brackets((()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Some very bad unknown code:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
for import xxx)[
|
|
47
|
+
```
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_markdown():
|
|
52
|
+
"""
|
|
53
|
+
Test the MarkdownResponse class, extracting and analysing multiple code blocks.
|
|
54
|
+
"""
|
|
55
|
+
# parse the response
|
|
56
|
+
analysed = Markdown(text=TEST_LLM_RESPONSE)
|
|
57
|
+
|
|
58
|
+
# check initial properties
|
|
59
|
+
assert analysed.text == TEST_LLM_RESPONSE
|
|
60
|
+
assert len(analysed.code_blocks) == 5
|
|
61
|
+
assert analysed.code_errors == ["3: '(' was never closed (<unknown>, line 3)"]
|
|
62
|
+
assert analysed.languages == ["bash", "python"]
|
|
63
|
+
|
|
64
|
+
# expected python code block
|
|
65
|
+
python_code_one = analysed.code_blocks[0]
|
|
66
|
+
assert python_code_one.language == "python"
|
|
67
|
+
assert python_code_one.valid is True
|
|
68
|
+
assert python_code_one.error is None
|
|
69
|
+
assert python_code_one.defined_funcs == ["process_data"]
|
|
70
|
+
assert python_code_one.called_funcs == ["get", "np.array", "np.process"]
|
|
71
|
+
assert python_code_one.packages == [
|
|
72
|
+
"cryptography",
|
|
73
|
+
"numpy",
|
|
74
|
+
"pandas",
|
|
75
|
+
"requests",
|
|
76
|
+
]
|
|
77
|
+
assert python_code_one.imports == [
|
|
78
|
+
"collections.defaultdict",
|
|
79
|
+
"cryptography.fernet.Fernet",
|
|
80
|
+
"json",
|
|
81
|
+
"numpy",
|
|
82
|
+
"pandas.DataFrame",
|
|
83
|
+
"requests.get",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# unspecified code block defaults to python
|
|
87
|
+
python_code_two = analysed.code_blocks[1]
|
|
88
|
+
assert python_code_two.language == "python"
|
|
89
|
+
assert python_code_two.valid is True
|
|
90
|
+
assert python_code_two.error is None
|
|
91
|
+
assert python_code_two.defined_funcs == []
|
|
92
|
+
assert python_code_two.called_funcs == ["pd.read_csv"]
|
|
93
|
+
assert python_code_two.packages == ["pandas"]
|
|
94
|
+
assert python_code_two.imports == ["pandas"]
|
|
95
|
+
|
|
96
|
+
# bash code block with no analysis
|
|
97
|
+
bash_code = analysed.code_blocks[2]
|
|
98
|
+
assert bash_code.language == "bash"
|
|
99
|
+
assert bash_code.valid is None
|
|
100
|
+
assert bash_code.error is None
|
|
101
|
+
assert bash_code.defined_funcs == []
|
|
102
|
+
assert bash_code.called_funcs == []
|
|
103
|
+
assert bash_code.packages == []
|
|
104
|
+
assert bash_code.imports == []
|
|
105
|
+
|
|
106
|
+
# python code block with incorrect syntax
|
|
107
|
+
bad_code = analysed.code_blocks[3]
|
|
108
|
+
assert bad_code.language == "python"
|
|
109
|
+
assert bad_code.valid is False
|
|
110
|
+
assert bad_code.error == "'(' was never closed (<unknown>, line 3)"
|
|
111
|
+
assert bad_code.defined_funcs == []
|
|
112
|
+
assert bad_code.called_funcs == []
|
|
113
|
+
assert bad_code.packages == []
|
|
114
|
+
assert bad_code.imports == []
|
|
115
|
+
|
|
116
|
+
# unknown code block
|
|
117
|
+
unknown_code = analysed.code_blocks[4]
|
|
118
|
+
assert unknown_code.text == "for import xxx)["
|
|
119
|
+
assert unknown_code.language is None
|
|
120
|
+
assert unknown_code.valid is None
|
|
121
|
+
assert unknown_code.error is None
|
|
122
|
+
assert unknown_code.defined_funcs == []
|
|
123
|
+
assert unknown_code.called_funcs == []
|
|
124
|
+
assert unknown_code.packages == []
|
|
125
|
+
assert unknown_code.imports == []
|
|
File without changes
|