@looopy-ai/core 2.1.0 → 2.1.2
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.
- package/dist/core/agent.js +3 -0
- package/dist/core/loop.js +3 -2
- package/dist/core/tools.js +3 -0
- package/dist/events/utils.d.ts +2 -1
- package/dist/events/utils.js +3 -0
- package/dist/observability/spans/agent-turn.js +3 -0
- package/dist/observability/spans/iteration.js +3 -0
- package/dist/observability/spans/llm-call.js +3 -0
- package/dist/observability/spans/loop.js +3 -0
- package/dist/observability/spans/tool.js +2 -1
- package/dist/tools/agent-tool-provider.js +31 -23
- package/dist/types/event.d.ts +3 -0
- package/package.json +2 -2
package/dist/core/agent.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { catchError, concat, filter, Observable, of, tap } from 'rxjs';
|
|
2
2
|
import { createTaskStatusEvent } from '../events';
|
|
3
|
+
import { isChildTaskEvent } from '../events/utils';
|
|
3
4
|
import { addMessagesCompactedEvent, addMessagesLoadedEvent, completeAgentInitializeSpan, completeAgentTurnSpan, failAgentInitializeSpan, failAgentTurnSpan, setResumeAttributes, setTurnCountAttribute, startAgentInitializeSpan, startAgentTurnSpan, } from '../observability/spans';
|
|
4
5
|
import { serializeError } from '../utils/error';
|
|
5
6
|
import { getLogger } from './logger';
|
|
@@ -178,6 +179,8 @@ export class Agent {
|
|
|
178
179
|
maxIterations: 5,
|
|
179
180
|
stopOnToolError: false,
|
|
180
181
|
}, messages).pipe(tap(async (event) => {
|
|
182
|
+
if (isChildTaskEvent(event))
|
|
183
|
+
return;
|
|
181
184
|
switch (event.kind) {
|
|
182
185
|
case 'content-complete':
|
|
183
186
|
if (event.content || event.toolCalls) {
|
package/dist/core/loop.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { concat, EMPTY, mergeMap, of, reduce, shareReplay } from 'rxjs';
|
|
2
2
|
import { createTaskCompleteEvent, createTaskCreatedEvent, createTaskStatusEvent } from '../events';
|
|
3
|
+
import { isChildTaskEvent } from '../events/utils';
|
|
3
4
|
import { startAgentLoopSpan } from '../observability/spans';
|
|
4
5
|
import { recursiveMerge } from '../utils/recursive-merge';
|
|
5
6
|
import { runIteration } from './iteration';
|
|
@@ -37,8 +38,8 @@ export const runLoop = (context, config, history) => {
|
|
|
37
38
|
}, state.messages), (state, { events }) => ({
|
|
38
39
|
...state,
|
|
39
40
|
messages: [...state.messages, ...eventsToMessages(events)],
|
|
40
|
-
}), (e) => e.kind === 'content-complete' && e.finishReason !== 'tool_calls').pipe(shareReplay({ refCount: true }));
|
|
41
|
-
const finalSummary$ = merged$.pipe(reduce((last, e) => (e.kind === 'content-complete' ? e : last), null), mergeMap((last) => {
|
|
41
|
+
}), (e) => !isChildTaskEvent(e) && e.kind === 'content-complete' && e.finishReason !== 'tool_calls').pipe(shareReplay({ refCount: true }));
|
|
42
|
+
const finalSummary$ = merged$.pipe(reduce((last, e) => (!isChildTaskEvent(e) && e.kind === 'content-complete' ? e : last), null), mergeMap((last) => {
|
|
42
43
|
if (!last)
|
|
43
44
|
return EMPTY;
|
|
44
45
|
return of(createTaskCompleteEvent({
|
package/dist/core/tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { catchError, concat, defer, mergeMap, of, tap } from 'rxjs';
|
|
2
|
+
import { isChildTaskEvent } from '../events/utils';
|
|
2
3
|
import { startToolExecuteSpan } from '../observability/spans';
|
|
3
4
|
import { toolErrorEvent } from '../tools/tool-result-events';
|
|
4
5
|
export const runToolCall = (context, toolCall) => {
|
|
@@ -42,6 +43,8 @@ export const runToolCall = (context, toolCall) => {
|
|
|
42
43
|
try {
|
|
43
44
|
logger.trace({ providerName: provider.name }, 'Executing tool');
|
|
44
45
|
return provider.execute(toolCallInput, context).pipe(tap((event) => {
|
|
46
|
+
if (isChildTaskEvent(event))
|
|
47
|
+
return;
|
|
45
48
|
if (event.kind !== 'tool-complete') {
|
|
46
49
|
return;
|
|
47
50
|
}
|
package/dist/events/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuthCompletedEvent, AuthRequiredEvent, AuthType, ContentCompleteEvent, ContentDeltaEvent, ContextAnyEvent, ContextEvent, DatasetWriteEvent, DataWriteEvent, FileWriteEvent, FinishReason, InputProvider, InputReceivedEvent, InputRequiredEvent, InputType, InternalCheckpointEvent, InternalLLMCallEvent, InternalThoughtProcessEvent, JSONSchema, SubtaskCreatedEvent, TaskCompleteEvent, TaskCreatedEvent, TaskInitiator, TaskStatus, TaskStatusEvent, ThoughtStreamEvent, ThoughtType, ThoughtVerbosity, ToolCompleteEvent, ToolProgressEvent, ToolStartEvent } from '../types/event';
|
|
1
|
+
import type { AnyEvent, AuthCompletedEvent, AuthRequiredEvent, AuthType, ContentCompleteEvent, ContentDeltaEvent, ContextAnyEvent, ContextEvent, DatasetWriteEvent, DataWriteEvent, FileWriteEvent, FinishReason, InputProvider, InputReceivedEvent, InputRequiredEvent, InputType, InternalCheckpointEvent, InternalLLMCallEvent, InternalThoughtProcessEvent, JSONSchema, SubtaskCreatedEvent, TaskCompleteEvent, TaskCreatedEvent, TaskInitiator, TaskStatus, TaskStatusEvent, ThoughtStreamEvent, ThoughtType, ThoughtVerbosity, ToolCompleteEvent, ToolProgressEvent, ToolStartEvent } from '../types/event';
|
|
2
2
|
export declare function generateEventId(): string;
|
|
3
3
|
export interface CreateTaskCreatedEventOptions {
|
|
4
4
|
contextId: string;
|
|
@@ -248,3 +248,4 @@ export declare function filterByContextId(events: ContextAnyEvent[], contextId:
|
|
|
248
248
|
export declare function filterByKind<K extends ContextAnyEvent['kind']>(events: ContextAnyEvent[], kind: K): Extract<ContextAnyEvent, {
|
|
249
249
|
kind: K;
|
|
250
250
|
}>[];
|
|
251
|
+
export declare const isChildTaskEvent: (event: AnyEvent) => boolean;
|
package/dist/events/utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { context, SpanStatusCode, trace } from '@opentelemetry/api';
|
|
2
2
|
import { tap } from 'rxjs/internal/operators/tap';
|
|
3
|
+
import { isChildTaskEvent } from '../../events/utils';
|
|
3
4
|
import { SpanAttributes } from '../tracing';
|
|
4
5
|
const safeName = (name) => name.replace(/[^a-zA-Z0-9_-]+/g, '-');
|
|
5
6
|
export const startAgentTurnSpan = (params) => {
|
|
@@ -20,6 +21,8 @@ export const startAgentTurnSpan = (params) => {
|
|
|
20
21
|
traceContext,
|
|
21
22
|
tapFinish: tap({
|
|
22
23
|
next: (event) => {
|
|
24
|
+
if (isChildTaskEvent(event))
|
|
25
|
+
return;
|
|
23
26
|
switch (event.kind) {
|
|
24
27
|
case 'task-complete':
|
|
25
28
|
if (event.content) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SpanStatusCode, trace } from '@opentelemetry/api';
|
|
2
2
|
import { tap } from 'rxjs/internal/operators/tap';
|
|
3
|
+
import { isChildTaskEvent } from '../../events/utils';
|
|
3
4
|
import { SpanAttributes, SpanNames } from '../tracing';
|
|
4
5
|
export const startLoopIterationSpan = (context, iteration) => {
|
|
5
6
|
const logger = context.logger;
|
|
@@ -20,6 +21,8 @@ export const startLoopIterationSpan = (context, iteration) => {
|
|
|
20
21
|
traceContext,
|
|
21
22
|
tapFinish: tap({
|
|
22
23
|
next: (event) => {
|
|
24
|
+
if (isChildTaskEvent(event))
|
|
25
|
+
return;
|
|
23
26
|
if (event.kind === 'content-complete') {
|
|
24
27
|
if (event.content) {
|
|
25
28
|
span.setAttribute(SpanAttributes.OUTPUT, event.content);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SpanStatusCode, trace } from '@opentelemetry/api';
|
|
2
2
|
import { tap } from 'rxjs/internal/operators/tap';
|
|
3
|
+
import { isChildTaskEvent } from '../../events/utils';
|
|
3
4
|
import { SpanAttributes, SpanNames } from '../tracing';
|
|
4
5
|
export const startLLMCallSpan = (context, systemPrompt, messages, tools) => {
|
|
5
6
|
const tracer = trace.getTracer('looopy');
|
|
@@ -20,6 +21,8 @@ export const startLLMCallSpan = (context, systemPrompt, messages, tools) => {
|
|
|
20
21
|
traceContext,
|
|
21
22
|
tapFinish: tap({
|
|
22
23
|
next: (event) => {
|
|
24
|
+
if (isChildTaskEvent(event))
|
|
25
|
+
return;
|
|
23
26
|
switch (event.kind) {
|
|
24
27
|
case 'content-complete':
|
|
25
28
|
if (event.content) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SpanStatusCode, trace } from '@opentelemetry/api';
|
|
2
2
|
import { tap } from 'rxjs/internal/operators/tap';
|
|
3
|
+
import { isChildTaskEvent } from '../../events/utils';
|
|
3
4
|
import { SpanAttributes, SpanNames } from '../tracing';
|
|
4
5
|
export const startAgentLoopSpan = (params) => {
|
|
5
6
|
const tracer = trace.getTracer('looopy');
|
|
@@ -18,6 +19,8 @@ export const startAgentLoopSpan = (params) => {
|
|
|
18
19
|
traceContext,
|
|
19
20
|
tapFinish: tap({
|
|
20
21
|
next: (event) => {
|
|
22
|
+
if (isChildTaskEvent(event))
|
|
23
|
+
return;
|
|
21
24
|
switch (event.kind) {
|
|
22
25
|
case 'content-complete':
|
|
23
26
|
if (event.content) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { context as otelContext, SpanStatusCode, trace } from '@opentelemetry/api';
|
|
2
2
|
import { tap } from 'rxjs';
|
|
3
|
+
import { isChildTaskEvent } from '../../events/utils';
|
|
3
4
|
import { SpanAttributes, SpanNames } from '../tracing';
|
|
4
5
|
export const startToolExecuteSpan = (context, toolStart) => {
|
|
5
6
|
const tracer = trace.getTracer('looopy');
|
|
@@ -19,7 +20,7 @@ export const startToolExecuteSpan = (context, toolStart) => {
|
|
|
19
20
|
traceContext,
|
|
20
21
|
tapFinish: tap({
|
|
21
22
|
next: (event) => {
|
|
22
|
-
if (!isToolCompleteEvent(event)) {
|
|
23
|
+
if (isChildTaskEvent(event) || !isToolCompleteEvent(event)) {
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
26
|
try {
|
|
@@ -74,20 +74,24 @@ export class AgentToolProvider {
|
|
|
74
74
|
return Promise.resolve(this.tools);
|
|
75
75
|
}
|
|
76
76
|
execute(toolCall, context) {
|
|
77
|
-
this.logger.
|
|
77
|
+
const logger = this.logger.child({
|
|
78
|
+
taskId: context.taskId,
|
|
79
|
+
toolCallId: toolCall.id,
|
|
80
|
+
});
|
|
81
|
+
logger.debug({ toolCallId: toolCall.id }, 'Executing agent tool call');
|
|
78
82
|
return new Observable((subscriber) => {
|
|
79
83
|
const abortController = new AbortController();
|
|
80
84
|
const run = async () => {
|
|
81
85
|
const tool = await this.getTool(toolCall.function.name);
|
|
82
86
|
if (!tool) {
|
|
83
|
-
|
|
87
|
+
logger.error({ toolName: toolCall.function.name }, 'Tool not found');
|
|
84
88
|
subscriber.next(toolErrorEvent(context, toolCall, `Tool not found: ${toolCall.function.name}`));
|
|
85
89
|
subscriber.complete();
|
|
86
90
|
return;
|
|
87
91
|
}
|
|
88
92
|
const prompt = toolCall.function.arguments.prompt;
|
|
89
93
|
if (!prompt || typeof prompt !== 'string') {
|
|
90
|
-
|
|
94
|
+
logger.error('Invalid tool call arguments');
|
|
91
95
|
subscriber.next(toolErrorEvent(context, toolCall, 'Tool argument must include "prompt" and it must be a string'));
|
|
92
96
|
subscriber.complete();
|
|
93
97
|
return;
|
|
@@ -103,47 +107,51 @@ export class AgentToolProvider {
|
|
|
103
107
|
signal: abortController.signal,
|
|
104
108
|
});
|
|
105
109
|
if (!res.ok) {
|
|
106
|
-
|
|
110
|
+
logger.error({ status: res.status, statusText: res.statusText }, 'Agent call failed');
|
|
107
111
|
subscriber.next(toolErrorEvent(context, toolCall, `Agent endpoint responded with ${res.status} ${res.statusText}`));
|
|
108
112
|
subscriber.complete();
|
|
109
113
|
return;
|
|
110
114
|
}
|
|
111
115
|
const body = res.body;
|
|
112
116
|
if (!body) {
|
|
113
|
-
|
|
117
|
+
logger.error('Agent response has no body');
|
|
114
118
|
subscriber.next(toolErrorEvent(context, toolCall, 'Agent returned no response body'));
|
|
115
119
|
subscriber.complete();
|
|
116
120
|
return;
|
|
117
121
|
}
|
|
122
|
+
let content = '';
|
|
118
123
|
await consumeSSEStream(body, (e) => {
|
|
119
124
|
if (subscriber.closed)
|
|
120
125
|
return;
|
|
126
|
+
const data = JSON.parse(e.data);
|
|
121
127
|
subscriber.next({
|
|
122
128
|
kind: e.event,
|
|
123
129
|
parentTaskId: context.taskId,
|
|
124
|
-
...
|
|
130
|
+
...data,
|
|
125
131
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (!subscriber.closed) {
|
|
129
|
-
const toolCompleteEvent = {
|
|
130
|
-
kind: 'tool-complete',
|
|
131
|
-
contextId: context.contextId,
|
|
132
|
-
taskId: context.taskId,
|
|
133
|
-
toolCallId: toolCall.id,
|
|
134
|
-
toolName: toolCall.function.name,
|
|
135
|
-
success: true,
|
|
136
|
-
result: 'Complete',
|
|
137
|
-
timestamp: new Date().toISOString(),
|
|
138
|
-
};
|
|
139
|
-
subscriber.next(toolCompleteEvent);
|
|
140
|
-
subscriber.complete();
|
|
141
|
-
this.logger.debug('Tool execution complete');
|
|
132
|
+
if (e.event === 'task-complete') {
|
|
133
|
+
content = data.content;
|
|
142
134
|
}
|
|
135
|
+
logger.debug({ event: e.event }, 'Received SSE event');
|
|
143
136
|
});
|
|
137
|
+
if (!subscriber.closed) {
|
|
138
|
+
const toolCompleteEvent = {
|
|
139
|
+
kind: 'tool-complete',
|
|
140
|
+
contextId: context.contextId,
|
|
141
|
+
taskId: context.taskId,
|
|
142
|
+
toolCallId: toolCall.id,
|
|
143
|
+
toolName: toolCall.function.name,
|
|
144
|
+
success: true,
|
|
145
|
+
result: content || 'Complete',
|
|
146
|
+
timestamp: new Date().toISOString(),
|
|
147
|
+
};
|
|
148
|
+
subscriber.next(toolCompleteEvent);
|
|
149
|
+
subscriber.complete();
|
|
150
|
+
logger.debug('Tool execution complete');
|
|
151
|
+
}
|
|
144
152
|
};
|
|
145
153
|
run().catch((err) => {
|
|
146
|
-
|
|
154
|
+
logger.error({ err }, 'Tool execution error');
|
|
147
155
|
if (!subscriber.closed) {
|
|
148
156
|
subscriber.error(err);
|
|
149
157
|
}
|
package/dist/types/event.d.ts
CHANGED
|
@@ -315,6 +315,9 @@ export type ContextEvent<T> = T & {
|
|
|
315
315
|
contextId: string;
|
|
316
316
|
taskId: string;
|
|
317
317
|
};
|
|
318
|
+
export type ChildTaskEvent<T> = T & {
|
|
319
|
+
parentTaskId: string;
|
|
320
|
+
};
|
|
318
321
|
export type ContextAnyEvent = ContextEvent<AnyEvent>;
|
|
319
322
|
export type ExternalEvent = Exclude<AnyEvent, InternalDebugEvent>;
|
|
320
323
|
export type DebugEvent = InternalDebugEvent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@looopy-ai/core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "RxJS-based AI agent framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dist"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@geee-be/sse-stream-parser": "^1.0.
|
|
30
|
+
"@geee-be/sse-stream-parser": "^1.0.2",
|
|
31
31
|
"@opentelemetry/api": "^1.9.0",
|
|
32
32
|
"@opentelemetry/exporter-metrics-otlp-http": "^0.207.0",
|
|
33
33
|
"@opentelemetry/exporter-trace-otlp-http": "^0.207.0",
|