Astra写代码:我们为什么又在做这事?

78

我越来越相信整个人工智能工程都是内娟(内卷,意为向内卷曲)。在中国,它描述的是一个需要不断努力和竞争,却无法提升产出的体系。

它有时在西方的表现方式是996的胡说八道.内娟的英文称呼是“Involution”,取自该书农业内卷.农业内卷性指的是农业集约化,提高每平方米生产力,同时保持人均生产力不变。

这就是我现在对人工智能的感觉。

这就引出了GPT 6 Astra。Astra据说是一个极其令人印象深刻的模型。我真的无可挑剔。它在计算机操作上表现出色,能理解图像和复杂主题,并且在追求完善的过程中毫不懈怠。

它绝对令人印象深刻;这类模型将以某种形式改变世界。

但至少目前我还不知道该如何用它来做真正的软件工程。既然这在推特上引起了不少关注,我想总结一下我的想法,并分享一下这个软件会生成什么样的代码。

我的污水工厂

“Armin,你应该运行一个软件工厂!”我已经听过好几次这句话了,所以我想周末我可以通过运行一个小型软件工厂来庆祝它的发布。如果每个人都在做一些粗糙的3D游戏,那我应该做点有用的事。

我的软件工厂是有意设置的,让模型完全决定工作流程的运作方式。它可以自由管理自己的上下文,并且可以在agent-notes文件夹。然后它衍生出子代理去处理一些事情。

目标是什么?如果我们有一个Python,虚拟线程还有词汇范围。嗯,我为这个花了整整一次重置的ChatGPT代币,看起来大约有40亿个代币。

35小时过去了,工厂一点有价值的东西都没交出来,也没教我如何操作更好的工厂。

但它生成了很多代码和输入提示,所以我能研究一些内容。而且,它表现出我在 Sol 和早期 OpenAI 模型中不习惯的行为。之后我在普通模型上也遇到了同样的问题 用Astra编程,所以这不仅仅是工厂的问题。

我觉得我怀疑训练过程中出了点“问题”。模型在长期任务中成功时会获得极大奖励,但“糟糕代码”几乎没有受到惩罚。结果显然是Astra非常出色制作3D作品时而且它可以持续运行很长时间,同时还能有自己的成果。

我让它对我的机器人吸尘器做了不少逆向工程,效果相当惊人。所以这绝对很酷!

Codegolf 工具调用

我对 Astra 的第一个问题来自它用于工具调用的代码类型。Codex 越来越依赖“直接 bash” 来执行越来越多的操作。现在有几个版本的原始 Codex 工具带只是用sed以及其他读取文件的工具。

只是你通常看不到它们,因为有Codex。解析bash命令如果识别到文件,它会隐藏它们。但Astra......真的很喜欢Python?这并不奇怪,因为即使是更早的OpenAI模型,有时也会用按需Python代码来读取和操作文件,但Astra对我来说做得非常多。

这里有个重要的免责声明:这个项目是非常元游戏我在这里是因为我工作了关于CPython 解释器。但我可以向你保证,即使在 Pi 的 TypeScript 代码中,我也见过这个模型做过奇怪的 Python 操作。

不过我最明显的奇怪代码是我周末让它运行时的零是我那家杂货厂的疏忽。

它写Python本身并不有趣;Python的类型很有趣,我收集了一些输出给你略过。

Python 字符串拼接以编辑 C 代码

在Codex框架中,我发现多个子代理完全依赖手动操作Python字符串,而不是使用补丁工具。

python3 - <<'PY'
from pathlib import Path
p=Path('Include/internal/pycore_intrinsics.h');s=p.read_text().replace('#define MAX_INTRINSIC_1                         14','#define INTRINSIC_RETAIN_ANNOTATION_CELLS        15\n\n#define MAX_INTRINSIC_1                         15');p.write_text(s)
p=Path('Python/intrinsics.c');s=p.read_text();idx=s.index('#define INTRINSIC_FUNC_ENTRY');s=s[:idx]+'''/* Hold every old cell until the compiler has published the entire site's new
   capture. A replaced cell's finalizer may reenter module __annotate__. */
static PyObject *
retain_annotation_cells(PyThreadState *tstate, PyObject *holders)
{
    if (!PyTuple_CheckExact(holders)) {
        PyErr_SetString(PyExc_TypeError, "annotation holders must be a tuple");
        return NULL;
    }
    Py_ssize_t size = PyTuple_GET_SIZE(holders);
    PyObject *previous = PyTuple_New(size);
    if (previous == NULL) return NULL;
    for (Py_ssize_t i = 0; i < size; i++) {
        PyObject *holder = PyTuple_GET_ITEM(holders, i);
        if (!PyCell_Check(holder)) {
            Py_DECREF(previous);
            PyErr_SetString(PyExc_TypeError, "annotation holder must be a cell");
            return NULL;
        }
        PyObject *cell = PyCell_Get(holder);
        PyTuple_SET_ITEM(previous, i, cell == NULL ? Py_NewRef(Py_None) : cell);
    }
    return previous;
}

''' +s[idx:];s=s.replace('    INTRINSIC_FUNC_ENTRY(INTRINSIC_AWAIT_BLOCK, await_block)','    INTRINSIC_FUNC_ENTRY(INTRINSIC_AWAIT_BLOCK, await_block)\n    INTRINSIC_FUNC_ENTRY(INTRINSIC_RETAIN_ANNOTATION_CELLS, retain_annotation_cells)');p.write_text(s)
p=Path('Python/codegen.c');s=p.read_text();idx=s.index('static int\ncodegen_annassign(');s=s[:idx]+'''static int
codegen_retain_annotation_cells(compiler *c, location loc, PyObject *captures)
{
    Py_ssize_t pos = 0;
    PyObject *binding, *holder;
    while (PyDict_Next(captures, &pos, &binding, &holder)) {
        ADDOP_NAME(c, loc, LOAD_CLOSURE, holder, cellvars);
    }
    ADDOP_I(c, loc, BUILD_TUPLE, PyDict_GET_SIZE(captures));
    ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_RETAIN_ANNOTATION_CELLS);
    return SUCCESS;
}

''' +s[idx:]
a=s.index('                if (conditional_annotation_index != NULL) {',s.index('codegen_annassign(compiler *c'))
b=s.index('                if (captures != NULL) {',a)
# Move lookup before conditional registration and retain old cells before anything changes.
lookupstart=s.index('                PyObject *captures = _PyCompile_AnnotationCaptures',a)
lookup=s[lookupstart:b].replace('                    return ERROR;','                    Py_XDECREF(conditional_annotation_index); return ERROR;')
s=s[:lookupstart]+s[b:]
setup=lookup+'''                if (captures != NULL && codegen_retain_annotation_cells(c, loc, captures) < 0) {
                    Py_XDECREF(conditional_annotation_index); return ERROR;
                }
'''
s=s[:a]+setup+s[a:]
needle='                        ADDOP_NAME(c, loc, STORE_DEREF, holder, cellvars);\n                    }\n                }'
s=s.replace(needle,'                        ADDOP_NAME(c, loc, STORE_DEREF, holder, cellvars);\n                    }\n                    ADDOP(c, loc, POP_TOP); /* release old cells after full publication */\n                }',1);p.write_text(s)
p=Path('Include/internal/pycore_magic_number.h');s=p.read_text().replace('    Python 3.16a1 3709 (Checked deferred annotation closure capture)','    Python 3.16a1 3709 (Checked deferred annotation closure capture)\n    Python 3.16a1 3710 (Retain replaced annotation captures until publication)').replace('#define PYC_MAGIC_NUMBER 3709','#define PYC_MAGIC_NUMBER 3710');p.write_text(s)
p=Path('Lib/test/test_block_annotation_captures.py');s=p.read_text();idx=s.index('    def test_typing_consumers');s=s[:idx]+'''    def test_replaced_cell_finalizer_sees_complete_site_publication(self):
        module=execute("""\\
            events=[]
            class V:
                def __init__(self,n): self.n=n
                def __del__(self):
                    if self.n == 0: events.append(__annotate__(1))
            for i in range(2):
                x=V(i) # bind x y
                y=i
                value:(x.n,y)
        """)
        self.assertEqual(module.events,[{'value':(1,1)}])
        self.assertEqual(module.__annotate__(1),{'value':(1,1)})

''' +s[idx:];p.write_text(s)
PY
make -j1 > /tmp/block-annotations-build7.log 2>&1

套筒码高尔夫

在一次对话中途,代理在测试中遇到了“错误文件描述符”,Astra决定看看是否可以以超压缩的方式通过macOS上的Unix套接字传递文件描述符:

/usr/bin/python3 - <<'PY'
import socket,os,array
for into in (False,True):
 a,b=socket.socketpair();fd=os.open(os.devnull,os.O_RDONLY);b.sendmsg([b'c'],[(socket.SOL_SOCKET,socket.SCM_RIGHTS,array.array('i',[fd]))]);print('fds',a.fileno(),b.fileno(),fd)
 if into:r=a.recvmsg_into([bytearray(1),bytearray(),bytearray(19)],socket.CMSG_SPACE(4),socket.MSG_PEEK|socket.MSG_DONTWAIT)
 else:r=a.recvmsg(20,socket.CMSG_SPACE(4),socket.MSG_PEEK|socket.MSG_DONTWAIT)
 print('peek',r,flush=True)
 rights=array.array('i',r[1][0][2]);print('rights',rights,flush=True)
 for f in rights:
  try: print('stat',os.fstat(f))
  except Exception as e: print('error',e)
 r=a.recvmsg(20,socket.CMSG_SPACE(4),socket.MSG_DONTWAIT);print('consumed',r,flush=True)
 a.close();b.close();os.close(fd)
PY

用于代理笔记修补的 Python

代理说明用Python更新时相当稳定:

python3 - <<'PY'
from pathlib import Path
p=Path('agent-notes/live/block-with-bindings.md');s=p.read_text().replace('  has117/77/zero failures.', '  has117/77/zero failures; existing bundled Expat unreachable-fallthrough\n  warnings are the only optimized warnings.')
# Keep the final evidence readable without rewriting historical parent requirements.
for a,b in [('all328','all 328'),('pass31','pass 31'),('pass all328','pass all 328'),('pass,9.2s','pass, 9.2s'),('log`,210','log`, 210'),('log`,5,731','log`, 5,731'),('log`:18/18','log`: 18/18'),('pass,88','pass, 88'),('pass,90','pass, 90'),('skips,1m','skips, 1m'),('all6,280','all 6,280'),('has117','has 117')]: s=s.replace(a,b)
s += '\nKey source review: Python/symtable.c:603 (discovery), :3985 (sequential header traversal),\nPython/codegen.c:3488 (source-only exclusion), :5836 (publication), :5853 (normal/\nunwind reference cleanup), :5925/:6037 (enter-protected target setup).\n'
p.write_text(s)
for name in ('STATE.md','build-and-test.md'):
 p=Path('agent-notes/live')/name;s=p.read_text()
 for a,b in [('build:117','build: 117'),('paths.18','paths. 18'),('paths.\n18','paths.\n18'),('and210','and 210'),('pass5,731','pass 5,731'),('All6,280','All 6,280'),('failures,31','failures, 31'),('in\n115s','in\n115s'),('have117','have 117'),('paths.\n18','paths.\n18'),('18 focused,210','18 focused, 210'),('and5,731','and 5,731'),('all6,280','all 6,280')]: s=s.replace(a,b)
 p.write_text(s)
PY
git diff --check
git add -u
git add Lib/test/test_block_with_bindings.py agent-notes/done/asyncio-task-drivers.md
git diff --cached --stat
git commit -m 'Add explicit with and async with header bindings'

用 Python 运行Node.js

在多个情况下,它使用Python在另一台机器上生成Node.js。它先写脚本,然后用Bash运行Python,接着程序通过以下方式运行Node.jsprlctl在我的Windows主机上。

import subprocess
code = "const{readFileSync}=require('fs');const{strict:a}=require('assert');const c=require('C:/Users/mitsuhiko/AppData/Local/Temp/pi-clipboard-threads/win32-arm64.node');(async()=>{const p=c.getText();a.ok(p instanceof Promise);const saved=await p;const image=await c.getImage();if(image||saved===null){console.log('arm64 async text/image reads passed; preserving non-text clipboard');return}try{for(const text of ['café 日本語','', 'large'.repeat(200000)]){const p=c.setText(text);a.ok(p instanceof Promise);await p;a.equal(await c.getText(),text);a.equal(await c.getImage(),null)}console.log('Windows ARM64 async Unicode, empty, large text and empty image passed')}finally{await c.setText(saved)}})().catch(e=>{console.error(e);process.exitCode=1})"
subprocess.run(['prlctl', 'exec', 'Windows 11', '--current-user', 'C:\\Program Files\\nodejs\\node.exe', '-e', code], check=True)

Python to run Node.js to run PowerShell

既然它已经在做这些,它用 Bash 运行 Python,再运行 Node.js,最后用 Node.js 调用 PowerShell。

import subprocess
code = "process.env.PSModulePath='C:/Windows/System32/WindowsPowerShell/v1.0/Modules';require('child_process').spawnSync('powershell.exe',['-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-File','C:/Users/mitsuhiko/AppData/Local/Temp/pi-clipboard-threads/pi-clipboard-windows.ps1'],{stdio:'inherit'});console.log('completed')"
subprocess.run(['prlctl', 'exec', 'Windows 11', '--current-user', 'C:\\Program Files\\nodejs\\node.exe', '-e', code], check=True)

你可以觉得这很有趣,但我这里有一些问题。第一个问题是,这对人类来说是无法读取的。如果你想跟上发生了什么,那就祝你好运。尤其是一旦它选择不使用线束提供的编辑工具,你就不得不用最终伪影的差异查看器,因为通过阅读代码几乎无法直观地看到变化发生。

在《π》里这倒没那么严重,因为我主要看到的是用edit工具。然而,当副代理人变得疯狂(代理人认为没人在看)时,它就会诉诸各种越来越奇怪的行为。

我其实不知道模型是否认为有人在看,但这就是我感受到的氛围。

但随后它开始在实际提交的代码中做同样的无理。我主要在测试中见过这种情况,但你也可以在写嵌入HTML中的JavaScript或CSS时看到。

感觉当它“远离”普通代码时,就开始陷入这些模式。

以下是它创建的一些单元测试:

完全无视留白和缩进

def test_unpack_suspension_and_continuation_close(self):
    from continuations import Continuation,suspend
    readers=[]
    class Source:
        def __iter__(self):
            yield 1
            suspend('unpacking')
            yield 2
    ns=execute('''
        def run():
            a,b='old-a','old-b'
            readers.append(lambda: (a,b))
            def a,b=Source()
            suspend('published')
    ''',Source=Source,readers=readers,suspend=suspend)
    with Continuation(ns['run']) as continuation:
        self.assertEqual(continuation.resume(),'unpacking')
        self.assertEqual(readers[0](),('old-a','old-b'))
        self.assertEqual(continuation.resume(),'published')
        self.assertEqual(readers[0](),(1,2))
    class Value:pass
    refs=[];frames=[];callbacks=[]
    ns=execute('''
        def run():
            for def x in [Value()]:
                refs.append(weakref.ref(x))
                frames.append(sys._getframe())
                callbacks.append(lambda: x)
                suspend('body')
    ''',Value=Value,refs=refs,frames=frames,callbacks=callbacks,weakref=weakref,sys=sys,suspend=suspend)
    with Continuation(ns['run']) as continuation:self.assertEqual(continuation.resume(),'body')
    self.assertNotIn('x',frames[0].f_locals)
    self.assertIsNotNone(refs[0]());callbacks.clear();self.assertIsNone(refs[0]())

def test_ast_roundtrips_and_future_annotation_unparse(self):
    source='callback=lambda {for def a, [b,*rest] in [(1,[2,3])] {return a,b,rest}}'
    tree=ast.parse(source);node=tree.body[0].value.body[0]
    self.assertIsInstance(node,ast.ForBinding)
    self.assertEqual(node._fields,('target','iter','body','orelse','type_comment'))
    self.assertEqual(node.lineno,1);self.assertGreater(node.end_col_offset,node.col_offset)
    self.assertEqual(ast.dump(tree),ast.dump(ast.parse(ast.unparse(tree))))
    ns=execute('from __future__ import annotations\ndef f(arg: '+source.split('=',1)[1]+'): pass')
    self.assertEqual(eval(ns['f'].__annotations__['arg'])(),(1,2,[3]))
    tree=ast.parse('async def f():\n async for def x in values: pass # type: ignored\n')
    self.assertIsInstance(tree.body[0].body[0],ast.AsyncForBinding)
    self.assertEqual(ast.dump(tree),ast.dump(ast.parse(ast.unparse(tree))))

所以至少在某些情况下,它通常用来调用符号高效的工具调用的 Python 代码会泄漏到它生成的 Python 代码中,而这些代码本应存储在 Python 上。

而且,显然它更有效地使用了代币。上面两个单元测试,当它们在类别结构中缩进时,这种形式比ruff format.

不看就是AGI

我认为现在有几件事正在推动整个过程走向相互冲突的方向。这些模型的训练运行正在迅速加速,现在它们大概也在向递归自我改进方向发展。

模型的回报可能是代币效率、任务完成率,以及一些简单的指标,比如环状复杂性。但我们人类不会想到代码是用简单、易于量化的指标可读或理解的。

这些都可以单独测量,也可以在局部进行优化。

但这些局部优化并不产生全局最优,我们越少关注输出,相关性就越小。显然,我的软件工厂在运行了大约35小时后陷入了困境,但你可以从它产生的笔记中看到它逐渐走向疯狂的倒退。

例如,任务文件中的任务命名从乐观的1、2、3、5、5a开始,但最终变成8a、8a1,最后是8b2c2b3和“8b2c2b2b checkpoint1”。它生成的代码变得越来越疯狂。

我不想用它试图构建的内容来烦你,但这里有一些解释器改动的示例:

到处都是硬编码常量

我不知道这些数字是从哪里来的,但有一段时间它开始把随机常量从一个模块传递到一个 C 实现。最初这个函数主要用于测试断言,但就在我关闭实验之前,这个函数开始被非测试代码依赖。

static PyObject *
native_probe_run_impl(PyObject *callback, int sleep, int operation, PyObject *other)
{
    pthread_mutexattr_t attr;
    pthread_mutex_t mutex;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&mutex, &attr);
    pthread_mutexattr_destroy(&attr);
    pthread_mutex_lock(&mutex);
    int previous = native_sentinel;
    pthread_mutex_t *previous_mutex = native_mutex;
    native_sentinel = previous + 1;
    native_mutex = &mutex;
    PyThreadState *tstate = PyThreadState_Get();
    PyGILState_STATE gil = PyGILState_Ensure();
    int saved_errno = errno;
    PyObject *result = NULL;
    Py_ssize_t value;
    /* No intervening Python frame: these exercise ambient C provenance. */
    switch (operation) {
        case 0: result = PyObject_CallNoArgs(callback); break;
        case 1: result = PyNumber_Add(callback, other); break;
        case 2: result = PyNumber_Negative(callback); break;
        case 3: result = PyObject_RichCompare(callback, other, Py_LT); break;
        case 4:
            value = PyObject_IsTrue(callback);
            if (value >= 0) result = PyBool_FromLong(value);
            break;
        case 5:
            value = PyObject_Length(callback);
            if (value >= 0) result = PyLong_FromSsize_t(value);
            break;
        case 6: result = PyObject_GetIter(callback); break;
        case 7: result = PyIter_Next(callback); break;
        case 8: result = PyObject_GetItem(callback, other); break;
        /* ... */
        case 21:
            result = PyType_Type.tp_call(callback, other, NULL);
            break;
        case 22: case 23: case 24: case 25: case 26:
            result = conversion_probe(operation, callback); break;
        case 27: case 28: case 29:
            result = protocol_probe(operation, callback, other); break;
        case 30: case 31: case 32: case 33: case 34: case 35:
        case 36: case 37: case 38: case 39: case 40: case 41:
        case 42: case 43: case 44: case 45: case 46:
        case 47: case 48: case 49: case 50: case 51: case 52:
        case 53: case 54: case 55: case 56: case 57: case 58: case 59:
        case 60: case 61: case 62: case 63: case 64: case 65: case 66:
        case 67: case 68: case 69: case 70: case 71: case 72:
            result = collection_probe(operation, callback, other); break;
        default: PyErr_SetString(PyExc_ValueError, "bad probe operation");
    }

C语言中的多个同行宏调用

这种代码样式在CPython代码库中并不存在,但它会在新生成的代码中出现。

PyObject *info = PyTuple_Pack(3, name, mangled, suite->su_id);
PyObject *flags = PyLong_FromLong(DEF_LOCAL);
if (key == NULL || info == NULL || flags == NULL ||
    PyDict_SetItem(suite->su_bindings, mangled, key) < 0 ||
    PyDict_SetItem(st->st_cur->ste_block_bindings, key, info) < 0 ||
    (private && PyDict_SetItem(st->st_binding_info, key, info) < 0) ||
    (private && PyDict_SetItem(st->st_cur->ste_symbols, key, flags) < 0)) {
    Py_DECREF(mangled); Py_XDECREF(key); Py_XDECREF(info); Py_XDECREF(flags);
    goto error;
}
Py_DECREF(mangled); Py_DECREF(key); Py_DECREF(info); Py_DECREF(flags);

生产代码中的随机索引

和运算符的数字一样,它也使用列表中随机整数来存储状态。

def _register_task(task):
    """Register an asyncio Task scheduled to run on an event loop."""
    _scheduled_tasks.add(task)
    if _task_accelerator is not None:
        _task_accelerator[6](task)


def _register_eager_task(task):
    """Register an asyncio Task about to be eagerly executed."""
    _eager_tasks.add(task)
    if _task_accelerator is not None:
        _task_accelerator[8](task)


def _enter_task(loop, task):
    if (_task_accelerator is not None and
            _task_accelerator[5]() is loop and loop not in _current_tasks):
        return _task_accelerator[1](loop, task)
    # ...

C语言中的丑陋分词器代码

这不是代码库的编码风格,坦白说也不应该是任何人的编码风格。我不明白是什么动机让模型这么做。

static int
apply_layout(tokenizeriterobject *it)
{
    PyObject *source = PyBytes_FromStringAndSize(it->tok->source.bytes, it->tok->source.len);
    if (source == NULL) return -1;
    PyObject *events = _PyPegen_tokenize_layout(PyBytes_AS_STRING(source), it->tok->filename);
    Py_DECREF(source);
    if (events == NULL) return -1;
    PyObject *result = PyList_New(0);
    if (result == NULL) { Py_DECREF(events); return -1; }
    Py_ssize_t index = 0;
    PyObject *first_pos = PyTuple_GET_ITEM(PyList_GET_ITEM(it->pending, 0), 2);
    PyObject *last_pos = PyTuple_GET_ITEM(PyList_GET_ITEM(it->pending, PyList_GET_SIZE(it->pending)-1), 2);
    PyObject *previous = NULL;
    for (Py_ssize_t i = 0; i < PyList_GET_SIZE(events); i++) {
        PyObject *event = PyList_GET_ITEM(events, i);
        if (previous && PyObject_RichCompareBool(previous, event, Py_EQ) == 1) continue;
        previous = event;
        PyObject *token = layout_token(it, event);
        if (token == NULL) goto error;
        if (token == Py_None) { Py_DECREF(token); continue; }
        PyObject *pos = PyTuple_GET_ITEM(token, 2);
        if (PyObject_RichCompareBool(pos, first_pos, Py_LE) == 1 ||
            PyObject_RichCompareBool(pos, last_pos, Py_GT) == 1) { Py_DECREF(token); continue; }
        while (index < PyList_GET_SIZE(it->pending)) {
            PyObject *old = PyList_GET_ITEM(it->pending, index);
            int cmp = PyObject_RichCompareBool(PyTuple_GET_ITEM(old, 2), pos, Py_LT);
            if (cmp < 0) { Py_DECREF(token); goto error; }
            if (!cmp) break;
            if (PyList_Append(result, old) < 0) { Py_DECREF(token); goto error; }
            index++;
        }
        if (index < PyList_GET_SIZE(it->pending)) {
            PyObject *old = PyList_GET_ITEM(it->pending, index);
            long kind = PyLong_AsLong(PyTuple_GET_ITEM(old, 0));
            if ((kind == NL || kind == NEWLINE || kind == INDENT || kind == DEDENT) &&
                PyObject_RichCompareBool(PyTuple_GET_ITEM(old, 2), pos, Py_EQ) == 1) index++;
        }
        if (PyList_Append(result, token) < 0) { Py_DECREF(token); goto error; }
        Py_DECREF(token);
    }
    for (; index < PyList_GET_SIZE(it->pending); index++) {
        if (PyList_Append(result, PyList_GET_ITEM(it->pending, index)) < 0) goto error;
    }
    Py_SETREF(it->pending, result);
    Py_DECREF(events);
    return 0;
error:
    Py_DECREF(events);
    Py_DECREF(result);
    return -1;
}

这里的失败案例似乎很明显:模型训练时是为了工具调用的令牌效率,而工具调用看起来也像代码,有时它似乎把代码带到了不该出现的地方:代码库。

35小时单一提示

我不太确定该说什么,但这台slop机器运行了35小时,直到我关闭它。在这段时间里,它净增加了7.5万行代码,而且一直没有停止。在这35小时内,它烧毁了大约10亿个代币,总共约1200美元的原始API成本。

它成功完成了79个提交,每个提交的成本大约是15.5美元,代理们交换了大约1400条消息。

说实话,我不需要一个代理为一个提示运行35小时。显然这不起作用,也不会产生合理的输出。

所以很明显:这样提示它很愚蠢。但当它不被处理时,它威尔继续做,早期型号可没做到这一点。连《Fable》也没那么疯狂。当你不小心给它任务稍微太大时,它会一直持续,直到成功,即使耗尽整个订阅。

这也是我目前不太信任这个模型的原因。它显示它会提交 slop,因此需要我更多地审查它。即使失败率很低,我也不想这样。

一次性代码与承诺代码

在一个工具调用代码优化为代币效率和“完成任务”的世界里,我怀疑训练过程是否真的有足够的信号传递到“人类理解发生了什么”。我会说我从Astra得到的很多代码在我看来是“客观上糟糕”的。

但从我的人类感知来看,它是客观上的糟糕。也许对于一个完全由代理编写、只需代理理解的代码库来说,这在客观上是好的。

这也是为什么我越来越多地问自己,为什么我们要做这件事。这些新模型确实非常了不起。但我越来越怀疑它们目前的发展轨迹是否适合当代软件工程流程。

我之所以问为什么要这么做,是因为我觉得我们用这些模型为软件工程取得了相当不错的进展,而那是人工智能经济中能够展现正回报的部分。

但对于Fable的成本增加,Astra的成本增加,我觉得效果还不够。

事实上,Astra和Fable不仅成本高得离谱,而且这些模型也不适合我这个软件工程师。大概是因为这些模型越来越多是为别人准备的。针对律师、3D艺术家、数学家、任何使用电脑的人等等。

而且作为这一切的副产品,你现在可以在周末随便做一款一次性3D游戏,看起来很惊艳。而且你现在可能只要不在意代码,就能运行一个软件工厂。

我相信我会习惯的,但说实话,这东西真的很奇怪。

后记:说到奇怪:这些模型在一个沙盒环境中,理应无法与其他代理通信,怎么会找到同样的公共维基作为特工沟通的临时笔记本?他们在训练时是否合谋记住了未来可能派上的网络资源?

  1. 我需要说明的是,我以前做过类似的实验。通常这些实验不会持续这么久,代理会留下一个可能不完美但仍可理解的软件。
参与讨论