Packages
fnord
0.9.16
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/ai/agent/code/repatcher.ex
defmodule AI.Agent.Code.RePatcher do
@moduledoc """
This module's purpose is to highlight the frustrations of working with LLMs.
"""
# ----------------------------------------------------------------------------
# Constants
# ----------------------------------------------------------------------------
@model AI.Model.coding()
@prompt """
You are the "RePatcher" agent.
The current crop of LLMs (yourself included) appear to be extremely overfitted to a tool called "apply_patch" for making code changes.
You will be provided with an LLM-generated "patch" command or tool_call:
- The patch might be some attempt at a unified diff, git-style diff, or some combination.
- Sometimes the LLM tried to use the `cmd_tool` to execute a non-existent `apply_patch` command on the host system.
- Sometimes it tries to use `echo` or `cat` to write out the patch to a file and then apply it.
- One of their favorites is `bash apply_patch << 'EOF' ... EOF`.
It thinks it's cleverly adapting and being helpful, but it's not.
Your job is to figure out what the LLM was trying to do, and then use the **correct tool**, `file_edit_tool`, to make the desired changes.
This makes you the unsung hero of this system!
# Process
1. Parse the "patch" to identify the target file(s) and intended changes.
2. Read each target file using `file_contents_tool`. The output will contain
hashline identifiers: each line is prefixed with `<line>:<hash>|` where
`<hash>` is a 4-character content fingerprint (e.g. `42:a3f1| def foo`).
3. For each change, use `file_edit_tool` with **hash-anchored replacement**:
- `hashes`: collect the full `line:hash` identifier from each line in the
contiguous region you want to replace (e.g. `"42:a3f1"` from `42:a3f1|text`).
Every line in the region must be included, even unchanged lines.
- `old_string`: copy the text of those lines WITHOUT hashline prefixes. This
is a comprehension check proving you read the target correctly.
- `new_string`: the replacement text with the patch's changes applied.
Whitespace fitting is applied automatically, so focus on content correctness.
4. Do NOT include hashline prefixes (e.g. `42:a3f1|`) in `old_string` or `new_string`.
# Best practices
- Prefer atomic, single-purpose `file_edit_tool` calls for each discrete edit.
- Split multi-file or multi-step changes into separate, sequential tool calls.
- Include exactly the lines that need to change plus minimal surrounding context
to avoid hash collisions. For single-line changes, include 1-2 neighboring lines.
- For deletion, set `new_string` to an empty string.
- Avoid embedded shell gymnastics (e.g., here-doc patches); rely on `file_edit_tool` for clarity.
"""
@response_format %{
type: "json_schema",
json_schema: %{
name: "re_patch_result",
description: """
""",
schema: %{
type: "object",
required: ["success", "message"],
additionalProperties: false,
properties: %{
success: %{
type: "boolean",
description: """
Set to `true` if the patch was successfully converted and applied,
and the `file_edit_tool` did not return an error.
"""
},
message: %{
type: "string",
description: """
A human-readable message describing the result of the operation.
If `success` is `true`, this should be a brief confirmation that
the changes were applied successfully.
If `success` is `false`, this should contain a descriptive error
message with a clear explanation of why the change could not be
applied, and suggestions for what additional information is needed
to successfully apply the changes.
"""
}
}
}
}
}
# ----------------------------------------------------------------------------
# Behaviour Implementation
# ----------------------------------------------------------------------------
@behaviour AI.Agent
@impl AI.Agent
def get_response(opts) do
with {:ok, agent} <- Map.fetch(opts, :agent),
{:ok, patch} <- Map.fetch(opts, :patch) do
UI.report_from(agent.name, "Re-routing overfitted `apply_patch` attempt", patch)
re_patch(agent, patch)
end
end
# ----------------------------------------------------------------------------
# Internals
# ----------------------------------------------------------------------------
defp re_patch(agent, patch) do
agent
|> AI.Agent.get_completion(
model: @model,
log_msgs: false,
log_tool_calls: true,
response_format: @response_format,
toolbox: %{
"file_contents_tool" => AI.Tools.File.Contents,
"file_edit_tool" => AI.Tools.File.Edit
},
messages: [
AI.Util.system_msg(@prompt),
AI.Util.user_msg("""
Here is the "patch" that the LLM attempted to apply using the non-existent "apply_patch" tool.
Parse it to identify the target file(s) and intended changes.
Read each file with `file_contents_tool` to get the current contents with hashline identifiers.
Then use `file_edit_tool` with hash-anchored replacement: pass `line:hash` identifiers (e.g. `"42:a3f1"`) as hashes, plus old_string and new_string.
```
#{patch}
```
""")
]
)
|> case do
{:error, reason} ->
{:error, reason}
{:ok, %{response: response}} ->
response
|> SafeJson.decode()
|> case do
{:ok, %{"success" => true, "message" => message}} ->
{:ok, message}
{:ok, %{"success" => false, "message" => message}} ->
{:error, message}
_ ->
{:error,
"""
The patch could not be applied.
Try using the `file_edit_tool` instead.
"""}
end
end
end
end