Skip to content

Commit

Permalink
Merge pull request #51 from meta-d/develop
Browse files Browse the repository at this point in the history
version 2.5.8
  • Loading branch information
meta-d authored Aug 22, 2024
2 parents 64d2d33 + 7f08006 commit 9678ea7
Show file tree
Hide file tree
Showing 52 changed files with 935 additions and 328 deletions.
2 changes: 1 addition & 1 deletion .deploy/api/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ocap-server",
"author": "Metad",
"version": "2.5.7",
"version": "2.5.8",
"scripts": {
"start": "nx serve",
"build": "nx build",
Expand Down
2 changes: 1 addition & 1 deletion apps/cloud/src/app/@core/copilot/references-retriever.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function injectReferencesRetrieverTool(command: string | string[], option
},
{
name: 'referencesRetriever',
description: 'Retrieve references for a list of questions',
description: `Retrieve references docs for a list of questions, such as: how to create a formula for calculated measure, how to create a time slicer for relative time`,
schema: z.object({
questions: z.array(z.string().describe('The question to retrieve references'))
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ export class ChatBIConversationService extends OrganizationBaseService {
getMy() {
return this.selectOrganizationId().pipe(
switchMap(() =>
this.httpClient.get<{ items: IChatBIConversation[] }>(API_CHATBI_CONVERSATION + '/my', {
this.httpClient.get<{ items: IChatBIConversation[]; total: number; }>(API_CHATBI_CONVERSATION + '/my', {
params: {
data: JSON.stringify({
take: 20,
skip: 0,
order: {
createdAt: OrderTypeEnum.DESC
}
})
}
} as any
})
),
map(({ items }) => items.map(convertChatBIConversationResult))
Expand Down
10 changes: 9 additions & 1 deletion apps/cloud/src/app/features/chatbi/answer/answer.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</div>
</div>
<div class="flex-1 inline-flex flex-col items-start gap-y-3 overflow-hidden">
<div class="answer-content flex-1 w-full flex flex-col items-start">
<div class="answer-content flex-1 w-full flex flex-col items-stretch">
<div class="w-full mb-1 font-semibold">
{{'PAC.KEY_WORDS.Copilot' | translate: {Default: 'Copilot'} }}<span class="text-xs text-violet-500 bg-violet-500/10 px-1.5 py-1 rounded-full m-2">ChatBI</span>
</div>
Expand All @@ -39,6 +39,14 @@
/>
}
@case ('object') {
@if (isQuestions(item); as q) {
<h3 class="my-2">{{'PAC.ChatBI.TryFollowing' | translate: {Default: 'You can also try the following'} }}:</h3>
<ul class="list-decimal pl-4">
@for (question of q.questions; track question) {
<li class="my-2 text-sm"><a class="cursor-pointer" (click)="edit(question)">{{question}}</a></li>
}
</ul>
}
@if (isAnswer(item); as answer) {
@if (answer.indicators) {
<div class="flex flex-col justify-start items-stretch gap-2">
Expand Down
10 changes: 10 additions & 0 deletions apps/cloud/src/app/features/chatbi/answer/answer.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { ExplainComponent } from '@metad/story/story'
import { NxWidgetKpiComponent } from '@metad/story/widgets/kpi'
import { WidgetService } from '@metad/core'
import { ChatbiChatComponent } from '../chat/chat.component'
import { ChatbiInputComponent } from '../input/input.component'

@Component({
standalone: true,
Expand Down Expand Up @@ -89,6 +90,7 @@ export class ChatbiAnswerComponent {
TOPS = [5, 10, 20, 100]

readonly message = input<CopilotChatMessage>(null)
readonly chatInput = input<ChatbiInputComponent>(null)
readonly analyticalCard = viewChild(AnalyticalCardComponent)
readonly analyticalGrid = viewChild(AnalyticalGridComponent)

Expand Down Expand Up @@ -183,6 +185,10 @@ export class ChatbiAnswerComponent {
return value as unknown as QuestionAnswer
}

isQuestions(value: string | any) {
return typeof value === 'object' && Array.isArray(value['questions']) ? value as { questions: string[] } : null
}

openExplore(item: string | QuestionAnswer) {
this.homeComponent.openExplore(this.message(), item as unknown as QuestionAnswer)
}
Expand Down Expand Up @@ -362,6 +368,10 @@ export class ChatbiAnswerComponent {
}
}

edit(text: string) {
this.chatInput().prompt.set(text)
}

@HostBinding('class.full-screen') get isFullscreen() {
return this.fullscreen()
}
Expand Down
7 changes: 5 additions & 2 deletions apps/cloud/src/app/features/chatbi/chat/chat.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@
@for (message of conversation()?.messages; track $index) {
@switch (message.role) {
@case ('assistant') {
<pac-chatbi-answer class="group w-full p-4 md:p-6 mb-4 flex rounded-xl bg-gray-50 dark:bg-neutral-900" [message]="message" />
<pac-chatbi-answer class="group w-full p-4 md:p-6 mb-4 flex rounded-xl bg-gray-50 dark:bg-neutral-900"
[message]="message"
[chatInput]="chatInput"
/>
}
@case ('user') {
<div class="w-full p-4 md:p-6 flex rounded-xl">
Expand Down Expand Up @@ -110,6 +113,6 @@
</div>
</div>

<pac-chatbi-input class="w-full py-2 md:pb-4 lg:px-2 flex flex-col sticky bottom-0 left-0"
<pac-chatbi-input #chatInput class="w-full py-2 md:pb-4 lg:px-2 flex flex-col sticky bottom-0 left-0"
[(prompt)]="prompt"
/>
34 changes: 9 additions & 25 deletions apps/cloud/src/app/features/chatbi/chat/chat.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { DensityDirective } from '@metad/ocap-angular/core'
import { nonNullable } from '@metad/ocap-core'
import { TranslateModule, TranslateService } from '@ngx-translate/core'
import { MarkdownModule } from 'ngx-markdown'
import { debounceTime, filter } from 'rxjs'
import { debounceTime, distinctUntilChanged, filter } from 'rxjs'
import { ChatbiAnswerComponent } from '../answer/answer.component'
import { ChatbiService } from '../chatbi.service'
import { injectExamplesAgent } from '../copilot'
Expand Down Expand Up @@ -64,20 +64,6 @@ export class ChatbiChatComponent {

readonly examples = this.chatbiService.examples

// readonly examples = toSignal(
// this.translate
// .stream('PAC.ChatBI.SystemMessage_Samples', {
// Default: [
// 'Monthly sales trends of Canadian customers in 2023',
// 'Top 10 users in terms of spending in 2024',
// 'What is the spending amount in each month of 2023',
// 'Spending amount distribution of users in each channel in 2023',
// 'How many users are there in each channel in 2023'
// ]
// })
// .pipe(map((examples) => examples))
// )

readonly cube = this.chatbiService.entity
readonly entityType = this.chatbiService.entityType

Expand All @@ -91,16 +77,14 @@ export class ChatbiChatComponent {
.pipe(filter(nonNullable), debounceTime(1000), takeUntilDestroyed())
.subscribe(() => this.scrollBottom())

constructor() {
effect(
() => {
if (this.chatbiService.context() && this.examplesEmpty()) {
this.refresh()
}
},
{ allowSignalWrites: true }
)
}
private examplesSub = toObservable(this.chatbiService.context).pipe(
filter(nonNullable), debounceTime(1000), distinctUntilChanged(), takeUntilDestroyed()
).subscribe(() => {
if (this.examplesEmpty()) {
this.refresh()
}
})


editQuestion(message: CopilotChatMessage) {
this.prompt.set(message.content)
Expand Down
40 changes: 25 additions & 15 deletions apps/cloud/src/app/features/chatbi/chatbi.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CopilotChatMessage, nanoid } from '@metad/copilot'
import { markdownModelCube } from '@metad/core'
import { NgmDSCoreService } from '@metad/ocap-angular/core'
import { WasmAgentService } from '@metad/ocap-angular/wasm-agent'
import { EntityType, Indicator, isEntityType, isEqual, isString, omitBlank, Schema } from '@metad/ocap-core'
import { EntityType, Indicator, isEntityType, isEqual, isString, nonNullable, omit, omitBlank, Schema } from '@metad/ocap-core'
import { getSemanticModelKey } from '@metad/story/core'
import { derivedAsync } from 'ngxtension/derived-async'
import {
Expand All @@ -22,13 +22,15 @@ import {
} from 'rxjs'
import { ChatBIConversationService, ChatbiConverstion, registerModel } from '../../@core'
import { QuestionAnswer } from './types'
import { injectQueryParams } from 'ngxtension/inject-query-params'

@Injectable()
export class ChatbiService {
readonly #modelsService = inject(ModelsService)
readonly #dsCoreService = inject(NgmDSCoreService)
readonly #wasmAgent = inject(WasmAgentService)
readonly conversationService = inject(ChatBIConversationService)
readonly paramId = injectQueryParams('id')

readonly models$ = this.#modelsService.getMy()
readonly detailModels = signal<Record<string, NgmSemanticModel>>({})
Expand Down Expand Up @@ -70,9 +72,8 @@ export class ChatbiService {
readonly entityType = derivedAsync<EntityType>(() => {
const dataSourceName = this.dataSourceName()
const cube = this.entity()
if (dataSourceName && cube) {
return this.#dsCoreService.getDataSource(dataSourceName).pipe(
switchMap((dataSource) => dataSource.selectEntityType(cube)),
if (dataSourceName && this.dataSource() && cube) {
return this.dataSource().selectEntityType(cube).pipe(
filter((entityType) => isEntityType(entityType))
) as Observable<EntityType>
}
Expand Down Expand Up @@ -100,6 +101,7 @@ export class ChatbiService {

readonly pristineConversation = signal<ChatbiConverstion | null>(null)
readonly indicators = computed(() => this.conversation()?.indicators ?? [])
readonly modelIndicators = computed(() => this.model()?.indicators)

readonly aiMessage = signal<CopilotChatMessage>(null)

Expand All @@ -108,10 +110,10 @@ export class ChatbiService {
.pipe(takeUntilDestroyed())
.subscribe((items) => {
this.conversations.set(items)
if (!this.conversationId()) {
this.setConversation(items[0]?.key)
}
if (!this.conversationKey()) {
// if (!this.conversationId()) {
// this.setConversation(items[0]?.key)
// }
if (!this.paramId() && !this.conversationKey()) {
this.newConversation()
}
})
Expand Down Expand Up @@ -143,6 +145,14 @@ export class ChatbiService {
}
})

private modelSub = toObservable(this.model).pipe(filter(nonNullable), takeUntilDestroyed())
.subscribe((model) => {
this.registerModel(model)
if (!this.entity() && model.cube) {
this.setEntity(model.cube)
}
})

constructor() {
effect(
async () => {
Expand All @@ -159,10 +169,10 @@ export class ChatbiService {
)
)
this.detailModels.update((state) => ({ ...state, [model.id]: model }))
this.registerModel(model)
if (!this.entity() && model.cube) {
this.setEntity(model.cube)
}
// this.registerModel(model)
// if (!this.entity() && model.cube) {
// this.setEntity(model.cube)
// }
}
},
{ allowSignalWrites: true }
Expand All @@ -183,8 +193,8 @@ export class ChatbiService {
const dataSource = this.dataSource()
const indicators = this.indicators()
if (dataSource && indicators) {
const schema = dataSource.options.schema
const _indicators = [...(schema?.indicators ?? [])].filter(
// const schema = dataSource.options.schema
const _indicators = [...(this.modelIndicators() ?? [])].filter(
(indicator) => !indicators.some((item) => item.id === indicator.id || item.code === indicator.code)
)
_indicators.push(...indicators)
Expand All @@ -210,7 +220,7 @@ export class ChatbiService {
}

private registerModel(model: NgmSemanticModel) {
registerModel(model, this.#dsCoreService, this.#wasmAgent)
registerModel(omit(model, 'indicators'), this.#dsCoreService, this.#wasmAgent)
}

newConversation() {
Expand Down
40 changes: 27 additions & 13 deletions apps/cloud/src/app/features/chatbi/copilot/chatbi/graph.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { inject } from '@angular/core'
import { SystemMessage } from '@langchain/core/messages'
import { isAIMessage, SystemMessage } from '@langchain/core/messages'
import { SystemMessagePromptTemplate } from '@langchain/core/prompts'
import { END } from '@langchain/langgraph/web'
import {
createAgentStepsInstructions,
CreateGraphOptions,
Expand All @@ -15,23 +16,27 @@ import {
} from '@metad/core'
import { injectReferencesRetrieverTool } from 'apps/cloud/src/app/@core/copilot'
import { ChatbiService } from '../../chatbi.service'
import { injectCreateChartTool, injectCreateFormulaTool } from '../tools'
import { injectCreateChartTool } from '../tools'
import { injectCreateIndicatorTool, injectMoreQuestionsTool } from './tools'
import { CHATBI_COMMAND_NAME, insightAgentState } from './types'

export function injectCreateInsightGraph() {
const chatbiService = inject(ChatbiService)
const memberRetrieverTool = injectDimensionMemberTool()
const createChartTool = injectCreateChartTool()
const createFormulaTool = injectCreateFormulaTool()
const createIndicatorTool = injectCreateIndicatorTool()
const moreQuestionsTool = injectMoreQuestionsTool()
const referencesRetrieverTool = injectReferencesRetrieverTool(
[referencesCommandName(CHATBI_COMMAND_NAME), referencesCommandName('calculated')],
{ k: 3 }
)

const context = chatbiService.context

const tools = [referencesRetrieverTool, memberRetrieverTool, createFormulaTool, createChartTool]
return async ({ llm, checkpointer, interruptBefore, interruptAfter }: CreateGraphOptions) => {
const indicatorTool = createIndicatorTool(llm)
const tools = [referencesRetrieverTool, memberRetrieverTool, indicatorTool, createChartTool, moreQuestionsTool]

return createReactAgent({
state: insightAgentState,
llm,
Expand All @@ -40,34 +45,43 @@ export function injectCreateInsightGraph() {
interruptAfter,
tools: [...tools],
messageModifier: async (state) => {
const systemTemplate = `You are a professional BI data analyst.
const systemTemplate = `You are a professional BI data analyst. Use 'answerQuestion' to ask questions and 'giveMoreQuestions' to get more questions.
{{role}}
{{language}}
Reference Documentations:
{{references}}
The cube context is:
{{context}}
${makeCubeRulesPrompt()}
${PROMPT_RETRIEVE_DIMENSION_MEMBER}
If you have any questions about how to analysis data (such as 'how to create a formula of calculated measure', 'how to create a time slicer about relative time'), please call 'referencesRetriever' tool to get the reference documentations.
If you have any questions about how to analysis data (such as 'how to create some type chart', 'how to create a time slicer about relative time'), please call 'referencesRetriever' tool to get the reference documentations.
${createAgentStepsInstructions(
`Extract the information mentioned in the problem into 'dimensions', 'measurements', 'time', 'slicers', etc.`,
`Determine whether measure exists in the Cube information. If it does, proceed directly to the next step. If not found, call the 'createFormula' tool to create a indicator for that. After creating the indicator, you need to call the subsequent steps to re-answer the complete answer.`,
`For every measure, determine whether it exists in the cube context, if it does, proceed directly to the next step, if not found, call the 'createIndicator' tool to create new calculated measure for it. After creating the measure, you need to call the subsequent steps to re-answer the complete answer.`,
PROMPT_RETRIEVE_DIMENSION_MEMBER,
CubeVariablePrompt,
`Add the time and slicers to slicers in tool, if the measure to be displayed is time-related, add the current period as a filter to the 'timeSlicers'.`,
`Final call 'answerQuestion' tool to answer question, use the complete conditions to answer`
`If the time condition is a specified fixed time (such as 2023 year, 202202, 2020 Q1), please add it to 'slicers' according to the time dimension. If the time condition is relative (such as this month, last month, last year), please add it to 'timeSlicers'.`,
`Then call 'answerQuestion' tool to answer question, use the complete conditions to answer`,
`Then call 'giveMoreQuestions' tool to give more analysis suggestions`
)}
Disable parallel tool calls.
Answer using tools only.
`
const system = await SystemMessagePromptTemplate.fromTemplate(systemTemplate, {
templateFormat: 'mustache'
}).format({ ...state, context: context() })
return [new SystemMessage(system), ...state.messages]
},
toolsRouter: (state) => {
const lastMessage = state.messages[state.messages.length - 1]
if (isAIMessage(lastMessage)) {
if (['giveMoreQuestions'].includes(lastMessage.name)) {
return END
}
}
return 'agent'
}
})
}
Expand Down
Loading

0 comments on commit 9678ea7

Please sign in to comment.