Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5125f9788
|
||
|
|
3de664e0fd | ||
|
|
2caa634044 | ||
|
|
c77e5b0123
|
||
|
|
f46735b1c0 | ||
|
|
86563c64d2 | ||
|
|
82febe0f19 | ||
|
|
1ea23dfc49
|
||
|
|
9d31de46a6 | ||
|
|
24b0b3b14a
|
||
|
|
1c33e97664 | ||
|
|
fcaf2ded7e
|
||
|
|
2cea8ffd44 | ||
|
|
6b98d5216b
|
||
|
|
6adf390bf8 | ||
|
|
bc92c89665
|
||
|
|
d43402e262 | ||
|
|
fe725ac8ff
|
||
|
|
dfb05d8935 | ||
|
|
523fd0128a
|
||
|
|
47115fe359 | ||
|
|
f2d2b50459
|
||
|
|
8c33db9fcf | ||
|
|
75a5dedf7f
|
||
|
|
c1cceaf4c3 | ||
|
|
f8a114bbb8
|
||
|
|
3a7ff1c659 | ||
|
|
ea81166454
|
||
|
|
cfcc044055 | ||
|
|
620dee488d
|
||
|
|
e23206f1e8 | ||
|
|
c6bae3bcf2
|
||
|
|
2d8459bb24 | ||
|
|
ac06a96787
|
||
|
|
d4a1f64480 | ||
|
|
c28e782075
|
||
|
|
5c76a13245 | ||
|
|
8d1348888d
|
||
|
|
47cc572c36 | ||
|
|
8099f64bf2
|
||
|
|
ac6ec75deb | ||
|
|
1f48a370e3
|
||
|
|
d0ad2aa0fa | ||
|
|
16c8500f59
|
||
|
|
a8c5f586ee | ||
|
|
3dfa775b21
|
||
|
|
67f78fd660 | ||
|
|
ff67c6f7cc | ||
|
|
19551ac2af
|
||
|
|
7263ac627d
|
No files matched your search
+11
-57
@@ -1,60 +1,14 @@
|
|||||||
version: 2.1
|
version: 2.1
|
||||||
|
setup: true
|
||||||
orbs:
|
orbs:
|
||||||
qqq-orb: kingsrook/qqq-orb@0.6.9
|
qqq-orb: kingsrook/qqq-orb@0.7.15
|
||||||
|
|
||||||
commands:
|
|
||||||
configure-package-install:
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Keep package installation unattended
|
|
||||||
command: |
|
|
||||||
# These build machines are discarded after the job.
|
|
||||||
sudo install -d /etc/needrestart/conf.d
|
|
||||||
printf '%s\n' '$nrconf{restart} = "l";' | sudo tee /etc/needrestart/conf.d/99-qqq-ci.conf >/dev/null
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
test_only:
|
setup:
|
||||||
jobs:
|
jobs:
|
||||||
- qqq-orb/mvn_test_only:
|
- qqq-orb/generate_pipeline:
|
||||||
pre-steps:
|
orb_version: 0.7.15
|
||||||
- configure-package-install
|
filters:
|
||||||
context: [qqq-maven-registry-credentials]
|
branches:
|
||||||
filters:
|
ignore: automation/qbit-rc-pending
|
||||||
branches:
|
tags:
|
||||||
ignore: /(develop|release\/.*)/
|
only: /^v\d+\.\d+\.\d+$/
|
||||||
|
|
||||||
publish_snapshot:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
pre-steps:
|
|
||||||
- configure-package-install
|
|
||||||
context: [qqq-maven-registry-credentials]
|
|
||||||
branch_type: snapshot
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: [develop]
|
|
||||||
|
|
||||||
publish_release_candidate:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
pre-steps:
|
|
||||||
- configure-package-install
|
|
||||||
context: [qqq-maven-registry-credentials]
|
|
||||||
branch_type: release_candidate
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: [/release\/.*/]
|
|
||||||
|
|
||||||
publish_release:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
pre-steps:
|
|
||||||
- configure-package-install
|
|
||||||
context: [qqq-maven-registry-credentials]
|
|
||||||
branch_type: release
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
ignore: /.*/
|
|
||||||
tags:
|
|
||||||
only: [/^v\d+\.\d+\.\d+$/]
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: CodeQL
|
||||||
|
'on':
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
- release/**
|
||||||
|
- hotfix/**
|
||||||
|
pull_request: {}
|
||||||
|
schedule:
|
||||||
|
- cron: 41 16 * * 0
|
||||||
|
workflow_dispatch: {}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
security-events: write
|
||||||
|
packages: read
|
||||||
|
concurrency:
|
||||||
|
group: codeql-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
|
||||||
@@ -1,26 +1,74 @@
|
|||||||
# Cut the next release candidate (QRun-IO/qqq docs/QRUN-QBIT-REPO-STANDARD.md, C6):
|
|
||||||
# run this workflow by hand, or merge a develop PR labelled `release:rc`. It merges develop into
|
|
||||||
# the release line and the release/* push runs CircleCI publish_release_candidate.
|
|
||||||
name: Release candidate
|
name: Release candidate
|
||||||
|
'on':
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
dry_run:
|
dry_run:
|
||||||
description: Print the plan without merging or creating branches
|
description: Plan a cut without changing release state
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
finalize:
|
||||||
|
description: Retry finalization of an existing cut; do not cut another RC
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
recover:
|
||||||
|
description: Recover the same published RC after a deployment timeout; never
|
||||||
|
reupload
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
circle_build:
|
||||||
|
description: Original Circle publisher build number (recovery only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
merge_sha:
|
||||||
|
description: Existing cut merge_sha (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
bump_sha:
|
||||||
|
description: Existing cut bump_sha (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
branch:
|
||||||
|
description: Existing cut branch (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
version:
|
||||||
|
description: Existing cut version (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [closed]
|
types:
|
||||||
branches: [develop]
|
- closed
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
repository_dispatch:
|
||||||
|
types:
|
||||||
|
- qbit-rc-published
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
actions: read
|
||||||
|
checks: read
|
||||||
jobs:
|
jobs:
|
||||||
release-rc:
|
release-rc:
|
||||||
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release:rc'))
|
if: github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch'
|
||||||
uses: QRun-IO/.github/.github/workflows/qbit-release-rc.yml@main
|
|| (github.event_name == 'pull_request' && github.event.pull_request.merged
|
||||||
secrets: inherit
|
== true && github.event.pull_request.base.ref == 'develop' && contains(github.event.pull_request.labels.*.name,
|
||||||
|
'release:rc'))
|
||||||
|
uses: QRun-IO/qqq-orb/.github/workflows/qbit-release-rc.yml@cc30f599e63c8e41629c99d23395bac1adb8bc44
|
||||||
with:
|
with:
|
||||||
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
|
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false
|
||||||
|
}}
|
||||||
|
finalize: ${{ github.event_name == 'repository_dispatch' || (github.event_name
|
||||||
|
== 'workflow_dispatch' && inputs.finalize) }}
|
||||||
|
recover: ${{ github.event_name == 'workflow_dispatch' && inputs.recover || false
|
||||||
|
}}
|
||||||
|
circle_build: ${{ github.event_name == 'workflow_dispatch' && inputs.circle_build
|
||||||
|
|| '' }}
|
||||||
|
merge_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.merge_sha
|
||||||
|
|| inputs.merge_sha || '' }}
|
||||||
|
bump_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.bump_sha
|
||||||
|
|| inputs.bump_sha || '' }}
|
||||||
|
branch: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch
|
||||||
|
|| inputs.branch || '' }}
|
||||||
|
version: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.version
|
||||||
|
|| inputs.version || '' }}
|
||||||
|
secrets: inherit
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
profile: qbit
|
||||||
|
orb_version: 0.7.15
|
||||||
|
default_branch: develop
|
||||||
|
java_version: '21'
|
||||||
|
browsers: true
|
||||||
|
api_checks: false
|
||||||
|
docs_only: false
|
||||||
|
release_on_main: false
|
||||||
|
release_tag_policy: stable
|
||||||
|
test_branch_policy: non-release
|
||||||
|
publish:
|
||||||
|
snapshot: true
|
||||||
|
release_candidate: true
|
||||||
|
release: true
|
||||||
|
asciidoc: false
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Unreleased - Answer visible at the end of the stream
|
||||||
|
|
||||||
|
- The turn's redacted transcript (the question and answer parts, pending actions, stored events) is now written before `message.end` is sent. The panel reloads the conversation the moment the stream ends, and used to read the empty placeholders (`[]`) the transcript redactor stores until its flush, so the newest question and answer showed as empty bubbles until the panel was closed and reopened.
|
||||||
|
|
||||||
## Unreleased - Optional provider health
|
## Unreleased - Optional provider health
|
||||||
|
|
||||||
- An enabled provider that is not the default and not the only route for any configured routing (an enabled profile's simple/complex model, a budget downgrade model or a knowledge source's embedding model, with no fallback on another usable provider) is optional: when it is unreachable, its `assistant.provider.<id>` and `assistant.model.<id>` checks report DEGRADED with a message naming the provider requests use instead, so one unfinished local server no longer turns the whole `assistant` component DOWN. The default provider and sole routes still report DOWN. A failed provider probe still carries `reachable=false`, so the circuit breaker keeps refusing the provider.
|
- An enabled provider that is not the default and not the only route for any configured routing (an enabled profile's simple/complex model, a budget downgrade model or a knowledge source's embedding model, with no fallback on another usable provider) is optional: when it is unreachable, its `assistant.provider.<id>` and `assistant.model.<id>` checks report DEGRADED with a message naming the provider requests use instead, so one unfinished local server no longer turns the whole `assistant` component DOWN. The default provider and sole routes still report DOWN. A failed provider probe still carries `reachable=false`, so the circuit breaker keeps refusing the provider.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<!-- Multi-module build. The qqq-orb sets the revision here; the modules -->
|
<!-- Multi-module build. The qqq-orb sets the revision here; the modules -->
|
||||||
<!-- inherit it, and the flatten plugin resolves it in the published POMs. -->
|
<!-- inherit it, and the flatten plugin resolves it in the published POMs. -->
|
||||||
<properties>
|
<properties>
|
||||||
<revision>0.1.0-SNAPSHOT</revision>
|
<revision>0.1.0-RC.24</revision>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|||||||
@@ -1321,6 +1321,17 @@ public class TurnEngine implements Runnable
|
|||||||
String finalErrorText = errorText;
|
String finalErrorText = errorText;
|
||||||
safely(() -> emit(TurnEvent.ERROR, map("code", finalErrorCode, "message", finalErrorText)));
|
safely(() -> emit(TurnEvent.ERROR, map("code", finalErrorCode, "message", finalErrorText)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
// the stream ends at message.end and the panel reloads the //
|
||||||
|
// conversation at once, so the redacted transcript (message parts, //
|
||||||
|
// pending actions, events) must be stored before that event is sent //
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
if(!alreadyTerminal)
|
||||||
|
{
|
||||||
|
safely(this::persistPendingEvents);
|
||||||
|
safely(() -> transcript.flush(store));
|
||||||
|
}
|
||||||
safely(() -> emit(TurnEvent.MESSAGE_END, map("messageId", assistantMessage.getId(), "status", finalStatus, "stopReason", finalStopReason)));
|
safely(() -> emit(TurnEvent.MESSAGE_END, map("messageId", assistantMessage.getId(), "status", finalStatus, "stopReason", finalStopReason)));
|
||||||
if(!alreadyTerminal)
|
if(!alreadyTerminal)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -141,6 +141,27 @@ class TurnEngineTest extends BaseAssistantTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** The panel reloads the conversation as soon as message.end arrives, so the
|
||||||
|
** question and the answer must already be stored then, not after it.
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testMessagesAreStoredBeforeMessageEnd() throws Exception
|
||||||
|
{
|
||||||
|
provider.thenText("Four posts are published.");
|
||||||
|
|
||||||
|
TurnRunner.Outcome outcome = runner.run(EDITOR, "how many published posts?");
|
||||||
|
assertThat(outcome.end()).containsEntry("status", "COMPLETE");
|
||||||
|
|
||||||
|
List<AssistantMessage> stored = runner.messagesAtEnd();
|
||||||
|
assertThat(stored).hasSize(2);
|
||||||
|
assertThat(stored).extracting(AssistantMessage::getPartsJson).allSatisfy(parts -> assertThat(parts).isNotEqualTo("[]"));
|
||||||
|
assertThat(stored.get(0).getPartsJson()).contains("how many published posts?");
|
||||||
|
assertThat(stored.get(1).getPartsJson()).contains("Four posts are published.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Parallel tool calls: all results in ONE user message, in order.
|
** Parallel tool calls: all results in ONE user message, in order.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.kingsrook.qbits.assistant.AssistantRuntime;
|
|||||||
import com.kingsrook.qbits.assistant.api.AssistantApi;
|
import com.kingsrook.qbits.assistant.api.AssistantApi;
|
||||||
import com.kingsrook.qbits.assistant.engine.RunningTurn;
|
import com.kingsrook.qbits.assistant.engine.RunningTurn;
|
||||||
import com.kingsrook.qbits.assistant.engine.TurnEvent;
|
import com.kingsrook.qbits.assistant.engine.TurnEvent;
|
||||||
|
import com.kingsrook.qbits.assistant.model.AssistantMessage;
|
||||||
import com.kingsrook.qbits.assistant.model.AssistantToolCall;
|
import com.kingsrook.qbits.assistant.model.AssistantToolCall;
|
||||||
import com.kingsrook.qbits.assistant.persistence.AssistantStore;
|
import com.kingsrook.qbits.assistant.persistence.AssistantStore;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
@@ -47,6 +48,8 @@ public class TurnRunner
|
|||||||
private final QInstance qInstance;
|
private final QInstance qInstance;
|
||||||
private final AssistantQBitConfig config;
|
private final AssistantQBitConfig config;
|
||||||
|
|
||||||
|
private List<AssistantMessage> messagesAtEnd = List.of();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@@ -169,6 +172,10 @@ public class TurnRunner
|
|||||||
{
|
{
|
||||||
List<TurnEvent> batch = running.getLog().after(last, 1000);
|
List<TurnEvent> batch = running.getLog().after(last, 1000);
|
||||||
events.addAll(batch);
|
events.addAll(batch);
|
||||||
|
if(batch.stream().anyMatch(e -> TurnEvent.MESSAGE_END.equals(e.getType())))
|
||||||
|
{
|
||||||
|
messagesAtEnd = messagesOf(userId, convId);
|
||||||
|
}
|
||||||
if(!batch.isEmpty())
|
if(!batch.isEmpty())
|
||||||
{
|
{
|
||||||
last = batch.get(batch.size() - 1).getSeq();
|
last = batch.get(batch.size() - 1).getSeq();
|
||||||
@@ -194,6 +201,36 @@ public class TurnRunner
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** The conversation's stored messages, read the moment the run's message.end
|
||||||
|
** was seen: what the panel finds when it reloads the conversation at the end
|
||||||
|
** of the stream.
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<AssistantMessage> messagesAtEnd()
|
||||||
|
{
|
||||||
|
return (messagesAtEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** The stored messages of a conversation (read as the user).
|
||||||
|
*******************************************************************************/
|
||||||
|
private List<AssistantMessage> messagesOf(String userId, Integer conversationId) throws Exception
|
||||||
|
{
|
||||||
|
QContext.init(qInstance, AssistantTestFixture.ownerSession(userId, config));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (new AssistantStore().listMessages(userId, conversationId, null, 50));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
QContext.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** The audited tool calls of a turn (read as the user).
|
** The audited tool calls of a turn (read as the user).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|||||||
Reference in new issue
Block a user