Merge Yuxi teacher support with classroom discussions
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
teacherAvailability,
|
||||
teacherVersion,
|
||||
teacherPreview,
|
||||
teacherCatalog,
|
||||
TeacherError,
|
||||
type TeacherAccount,
|
||||
} from './config-client';
|
||||
@@ -29,6 +30,7 @@ import { TeacherTopicStore, teacherTopicId } from './store';
|
||||
import { compileTeacherContext } from './context';
|
||||
import { prepareTeacherModel } from './model-runner';
|
||||
import { consultationDefinition } from './consultation-role';
|
||||
import { prepareCloudTeacher } from './cloud-runner';
|
||||
import { readTeacherSource } from './source-reader';
|
||||
import { parseTeacherSuggestions } from './suggestions';
|
||||
import { applyDiscussionReply, discussionInstructions, editDiscussion, validateDiscussionContext } from './discussion';
|
||||
@@ -53,6 +55,8 @@ export interface TeacherServiceOptions {
|
||||
version?: typeof teacherVersion;
|
||||
preview?: typeof teacherPreview;
|
||||
prepareModel?: typeof prepareTeacherModel;
|
||||
prepareCloud?: typeof prepareCloudTeacher;
|
||||
catalog?: typeof teacherCatalog;
|
||||
readSource?(scope: TeacherScope): Promise<TeacherSourceContext>;
|
||||
}
|
||||
export class CodingTeacherService {
|
||||
@@ -93,6 +97,9 @@ export class CodingTeacherService {
|
||||
: null;
|
||||
return { ...status, definition: published ? consultationDefinition(published.payload, role) : null };
|
||||
}
|
||||
async catalog() {
|
||||
return (this.options.catalog ?? teacherCatalog)(await this.account());
|
||||
}
|
||||
private async scopedStore(
|
||||
account: TeacherAccount,
|
||||
scope: TeacherScope
|
||||
@@ -144,7 +151,8 @@ export class CodingTeacherService {
|
||||
async create(
|
||||
scope: TeacherScope,
|
||||
draftRevision?: number,
|
||||
sampleContext = ''
|
||||
sampleContext = '',
|
||||
teacherVersionNumber?: number,
|
||||
): Promise<TeacherTopic> {
|
||||
const account = await this.account();
|
||||
const store = await this.scopedStore(account, scope);
|
||||
@@ -160,6 +168,14 @@ export class CodingTeacherService {
|
||||
definition = (await (this.options.preview ?? teacherPreview)(account, draftRevision!))
|
||||
.payload;
|
||||
version = 0;
|
||||
} else if (teacherVersionNumber !== undefined) {
|
||||
if (!Number.isSafeInteger(teacherVersionNumber) || teacherVersionNumber < 1)
|
||||
throw new TeacherError(422, 'teacher_version_invalid', '老师版本无效。');
|
||||
const catalog = await (this.options.catalog ?? teacherCatalog)(account);
|
||||
const selected = catalog.items.find(item => item.version === teacherVersionNumber);
|
||||
if (!selected) throw new TeacherError(409, 'teacher_disabled', '该老师暂未开放,请刷新后选择。');
|
||||
definition = consultationDefinition(selected.definition, scope.role ?? 'teacher');
|
||||
version = selected.version;
|
||||
} else {
|
||||
const status = await (this.options.availability ?? teacherAvailability)(account);
|
||||
if (!status.enabled || !status.published_version)
|
||||
@@ -346,7 +362,11 @@ export class CodingTeacherService {
|
||||
const discussionContext = validateDiscussionContext(topic, input.discussion);
|
||||
if (topic.draftRevision) {
|
||||
await (this.options.preview ?? teacherPreview)(account, topic.draftRevision);
|
||||
} else {
|
||||
} else if (topic.definition.runtime !== 'yuxi' && topic.definition.config_id) {
|
||||
const catalog = await (this.options.catalog ?? teacherCatalog)(account);
|
||||
if (!catalog.items.some(item => item.teacher_id === topic.definition.config_id))
|
||||
throw new TeacherError(409, 'teacher_disabled', '老师已停用,历史仍可查看。');
|
||||
} else if (topic.definition.runtime !== 'yuxi') {
|
||||
const available = await (this.options.availability ?? teacherAvailability)(account);
|
||||
if (!available.enabled)
|
||||
throw new TeacherError(409, 'teacher_disabled', '老师已停用,历史仍可查看。');
|
||||
@@ -395,22 +415,40 @@ export class CodingTeacherService {
|
||||
);
|
||||
return structuredClone(ref);
|
||||
});
|
||||
if (!topic.definition.system_prompt.trim())
|
||||
const isCloud = topic.definition.runtime === 'yuxi';
|
||||
if (!isCloud && !topic.definition.system_prompt.trim())
|
||||
throw new TeacherError(422, 'teacher_definition_invalid', '请先配置老师的系统提示词。');
|
||||
const model = await (this.options.prepareModel ?? prepareTeacherModel)(
|
||||
account,
|
||||
topic.definition
|
||||
);
|
||||
const access = scope.projectId === 'preview' ? undefined : {
|
||||
projectPath: (await this.options.projects.getProject(scope.projectId)).path,
|
||||
source,
|
||||
history: topic.requests,
|
||||
assertCurrent: () => this.assertAccount(account),
|
||||
};
|
||||
const model = isCloud && access
|
||||
? (this.options.prepareCloud ?? prepareCloudTeacher)(account, topic, input.requestId, access,
|
||||
(progress) => {
|
||||
const current = topic.requests.at(-1)!;
|
||||
if (current.progress === progress) return;
|
||||
current.progress = progress; topic.revision++;
|
||||
this.events.emit(key, structuredClone(topic));
|
||||
}, async cloudRequestId => {
|
||||
topic.requests.at(-1)!.cloudRequestId = cloudRequestId;
|
||||
await store.save(topic);
|
||||
})
|
||||
: await (this.options.prepareModel ?? prepareTeacherModel)(account, topic.definition,
|
||||
(scope.role ?? 'teacher') === 'teacher' ? access : undefined,
|
||||
{ finalOnly: structuredReply || intent === 'suggestions' });
|
||||
if (checkIn) this.assertAccount(account);
|
||||
const compiled = compileTeacherContext(
|
||||
topic.definition,
|
||||
source,
|
||||
topic.requests,
|
||||
isCloud ? [] : topic.requests,
|
||||
input.text,
|
||||
references,
|
||||
model.inputLimit,
|
||||
intent,
|
||||
structuredReply ? discussionInstructions(topic, discussionContext) : undefined
|
||||
structuredReply ? discussionInstructions(topic, discussionContext) : undefined,
|
||||
scope.projectId !== 'preview'
|
||||
);
|
||||
// Reading context and resolving model credentials can yield while a source
|
||||
// is being deleted. Project consultations must recheck the actual source.
|
||||
@@ -439,6 +477,7 @@ export class CodingTeacherService {
|
||||
sourceCapturedAt: source.capturedAt,
|
||||
includedSourceMessageIds: compiled.includedSourceMessageIds,
|
||||
omittedMessages: compiled.omittedMessages,
|
||||
truncatedMessages: compiled.truncatedMessages,
|
||||
status: 'preparing' as const,
|
||||
response: '',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user